Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge tag 'vfs-6.15-rc1.mount.namespace' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull vfs mount namespace updates from Christian Brauner:
"This expands the ability of anonymous mount namespaces:

- Creating detached mounts from detached mounts

Currently, detached mounts can only be created from attached
mounts. This limitaton prevents various use-cases. For example, the
ability to mount a subdirectory without ever having to make the
whole filesystem visible first.

The current permission modelis:

(1) Check that the caller is privileged over the owning user
namespace of it's current mount namespace.

(2) Check that the caller is located in the mount namespace of the
mount it wants to create a detached copy of.

While it is not strictly necessary to do it this way it is
consistently applied in the new mount api. This model will also be
used when allowing the creation of detached mount from another
detached mount.

The (1) requirement can simply be met by performing the same check
as for the non-detached case, i.e., verify that the caller is
privileged over its current mount namespace.

To meet the (2) requirement it must be possible to infer the origin
mount namespace that the anonymous mount namespace of the detached
mount was created from.

The origin mount namespace of an anonymous mount is the mount
namespace that the mounts that were copied into the anonymous mount
namespace originate from.

In order to check the origin mount namespace of an anonymous mount
namespace the sequence number of the original mount namespace is
recorded in the anonymous mount namespace.

With this in place it is possible to perform an equivalent check
(2') to (2). The origin mount namespace of the anonymous mount
namespace must be the same as the caller's mount namespace. To
establish this the sequence number of the caller's mount namespace
and the origin sequence number of the anonymous mount namespace are
compared.

The caller is always located in a non-anonymous mount namespace
since anonymous mount namespaces cannot be setns()ed into. The
caller's mount namespace will thus always have a valid sequence
number.

The owning namespace of any mount namespace, anonymous or
non-anonymous, can never change. A mount attached to a
non-anonymous mount namespace can never change mount namespace.

If the sequence number of the non-anonymous mount namespace and the
origin sequence number of the anonymous mount namespace match, the
owning namespaces must match as well.

Hence, the capability check on the owning namespace of the caller's
mount namespace ensures that the caller has the ability to copy the
mount tree.

- Allow mount detached mounts on detached mounts

Currently, detached mounts can only be mounted onto attached
mounts. This limitation makes it impossible to assemble a new
private rootfs and move it into place. Instead, a detached tree
must be created, attached, then mounted open and then either moved
or detached again. Lift this restriction.

In order to allow mounting detached mounts onto other detached
mounts the same permission model used for creating detached mounts
from detached mounts can be used (cf. above).

Allowing to mount detached mounts onto detached mounts leaves three
cases to consider:

(1) The source mount is an attached mount and the target mount is
a detached mount. This would be equivalent to moving a mount
between different mount namespaces. A caller could move an
attached mount to a detached mount. The detached mount can now
be freely attached to any mount namespace. This changes the
current delegatioh model significantly for no good reason. So
this will fail.

(2) Anonymous mount namespaces are always attached fully, i.e., it
is not possible to only attach a subtree of an anoymous mount
namespace. This simplifies the implementation and reasoning.

Consequently, if the anonymous mount namespace of the source
detached mount and the target detached mount are the identical
the mount request will fail.

(3) The source mount's anonymous mount namespace is different from
the target mount's anonymous mount namespace.

In this case the source anonymous mount namespace of the
source mount tree must be freed after its mounts have been
moved to the target anonymous mount namespace. The source
anonymous mount namespace must be empty afterwards.

By allowing to mount detached mounts onto detached mounts a caller
may do the following:

fd_tree1 = open_tree(-EBADF, "/mnt", OPEN_TREE_CLONE)
fd_tree2 = open_tree(-EBADF, "/tmp", OPEN_TREE_CLONE)

fd_tree1 and fd_tree2 refer to two different detached mount trees
that belong to two different anonymous mount namespace.

It is important to note that fd_tree1 and fd_tree2 both refer to
the root of their respective anonymous mount namespaces.

By allowing to mount detached mounts onto detached mounts the
caller may now do:

move_mount(fd_tree1, "", fd_tree2, "",
MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH)

This will cause the detached mount referred to by fd_tree1 to be
mounted on top of the detached mount referred to by fd_tree2.

Thus, the detached mount fd_tree1 is moved from its separate
anonymous mount namespace into fd_tree2's anonymous mount
namespace.

It also means that while fd_tree2 continues to refer to the root of
its respective anonymous mount namespace fd_tree1 doesn't anymore.

This has the consequence that only fd_tree2 can be moved to another
anonymous or non-anonymous mount namespace. Moving fd_tree1 will
now fail as fd_tree1 doesn't refer to the root of an anoymous mount
namespace anymore.

Now fd_tree1 and fd_tree2 refer to separate detached mount trees
referring to the same anonymous mount namespace.

This is conceptually fine. The new mount api does allow for this to
happen already via:

mount -t tmpfs tmpfs /mnt
mkdir -p /mnt/A
mount -t tmpfs tmpfs /mnt/A

fd_tree3 = open_tree(-EBADF, "/mnt", OPEN_TREE_CLONE | AT_RECURSIVE)
fd_tree4 = open_tree(-EBADF, "/mnt/A", 0)

Both fd_tree3 and fd_tree4 refer to two different detached mount
trees but both detached mount trees refer to the same anonymous
mount namespace. An as with fd_tree1 and fd_tree2, only fd_tree3
may be moved another mount namespace as fd_tree3 refers to the root
of the anonymous mount namespace just while fd_tree4 doesn't.

However, there's an important difference between the
fd_tree3/fd_tree4 and the fd_tree1/fd_tree2 example.

Closing fd_tree4 and releasing the respective struct file will have
no further effect on fd_tree3's detached mount tree.

However, closing fd_tree3 will cause the mount tree and the
respective anonymous mount namespace to be destroyed causing the
detached mount tree of fd_tree4 to be invalid for further mounting.

By allowing to mount detached mounts on detached mounts as in the
fd_tree1/fd_tree2 example both struct files will affect each other.

Both fd_tree1 and fd_tree2 refer to struct files that have
FMODE_NEED_UNMOUNT set.

To handle this we use the fact that @fd_tree1 will have a parent
mount once it has been attached to @fd_tree2.

When dissolve_on_fput() is called the mount that has been passed in
will refer to the root of the anonymous mount namespace. If it
doesn't it would mean that mounts are leaked. So before allowing to
mount detached mounts onto detached mounts this would be a bug.

Now that detached mounts can be mounted onto detached mounts it
just means that the mount has been attached to another anonymous
mount namespace and thus dissolve_on_fput() must not unmount the
mount tree or free the anonymous mount namespace as the file
referring to the root of the namespace hasn't been closed yet.

If it had been closed yet it would be obvious because the mount
namespace would be NULL, i.e., the @fd_tree1 would have already
been unmounted. If @fd_tree1 hasn't been unmounted yet and has a
parent mount it is safe to skip any cleanup as closing @fd_tree2
will take care of all cleanup operations.

- Allow mount propagation for detached mount trees

In commit ee2e3f50629f ("mount: fix mounting of detached mounts
onto targets that reside on shared mounts") I fixed a bug where
propagating the source mount tree of an anonymous mount namespace
into a target mount tree of a non-anonymous mount namespace could
be used to trigger an integer overflow in the non-anonymous mount
namespace causing any new mounts to fail.

The cause of this was that the propagation algorithm was unable to
recognize mounts from the source mount tree that were already
propagated into the target mount tree and then reappeared as
propagation targets when walking the destination propagation mount
tree.

When fixing this I disabled mount propagation into anonymous mount
namespaces. Make it possible for anonymous mount namespace to
receive mount propagation events correctly. This is now also a
correctness issue now that we allow mounting detached mount trees
onto detached mount trees.

Mark the source anonymous mount namespace with MNTNS_PROPAGATING
indicating that all mounts belonging to this mount namespace are
currently in the process of being propagated and make the
propagation algorithm discard those if they appear as propagation
targets"

* tag 'vfs-6.15-rc1.mount.namespace' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (21 commits)
selftests: test subdirectory mounting
selftests: add test for detached mount tree propagation
fs: namespace: fix uninitialized variable use
mount: handle mount propagation for detached mount trees
fs: allow creating detached mounts from fsmount() file descriptors
selftests: seventh test for mounting detached mounts onto detached mounts
selftests: sixth test for mounting detached mounts onto detached mounts
selftests: fifth test for mounting detached mounts onto detached mounts
selftests: fourth test for mounting detached mounts onto detached mounts
selftests: third test for mounting detached mounts onto detached mounts
selftests: second test for mounting detached mounts onto detached mounts
selftests: first test for mounting detached mounts onto detached mounts
fs: mount detached mounts onto detached mounts
fs: support getname_maybe_null() in move_mount()
selftests: create detached mounts from detached mounts
fs: create detached mounts from detached mounts
fs: add may_copy_tree()
fs: add fastpath for dissolve_on_fput()
fs: add assert for move_mount()
fs: add mnt_ns_empty() helper
...

+977 -66
+11
fs/mount.h
··· 7 7 8 8 extern struct list_head notify_list; 9 9 10 + typedef __u32 __bitwise mntns_flags_t; 11 + 12 + #define MNTNS_PROPAGATING ((__force mntns_flags_t)(1 << 0)) 13 + 10 14 struct mnt_namespace { 11 15 struct ns_common ns; 12 16 struct mount * root; ··· 26 22 wait_queue_head_t poll; 27 23 struct rcu_head mnt_ns_rcu; 28 24 }; 25 + u64 seq_origin; /* Sequence number of origin mount namespace */ 29 26 u64 event; 30 27 #ifdef CONFIG_FSNOTIFY 31 28 __u32 n_fsnotify_mask; ··· 37 32 struct rb_node mnt_ns_tree_node; /* node in the mnt_ns_tree */ 38 33 struct list_head mnt_ns_list; /* entry in the sequential list of mounts namespace */ 39 34 refcount_t passive; /* number references not pinning @mounts */ 35 + mntns_flags_t mntns_flags; 40 36 } __randomize_layout; 41 37 42 38 struct mnt_pcp { ··· 168 162 static inline bool mnt_ns_attached(const struct mount *mnt) 169 163 { 170 164 return !RB_EMPTY_NODE(&mnt->mnt_node); 165 + } 166 + 167 + static inline bool mnt_ns_empty(const struct mnt_namespace *ns) 168 + { 169 + return RB_EMPTY_ROOT(&ns->mounts); 171 170 } 172 171 173 172 static inline void move_from_ns(struct mount *mnt, struct list_head *dt_list)
+307 -60
fs/namespace.c
··· 1007 1007 return mnt->mnt_ns == current->nsproxy->mnt_ns; 1008 1008 } 1009 1009 1010 + static inline bool check_anonymous_mnt(struct mount *mnt) 1011 + { 1012 + u64 seq; 1013 + 1014 + if (!is_anon_ns(mnt->mnt_ns)) 1015 + return false; 1016 + 1017 + seq = mnt->mnt_ns->seq_origin; 1018 + return !seq || (seq == current->nsproxy->mnt_ns->seq); 1019 + } 1020 + 1010 1021 /* 1011 1022 * vfsmount lock must be held for write 1012 1023 */ ··· 2345 2334 static void free_mnt_ns(struct mnt_namespace *); 2346 2335 static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *, bool); 2347 2336 2337 + static inline bool must_dissolve(struct mnt_namespace *mnt_ns) 2338 + { 2339 + /* 2340 + * This mount belonged to an anonymous mount namespace 2341 + * but was moved to a non-anonymous mount namespace and 2342 + * then unmounted. 2343 + */ 2344 + if (unlikely(!mnt_ns)) 2345 + return false; 2346 + 2347 + /* 2348 + * This mount belongs to a non-anonymous mount namespace 2349 + * and we know that such a mount can never transition to 2350 + * an anonymous mount namespace again. 2351 + */ 2352 + if (!is_anon_ns(mnt_ns)) { 2353 + /* 2354 + * A detached mount either belongs to an anonymous mount 2355 + * namespace or a non-anonymous mount namespace. It 2356 + * should never belong to something purely internal. 2357 + */ 2358 + VFS_WARN_ON_ONCE(mnt_ns == MNT_NS_INTERNAL); 2359 + return false; 2360 + } 2361 + 2362 + return true; 2363 + } 2364 + 2348 2365 void dissolve_on_fput(struct vfsmount *mnt) 2349 2366 { 2350 2367 struct mnt_namespace *ns; 2351 - namespace_lock(); 2352 - lock_mount_hash(); 2353 - ns = real_mount(mnt)->mnt_ns; 2354 - if (ns) { 2355 - if (is_anon_ns(ns)) 2356 - umount_tree(real_mount(mnt), UMOUNT_CONNECTED); 2357 - else 2358 - ns = NULL; 2368 + struct mount *m = real_mount(mnt); 2369 + 2370 + scoped_guard(rcu) { 2371 + if (!must_dissolve(READ_ONCE(m->mnt_ns))) 2372 + return; 2359 2373 } 2360 - unlock_mount_hash(); 2361 - namespace_unlock(); 2362 - if (ns) 2363 - free_mnt_ns(ns); 2374 + 2375 + scoped_guard(rwsem_write, &namespace_sem) { 2376 + ns = m->mnt_ns; 2377 + if (!must_dissolve(ns)) 2378 + return; 2379 + 2380 + /* 2381 + * After must_dissolve() we know that this is a detached 2382 + * mount in an anonymous mount namespace. 2383 + * 2384 + * Now when mnt_has_parent() reports that this mount 2385 + * tree has a parent, we know that this anonymous mount 2386 + * tree has been moved to another anonymous mount 2387 + * namespace. 2388 + * 2389 + * So when closing this file we cannot unmount the mount 2390 + * tree. This will be done when the file referring to 2391 + * the root of the anonymous mount namespace will be 2392 + * closed (It could already be closed but it would sync 2393 + * on @namespace_sem and wait for us to finish.). 2394 + */ 2395 + if (mnt_has_parent(m)) 2396 + return; 2397 + 2398 + lock_mount_hash(); 2399 + umount_tree(m, UMOUNT_CONNECTED); 2400 + unlock_mount_hash(); 2401 + } 2402 + 2403 + /* Make sure we notice when we leak mounts. */ 2404 + VFS_WARN_ON_ONCE(!mnt_ns_empty(ns)); 2405 + free_mnt_ns(ns); 2364 2406 } 2365 2407 2366 2408 void drop_collected_mounts(struct vfsmount *mnt) ··· 2606 2542 enum mnt_tree_flags_t { 2607 2543 MNT_TREE_MOVE = BIT(0), 2608 2544 MNT_TREE_BENEATH = BIT(1), 2545 + MNT_TREE_PROPAGATION = BIT(2), 2609 2546 }; 2610 2547 2611 2548 /** ··· 2957 2892 return err; 2958 2893 } 2959 2894 2895 + /* may_copy_tree() - check if a mount tree can be copied 2896 + * @path: path to the mount tree to be copied 2897 + * 2898 + * This helper checks if the caller may copy the mount tree starting 2899 + * from @path->mnt. The caller may copy the mount tree under the 2900 + * following circumstances: 2901 + * 2902 + * (1) The caller is located in the mount namespace of the mount tree. 2903 + * This also implies that the mount does not belong to an anonymous 2904 + * mount namespace. 2905 + * (2) The caller tries to copy an nfs mount referring to a mount 2906 + * namespace, i.e., the caller is trying to copy a mount namespace 2907 + * entry from nsfs. 2908 + * (3) The caller tries to copy a pidfs mount referring to a pidfd. 2909 + * (4) The caller is trying to copy a mount tree that belongs to an 2910 + * anonymous mount namespace. 2911 + * 2912 + * For that to be safe, this helper enforces that the origin mount 2913 + * namespace the anonymous mount namespace was created from is the 2914 + * same as the caller's mount namespace by comparing the sequence 2915 + * numbers. 2916 + * 2917 + * This is not strictly necessary. The current semantics of the new 2918 + * mount api enforce that the caller must be located in the same 2919 + * mount namespace as the mount tree it interacts with. Using the 2920 + * origin sequence number preserves these semantics even for 2921 + * anonymous mount namespaces. However, one could envision extending 2922 + * the api to directly operate across mount namespace if needed. 2923 + * 2924 + * The ownership of a non-anonymous mount namespace such as the 2925 + * caller's cannot change. 2926 + * => We know that the caller's mount namespace is stable. 2927 + * 2928 + * If the origin sequence number of the anonymous mount namespace is 2929 + * the same as the sequence number of the caller's mount namespace. 2930 + * => The owning namespaces are the same. 2931 + * 2932 + * ==> The earlier capability check on the owning namespace of the 2933 + * caller's mount namespace ensures that the caller has the 2934 + * ability to copy the mount tree. 2935 + * 2936 + * Returns true if the mount tree can be copied, false otherwise. 2937 + */ 2938 + static inline bool may_copy_tree(struct path *path) 2939 + { 2940 + struct mount *mnt = real_mount(path->mnt); 2941 + const struct dentry_operations *d_op; 2942 + 2943 + if (check_mnt(mnt)) 2944 + return true; 2945 + 2946 + d_op = path->dentry->d_op; 2947 + if (d_op == &ns_dentry_operations) 2948 + return true; 2949 + 2950 + if (d_op == &pidfs_dentry_operations) 2951 + return true; 2952 + 2953 + if (!is_mounted(path->mnt)) 2954 + return false; 2955 + 2956 + return check_anonymous_mnt(mnt); 2957 + } 2958 + 2959 + 2960 2960 static struct mount *__do_loopback(struct path *old_path, int recurse) 2961 2961 { 2962 2962 struct mount *mnt = ERR_PTR(-EINVAL), *old = real_mount(old_path->mnt); ··· 3029 2899 if (IS_MNT_UNBINDABLE(old)) 3030 2900 return mnt; 3031 2901 3032 - if (!check_mnt(old)) { 3033 - const struct dentry_operations *d_op = old_path->dentry->d_op; 3034 - 3035 - if (d_op != &ns_dentry_operations && 3036 - d_op != &pidfs_dentry_operations) 3037 - return mnt; 3038 - } 2902 + if (!may_copy_tree(old_path)) 2903 + return mnt; 3039 2904 3040 2905 if (!recurse && has_locked_children(old, old_path->dentry)) 3041 2906 return mnt; ··· 3097 2972 3098 2973 static struct file *open_detached_copy(struct path *path, bool recursive) 3099 2974 { 3100 - struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns; 3101 - struct mnt_namespace *ns = alloc_mnt_ns(user_ns, true); 2975 + struct mnt_namespace *ns, *mnt_ns = current->nsproxy->mnt_ns, *src_mnt_ns; 2976 + struct user_namespace *user_ns = mnt_ns->user_ns; 3102 2977 struct mount *mnt, *p; 3103 2978 struct file *file; 3104 2979 2980 + ns = alloc_mnt_ns(user_ns, true); 3105 2981 if (IS_ERR(ns)) 3106 2982 return ERR_CAST(ns); 3107 2983 3108 2984 namespace_lock(); 2985 + 2986 + /* 2987 + * Record the sequence number of the source mount namespace. 2988 + * This needs to hold namespace_sem to ensure that the mount 2989 + * doesn't get attached. 2990 + */ 2991 + if (is_mounted(path->mnt)) { 2992 + src_mnt_ns = real_mount(path->mnt)->mnt_ns; 2993 + if (is_anon_ns(src_mnt_ns)) 2994 + ns->seq_origin = src_mnt_ns->seq_origin; 2995 + else 2996 + ns->seq_origin = src_mnt_ns->seq; 2997 + } 2998 + 3109 2999 mnt = __do_loopback(path, recursive); 3110 3000 if (IS_ERR(mnt)) { 3111 3001 namespace_unlock(); ··· 3560 3420 return 0; 3561 3421 } 3562 3422 3563 - static int do_move_mount(struct path *old_path, struct path *new_path, 3564 - bool beneath) 3423 + /* may_use_mount() - check if a mount tree can be used 3424 + * @mnt: vfsmount to be used 3425 + * 3426 + * This helper checks if the caller may use the mount tree starting 3427 + * from @path->mnt. The caller may use the mount tree under the 3428 + * following circumstances: 3429 + * 3430 + * (1) The caller is located in the mount namespace of the mount tree. 3431 + * This also implies that the mount does not belong to an anonymous 3432 + * mount namespace. 3433 + * (2) The caller is trying to use a mount tree that belongs to an 3434 + * anonymous mount namespace. 3435 + * 3436 + * For that to be safe, this helper enforces that the origin mount 3437 + * namespace the anonymous mount namespace was created from is the 3438 + * same as the caller's mount namespace by comparing the sequence 3439 + * numbers. 3440 + * 3441 + * The ownership of a non-anonymous mount namespace such as the 3442 + * caller's cannot change. 3443 + * => We know that the caller's mount namespace is stable. 3444 + * 3445 + * If the origin sequence number of the anonymous mount namespace is 3446 + * the same as the sequence number of the caller's mount namespace. 3447 + * => The owning namespaces are the same. 3448 + * 3449 + * ==> The earlier capability check on the owning namespace of the 3450 + * caller's mount namespace ensures that the caller has the 3451 + * ability to use the mount tree. 3452 + * 3453 + * Returns true if the mount tree can be used, false otherwise. 3454 + */ 3455 + static inline bool may_use_mount(struct mount *mnt) 3456 + { 3457 + if (check_mnt(mnt)) 3458 + return true; 3459 + 3460 + /* 3461 + * Make sure that noone unmounted the target path or somehow 3462 + * managed to get their hands on something purely kernel 3463 + * internal. 3464 + */ 3465 + if (!is_mounted(&mnt->mnt)) 3466 + return false; 3467 + 3468 + return check_anonymous_mnt(mnt); 3469 + } 3470 + 3471 + static int do_move_mount(struct path *old_path, 3472 + struct path *new_path, enum mnt_tree_flags_t flags) 3565 3473 { 3566 3474 struct mnt_namespace *ns; 3567 3475 struct mount *p; ··· 3617 3429 struct mount *parent; 3618 3430 struct mountpoint *mp, *old_mp; 3619 3431 int err; 3620 - bool attached; 3621 - enum mnt_tree_flags_t flags = 0; 3432 + bool attached, beneath = flags & MNT_TREE_BENEATH; 3622 3433 3623 3434 mp = do_lock_mount(new_path, beneath); 3624 3435 if (IS_ERR(mp)) ··· 3633 3446 ns = old->mnt_ns; 3634 3447 3635 3448 err = -EINVAL; 3636 - /* The mountpoint must be in our namespace. */ 3637 - if (!check_mnt(p)) 3449 + if (!may_use_mount(p)) 3638 3450 goto out; 3639 3451 3640 3452 /* The thing moved must be mounted... */ ··· 3643 3457 /* ... and either ours or the root of anon namespace */ 3644 3458 if (!(attached ? check_mnt(old) : is_anon_ns(ns))) 3645 3459 goto out; 3460 + 3461 + if (is_anon_ns(ns)) { 3462 + /* 3463 + * Ending up with two files referring to the root of the 3464 + * same anonymous mount namespace would cause an error 3465 + * as this would mean trying to move the same mount 3466 + * twice into the mount tree which would be rejected 3467 + * later. But be explicit about it right here. 3468 + */ 3469 + if ((is_anon_ns(p->mnt_ns) && ns == p->mnt_ns)) 3470 + goto out; 3471 + 3472 + /* 3473 + * If this is an anonymous mount tree ensure that mount 3474 + * propagation can detect mounts that were just 3475 + * propagated to the target mount tree so we don't 3476 + * propagate onto them. 3477 + */ 3478 + ns->mntns_flags |= MNTNS_PROPAGATING; 3479 + } else if (is_anon_ns(p->mnt_ns)) { 3480 + /* 3481 + * Don't allow moving an attached mount tree to an 3482 + * anonymous mount tree. 3483 + */ 3484 + goto out; 3485 + } 3646 3486 3647 3487 if (old->mnt.mnt_flags & MNT_LOCKED) 3648 3488 goto out; ··· 3712 3500 if (err) 3713 3501 goto out; 3714 3502 3503 + if (is_anon_ns(ns)) 3504 + ns->mntns_flags &= ~MNTNS_PROPAGATING; 3505 + 3715 3506 /* if the mount is moved, it should no longer be expire 3716 3507 * automatically */ 3717 3508 list_del_init(&old->mnt_expire); ··· 3723 3508 out: 3724 3509 unlock_mount(mp); 3725 3510 if (!err) { 3726 - if (attached) 3511 + if (attached) { 3727 3512 mntput_no_expire(parent); 3728 - else 3513 + } else { 3514 + /* Make sure we notice when we leak mounts. */ 3515 + VFS_WARN_ON_ONCE(!mnt_ns_empty(ns)); 3729 3516 free_mnt_ns(ns); 3517 + } 3730 3518 } 3731 3519 return err; 3732 3520 } ··· 3746 3528 if (err) 3747 3529 return err; 3748 3530 3749 - err = do_move_mount(&old_path, path, false); 3531 + err = do_move_mount(&old_path, path, 0); 3750 3532 path_put(&old_path); 3751 3533 return err; 3752 3534 } ··· 4587 4369 return ret; 4588 4370 } 4589 4371 4372 + static inline int vfs_move_mount(struct path *from_path, struct path *to_path, 4373 + enum mnt_tree_flags_t mflags) 4374 + { 4375 + int ret; 4376 + 4377 + ret = security_move_mount(from_path, to_path); 4378 + if (ret) 4379 + return ret; 4380 + 4381 + if (mflags & MNT_TREE_PROPAGATION) 4382 + return do_set_group(from_path, to_path); 4383 + 4384 + return do_move_mount(from_path, to_path, mflags); 4385 + } 4386 + 4590 4387 /* 4591 4388 * Move a mount from one place to another. In combination with 4592 4389 * fsopen()/fsmount() this is used to install a new mount and in combination ··· 4615 4382 int, to_dfd, const char __user *, to_pathname, 4616 4383 unsigned int, flags) 4617 4384 { 4618 - struct path from_path, to_path; 4619 - unsigned int lflags; 4385 + struct path to_path __free(path_put) = {}; 4386 + struct path from_path __free(path_put) = {}; 4387 + struct filename *to_name __free(putname) = NULL; 4388 + struct filename *from_name __free(putname) = NULL; 4389 + unsigned int lflags, uflags; 4390 + enum mnt_tree_flags_t mflags = 0; 4620 4391 int ret = 0; 4621 4392 4622 4393 if (!may_mount()) ··· 4633 4396 (MOVE_MOUNT_BENEATH | MOVE_MOUNT_SET_GROUP)) 4634 4397 return -EINVAL; 4635 4398 4636 - /* If someone gives a pathname, they aren't permitted to move 4637 - * from an fd that requires unmount as we can't get at the flag 4638 - * to clear it afterwards. 4639 - */ 4399 + if (flags & MOVE_MOUNT_SET_GROUP) mflags |= MNT_TREE_PROPAGATION; 4400 + if (flags & MOVE_MOUNT_BENEATH) mflags |= MNT_TREE_BENEATH; 4401 + 4640 4402 lflags = 0; 4641 4403 if (flags & MOVE_MOUNT_F_SYMLINKS) lflags |= LOOKUP_FOLLOW; 4642 4404 if (flags & MOVE_MOUNT_F_AUTOMOUNTS) lflags |= LOOKUP_AUTOMOUNT; 4643 - if (flags & MOVE_MOUNT_F_EMPTY_PATH) lflags |= LOOKUP_EMPTY; 4644 - 4645 - ret = user_path_at(from_dfd, from_pathname, lflags, &from_path); 4646 - if (ret < 0) 4647 - return ret; 4405 + uflags = 0; 4406 + if (flags & MOVE_MOUNT_F_EMPTY_PATH) uflags = AT_EMPTY_PATH; 4407 + from_name = getname_maybe_null(from_pathname, uflags); 4408 + if (IS_ERR(from_name)) 4409 + return PTR_ERR(from_name); 4648 4410 4649 4411 lflags = 0; 4650 4412 if (flags & MOVE_MOUNT_T_SYMLINKS) lflags |= LOOKUP_FOLLOW; 4651 4413 if (flags & MOVE_MOUNT_T_AUTOMOUNTS) lflags |= LOOKUP_AUTOMOUNT; 4652 - if (flags & MOVE_MOUNT_T_EMPTY_PATH) lflags |= LOOKUP_EMPTY; 4414 + uflags = 0; 4415 + if (flags & MOVE_MOUNT_T_EMPTY_PATH) uflags = AT_EMPTY_PATH; 4416 + to_name = getname_maybe_null(to_pathname, uflags); 4417 + if (IS_ERR(to_name)) 4418 + return PTR_ERR(to_name); 4653 4419 4654 - ret = user_path_at(to_dfd, to_pathname, lflags, &to_path); 4655 - if (ret < 0) 4656 - goto out_from; 4420 + if (!to_name && to_dfd >= 0) { 4421 + CLASS(fd_raw, f_to)(to_dfd); 4422 + if (fd_empty(f_to)) 4423 + return -EBADF; 4657 4424 4658 - ret = security_move_mount(&from_path, &to_path); 4659 - if (ret < 0) 4660 - goto out_to; 4425 + to_path = fd_file(f_to)->f_path; 4426 + path_get(&to_path); 4427 + } else { 4428 + ret = filename_lookup(to_dfd, to_name, lflags, &to_path, NULL); 4429 + if (ret) 4430 + return ret; 4431 + } 4661 4432 4662 - if (flags & MOVE_MOUNT_SET_GROUP) 4663 - ret = do_set_group(&from_path, &to_path); 4664 - else 4665 - ret = do_move_mount(&from_path, &to_path, 4666 - (flags & MOVE_MOUNT_BENEATH)); 4433 + if (!from_name && from_dfd >= 0) { 4434 + CLASS(fd_raw, f_from)(from_dfd); 4435 + if (fd_empty(f_from)) 4436 + return -EBADF; 4667 4437 4668 - out_to: 4669 - path_put(&to_path); 4670 - out_from: 4671 - path_put(&from_path); 4672 - return ret; 4438 + return vfs_move_mount(&fd_file(f_from)->f_path, &to_path, mflags); 4439 + } 4440 + 4441 + ret = filename_lookup(from_dfd, from_name, lflags, &from_path, NULL); 4442 + if (ret) 4443 + return ret; 4444 + 4445 + return vfs_move_mount(&from_path, &to_path, mflags); 4673 4446 } 4674 4447 4675 4448 /* ··· 5763 5516 * We have to find the first mount in our ns and use that, however it 5764 5517 * may not exist, so handle that properly. 5765 5518 */ 5766 - if (RB_EMPTY_ROOT(&ns->mounts)) 5519 + if (mnt_ns_empty(ns)) 5767 5520 return -ENOENT; 5768 5521 5769 5522 first = child = ns->root; ··· 5803 5556 int err; 5804 5557 5805 5558 /* Has the namespace already been emptied? */ 5806 - if (mnt_ns_id && RB_EMPTY_ROOT(&ns->mounts)) 5559 + if (mnt_ns_id && mnt_ns_empty(ns)) 5807 5560 return -ENOENT; 5808 5561 5809 5562 s->mnt = lookup_mnt_in_ns(mnt_id, ns);
+5 -5
fs/pnode.c
··· 150 150 struct mount *origin) 151 151 { 152 152 /* are there any slaves of this mount? */ 153 - if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list)) 153 + if (!IS_MNT_PROPAGATED(m) && !list_empty(&m->mnt_slave_list)) 154 154 return first_slave(m); 155 155 156 156 while (1) { ··· 174 174 * Advance m such that propagation_next will not return 175 175 * the slaves of m. 176 176 */ 177 - if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list)) 177 + if (!IS_MNT_PROPAGATED(m) && !list_empty(&m->mnt_slave_list)) 178 178 m = last_slave(m); 179 179 180 180 return m; ··· 185 185 while (1) { 186 186 while (1) { 187 187 struct mount *next; 188 - if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list)) 188 + if (!IS_MNT_PROPAGATED(m) && !list_empty(&m->mnt_slave_list)) 189 189 return first_slave(m); 190 190 next = next_peer(m); 191 191 if (m->mnt_group_id == origin->mnt_group_id) { ··· 226 226 struct mount *child; 227 227 int type; 228 228 /* skip ones added by this propagate_mnt() */ 229 - if (IS_MNT_NEW(m)) 229 + if (IS_MNT_PROPAGATED(m)) 230 230 return 0; 231 231 /* skip if mountpoint isn't covered by it */ 232 232 if (!is_subdir(dest_mp->m_dentry, m->mnt.mnt_root)) ··· 380 380 if (!IS_MNT_SHARED(from)) 381 381 return false; 382 382 383 - if (IS_MNT_NEW(to)) 383 + if (IS_MNT_PROPAGATED(to)) 384 384 return false; 385 385 386 386 if (to->mnt.mnt_root != mp->m_dentry)
+1 -1
fs/pnode.h
··· 12 12 13 13 #define IS_MNT_SHARED(m) ((m)->mnt.mnt_flags & MNT_SHARED) 14 14 #define IS_MNT_SLAVE(m) ((m)->mnt_master) 15 - #define IS_MNT_NEW(m) (!(m)->mnt_ns || is_anon_ns((m)->mnt_ns)) 15 + #define IS_MNT_PROPAGATED(m) (!(m)->mnt_ns || ((m)->mnt_ns->mntns_flags & MNTNS_PROPAGATING)) 16 16 #define CLEAR_MNT_SHARED(m) ((m)->mnt.mnt_flags &= ~MNT_SHARED) 17 17 #define IS_MNT_UNBINDABLE(m) ((m)->mnt.mnt_flags & MNT_UNBINDABLE) 18 18 #define IS_MNT_MARKED(m) ((m)->mnt.mnt_flags & MNT_MARKED)
+1
include/linux/fs.h
··· 2854 2854 return __getname_maybe_null(name); 2855 2855 } 2856 2856 extern void putname(struct filename *name); 2857 + DEFINE_FREE(putname, struct filename *, if (!IS_ERR_OR_NULL(_T)) putname(_T)) 2857 2858 2858 2859 static inline struct filename *refname(struct filename *name) 2859 2860 {
+652
tools/testing/selftests/mount_setattr/mount_setattr_test.c
··· 20 20 #include <stdarg.h> 21 21 #include <linux/mount.h> 22 22 23 + #include "../filesystems/overlayfs/wrappers.h" 23 24 #include "../kselftest_harness.h" 24 25 25 26 #ifndef CLONE_NEWNS ··· 124 123 #define __NR_open_tree (428 + 1024) 125 124 #else 126 125 #define __NR_open_tree 428 126 + #endif 127 + #endif 128 + 129 + #ifndef __NR_move_mount 130 + #if defined __alpha__ 131 + #define __NR_move_mount 539 132 + #elif defined _MIPS_SIM 133 + #if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */ 134 + #define __NR_move_mount 4429 135 + #endif 136 + #if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */ 137 + #define __NR_move_mount 6429 138 + #endif 139 + #if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */ 140 + #define __NR_move_mount 5429 141 + #endif 142 + #elif defined __ia64__ 143 + #define __NR_move_mount (428 + 1024) 144 + #else 145 + #define __NR_move_mount 429 127 146 #endif 128 147 #endif 129 148 ··· 417 396 "size=100000,mode=700"), 0); 418 397 419 398 ASSERT_EQ(mkdir("/tmp/B/BB", 0777), 0); 399 + 400 + ASSERT_EQ(mkdir("/tmp/target1", 0777), 0); 401 + 402 + ASSERT_EQ(mkdir("/tmp/target2", 0777), 0); 420 403 421 404 ASSERT_EQ(mount("testing", "/tmp/B/BB", "tmpfs", MS_NOATIME | MS_NODEV, 422 405 "size=100000,mode=700"), 0); ··· 1529 1504 fd = open(NOSYMFOLLOW_SYMLINK, O_RDWR | O_CLOEXEC); 1530 1505 ASSERT_GT(fd, 0); 1531 1506 ASSERT_EQ(close(fd), 0); 1507 + } 1508 + 1509 + TEST_F(mount_setattr, open_tree_detached) 1510 + { 1511 + int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF; 1512 + struct statx stx; 1513 + 1514 + fd_tree_base = sys_open_tree(-EBADF, "/mnt", 1515 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1516 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1517 + OPEN_TREE_CLONE); 1518 + ASSERT_GE(fd_tree_base, 0); 1519 + /* 1520 + * /mnt testing tmpfs 1521 + * |-/mnt/A testing tmpfs 1522 + * | `-/mnt/A/AA testing tmpfs 1523 + * | `-/mnt/A/AA/B testing tmpfs 1524 + * | `-/mnt/A/AA/B/BB testing tmpfs 1525 + * `-/mnt/B testing ramfs 1526 + */ 1527 + ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0); 1528 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1529 + ASSERT_EQ(statx(fd_tree_base, "A/AA", 0, 0, &stx), 0); 1530 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1531 + ASSERT_EQ(statx(fd_tree_base, "A/AA/B", 0, 0, &stx), 0); 1532 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1533 + ASSERT_EQ(statx(fd_tree_base, "A/AA/B/BB", 0, 0, &stx), 0); 1534 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1535 + 1536 + fd_tree_subdir = sys_open_tree(fd_tree_base, "A/AA", 1537 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1538 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1539 + OPEN_TREE_CLONE); 1540 + ASSERT_GE(fd_tree_subdir, 0); 1541 + /* 1542 + * /AA testing tmpfs 1543 + * `-/AA/B testing tmpfs 1544 + * `-/AA/B/BB testing tmpfs 1545 + */ 1546 + ASSERT_EQ(statx(fd_tree_subdir, "B", 0, 0, &stx), 0); 1547 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1548 + ASSERT_EQ(statx(fd_tree_subdir, "B/BB", 0, 0, &stx), 0); 1549 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1550 + 1551 + ASSERT_EQ(move_mount(fd_tree_subdir, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0); 1552 + /* 1553 + * /tmp/target1 testing tmpfs 1554 + * `-/tmp/target1/B testing tmpfs 1555 + * `-/tmp/target1/B/BB testing tmpfs 1556 + */ 1557 + ASSERT_EQ(statx(-EBADF, "/tmp/target1", 0, 0, &stx), 0); 1558 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1559 + ASSERT_EQ(statx(-EBADF, "/tmp/target1/B", 0, 0, &stx), 0); 1560 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1561 + ASSERT_EQ(statx(-EBADF, "/tmp/target1/B/BB", 0, 0, &stx), 0); 1562 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1563 + 1564 + ASSERT_EQ(move_mount(fd_tree_base, "", -EBADF, "/tmp/target2", MOVE_MOUNT_F_EMPTY_PATH), 0); 1565 + /* 1566 + * /tmp/target2 testing tmpfs 1567 + * |-/tmp/target2/A testing tmpfs 1568 + * | `-/tmp/target2/A/AA testing tmpfs 1569 + * | `-/tmp/target2/A/AA/B testing tmpfs 1570 + * | `-/tmp/target2/A/AA/B/BB testing tmpfs 1571 + * `-/tmp/target2/B testing ramfs 1572 + */ 1573 + ASSERT_EQ(statx(-EBADF, "/tmp/target2", 0, 0, &stx), 0); 1574 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1575 + ASSERT_EQ(statx(-EBADF, "/tmp/target2/A", 0, 0, &stx), 0); 1576 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1577 + ASSERT_EQ(statx(-EBADF, "/tmp/target2/A/AA", 0, 0, &stx), 0); 1578 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1579 + ASSERT_EQ(statx(-EBADF, "/tmp/target2/A/AA/B", 0, 0, &stx), 0); 1580 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1581 + ASSERT_EQ(statx(-EBADF, "/tmp/target2/A/AA/B/BB", 0, 0, &stx), 0); 1582 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1583 + ASSERT_EQ(statx(-EBADF, "/tmp/target2/B", 0, 0, &stx), 0); 1584 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1585 + 1586 + EXPECT_EQ(close(fd_tree_base), 0); 1587 + EXPECT_EQ(close(fd_tree_subdir), 0); 1588 + } 1589 + 1590 + TEST_F(mount_setattr, open_tree_detached_fail) 1591 + { 1592 + int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF; 1593 + struct statx stx; 1594 + 1595 + fd_tree_base = sys_open_tree(-EBADF, "/mnt", 1596 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1597 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1598 + OPEN_TREE_CLONE); 1599 + ASSERT_GE(fd_tree_base, 0); 1600 + /* 1601 + * /mnt testing tmpfs 1602 + * |-/mnt/A testing tmpfs 1603 + * | `-/mnt/A/AA testing tmpfs 1604 + * | `-/mnt/A/AA/B testing tmpfs 1605 + * | `-/mnt/A/AA/B/BB testing tmpfs 1606 + * `-/mnt/B testing ramfs 1607 + */ 1608 + ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0); 1609 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1610 + ASSERT_EQ(statx(fd_tree_base, "A/AA", 0, 0, &stx), 0); 1611 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1612 + ASSERT_EQ(statx(fd_tree_base, "A/AA/B", 0, 0, &stx), 0); 1613 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1614 + ASSERT_EQ(statx(fd_tree_base, "A/AA/B/BB", 0, 0, &stx), 0); 1615 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1616 + 1617 + ASSERT_EQ(unshare(CLONE_NEWNS), 0); 1618 + 1619 + /* 1620 + * The origin mount namespace of the anonymous mount namespace 1621 + * of @fd_tree_base doesn't match the caller's mount namespace 1622 + * anymore so creation of another detached mounts must fail. 1623 + */ 1624 + fd_tree_subdir = sys_open_tree(fd_tree_base, "A/AA", 1625 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1626 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1627 + OPEN_TREE_CLONE); 1628 + ASSERT_LT(fd_tree_subdir, 0); 1629 + ASSERT_EQ(errno, EINVAL); 1630 + } 1631 + 1632 + TEST_F(mount_setattr, open_tree_detached_fail2) 1633 + { 1634 + int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF; 1635 + struct statx stx; 1636 + 1637 + fd_tree_base = sys_open_tree(-EBADF, "/mnt", 1638 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1639 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1640 + OPEN_TREE_CLONE); 1641 + ASSERT_GE(fd_tree_base, 0); 1642 + /* 1643 + * /mnt testing tmpfs 1644 + * |-/mnt/A testing tmpfs 1645 + * | `-/mnt/A/AA testing tmpfs 1646 + * | `-/mnt/A/AA/B testing tmpfs 1647 + * | `-/mnt/A/AA/B/BB testing tmpfs 1648 + * `-/mnt/B testing ramfs 1649 + */ 1650 + ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0); 1651 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1652 + ASSERT_EQ(statx(fd_tree_base, "A/AA", 0, 0, &stx), 0); 1653 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1654 + ASSERT_EQ(statx(fd_tree_base, "A/AA/B", 0, 0, &stx), 0); 1655 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1656 + ASSERT_EQ(statx(fd_tree_base, "A/AA/B/BB", 0, 0, &stx), 0); 1657 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1658 + 1659 + EXPECT_EQ(create_and_enter_userns(), 0); 1660 + 1661 + /* 1662 + * The caller entered a new user namespace. They will have 1663 + * CAP_SYS_ADMIN in this user namespace. However, they're still 1664 + * located in a mount namespace that is owned by an ancestor 1665 + * user namespace in which they hold no privilege. Creating a 1666 + * detached mount must thus fail. 1667 + */ 1668 + fd_tree_subdir = sys_open_tree(fd_tree_base, "A/AA", 1669 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1670 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1671 + OPEN_TREE_CLONE); 1672 + ASSERT_LT(fd_tree_subdir, 0); 1673 + ASSERT_EQ(errno, EPERM); 1674 + } 1675 + 1676 + TEST_F(mount_setattr, open_tree_detached_fail3) 1677 + { 1678 + int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF; 1679 + struct statx stx; 1680 + 1681 + fd_tree_base = sys_open_tree(-EBADF, "/mnt", 1682 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1683 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1684 + OPEN_TREE_CLONE); 1685 + ASSERT_GE(fd_tree_base, 0); 1686 + /* 1687 + * /mnt testing tmpfs 1688 + * |-/mnt/A testing tmpfs 1689 + * | `-/mnt/A/AA testing tmpfs 1690 + * | `-/mnt/A/AA/B testing tmpfs 1691 + * | `-/mnt/A/AA/B/BB testing tmpfs 1692 + * `-/mnt/B testing ramfs 1693 + */ 1694 + ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0); 1695 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1696 + ASSERT_EQ(statx(fd_tree_base, "A/AA", 0, 0, &stx), 0); 1697 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1698 + ASSERT_EQ(statx(fd_tree_base, "A/AA/B", 0, 0, &stx), 0); 1699 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1700 + ASSERT_EQ(statx(fd_tree_base, "A/AA/B/BB", 0, 0, &stx), 0); 1701 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1702 + 1703 + EXPECT_EQ(prepare_unpriv_mountns(), 0); 1704 + 1705 + /* 1706 + * The caller entered a new mount namespace. They will have 1707 + * CAP_SYS_ADMIN in the owning user namespace of their mount 1708 + * namespace. 1709 + * 1710 + * However, the origin mount namespace of the anonymous mount 1711 + * namespace of @fd_tree_base doesn't match the caller's mount 1712 + * namespace anymore so creation of another detached mounts must 1713 + * fail. 1714 + */ 1715 + fd_tree_subdir = sys_open_tree(fd_tree_base, "A/AA", 1716 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1717 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1718 + OPEN_TREE_CLONE); 1719 + ASSERT_LT(fd_tree_subdir, 0); 1720 + ASSERT_EQ(errno, EINVAL); 1721 + } 1722 + 1723 + TEST_F(mount_setattr, open_tree_subfolder) 1724 + { 1725 + int fd_context, fd_tmpfs, fd_tree; 1726 + 1727 + fd_context = sys_fsopen("tmpfs", 0); 1728 + ASSERT_GE(fd_context, 0); 1729 + 1730 + ASSERT_EQ(sys_fsconfig(fd_context, FSCONFIG_CMD_CREATE, NULL, NULL, 0), 0); 1731 + 1732 + fd_tmpfs = sys_fsmount(fd_context, 0, 0); 1733 + ASSERT_GE(fd_tmpfs, 0); 1734 + 1735 + EXPECT_EQ(close(fd_context), 0); 1736 + 1737 + ASSERT_EQ(mkdirat(fd_tmpfs, "subdir", 0755), 0); 1738 + 1739 + fd_tree = sys_open_tree(fd_tmpfs, "subdir", 1740 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1741 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1742 + OPEN_TREE_CLONE); 1743 + ASSERT_GE(fd_tree, 0); 1744 + 1745 + EXPECT_EQ(close(fd_tmpfs), 0); 1746 + 1747 + ASSERT_EQ(mkdirat(-EBADF, "/mnt/open_tree_subfolder", 0755), 0); 1748 + 1749 + ASSERT_EQ(sys_move_mount(fd_tree, "", -EBADF, "/mnt/open_tree_subfolder", MOVE_MOUNT_F_EMPTY_PATH), 0); 1750 + 1751 + EXPECT_EQ(close(fd_tree), 0); 1752 + 1753 + ASSERT_EQ(umount2("/mnt/open_tree_subfolder", 0), 0); 1754 + 1755 + EXPECT_EQ(rmdir("/mnt/open_tree_subfolder"), 0); 1756 + } 1757 + 1758 + TEST_F(mount_setattr, mount_detached_mount_on_detached_mount_then_close) 1759 + { 1760 + int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF; 1761 + struct statx stx; 1762 + 1763 + fd_tree_base = sys_open_tree(-EBADF, "/mnt", 1764 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1765 + OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE); 1766 + ASSERT_GE(fd_tree_base, 0); 1767 + /* 1768 + * /mnt testing tmpfs 1769 + */ 1770 + ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0); 1771 + ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1772 + 1773 + fd_tree_subdir = sys_open_tree(fd_tree_base, "", 1774 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1775 + AT_EMPTY_PATH | OPEN_TREE_CLOEXEC | 1776 + OPEN_TREE_CLONE); 1777 + ASSERT_GE(fd_tree_subdir, 0); 1778 + /* 1779 + * /mnt testing tmpfs 1780 + */ 1781 + ASSERT_EQ(statx(fd_tree_subdir, "A", 0, 0, &stx), 0); 1782 + ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1783 + 1784 + /* 1785 + * /mnt testing tmpfs 1786 + * `-/mnt testing tmpfs 1787 + */ 1788 + ASSERT_EQ(move_mount(fd_tree_subdir, "", fd_tree_base, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0); 1789 + ASSERT_EQ(statx(fd_tree_subdir, "", AT_EMPTY_PATH, 0, &stx), 0); 1790 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1791 + 1792 + ASSERT_NE(move_mount(fd_tree_subdir, "", fd_tree_base, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0); 1793 + 1794 + EXPECT_EQ(close(fd_tree_base), 0); 1795 + EXPECT_EQ(close(fd_tree_subdir), 0); 1796 + } 1797 + 1798 + TEST_F(mount_setattr, mount_detached_mount_on_detached_mount_and_attach) 1799 + { 1800 + int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF; 1801 + struct statx stx; 1802 + __u64 mnt_id = 0; 1803 + 1804 + fd_tree_base = sys_open_tree(-EBADF, "/mnt", 1805 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1806 + OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE); 1807 + ASSERT_GE(fd_tree_base, 0); 1808 + /* 1809 + * /mnt testing tmpfs 1810 + */ 1811 + ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0); 1812 + ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1813 + 1814 + fd_tree_subdir = sys_open_tree(fd_tree_base, "", 1815 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1816 + AT_EMPTY_PATH | OPEN_TREE_CLOEXEC | 1817 + OPEN_TREE_CLONE); 1818 + ASSERT_GE(fd_tree_subdir, 0); 1819 + /* 1820 + * /mnt testing tmpfs 1821 + */ 1822 + ASSERT_EQ(statx(fd_tree_subdir, "A", 0, 0, &stx), 0); 1823 + ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1824 + 1825 + /* 1826 + * /mnt testing tmpfs 1827 + * `-/mnt testing tmpfs 1828 + */ 1829 + ASSERT_EQ(move_mount(fd_tree_subdir, "", fd_tree_base, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0); 1830 + ASSERT_EQ(statx(fd_tree_subdir, "", AT_EMPTY_PATH, STATX_MNT_ID_UNIQUE, &stx), 0); 1831 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1832 + ASSERT_TRUE(stx.stx_mask & STATX_MNT_ID_UNIQUE); 1833 + mnt_id = stx.stx_mnt_id; 1834 + 1835 + ASSERT_NE(move_mount(fd_tree_subdir, "", fd_tree_base, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0); 1836 + 1837 + ASSERT_EQ(move_mount(fd_tree_base, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0); 1838 + ASSERT_EQ(statx(-EBADF, "/tmp/target1", 0, STATX_MNT_ID_UNIQUE, &stx), 0); 1839 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1840 + ASSERT_TRUE(stx.stx_mask & STATX_MNT_ID_UNIQUE); 1841 + ASSERT_EQ(stx.stx_mnt_id, mnt_id); 1842 + 1843 + EXPECT_EQ(close(fd_tree_base), 0); 1844 + EXPECT_EQ(close(fd_tree_subdir), 0); 1845 + } 1846 + 1847 + TEST_F(mount_setattr, move_mount_detached_fail) 1848 + { 1849 + int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF; 1850 + struct statx stx; 1851 + 1852 + fd_tree_base = sys_open_tree(-EBADF, "/mnt", 1853 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1854 + OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE); 1855 + ASSERT_GE(fd_tree_base, 0); 1856 + 1857 + /* Attach the mount to the caller's mount namespace. */ 1858 + ASSERT_EQ(move_mount(fd_tree_base, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0); 1859 + 1860 + ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0); 1861 + ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1862 + 1863 + fd_tree_subdir = sys_open_tree(-EBADF, "/tmp/B", 1864 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1865 + OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE); 1866 + ASSERT_GE(fd_tree_subdir, 0); 1867 + ASSERT_EQ(statx(fd_tree_subdir, "BB", 0, 0, &stx), 0); 1868 + ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1869 + 1870 + /* Not allowed to move an attached mount to a detached mount. */ 1871 + ASSERT_NE(move_mount(fd_tree_base, "", fd_tree_subdir, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0); 1872 + ASSERT_EQ(errno, EINVAL); 1873 + 1874 + EXPECT_EQ(close(fd_tree_base), 0); 1875 + EXPECT_EQ(close(fd_tree_subdir), 0); 1876 + } 1877 + 1878 + TEST_F(mount_setattr, attach_detached_mount_then_umount_then_close) 1879 + { 1880 + int fd_tree = -EBADF; 1881 + struct statx stx; 1882 + 1883 + fd_tree = sys_open_tree(-EBADF, "/mnt", 1884 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1885 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1886 + OPEN_TREE_CLONE); 1887 + ASSERT_GE(fd_tree, 0); 1888 + 1889 + ASSERT_EQ(statx(fd_tree, "A", 0, 0, &stx), 0); 1890 + /* We copied with AT_RECURSIVE so /mnt/A must be a mountpoint. */ 1891 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1892 + 1893 + /* Attach the mount to the caller's mount namespace. */ 1894 + ASSERT_EQ(move_mount(fd_tree, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0); 1895 + 1896 + ASSERT_EQ(statx(-EBADF, "/tmp/target1", 0, 0, &stx), 0); 1897 + ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT); 1898 + 1899 + ASSERT_EQ(umount2("/tmp/target1", MNT_DETACH), 0); 1900 + 1901 + /* 1902 + * This tests whether dissolve_on_fput() handles a NULL mount 1903 + * namespace correctly, i.e., that it doesn't splat. 1904 + */ 1905 + EXPECT_EQ(close(fd_tree), 0); 1906 + } 1907 + 1908 + TEST_F(mount_setattr, mount_detached1_onto_detached2_then_close_detached1_then_mount_detached2_onto_attached) 1909 + { 1910 + int fd_tree1 = -EBADF, fd_tree2 = -EBADF; 1911 + 1912 + /* 1913 + * |-/mnt/A testing tmpfs 1914 + * `-/mnt/A/AA testing tmpfs 1915 + * `-/mnt/A/AA/B testing tmpfs 1916 + * `-/mnt/A/AA/B/BB testing tmpfs 1917 + */ 1918 + fd_tree1 = sys_open_tree(-EBADF, "/mnt/A", 1919 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1920 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 1921 + OPEN_TREE_CLONE); 1922 + ASSERT_GE(fd_tree1, 0); 1923 + 1924 + /* 1925 + * `-/mnt/B testing ramfs 1926 + */ 1927 + fd_tree2 = sys_open_tree(-EBADF, "/mnt/B", 1928 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 1929 + AT_EMPTY_PATH | OPEN_TREE_CLOEXEC | 1930 + OPEN_TREE_CLONE); 1931 + ASSERT_GE(fd_tree2, 0); 1932 + 1933 + /* 1934 + * Move the source detached mount tree to the target detached 1935 + * mount tree. This will move all the mounts in the source mount 1936 + * tree from the source anonymous mount namespace to the target 1937 + * anonymous mount namespace. 1938 + * 1939 + * The source detached mount tree and the target detached mount 1940 + * tree now both refer to the same anonymous mount namespace. 1941 + * 1942 + * |-"" testing ramfs 1943 + * `-"" testing tmpfs 1944 + * `-""/AA testing tmpfs 1945 + * `-""/AA/B testing tmpfs 1946 + * `-""/AA/B/BB testing tmpfs 1947 + */ 1948 + ASSERT_EQ(move_mount(fd_tree1, "", fd_tree2, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0); 1949 + 1950 + /* 1951 + * The source detached mount tree @fd_tree1 is now an attached 1952 + * mount, i.e., it has a parent. Specifically, it now has the 1953 + * root mount of the mount tree of @fd_tree2 as its parent. 1954 + * 1955 + * That means we are no longer allowed to attach it as we only 1956 + * allow attaching the root of an anonymous mount tree, not 1957 + * random bits and pieces. Verify that the kernel enforces this. 1958 + */ 1959 + ASSERT_NE(move_mount(fd_tree1, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0); 1960 + 1961 + /* 1962 + * Closing the source detached mount tree must not unmount and 1963 + * free the shared anonymous mount namespace. The kernel will 1964 + * quickly yell at us because the anonymous mount namespace 1965 + * won't be empty when it's freed. 1966 + */ 1967 + EXPECT_EQ(close(fd_tree1), 0); 1968 + 1969 + /* 1970 + * Attach the mount tree to a non-anonymous mount namespace. 1971 + * This can only succeed if closing fd_tree1 had proper 1972 + * semantics and didn't cause the anonymous mount namespace to 1973 + * be freed. If it did this will trigger a UAF which will be 1974 + * visible on any KASAN enabled kernel. 1975 + * 1976 + * |-/tmp/target1 testing ramfs 1977 + * `-/tmp/target1 testing tmpfs 1978 + * `-/tmp/target1/AA testing tmpfs 1979 + * `-/tmp/target1/AA/B testing tmpfs 1980 + * `-/tmp/target1/AA/B/BB testing tmpfs 1981 + */ 1982 + ASSERT_EQ(move_mount(fd_tree2, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0); 1983 + EXPECT_EQ(close(fd_tree2), 0); 1984 + } 1985 + 1986 + TEST_F(mount_setattr, two_detached_mounts_referring_to_same_anonymous_mount_namespace) 1987 + { 1988 + int fd_tree1 = -EBADF, fd_tree2 = -EBADF; 1989 + 1990 + /* 1991 + * Copy the following mount tree: 1992 + * 1993 + * |-/mnt/A testing tmpfs 1994 + * `-/mnt/A/AA testing tmpfs 1995 + * `-/mnt/A/AA/B testing tmpfs 1996 + * `-/mnt/A/AA/B/BB testing tmpfs 1997 + */ 1998 + fd_tree1 = sys_open_tree(-EBADF, "/mnt/A", 1999 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 2000 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 2001 + OPEN_TREE_CLONE); 2002 + ASSERT_GE(fd_tree1, 0); 2003 + 2004 + /* 2005 + * Create an O_PATH file descriptors with a separate struct file 2006 + * that refers to the same detached mount tree as @fd_tree1 2007 + */ 2008 + fd_tree2 = sys_open_tree(fd_tree1, "", 2009 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 2010 + AT_EMPTY_PATH | OPEN_TREE_CLOEXEC); 2011 + ASSERT_GE(fd_tree2, 0); 2012 + 2013 + /* 2014 + * Copy the following mount tree: 2015 + * 2016 + * |-/tmp/target1 testing tmpfs 2017 + * `-/tmp/target1/AA testing tmpfs 2018 + * `-/tmp/target1/AA/B testing tmpfs 2019 + * `-/tmp/target1/AA/B/BB testing tmpfs 2020 + */ 2021 + ASSERT_EQ(move_mount(fd_tree2, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0); 2022 + 2023 + /* 2024 + * This must fail as this would mean adding the same mount tree 2025 + * into the same mount tree. 2026 + */ 2027 + ASSERT_NE(move_mount(fd_tree1, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0); 2028 + } 2029 + 2030 + TEST_F(mount_setattr, two_detached_subtrees_of_same_anonymous_mount_namespace) 2031 + { 2032 + int fd_tree1 = -EBADF, fd_tree2 = -EBADF; 2033 + 2034 + /* 2035 + * Copy the following mount tree: 2036 + * 2037 + * |-/mnt/A testing tmpfs 2038 + * `-/mnt/A/AA testing tmpfs 2039 + * `-/mnt/A/AA/B testing tmpfs 2040 + * `-/mnt/A/AA/B/BB testing tmpfs 2041 + */ 2042 + fd_tree1 = sys_open_tree(-EBADF, "/mnt/A", 2043 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 2044 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 2045 + OPEN_TREE_CLONE); 2046 + ASSERT_GE(fd_tree1, 0); 2047 + 2048 + /* 2049 + * Create an O_PATH file descriptors with a separate struct file that 2050 + * refers to a subtree of the same detached mount tree as @fd_tree1 2051 + */ 2052 + fd_tree2 = sys_open_tree(fd_tree1, "AA", 2053 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 2054 + AT_EMPTY_PATH | OPEN_TREE_CLOEXEC); 2055 + ASSERT_GE(fd_tree2, 0); 2056 + 2057 + /* 2058 + * This must fail as it is only possible to attach the root of a 2059 + * detached mount tree. 2060 + */ 2061 + ASSERT_NE(move_mount(fd_tree2, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0); 2062 + 2063 + ASSERT_EQ(move_mount(fd_tree1, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0); 2064 + } 2065 + 2066 + TEST_F(mount_setattr, detached_tree_propagation) 2067 + { 2068 + int fd_tree = -EBADF; 2069 + struct statx stx1, stx2, stx3, stx4; 2070 + 2071 + ASSERT_EQ(unshare(CLONE_NEWNS), 0); 2072 + ASSERT_EQ(mount(NULL, "/mnt", NULL, MS_REC | MS_SHARED, NULL), 0); 2073 + 2074 + /* 2075 + * Copy the following mount tree: 2076 + * 2077 + * /mnt testing tmpfs 2078 + * |-/mnt/A testing tmpfs 2079 + * | `-/mnt/A/AA testing tmpfs 2080 + * | `-/mnt/A/AA/B testing tmpfs 2081 + * | `-/mnt/A/AA/B/BB testing tmpfs 2082 + * `-/mnt/B testing ramfs 2083 + */ 2084 + fd_tree = sys_open_tree(-EBADF, "/mnt", 2085 + AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW | 2086 + AT_RECURSIVE | OPEN_TREE_CLOEXEC | 2087 + OPEN_TREE_CLONE); 2088 + ASSERT_GE(fd_tree, 0); 2089 + 2090 + ASSERT_EQ(statx(-EBADF, "/mnt/A", 0, 0, &stx1), 0); 2091 + ASSERT_EQ(statx(fd_tree, "A", 0, 0, &stx2), 0); 2092 + 2093 + /* 2094 + * Copying the mount namespace like done above doesn't alter the 2095 + * mounts in any way so the filesystem mounted on /mnt must be 2096 + * identical even though the mounts will differ. Use the device 2097 + * information to verify that. Note that tmpfs will have a 0 2098 + * major number so comparing the major number is misleading. 2099 + */ 2100 + ASSERT_EQ(stx1.stx_dev_minor, stx2.stx_dev_minor); 2101 + 2102 + /* Mount a tmpfs filesystem over /mnt/A. */ 2103 + ASSERT_EQ(mount(NULL, "/mnt/A", "tmpfs", 0, NULL), 0); 2104 + 2105 + 2106 + ASSERT_EQ(statx(-EBADF, "/mnt/A", 0, 0, &stx3), 0); 2107 + ASSERT_EQ(statx(fd_tree, "A", 0, 0, &stx4), 0); 2108 + 2109 + /* 2110 + * A new filesystem has been mounted on top of /mnt/A which 2111 + * means that the device information will be different for any 2112 + * statx() that was taken from /mnt/A before the mount compared 2113 + * to one after the mount. 2114 + * 2115 + * Since we already now that the device information between the 2116 + * stx1 and stx2 samples are identical we also now that stx2 and 2117 + * stx3 device information will necessarily differ. 2118 + */ 2119 + ASSERT_NE(stx1.stx_dev_minor, stx3.stx_dev_minor); 2120 + 2121 + /* 2122 + * If mount propagation worked correctly then the tmpfs mount 2123 + * that was created after the mount namespace was unshared will 2124 + * have propagated onto /mnt/A in the detached mount tree. 2125 + * 2126 + * Verify that the device information for stx3 and stx4 are 2127 + * identical. It is already established that stx3 is different 2128 + * from both stx1 and stx2 sampled before the tmpfs mount was 2129 + * done so if stx3 and stx4 are identical the proof is done. 2130 + */ 2131 + ASSERT_EQ(stx3.stx_dev_minor, stx4.stx_dev_minor); 2132 + 2133 + EXPECT_EQ(close(fd_tree), 0); 1532 2134 } 1533 2135 1534 2136 TEST_HARNESS_MAIN