History

namespace: allow creating empty mount namespaces

Add support for creating a mount namespace that contains only a copy of the root mount from the caller's mount namespace, with none of the child mounts. This is useful for containers and sandboxes that want to start with a minimal mount table and populate it from scratch rather than inheriting and then tearing down the full mount tree. Two new flags are introduced: - CLONE_EMPTY_MNTNS for clone3(), using the 64-bit flag space. - UNSHARE_EMPTY_MNTNS for unshare(), reusing the CLONE_PARENT_SETTID bit which has no meaning for unshare. Both flags imply CLONE_NEWNS. For the unshare path, UNSHARE_EMPTY_MNTNS is converted to CLONE_EMPTY_MNTNS in unshare_nsproxy_namespaces() before it reaches copy_mnt_ns(), so the mount namespace code only needs to handle a single flag. In copy_mnt_ns(), when CLONE_EMPTY_MNTNS is set, clone_mnt() is used instead of copy_tree() to clone only the root mount. The caller's root and working directory are both reset to the root dentry of the new mount. The cleanup variables are changed from vfsmount pointers with __free(mntput) to struct path with __free(path_put) because the empty mount namespace path needs to release both mount and dentry references when replacing the caller's root and pwd. In the normal (non-empty) path only the mount component is set, and dput(NULL) is a no-op so path_put remains correct there as well.

Christian Brauner committed 8 months ago

Commit 9d4e7521 parent 0209e314 files · +94 −36Browse files

Unified · Split

4 files
  1. fs/namespace.c +56 −29
  2. include/uapi/linux/sched.h +7 −0
  3. kernel/fork.c +15 −2
  4. kernel/nsproxy.c +16 −5
