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
4233 struct user_namespace *user_ns, struct fs_struct *new_fs)4233 struct user_namespace *user_ns, struct fs_struct *new_fs)
4234{4234{
4235 struct mnt_namespace *new_ns;4235 struct mnt_namespace *new_ns;
4236 struct vfsmount *rootmnt __free(mntput) = NULL;4236 struct path old_root __free(path_put) = {};
4237 struct vfsmount *pwdmnt __free(mntput) = NULL;4237 struct path old_pwd __free(path_put) = {};
4238 struct mount *p, *q;4238 struct mount *p, *q;
4239 struct mount *old;4239 struct mount *old;
4240 struct mount *new;4240 struct mount *new;
@@ -4254,11 +4254,18 @@ struct mnt_namespace *copy_mnt_ns(u64 flags, struct mnt_namespace *ns, · 13 unmodified lines
4254 return new_ns;4254 return new_ns;
4255 4255
4256 guard(namespace_excl)();4256 guard(namespace_excl)();
4257 /* First pass: copy the tree topology */4257
4258 copy_flags = CL_COPY_UNBINDABLE | CL_EXPIRE;4258 if (flags & CLONE_EMPTY_MNTNS)
4259 copy_flags = 0;
4260 else
4261 copy_flags = CL_COPY_UNBINDABLE | CL_EXPIRE;
4259 if (user_ns != ns->user_ns)4262 if (user_ns != ns->user_ns)
4260 copy_flags |= CL_SLAVE;4263 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);
4262 if (IS_ERR(new)) {4269 if (IS_ERR(new)) {
4263 emptied_ns = new_ns;4270 emptied_ns = new_ns;
4264 return ERR_CAST(new);4271 return ERR_CAST(new);
@@ -4269,33 +4276,53 @@ struct mnt_namespace *copy_mnt_ns(u64 flags, struct mnt_namespace *ns, · 4 unmodified lines
4269 }4276 }
4270 new_ns->root = new;4277 new_ns->root = new;
4271 4278
4272 /*4279 if (flags & CLONE_EMPTY_MNTNS) {
4273 * Second pass: switch the tsk->fs->* elements and mark new vfsmounts4280 /*
4274 * as belonging to new namespace. We have already acquired a private4281 * Empty mount namespace: only the root mount exists.
4275 * fs_struct, so tsk->fs->lock is not needed.4282 * Reset root and pwd to the cloned mount's root dentry.
4276 */4283 */
4277 p = old;
4278 q = new;
4279 while (p) {
4280 mnt_add_to_ns(new_ns, q);
4281 new_ns->nr_mounts++;
4282 if (new_fs) {4284 if (new_fs) {
4283 if (&p->mnt == new_fs->root.mnt) {4285 old_root = new_fs->root;
4284 new_fs->root.mnt = mntget(&q->mnt);4286 old_pwd = new_fs->pwd;
4285 rootmnt = &p->mnt;4287
4286 }4288 new_fs->root.mnt = mntget(&new->mnt);
4287 if (&p->mnt == new_fs->pwd.mnt) {4289 new_fs->root.dentry = dget(new->mnt.mnt_root);
4288 new_fs->pwd.mnt = mntget(&q->mnt);4290
4289 pwdmnt = &p->mnt;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 }
4290 }4317 }
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);
4291 }4325 }
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);
4299 }4326 }
4300 ns_tree_add_raw(new_ns);4327 ns_tree_add_raw(new_ns);
4301 return new_ns;4328 return new_ns;
include/uapi/linux/sched.h+7−0
@@ -36,6 +36,7 @@ · 35 unmodified lines
36/* Flags for the clone3() syscall. */36/* Flags for the clone3() syscall. */
37#define CLONE_CLEAR_SIGHAND 0x100000000ULL /* Clear any signal handler and reset to SIG_DFL. */37#define CLONE_CLEAR_SIGHAND 0x100000000ULL /* Clear any signal handler and reset to SIG_DFL. */
38#define CLONE_INTO_CGROUP 0x200000000ULL /* Clone into a specific cgroup given the right permissions. */38#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. */
39 40
40/*41/*
41 * cloning flags intersect with CSIGNAL so can be used with unshare and clone342 * cloning flags intersect with CSIGNAL so can be used with unshare and clone3
@@ -43,6 +44,12 @@ · 1 unmodified line
43 */44 */
44#define CLONE_NEWTIME 0x00000080 /* New time namespace */45#define CLONE_NEWTIME 0x00000080 /* New time namespace */
45 46
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
46#ifndef __ASSEMBLY__53#ifndef __ASSEMBLY__
47/**54/**
48 * struct clone_args - arguments for the clone3 syscall55 * 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
2619 int trace = 0;2619 int trace = 0;
2620 pid_t nr;2620 pid_t nr;
2621 2621
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
2622 /*2632 /*
2623 * For legacy clone() calls, CLONE_PIDFD uses the parent_tid argument2633 * For legacy clone() calls, CLONE_PIDFD uses the parent_tid argument
2624 * to return the pidfd. Hence, CLONE_PIDFD and CLONE_PARENT_SETTID are2634 * 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
2897{2907{
2898 /* Verify that no unknown flags are passed along. */2908 /* Verify that no unknown flags are passed along. */
2899 if (kargs->flags &2909 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))
2901 return false;2912 return false;
2902 2913
2903 /*2914 /*
@@ -3050,7 +3061,7 @@ static int check_unshare_flags(unsigned long unshare_flags) · 146 unmodified lines
3050 CLONE_VM|CLONE_FILES|CLONE_SYSVSEM|3061 CLONE_VM|CLONE_FILES|CLONE_SYSVSEM|
3051 CLONE_NEWUTS|CLONE_NEWIPC|CLONE_NEWNET|3062 CLONE_NEWUTS|CLONE_NEWIPC|CLONE_NEWNET|
3052 CLONE_NEWUSER|CLONE_NEWPID|CLONE_NEWCGROUP|3063 CLONE_NEWUSER|CLONE_NEWPID|CLONE_NEWCGROUP|
3053 CLONE_NEWTIME))3064 CLONE_NEWTIME | UNSHARE_EMPTY_MNTNS))
3054 return -EINVAL;3065 return -EINVAL;
3055 /*3066 /*
3056 * Not implemented, but pretend it works if there is nothing3067 * Not implemented, but pretend it works if there is nothing
@@ -3149,6 +3160,8 @@ int ksys_unshare(unsigned long unshare_flags) · 92 unmodified lines
3149 /*3160 /*
3150 * If unsharing namespace, must also unshare filesystem information.3161 * If unsharing namespace, must also unshare filesystem information.
3151 */3162 */
3163 if (unshare_flags & UNSHARE_EMPTY_MNTNS)
3164 unshare_flags |= CLONE_NEWNS;
3152 if (unshare_flags & CLONE_NEWNS)3165 if (unshare_flags & CLONE_NEWNS)
3153 unshare_flags |= CLONE_FS;3166 unshare_flags |= CLONE_FS;
3154 3167
kernel/nsproxy.c+16−5
@@ -95,7 +95,8 @@ static struct nsproxy *create_new_namespaces(u64 flags, · 94 unmodified lines
95 if (!new_nsp)95 if (!new_nsp)
96 return ERR_PTR(-ENOMEM);96 return ERR_PTR(-ENOMEM);
97 97
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);
99 if (IS_ERR(new_nsp->mnt_ns)) {100 if (IS_ERR(new_nsp->mnt_ns)) {
100 err = PTR_ERR(new_nsp->mnt_ns);101 err = PTR_ERR(new_nsp->mnt_ns);
101 goto out_ns;102 goto out_ns;
@@ -212,18 +213,28 @@ int unshare_nsproxy_namespaces(unsigned long unshare_flags, · 110 unmodified lines
212 struct nsproxy **new_nsp, struct cred *new_cred, struct fs_struct *new_fs)213 struct nsproxy **new_nsp, struct cred *new_cred, struct fs_struct *new_fs)
213{214{
214 struct user_namespace *user_ns;215 struct user_namespace *user_ns;
216 u64 flags = unshare_flags;
215 int err = 0;217 int err = 0;
216 218
217 if (!(unshare_flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |219 if (!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
218 CLONE_NEWNET | CLONE_NEWPID | CLONE_NEWCGROUP |220 CLONE_NEWNET | CLONE_NEWPID | CLONE_NEWCGROUP |
219 CLONE_NEWTIME)))221 CLONE_NEWTIME)))
220 return 0;222 return 0;
221 223
222 user_ns = new_cred ? new_cred->user_ns : current_user_ns();224 user_ns = new_cred ? new_cred->user_ns : current_user_ns();
223 if (!ns_capable(user_ns, CAP_SYS_ADMIN))225 if (!ns_capable(user_ns, CAP_SYS_ADMIN))
224 return -EPERM;226 return -EPERM;
225 227
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,
227 new_fs ? new_fs : current->fs);238 new_fs ? new_fs : current->fs);
228 if (IS_ERR(*new_nsp)) {239 if (IS_ERR(*new_nsp)) {
229 err = PTR_ERR(*new_nsp);240 err = PTR_ERR(*new_nsp);