Skip to content

Commit 80c3594

Browse files
Will Andrewsallanjude
authored andcommitted
Add Linux namespace delegation support
This allows ZFS datasets to be delegated to a user/mount namespace Within that namespace, only the delegated datasets are visible Works very similarly to Zones/Jailes on other ZFS OSes As a user: ``` $ unshare -Um $ zfs list no datasets available $ readlink /proc/self/ns/user user:[4026532291] ``` As root: ``` # zfs list NAME ZONED MOUNTPOINT containers off /containers containers/host off /containers/host containers/host/child off /containers/host/child containers/host/child/gchild off /containers/host/child/gchild containers/unpriv on /unpriv containers/unpriv/child on /unpriv/child containers/unpriv/child/gchild on /unpriv/child/gchild # zfs zone 4026532291 containers/unpriv ``` Back to the user namespace: ``` $ zfs list NAME USED AVAIL REFER MOUNTPOINT containers 129M 47.8G 24K /containers containers/unpriv 128M 47.8G 24K /unpriv containers/unpriv/child 128M 47.8G 128M /unpriv/child ``` Signed-off-by: Will Andrews <will.andrews@klarasystems.com> Signed-off-by: Allan Jude <allan@klarasystems.com> Sponsored-by: Buddy <https://buddy.works>
1 parent 71ec9e5 commit 80c3594

File tree

27 files changed

+829
-16
lines changed

27 files changed

+829
-16
lines changed

cmd/zfs/zfs_main.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ static int zfs_do_jail(int argc, char **argv);
127127
static int zfs_do_unjail(int argc, char **argv);
128128
#endif
129129

130+
#ifdef __linux__
131+
static int zfs_do_zone(int argc, char **argv);
132+
static int zfs_do_unzone(int argc, char **argv);
133+
#endif
134+
130135
/*
131136
* Enable a reasonable set of defaults for libumem debugging on DEBUG builds.
132137
*/
@@ -184,6 +189,8 @@ typedef enum {
184189
HELP_JAIL,
185190
HELP_UNJAIL,
186191
HELP_WAIT,
192+
HELP_ZONE,
193+
HELP_UNZONE,
187194
} zfs_help_t;
188195

189196
typedef struct zfs_command {
@@ -254,6 +261,11 @@ static zfs_command_t command_table[] = {
254261
{ "jail", zfs_do_jail, HELP_JAIL },
255262
{ "unjail", zfs_do_unjail, HELP_UNJAIL },
256263
#endif
264+
265+
#ifdef __linux__
266+
{ "zone", zfs_do_zone, HELP_ZONE },
267+
{ "unzone", zfs_do_unzone, HELP_UNZONE },
268+
#endif
257269
};
258270

259271
#define NCOMMAND (sizeof (command_table) / sizeof (command_table[0]))
@@ -414,6 +426,10 @@ get_usage(zfs_help_t idx)
414426
return (gettext("\tunjail <jailid|jailname> <filesystem>\n"));
415427
case HELP_WAIT:
416428
return (gettext("\twait [-t <activity>] <filesystem>\n"));
429+
case HELP_ZONE:
430+
return (gettext("\tzone <nsnum> <filesystem>\n"));
431+
case HELP_UNZONE:
432+
return (gettext("\tunzone <nsnum> <filesystem>\n"));
417433
default:
418434
__builtin_unreachable();
419435
}
@@ -8728,6 +8744,57 @@ main(int argc, char **argv)
87288744
return (ret);
87298745
}
87308746

8747+
/*
8748+
* zfs zone nsnum filesystem
8749+
*
8750+
* Add or delete the given dataset to/from the namespace.
8751+
*/
8752+
#ifdef __linux__
8753+
static int
8754+
zfs_do_zone_impl(int argc, char **argv, boolean_t attach)
8755+
{
8756+
zfs_handle_t *zhp;
8757+
unsigned long nsnum;
8758+
int ret;
8759+
8760+
if (argc < 3) {
8761+
(void) fprintf(stderr, gettext("missing argument(s)\n"));
8762+
usage(B_FALSE);
8763+
}
8764+
if (argc > 3) {
8765+
(void) fprintf(stderr, gettext("too many arguments\n"));
8766+
usage(B_FALSE);
8767+
}
8768+
8769+
nsnum = strtoul(argv[1], NULL, 10);
8770+
if (nsnum > UINT_MAX) {
8771+
(void) fprintf(stderr, gettext("invalid namespace number\n"));
8772+
usage(B_FALSE);
8773+
}
8774+
8775+
zhp = zfs_open(g_zfs, argv[3], ZFS_TYPE_FILESYSTEM);
8776+
if (zhp == NULL)
8777+
return (1);
8778+
8779+
ret = (zfs_userns(zhp, (unsigned int)nsnum, attach) != 0);
8780+
8781+
zfs_close(zhp);
8782+
return (ret);
8783+
}
8784+
8785+
static int
8786+
zfs_do_zone(int argc, char **argv)
8787+
{
8788+
return (zfs_do_zone_impl(argc, argv, 1));
8789+
}
8790+
8791+
static int
8792+
zfs_do_unzone(int argc, char **argv)
8793+
{
8794+
return (zfs_do_zone_impl(argc, argv, 0));
8795+
}
8796+
#endif
8797+
87318798
#ifdef __FreeBSD__
87328799
#include <sys/jail.h>
87338800
#include <jail.h>