fs/namespace.c+56−29
@@ -4233,8 +4233,8 @@ struct mnt_namespace *copy_mnt_ns(u64 flags, struct mnt_namespace *ns, · 4232 unmodified lines
42334233 struct user_namespace *user_ns, struct fs_struct *new_fs)
42344234 {
42354235 struct mnt_namespace *new_ns;
4236− struct vfsmount *rootmnt __free(mntput) = NULL;
4237− struct vfsmount *pwdmnt __free(mntput) = NULL;
4236+ struct path old_root __free(path_put) = {};
4237+ struct path old_pwd __free(path_put) = {};
42384238 struct mount *p, *q;
42394239 struct mount *old;
42404240 struct mount *new;
@@ -4254,11 +4254,18 @@ struct mnt_namespace *copy_mnt_ns(u64 flags, struct mnt_namespace *ns, · 13 unmodified lines
42544254 return new_ns;
42554255
42564256 guard(namespace_excl)();
4257− /* First pass: copy the tree topology */
4258− copy_flags = CL_COPY_UNBINDABLE | CL_EXPIRE;
4257+
4258+ if (flags & CLONE_EMPTY_MNTNS)
4259+ copy_flags = 0;
4260+ else
4261+ copy_flags = CL_COPY_UNBINDABLE | CL_EXPIRE;
42594262 if (user_ns != ns->user_ns)
42604263 copy_flags |= CL_SLAVE;
4261− new = copy_tree(old, old->mnt.mnt_root, copy_flags);
4264+
4265+ if (flags & CLONE_EMPTY_MNTNS)
4266+ new = clone_mnt(old, old->mnt.mnt_root, copy_flags);
4267+ else
4268+ new = copy_tree(old, old->mnt.mnt_root, copy_flags);
42624269 if (IS_ERR(new)) {
42634270 emptied_ns = new_ns;
42644271 return ERR_CAST(new);
@@ -4269,33 +4276,53 @@ struct mnt_namespace *copy_mnt_ns(u64 flags, struct mnt_namespace *ns, · 4 unmodified lines
42694276 }
42704277 new_ns->root = new;
42714278
4272− /*
4273− * Second pass: switch the tsk->fs->* elements and mark new vfsmounts
4274− * as belonging to new namespace. We have already acquired a private
4275− * fs_struct, so tsk->fs->lock is not needed.
4276− */
4277− p = old;
4278− q = new;
4279− while (p) {
4280− mnt_add_to_ns(new_ns, q);
4281− new_ns->nr_mounts++;
4279+ if (flags & CLONE_EMPTY_MNTNS) {
4280+ /*
4281+ * Empty mount namespace: only the root mount exists.
4282+ * Reset root and pwd to the cloned mount's root dentry.
4283+ */
42824284 if (new_fs) {
4283− if (&p->mnt == new_fs->root.mnt) {
4284− new_fs->root.mnt = mntget(&q->mnt);
4285− rootmnt = &p->mnt;
4286− }
4287− if (&p->mnt == new_fs->pwd.mnt) {
4288− new_fs->pwd.mnt = mntget(&q->mnt);
4289− pwdmnt = &p->mnt;
4285+ old_root = new_fs->root;
4286+ old_pwd = new_fs->pwd;
4287+
4288+ new_fs->root.mnt = mntget(&new->mnt);
4289+ new_fs->root.dentry = dget(new->mnt.mnt_root);
4290+
4291+ new_fs->pwd.mnt = mntget(&new->mnt);
4292+ new_fs->pwd.dentry = dget(new->mnt.mnt_root);
4293+ }
4294+ mnt_add_to_ns(new_ns, new);
4295+ new_ns->nr_mounts++;
4296+ } else {
4297+ /*
4298+ * Full copy: walk old and new trees in parallel, switching
4299+ * the tsk->fs->* elements and marking new vfsmounts as
4300+ * belonging to new namespace. We have already acquired a
4301+ * private fs_struct, so tsk->fs->lock is not needed.
4302+ */
4303+ p = old;
4304+ q = new;
4305+ while (p) {
4306+ mnt_add_to_ns(new_ns, q);
4307+ new_ns->nr_mounts++;
4308+ if (new_fs) {
4309+ if (&p->mnt == new_fs->root.mnt) {
4310+ old_root.mnt = new_fs->root.mnt;
4311+ new_fs->root.mnt = mntget(&q->mnt);
4312+ }
4313+ if (&p->mnt == new_fs->pwd.mnt) {
4314+ old_pwd.mnt = new_fs->pwd.mnt;
4315+ new_fs->pwd.mnt = mntget(&q->mnt);
4316+ }
42904317 }
4318+ p = next_mnt(p, old);
4319+ q = next_mnt(q, new);
4320+ if (!q)
4321+ break;
4322+ // an mntns binding we'd skipped?
4323+ while (p->mnt.mnt_root != q->mnt.mnt_root)
4324+ p = next_mnt(skip_mnt_tree(p), old);
42914325 }
4292− p = next_mnt(p, old);
4293− q = next_mnt(q, new);
4294− if (!q)
4295− break;
4296− // an mntns binding we'd skipped?
4297− while (p->mnt.mnt_root != q->mnt.mnt_root)
4298− p = next_mnt(skip_mnt_tree(p), old);
42994326 }
43004327 ns_tree_add_raw(new_ns);
43014328 return new_ns;
include/uapi/linux/sched.h+7−0
@@ -36,6 +36,7 @@ · 35 unmodified lines
3636 /* Flags for the clone3() syscall. */
3737 #define CLONE_CLEAR_SIGHAND 0x100000000ULL /* Clear any signal handler and reset to SIG_DFL. */
3838 #define CLONE_INTO_CGROUP 0x200000000ULL /* Clone into a specific cgroup given the right permissions. */
39+#define CLONE_EMPTY_MNTNS (1ULL << 37) /* Create an empty mount namespace. */
3940
4041 /*
4142 * cloning flags intersect with CSIGNAL so can be used with unshare and clone3
@@ -43,6 +44,12 @@ · 1 unmodified line
4344 */
4445 #define CLONE_NEWTIME 0x00000080 /* New time namespace */
4546
47+/*
48+ * unshare flags share the bit space with clone flags but only apply to the
49+ * unshare syscall:
50+ */
51+#define UNSHARE_EMPTY_MNTNS 0x00100000 /* Unshare an empty mount namespace. */
52+
4653 #ifndef __ASSEMBLY__
4754 /**
4855 * struct clone_args - arguments for the clone3 syscall
kernel/fork.c+15−2
@@ -2619,6 +2619,16 @@ pid_t kernel_clone(struct kernel_clone_args *args) · 2618 unmodified lines
26192619 int trace = 0;
26202620 pid_t nr;
26212621
2622+ /*
2623+ * Creating an empty mount namespace implies creating a new mount
2624+ * namespace. Set this before copy_process() so that the
2625+ * CLONE_NEWNS|CLONE_FS mutual exclusion check works correctly.
2626+ */
2627+ if (clone_flags & CLONE_EMPTY_MNTNS) {
2628+ clone_flags |= CLONE_NEWNS;
2629+ args->flags = clone_flags;
2630+ }
2631+
26222632 /*
26232633 * For legacy clone() calls, CLONE_PIDFD uses the parent_tid argument
26242634 * to return the pidfd. Hence, CLONE_PIDFD and CLONE_PARENT_SETTID are
@@ -2897,7 +2907,8 @@ static bool clone3_args_valid(struct kernel_clone_args *kargs) · 272 unmodified lines
28972907 {
28982908 /* Verify that no unknown flags are passed along. */
28992909 if (kargs->flags &
2900− ~(CLONE_LEGACY_FLAGS | CLONE_CLEAR_SIGHAND | CLONE_INTO_CGROUP))
2910+ ~(CLONE_LEGACY_FLAGS | CLONE_CLEAR_SIGHAND |
2911+ CLONE_INTO_CGROUP | CLONE_EMPTY_MNTNS))
29012912 return false;
29022913
29032914 /*
@@ -3050,7 +3061,7 @@ static int check_unshare_flags(unsigned long unshare_flags) · 146 unmodified lines
30503061 CLONE_VM|CLONE_FILES|CLONE_SYSVSEM|
30513062 CLONE_NEWUTS|CLONE_NEWIPC|CLONE_NEWNET|
30523063 CLONE_NEWUSER|CLONE_NEWPID|CLONE_NEWCGROUP|
3053− CLONE_NEWTIME))
3064+ CLONE_NEWTIME | UNSHARE_EMPTY_MNTNS))
30543065 return -EINVAL;
30553066 /*
30563067 * Not implemented, but pretend it works if there is nothing
@@ -3149,6 +3160,8 @@ int ksys_unshare(unsigned long unshare_flags) · 92 unmodified lines
31493160 /*
31503161 * If unsharing namespace, must also unshare filesystem information.
31513162 */
3163+ if (unshare_flags & UNSHARE_EMPTY_MNTNS)
3164+ unshare_flags |= CLONE_NEWNS;
31523165 if (unshare_flags & CLONE_NEWNS)
31533166 unshare_flags |= CLONE_FS;
31543167
kernel/nsproxy.c+16−5
@@ -95,7 +95,8 @@ static struct nsproxy *create_new_namespaces(u64 flags, · 94 unmodified lines
9595 if (!new_nsp)
9696 return ERR_PTR(-ENOMEM);
9797
98− new_nsp->mnt_ns = copy_mnt_ns(flags, tsk->nsproxy->mnt_ns, user_ns, new_fs);
98+ new_nsp->mnt_ns = copy_mnt_ns(flags, tsk->nsproxy->mnt_ns,
99+ user_ns, new_fs);
99100 if (IS_ERR(new_nsp->mnt_ns)) {
100101 err = PTR_ERR(new_nsp->mnt_ns);
101102 goto out_ns;
@@ -212,18 +213,28 @@ int unshare_nsproxy_namespaces(unsigned long unshare_flags, · 110 unmodified lines
212213 struct nsproxy **new_nsp, struct cred *new_cred, struct fs_struct *new_fs)
213214 {
214215 struct user_namespace *user_ns;
216+ u64 flags = unshare_flags;
215217 int err = 0;
216218
217− if (!(unshare_flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
218− CLONE_NEWNET | CLONE_NEWPID | CLONE_NEWCGROUP |
219− CLONE_NEWTIME)))
219+ if (!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
220+ CLONE_NEWNET | CLONE_NEWPID | CLONE_NEWCGROUP |
221+ CLONE_NEWTIME)))
220222 return 0;
221223
222224 user_ns = new_cred ? new_cred->user_ns : current_user_ns();
223225 if (!ns_capable(user_ns, CAP_SYS_ADMIN))
224226 return -EPERM;
225227
226− *new_nsp = create_new_namespaces(unshare_flags, current, user_ns,
228+ /*
229+ * Convert the 32-bit UNSHARE_EMPTY_MNTNS (which aliases
230+ * CLONE_PARENT_SETTID) to the unique 64-bit CLONE_EMPTY_MNTNS.
231+ */
232+ if (flags & UNSHARE_EMPTY_MNTNS) {
233+ flags &= ~(u64)UNSHARE_EMPTY_MNTNS;
234+ flags |= CLONE_EMPTY_MNTNS;
235+ }
236+
237+ *new_nsp = create_new_namespaces(flags, current, user_ns,
227238 new_fs ? new_fs : current->fs);
228239 if (IS_ERR(*new_nsp)) {
229240 err = PTR_ERR(*new_nsp);