diff --git a/jail/fs.c b/jail/fs.c index c0a1af7..3a1ef1c 100644 --- a/jail/fs.c +++ b/jail/fs.c @@ -126,6 +126,31 @@ unsigned long detect_atime_flag(const char *mountpoint) #define MOUNT_ATTR_NODIRATIME 0x00000080 #endif +/* MS_* -> MOUNT_ATTR_* for fsmount()/mount_setattr() */ +static unsigned mountflags_to_attr(unsigned long mountflags) +{ + unsigned attr = 0; + + if (mountflags & MS_RDONLY) + attr |= MOUNT_ATTR_RDONLY; + if (mountflags & MS_NOSUID) + attr |= MOUNT_ATTR_NOSUID; + if (mountflags & MS_NODEV) + attr |= MOUNT_ATTR_NODEV; + if (mountflags & MS_NOEXEC) + attr |= MOUNT_ATTR_NOEXEC; + if (mountflags & MS_NODIRATIME) + attr |= MOUNT_ATTR_NODIRATIME; + if (mountflags & MS_NOATIME) + attr |= MOUNT_ATTR_NOATIME; + else if (mountflags & MS_STRICTATIME) + attr |= MOUNT_ATTR_STRICTATIME; + else + attr |= MOUNT_ATTR_RELATIME; + + return attr; +} + int sys_openat2(int dfd, const char *path, struct open_how *how, size_t size) { return syscall(SYS_openat2, dfd, path, how, size); @@ -312,6 +337,21 @@ int sys_move_mount(int from_dfd, const char *from_path, int to_dfd, const char * return syscall(SYS_move_mount, from_dfd, from_path, to_dfd, to_path, flags); } +int sys_fsopen(const char *fsname, unsigned flags) +{ + return syscall(SYS_fsopen, fsname, flags); +} + +int sys_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux) +{ + return syscall(SYS_fsconfig, fd, cmd, key, value, aux); +} + +int sys_fsmount(int fd, unsigned flags, unsigned attr_flags) +{ + return syscall(SYS_fsmount, fd, flags, attr_flags); +} + int sys_mount_setattr(int dfd, const char *path, unsigned flags, struct ujail_mount_attr *attr, size_t size) { return syscall(SYS_mount_setattr, dfd, path, flags, attr, size); @@ -339,7 +379,7 @@ int mask_path_now(const char *path) } else { if (mount(JAIL_NOAFILE, path, "bind", MS_BIND, NULL)) return -1; - if (mount(JAIL_NOAFILE, path, "bind", MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_RELATIME, NULL)) + if (remount_readonly(path, MS_NOSUID | MS_NOEXEC | MS_NODEV)) return -1; } @@ -424,6 +464,27 @@ static unsigned long mountinfo_current_flags(const char *path) return flags; } +/* + * Self-bind @path and remount it read-only, preserving the flags already in + * effect. Mounts copied in by unshare(CLONE_NEWNS) under a userns that does + * not own them are MNT_LOCK_{NOSUID,NODEV,NOEXEC,ATIME}; a remount clearing + * any of those fails with EPERM. + */ +int remount_readonly(const char *path, unsigned long flags) +{ + flags |= MS_REMOUNT | MS_BIND | MS_RDONLY | mountinfo_current_flags(path); + + return mount(NULL, path, NULL, flags, NULL); +} + +int bind_remount_readonly(const char *path, unsigned long flags) +{ + if (mount(path, path, "bind", MS_BIND | (flags & MS_REC), NULL)) + return -1; + + return remount_readonly(path, flags); +} + static bool fs_userns; void jail_fs_set_userns(bool enabled) @@ -471,19 +532,25 @@ static int do_mount(const char *root, const char *orig_source, const char *targe snprintf(new, sizeof(new), "%s%s", root, target?target:source); if (is_mask) { + int err; + if (stat(new, &s)) return 0; /* doesn't exists, nothing to mask */ if (S_ISDIR(s.st_mode)) {/* use empty 0-sized tmpfs for directories */ - if (mount("none", new, "tmpfs", MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_RELATIME, "size=0,mode=000")) - return error; + err = mount("none", new, "tmpfs", MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_RELATIME, "size=0,mode=000"); } else { /* mount-bind 0-sized file having mode 000 */ - if (mount(UJAIL_NOAFILE, new, "bind", MS_BIND, NULL)) - return error; + err = mount(UJAIL_NOAFILE, new, "bind", MS_BIND, NULL); + if (!err) + err = remount_readonly(new, MS_NOSUID | MS_NOEXEC | MS_NODEV); + } - if (mount(UJAIL_NOAFILE, new, "bind", MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_RELATIME, NULL)) - return error; + if (err) { + if (error) + ERROR("failed to mask %s%s: %m\n", new, + S_ISDIR(s.st_mode) ? "" : " with " UJAIL_NOAFILE); + return error; } DEBUG("masked path %s\n", new); @@ -744,6 +811,42 @@ int add_mount_fd(int fd, const char *target, int error) return 0; } +/* + * Mounting sysfs requires CAP_SYS_ADMIN in the userns owning the netns. A + * child with a userns from clone() that stays in the parent's netns cannot + * do it. Called in the parent before clone(): fsmount() every queued sysfs + * entry with its requested flags and leave the fd for do_mount_fd(). + */ +int premount_sysfs(void) +{ + struct mount *m; + + list_for_each_entry(m, &mounts_order, list) { + int fsfd, mfd; + + if (!m->filesystemtype || strcmp(m->filesystemtype, "sysfs") || + m->source_fd >= 0) + continue; + + fsfd = sys_fsopen("sysfs", FSOPEN_CLOEXEC); + if (fsfd < 0) + return -1; + if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) { + close(fsfd); + return -1; + } + mfd = sys_fsmount(fsfd, FSMOUNT_CLOEXEC, mountflags_to_attr(m->mountflags)); + close(fsfd); + if (mfd < 0) + return -1; + + m->source_fd = mfd; + DEBUG("pre-mounted sysfs for %s as fd:%d\n", m->target, mfd); + } + + return 0; +} + int add_mount_volume(const char *source, const char *target, int error) { struct mount *m; @@ -1305,26 +1408,9 @@ static int idmap_tree_fd(const char *source, int source_fd, int userns_fd, unsig return -1; } - attr.attr_set = MOUNT_ATTR_IDMAP; - if (mountflags & MS_RDONLY) - attr.attr_set |= MOUNT_ATTR_RDONLY; - if (mountflags & MS_NOSUID) - attr.attr_set |= MOUNT_ATTR_NOSUID; - if (mountflags & MS_NODEV) - attr.attr_set |= MOUNT_ATTR_NODEV; - if (mountflags & MS_NOEXEC) - attr.attr_set |= MOUNT_ATTR_NOEXEC; - if (mountflags & MS_NODIRATIME) - attr.attr_set |= MOUNT_ATTR_NODIRATIME; - if (mountflags & (MS_NOATIME | MS_RELATIME | MS_STRICTATIME)) { + attr.attr_set = MOUNT_ATTR_IDMAP | mountflags_to_attr(mountflags); + if (mountflags & (MS_NOATIME | MS_RELATIME | MS_STRICTATIME)) attr.attr_clr |= MOUNT_ATTR__ATIME; - if (mountflags & MS_NOATIME) - attr.attr_set |= MOUNT_ATTR_NOATIME; - else if (mountflags & MS_STRICTATIME) - attr.attr_set |= MOUNT_ATTR_STRICTATIME; - else - attr.attr_set |= MOUNT_ATTR_RELATIME; - } attr.userns_fd = userns_fd; if (sys_mount_setattr(treefd, "", setattr_flags, &attr, sizeof(attr)) < 0) { diff --git a/jail/fs.h b/jail/fs.h index d87a209..6f98eb4 100644 --- a/jail/fs.h +++ b/jail/fs.h @@ -44,6 +44,8 @@ int fs_mount_enable_idmap(const char *target, uint32_t uid, uint32_t gid); char *resolve_mount_source(const char *source); int add_mount_fd(int fd, const char *target, int error); int mask_path_now(const char *path); +int remount_readonly(const char *path, unsigned long flags); +int bind_remount_readonly(const char *path, unsigned long flags); /* open_tree()/mount_setattr() wrappers - no glibc wrappers yet. * Fields must match the kernel's struct mount_attr layout exactly @@ -56,6 +58,29 @@ int sys_open_tree(int dfd, const char *path, unsigned flags); int sys_move_mount(int from_dfd, const char *from_path, int to_dfd, const char *to_path, unsigned flags); int sys_mount_setattr(int dfd, const char *path, unsigned flags, struct ujail_mount_attr *attr, size_t size); +int sys_fsopen(const char *fsname, unsigned flags); +int sys_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux); +int sys_fsmount(int fd, unsigned flags, unsigned attr_flags); +int premount_sysfs(void); + +#ifndef FSOPEN_CLOEXEC +#define FSOPEN_CLOEXEC 0x00000001 +#endif +#ifndef FSMOUNT_CLOEXEC +#define FSMOUNT_CLOEXEC 0x00000001 +#endif +#ifndef FSCONFIG_CMD_CREATE +#define FSCONFIG_CMD_CREATE 6 +#endif +#ifndef MOUNT_ATTR_NOSUID +#define MOUNT_ATTR_NOSUID 0x00000002 +#endif +#ifndef MOUNT_ATTR_NODEV +#define MOUNT_ATTR_NODEV 0x00000004 +#endif +#ifndef MOUNT_ATTR_NOEXEC +#define MOUNT_ATTR_NOEXEC 0x00000008 +#endif #ifndef OPEN_TREE_CLONE #define OPEN_TREE_CLONE 1 diff --git a/jail/jail.c b/jail/jail.c index acae6dc..22b8d19 100644 --- a/jail/jail.c +++ b/jail/jail.c @@ -266,10 +266,17 @@ static char console_slave_name[64]; * Joining a namespace by path needs privilege in the user namespace owning it, * which our own user namespace would take away, so in that case it is created * after the joins instead of by clone(). crun makes the same distinction. + * + * A userns joined with -j (opts.setns.user) drops privilege the same way and + * never owns the pidns clone() created, so procfs must be mounted before + * entering it. Both cases are entered in enter_userns(), after build_jail_fs(). */ static inline bool userns_deferred(void) { - if (!(opts.namespace & CLONE_NEWUSER) || opts.setns.user != -1) + if (opts.setns.user != -1) + return true; + + if (!(opts.namespace & CLONE_NEWUSER)) return false; return (opts.setns.pid != -1) || @@ -284,6 +291,12 @@ static inline bool userns_deferred(void) false; } +/* jail root maps to a host uid: own userns or a joined one */ +static inline bool jail_has_userns(void) +{ + return (opts.namespace & CLONE_NEWUSER) || opts.setns.user != -1; +} + static inline bool has_namespaces(void) { return ((opts.setns.pid != -1) || @@ -886,7 +899,7 @@ static struct mknod_args default_devices[] = { static int prepare_jail_dev(void) { struct mknod_args **cur, *curdef; - uid_t base = (opts.namespace & CLONE_NEWUSER) ? opts.root_map_uid : 0; + uid_t base = jail_has_userns() ? opts.root_map_uid : 0; mode_t oldmask = umask(0); char path[PATH_MAX], *tmp; int consfd; @@ -1226,8 +1239,7 @@ static int mountinfo_detach_children(const char *prefix) /* * Make the mount namespace private and detach inherited /proc,/sys * children before build_jail_fs() mounts its own. Must run before - * setns_open(CLONE_NEWUSER) joins an external userns and drops - * privilege; see the call site in exec_jail(). + * enter_userns() drops privilege; see exec_jail(). */ static int isolate_mountns_and_detach_inherited(void) { @@ -1329,7 +1341,7 @@ static int build_jail_fs(void) return -1; } - jail_fs_set_userns((opts.namespace & CLONE_NEWUSER) || (opts.setns.user != -1)); + jail_fs_set_userns(jail_has_userns()); if (mount_all(jail_root, jail_dev)) { ERROR("mount_all() failed\n"); @@ -1538,6 +1550,7 @@ static void free_and_exit(int ret) exit(ret); } +static int setns_open(unsigned long nstype); static void post_jail_fs(void); static void enter_userns(void); static int userns_wait_idmaps(void); @@ -1593,19 +1606,26 @@ static int userns_wait_idmaps(void) return -1; } + return 0; +} + +/* become root in the userns just entered */ +static int userns_become_root(void) +{ if (setregid(0, 0) < 0 || setreuid(0, 0) < 0) { - ERROR("cannot become root in our user namespace: %m\n"); - return -1; - } - if (setgroups(0, NULL) < 0) { - ERROR("setgroups: %m\n"); + ERROR("cannot become root in the user namespace: %m\n"); return -1; } - if ((opts.namespace & CLONE_NEWNS) && - mount("none", "/", "none", MS_REC | MS_PRIVATE, NULL)) { - ERROR("private mount failed: %m\n"); - return -1; + if (setgroups(0, NULL) < 0) { + /* setgroups=deny is permanent once gid_map is written; only a + * joined userns can have it */ + if (errno != EPERM || opts.setns.user == -1) { + ERROR("setgroups: %m\n"); + return -1; + } + WARNING("setgroups(0, NULL) denied by the joined userns; " + "continuing without dropping supplementary groups\n"); } return 0; @@ -1618,12 +1638,25 @@ static void enter_userns(void) return; } - if (unshare(CLONE_NEWUSER)) { - ERROR("unshare(CLONE_NEWUSER) failed: %m\n"); - free_and_exit(-1); + if (opts.setns.user != -1) { + /* maps exist already, no handshake */ + int ret = setns_open(CLONE_NEWUSER); + + if (ret) { + ERROR("failed to join user namespace: %s\n", strerror(ret)); + free_and_exit(-1); + } + } else { + if (unshare(CLONE_NEWUSER)) { + ERROR("unshare(CLONE_NEWUSER) failed: %m\n"); + free_and_exit(-1); + } + + if (userns_wait_idmaps()) + free_and_exit(-1); } - if (userns_wait_idmaps()) + if (userns_become_root()) free_and_exit(-1); #ifdef CLONE_NEWTIME @@ -1860,9 +1893,7 @@ static int remount_readonly_now(const char *path) if (stat(path, &s)) return 0; /* doesn't exist, nothing to restrict */ - if (mount(path, path, "bind", MS_BIND | MS_REC, NULL)) - return -1; - if (mount(path, path, "bind", MS_REMOUNT | MS_BIND | MS_RDONLY | MS_REC, NULL)) + if (bind_remount_readonly(path, MS_REC)) return -1; DEBUG("read-only path %s\n", path); @@ -1919,10 +1950,8 @@ static void remount_proc_sys_after_unshare(void) if (opts.namespace & CLONE_NEWNET) mount("/proc/sys/net", "/proc/self/net", "bind", MS_BIND, NULL); - if (mount("/proc/sys", "/proc/sys", "bind", MS_BIND, NULL)) - return; - if (mount("/proc/sys", "/proc/sys", "bind", MS_REMOUNT | MS_BIND | MS_RDONLY, NULL)) - WARNING("could not remount /proc/sys read-only\n"); + if (bind_remount_readonly("/proc/sys", 0)) + WARNING("could not remount /proc/sys read-only: %m\n"); if (opts.namespace & CLONE_NEWNET) mount("/proc/self/net", "/proc/sys/net", "bind", MS_MOVE, NULL); @@ -2855,24 +2884,17 @@ static int exec_jail(void *arg) } /* - * Joining an external userns drops privilege immediately, so this has - * to run before it. A userns of our own owns the mount namespace it - * was created with and locks everything inherited into it, so there - * the detach neither works nor is needed. + * Must run before enter_userns() drops privilege over the inherited + * mounts. A userns from clone() owns its mntns and has everything + * inherited MNT_LOCKED, so there the detach is neither possible nor + * needed. */ - if ((opts.namespace & CLONE_NEWNS) && - (userns_deferred() || opts.setns.user != -1) && + if ((opts.namespace & CLONE_NEWNS) && userns_deferred() && isolate_mountns_and_detach_inherited()) { ERROR("failed to detach inherited mounts\n"); return EXIT_FAILURE; } - ret = setns_open(CLONE_NEWUSER); - if (ret) { - ERROR("failed to join user namespace: %s\n", strerror(ret)); - return EXIT_FAILURE; - } - buf[0] = 'i'; if (write(pipes[1], buf, 1) < 1) { ERROR("can't write to parent\n"); @@ -2894,14 +2916,15 @@ static int exec_jail(void *arg) false, recv_fds, nrecv, &extroot_idmap_fd, &overlay_idmap_fd); - if ((opts.namespace & CLONE_NEWUSER) && !userns_deferred() && - userns_wait_idmaps()) - return EXIT_FAILURE; + if ((opts.namespace & CLONE_NEWUSER) && !userns_deferred()) { + if (userns_wait_idmaps() || userns_become_root()) + return EXIT_FAILURE; - if (opts.setns.user != -1 && (opts.namespace & CLONE_NEWNS) && - unshare(CLONE_NEWNS)) { - ERROR("unshare(CLONE_NEWNS) failed: %m\n"); - return EXIT_FAILURE; + if ((opts.namespace & CLONE_NEWNS) && + mount("none", "/", "none", MS_REC | MS_PRIVATE, NULL)) { + ERROR("private mount failed: %m\n"); + return EXIT_FAILURE; + } } if (opts.namespace & CLONE_NEWCGROUP) @@ -2913,27 +2936,6 @@ static int exec_jail(void *arg) free_and_exit(EXIT_FAILURE); } - if (opts.setns.user != -1) { - if (setregid(0, 0) < 0) { - ERROR("setgid\n"); - free_and_exit(EXIT_FAILURE); - } - if (setreuid(0, 0) < 0) { - ERROR("setuid\n"); - free_and_exit(EXIT_FAILURE); - } - if (setgroups(0, NULL) < 0) { - if (errno != EPERM) { - ERROR("setgroups\n"); - free_and_exit(EXIT_FAILURE); - } - WARNING("setgroups(0, NULL) denied by the joined " - "userns (setgroups=deny is permanent once a " - "gid_map is written); continuing without " - "dropping supplementary groups\n"); - } - } - #ifdef CLONE_NEWTIME if ((opts.namespace & CLONE_NEWTIME) && opts.setns.time == -1 && !userns_deferred() && timens_create()) @@ -3090,7 +3092,7 @@ static void post_start_hook(void) /* restore securebits back to normal (and lock them if not in userns) */ if (opts.capset.apply) { - if (prctl(PR_SET_SECUREBITS, (opts.namespace & CLONE_NEWUSER)?0: + if (prctl(PR_SET_SECUREBITS, jail_has_userns() ? 0 : SECBIT_KEEP_CAPS_LOCKED|SECBIT_NO_SETUID_FIXUP_LOCKED|SECBIT_NOROOT_LOCKED)) { ERROR("prctl(PR_SET_SECUREBITS) failed: %m\n"); free_and_exit(EXIT_FAILURE); @@ -3909,6 +3911,62 @@ static int jail_join_ns(char *arg) return 0; } +/* + * Set opts.root_map_uid from a joined userns. A helper enters it with + * setns() and reports what uid 0 maps to from its own uid_map; that works + * for a namespace kept alive only by a bind-mounted nsfs file, which has no + * process to read /proc//uid_map from. The default stays on failure. + */ +static void userns_root_map(int nsfd) +{ + uint32_t inside, outside, count, uid = 0; + bool found = false; + int pfd[2], status; + pid_t pid; + FILE *f; + + if (pipe2(pfd, O_CLOEXEC)) + return; + + pid = fork(); + if (pid < 0) { + close(pfd[0]); + close(pfd[1]); + return; + } + + if (!pid) { + close(pfd[0]); + if (setns(nsfd, CLONE_NEWUSER)) + _exit(1); + f = fopen("/proc/self/uid_map", "re"); + if (!f) + _exit(1); + while (fscanf(f, "%u %u %u", &inside, &outside, &count) == 3) { + if (inside == 0 && count >= 1) { + if (write(pfd[1], &outside, sizeof(outside)) == sizeof(outside)) + _exit(0); + break; + } + } + _exit(1); + } + + close(pfd[1]); + if (read(pfd[0], &uid, sizeof(uid)) == sizeof(uid)) + found = true; + close(pfd[0]); + waitpid(pid, &status, 0); + + if (found) { + opts.root_map_uid = uid; + DEBUG("root of the joined user namespace is uid %d\n", uid); + } else { + WARNING("cannot determine the root uid of the joined user namespace; " + "assuming %d\n", opts.root_map_uid); + } +} + static void get_jail_root_user(bool is_gidmap, uint32_t container_id, uint32_t host_id, uint32_t size) { if (container_id == 0 && size >= 1) @@ -6249,9 +6307,18 @@ int main(int argc, char **argv) opts.namespace |= CLONE_NEWUTS; opts.hostname = strdup(optarg); break; - case 'j': - jail_join_ns(optarg); + case 'j': { + char *spec = strdup(optarg); + int err = jail_join_ns(optarg); + + if (err) { + ERROR("-j %s: %s\n", spec ?: optarg, strerror(err)); + free(spec); + return -1; + } + free(spec); break; + } case 'b': if (!opts.ocibundle) opts.namespace |= CLONE_NEWNS; @@ -6396,8 +6463,17 @@ int main(int argc, char **argv) } } - if (opts.namespace && !opts.ocibundle) - opts.namespace |= CLONE_NEWIPC | CLONE_NEWPID; + /* + * Not for namespaces joined via -j: clone(CLONE_NEWPID) is EINVAL + * after setns(CLONE_NEWPID), and a new ipcns would shadow the joined + * one. + */ + if (opts.namespace && !opts.ocibundle) { + if (opts.setns.ipc == -1) + opts.namespace |= CLONE_NEWIPC; + if (opts.setns.pid == -1) + opts.namespace |= CLONE_NEWPID; + } /* * env import from cmdline is not available for OCI containers @@ -6470,6 +6546,9 @@ int main(int argc, char **argv) } } + if (opts.setns.user != -1) + userns_root_map(opts.setns.user); + for (credidx = 0; credidx < n_cred_targets; credidx++) { ret = fs_mount_enable_idmap(cred_targets[credidx], opts.pw_uid > 0 ? (uint32_t)opts.pw_uid : 0, @@ -6752,7 +6831,7 @@ static void post_main(struct uloop_timeout *t) add_mount(NULL, "/dev", "tmpfs", MS_NOATIME | MS_NOEXEC | MS_NOSUID, 0, "size=1M", -1); add_mount("shm", "/dev/shm", "tmpfs", MS_NOSUID | MS_NOEXEC | MS_NODEV, 0, "mode=1777", -1); { - const char *ptsopts = (opts.namespace & CLONE_NEWUSER) ? + const char *ptsopts = jail_has_userns() ? "newinstance,ptmxmode=0666,mode=0620,gid=0" : "newinstance,ptmxmode=0666,mode=0620,gid=5"; @@ -6833,7 +6912,7 @@ static void post_main(struct uloop_timeout *t) free_and_exit(EXIT_FAILURE); } - if (opts.namespace & CLONE_NEWUSER) { + if (jail_has_userns()) { if (opts.overlaydir) { if (chown(opts.overlaydir, opts.root_map_uid, opts.root_map_uid)) { ERROR("chown(%s, %d, %d) failed: %m\n", @@ -6884,12 +6963,12 @@ static void post_main(struct uloop_timeout *t) close(parent_master); } else { console_fd = parent_master; - if ((opts.namespace & CLONE_NEWUSER) && seteuid(0)) { + if (jail_has_userns() && seteuid(0)) { ERROR("seteuid(0) failed: %m\n"); free_and_exit(EXIT_FAILURE); } pass_console(console_fd); - if ((opts.namespace & CLONE_NEWUSER) && seteuid(opts.root_map_uid)) { + if (jail_has_userns() && seteuid(opts.root_map_uid)) { ERROR("seteuid(%d) failed: %m\n", opts.root_map_uid); free_and_exit(EXIT_FAILURE); } @@ -6916,6 +6995,15 @@ static void post_main(struct uloop_timeout *t) } } + /* + * A userns from clone() cannot mount sysfs for a netns it does + * not own. Deferred/joined jails mount privileged anyway; a jail + * with its own netns can do it itself. + */ + if ((opts.namespace & CLONE_NEWUSER) && !userns_deferred() && + !(opts.namespace & CLONE_NEWNET) && premount_sysfs()) + WARNING("cannot mount sysfs for the jail: %m\n"); + prime_jail_mount(opts.extroot); prime_jail_mount(opts.overlaydir); for (size_t i = 0; i < (size_t)num_volume_sources; i++)