config/kernel-user-ns-inum.m4

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
dnl #
2+
dnl # 3.18 API change
3+
dnl # struct user_namespace inum moved from .proc_inum to .ns.inum.
4+
dnl #
5+
AC_DEFUN([ZFS_AC_KERNEL_SRC_USER_NS_COMMON_INUM], [
6+
ZFS_LINUX_TEST_SRC([user_ns_common_inum], [
7+
#include <linux/user_namespace.h>
8+
], [
9+
struct user_namespace uns;
10+
uns.ns.inum = 0;
11+
])
12+
])
13+
14+
AC_DEFUN([ZFS_AC_KERNEL_USER_NS_COMMON_INUM], [
15+
AC_MSG_CHECKING([whether user_namespace->ns.inum exists])
16+
ZFS_LINUX_TEST_RESULT([user_ns_common_inum], [
17+
AC_MSG_RESULT(yes)
18+
AC_DEFINE(HAVE_USER_NS_COMMON_INUM, 1,
19+
[user_namespace->ns.inum exists])
20+
],[
21+
AC_MSG_RESULT(no)
22+
])
23+
])

config/kernel.m4

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_SRC], [
134134
ZFS_AC_KERNEL_SRC_SET_SPECIAL_STATE
135135
ZFS_AC_KERNEL_SRC_VFS_SET_PAGE_DIRTY_NOBUFFERS
136136
ZFS_AC_KERNEL_SRC_STANDALONE_LINUX_STDARG
137+
ZFS_AC_KERNEL_SRC_USER_NS_COMMON_INUM
137138
138139
AC_MSG_CHECKING([for available kernel interfaces])
139140
ZFS_LINUX_TEST_COMPILE_ALL([kabi])
@@ -241,6 +242,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_RESULT], [
241242
ZFS_AC_KERNEL_SET_SPECIAL_STATE
242243
ZFS_AC_KERNEL_VFS_SET_PAGE_DIRTY_NOBUFFERS
243244
ZFS_AC_KERNEL_STANDALONE_LINUX_STDARG
245+
ZFS_AC_KERNEL_USER_NS_COMMON_INUM
244246
])
245247

246248
dnl #

include/libzfs.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,15 @@ _LIBZFS_H int zpool_nextboot(libzfs_handle_t *, uint64_t, uint64_t,
963963

964964
#endif /* __FreeBSD__ */
965965

966+
#ifdef __linux__
967+
968+
/*
969+
* Add or delete the given filesystem to/from the given user namespace.
970+
*/
971+
_LIBZFS_H int zfs_userns(zfs_handle_t *zhp, unsigned int nsnum, int attach);
972+
973+
#endif
974+
966975
#ifdef __cplusplus
967976
}
968977
#endif

include/os/linux/spl/sys/zone.h

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,34 @@
2525
#define _SPL_ZONE_H
2626

2727
#include <sys/byteorder.h>
28+
#include <sys/cred.h>
2829

29-
#define GLOBAL_ZONEID 0
30+
#include <linux/cred.h>
31+
#include <linux/user_namespace.h>
3032

31-
#define zone_dataset_visible(x, y) (1)
32-
#define crgetzoneid(x) (GLOBAL_ZONEID)
33-
#define INGLOBALZONE(z) (1)
33+
/*
34+
* Attach the given dataset to the given user namespace.
35+
*/
36+
extern int zone_dataset_attach(cred_t *, const char *, unsigned int);
37+
38+
/*
39+
* Detach the given dataset from the given user namespace.
40+
*/
41+
extern int zone_dataset_detach(cred_t *, const char *, unsigned int);
42+
43+
/*
44+
* Returns true if the named pool/dataset is visible in the current zone.
45+
*/
46+
extern int zone_dataset_visible(const char *dataset, int *write);
47+
48+
int spl_zone_init(void);
49+
void spl_zone_fini(void);
50+
51+
extern unsigned int crgetzoneid(const cred_t *);
52+
extern unsigned int global_zoneid(void);
53+
extern boolean_t inglobalzone(proc_t *);
54+
55+
#define INGLOBALZONE(x) inglobalzone(x)
56+
#define GLOBAL_ZONEID global_zoneid()
3457

3558
#endif /* SPL_ZONE_H */

include/sys/fs/zfs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,7 +1372,9 @@ typedef enum zfs_ioc {
13721372
ZFS_IOC_EVENTS_SEEK, /* 0x83 (Linux) */
13731373
ZFS_IOC_NEXTBOOT, /* 0x84 (FreeBSD) */
13741374
ZFS_IOC_JAIL, /* 0x85 (FreeBSD) */
1375+
ZFS_IOC_USERNS_ATTACH = ZFS_IOC_JAIL, /* 0x85 (Linux) */
13751376
ZFS_IOC_UNJAIL, /* 0x86 (FreeBSD) */
1377+
ZFS_IOC_USERNS_DETACH = ZFS_IOC_UNJAIL, /* 0x86 (Linux) */
13761378
ZFS_IOC_SET_BOOTENV, /* 0x87 */
13771379
ZFS_IOC_GET_BOOTENV, /* 0x88 */
13781380
ZFS_IOC_LAST

lib/libspl/include/sys/types.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
#include <inttypes.h>
4545
#endif /* HAVE_INTTYPES */
4646

47-
typedef int zoneid_t;
47+
typedef uint_t zoneid_t;
4848
typedef int projid_t;
4949

5050
/*

lib/libspl/include/zone.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,17 @@
3333
extern "C" {
3434
#endif
3535

36-
#define GLOBAL_ZONEID 0
36+
#ifdef __FreeBSD__
37+
#define GLOBAL_ZONEID 0
38+
#else
39+
/*
40+
* Hardcoded in the kernel's root user namespace. A "better" way to get
41+
* this would be by using ioctl_ns(2), but this would need to be performed
42+
* recursively on NS_GET_PARENT and then NS_GET_USERNS. Also, that's only
43+
* supported since Linux 4.9.
44+
*/
45+
#define GLOBAL_ZONEID 4026531837U
46+
#endif
3747

3848
extern zoneid_t getzoneid(void);
3949

lib/libspl/os/linux/zone.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,40 @@
2323
* Use is subject to license terms.
2424
*/
2525

26+
#include <unistd.h>
27+
#include <stdio.h>
28+
#include <errno.h>
29+
#include <stdlib.h>
30+
#include <limits.h>
31+
#include <string.h>
32+
2633
#include <zone.h>
2734

2835
zoneid_t
2936
getzoneid()
3037
{
31-
return (GLOBAL_ZONEID);
38+
char path[PATH_MAX];
39+
char buf[128] = { '\0' };
40+
char *cp;
41+
42+
int c = snprintf(path, sizeof (path), "/proc/self/ns/user");
43+
/* This API doesn't have any error checking... */
44+
if (c < 0)
45+
return (0);
46+
47+
ssize_t r = readlink(path, buf, sizeof (buf) - 1);
48+
if (r < 0)
49+
return (0);
50+
51+
cp = strchr(buf, '[');
52+
if (cp == NULL)
53+
return (0);
54+
cp++;
55+
56+
unsigned long n = strtoul(cp, NULL, 10);
57+
if (n == ULONG_MAX && errno == ERANGE)
58+
return (0);
59+
zoneid_t z = (zoneid_t)n;
60+
61+
return (z);
3262
}

lib/libuutil/libuutil.abi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1137,7 +1137,7 @@
11371137
<pointer-type-def type-id='a84c031d' size-in-bits='64' id='26a90f95'/>
11381138
</abi-instr>
11391139
<abi-instr version='1.0' address-size='64' path='os/linux/zone.c' language='LANG_C99'>
1140-
<typedef-decl name='zoneid_t' type-id='95e97e5e' id='4da03624'/>
1140+
<typedef-decl name='zoneid_t' type-id='3502e3ff' id='4da03624'/>
11411141
<function-decl name='getzoneid' mangled-name='getzoneid' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='getzoneid'>
11421142
<return type-id='4da03624'/>
11431143
</function-decl>

0 commit comments

Comments
 (0)