vfs-6.15-rc1.mount.namespace

-----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCZ90r2wAKCRCRxhvAZXjc
 ouC6AQCk3MoqskN0WeNcaZT23dB7dHbEhf/7YXOFC9MFRMKXqQD9Fbn95+GuIe3U
 nBVPbVyQfDtfXE08ml6gbDJrCsbkkQI=
 =Xm1C
 -----END PGP SIGNATURE-----

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
  ...
This commit is contained in:
Linus Torvalds 2025-03-24 11:41:41 -07:00
commit 130e696aa6
6 changed files with 977 additions and 66 deletions

View File

@ -7,6 +7,10 @@
extern struct list_head notify_list;
typedef __u32 __bitwise mntns_flags_t;
#define MNTNS_PROPAGATING ((__force mntns_flags_t)(1 << 0))
struct mnt_namespace {
struct ns_common ns;
struct mount * root;
@ -22,6 +26,7 @@ struct mnt_namespace {
wait_queue_head_t poll;
struct rcu_head mnt_ns_rcu;
};
u64 seq_origin; /* Sequence number of origin mount namespace */
u64 event;
#ifdef CONFIG_FSNOTIFY
__u32 n_fsnotify_mask;
@ -32,6 +37,7 @@ struct mnt_namespace {
struct rb_node mnt_ns_tree_node; /* node in the mnt_ns_tree */
struct list_head mnt_ns_list; /* entry in the sequential list of mounts namespace */
refcount_t passive; /* number references not pinning @mounts */
mntns_flags_t mntns_flags;
} __randomize_layout;
struct mnt_pcp {
@ -164,6 +170,11 @@ static inline bool mnt_ns_attached(const struct mount *mnt)
return !RB_EMPTY_NODE(&mnt->mnt_node);
}
static inline bool mnt_ns_empty(const struct mnt_namespace *ns)
{
return RB_EMPTY_ROOT(&ns->mounts);
}
static inline void move_from_ns(struct mount *mnt, struct list_head *dt_list)
{
struct mnt_namespace *ns = mnt->mnt_ns;

View File

@ -1007,6 +1007,17 @@ static inline int check_mnt(struct mount *mnt)
return mnt->mnt_ns == current->nsproxy->mnt_ns;
}
static inline bool check_anonymous_mnt(struct mount *mnt)
{
u64 seq;
if (!is_anon_ns(mnt->mnt_ns))
return false;
seq = mnt->mnt_ns->seq_origin;
return !seq || (seq == current->nsproxy->mnt_ns->seq);
}
/*
* vfsmount lock must be held for write
*/
@ -2334,22 +2345,75 @@ struct vfsmount *collect_mounts(const struct path *path)
static void free_mnt_ns(struct mnt_namespace *);
static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *, bool);
static inline bool must_dissolve(struct mnt_namespace *mnt_ns)
{
/*
* This mount belonged to an anonymous mount namespace
* but was moved to a non-anonymous mount namespace and
* then unmounted.
*/
if (unlikely(!mnt_ns))
return false;
/*
* This mount belongs to a non-anonymous mount namespace
* and we know that such a mount can never transition to
* an anonymous mount namespace again.
*/
if (!is_anon_ns(mnt_ns)) {
/*
* A detached mount either belongs to an anonymous mount
* namespace or a non-anonymous mount namespace. It
* should never belong to something purely internal.
*/
VFS_WARN_ON_ONCE(mnt_ns == MNT_NS_INTERNAL);
return false;
}
return true;
}
void dissolve_on_fput(struct vfsmount *mnt)
{
struct mnt_namespace *ns;
namespace_lock();
lock_mount_hash();
ns = real_mount(mnt)->mnt_ns;
if (ns) {
if (is_anon_ns(ns))
umount_tree(real_mount(mnt), UMOUNT_CONNECTED);
else
ns = NULL;
struct mount *m = real_mount(mnt);
scoped_guard(rcu) {
if (!must_dissolve(READ_ONCE(m->mnt_ns)))
return;
}
unlock_mount_hash();
namespace_unlock();
if (ns)
free_mnt_ns(ns);
scoped_guard(rwsem_write, &namespace_sem) {
ns = m->mnt_ns;
if (!must_dissolve(ns))
return;
/*
* After must_dissolve() we know that this is a detached
* mount in an anonymous mount namespace.
*
* Now when mnt_has_parent() reports that this mount
* tree has a parent, we know that this anonymous mount
* tree has been moved to another anonymous mount
* namespace.
*
* So when closing this file we cannot unmount the mount
* tree. This will be done when the file referring to
* the root of the anonymous mount namespace will be
* closed (It could already be closed but it would sync
* on @namespace_sem and wait for us to finish.).
*/
if (mnt_has_parent(m))
return;
lock_mount_hash();
umount_tree(m, UMOUNT_CONNECTED);
unlock_mount_hash();
}
/* Make sure we notice when we leak mounts. */
VFS_WARN_ON_ONCE(!mnt_ns_empty(ns));
free_mnt_ns(ns);
}
void drop_collected_mounts(struct vfsmount *mnt)
@ -2542,6 +2606,7 @@ int count_mounts(struct mnt_namespace *ns, struct mount *mnt)
enum mnt_tree_flags_t {
MNT_TREE_MOVE = BIT(0),
MNT_TREE_BENEATH = BIT(1),
MNT_TREE_PROPAGATION = BIT(2),
};
/**
@ -2892,6 +2957,71 @@ static int do_change_type(struct path *path, int ms_flags)
return err;
}
/* may_copy_tree() - check if a mount tree can be copied
* @path: path to the mount tree to be copied
*
* This helper checks if the caller may copy the mount tree starting
* from @path->mnt. The caller may copy the mount tree under the
* following circumstances:
*
* (1) The caller is located in the mount namespace of the mount tree.
* This also implies that the mount does not belong to an anonymous
* mount namespace.
* (2) The caller tries to copy an nfs mount referring to a mount
* namespace, i.e., the caller is trying to copy a mount namespace
* entry from nsfs.
* (3) The caller tries to copy a pidfs mount referring to a pidfd.
* (4) The caller is trying to copy a mount tree that belongs to an
* anonymous mount namespace.
*
* For that to be safe, this helper enforces that the origin mount
* namespace the anonymous mount namespace was created from is the
* same as the caller's mount namespace by comparing the sequence
* numbers.
*
* This is not strictly necessary. The current semantics of the new
* mount api enforce that the caller must be located in the same
* mount namespace as the mount tree it interacts with. Using the
* origin sequence number preserves these semantics even for
* anonymous mount namespaces. However, one could envision extending
* the api to directly operate across mount namespace if needed.
*
* The ownership of a non-anonymous mount namespace such as the
* caller's cannot change.
* => We know that the caller's mount namespace is stable.
*
* If the origin sequence number of the anonymous mount namespace is
* the same as the sequence number of the caller's mount namespace.
* => The owning namespaces are the same.
*
* ==> The earlier capability check on the owning namespace of the
* caller's mount namespace ensures that the caller has the
* ability to copy the mount tree.
*
* Returns true if the mount tree can be copied, false otherwise.
*/
static inline bool may_copy_tree(struct path *path)
{
struct mount *mnt = real_mount(path->mnt);
const struct dentry_operations *d_op;
if (check_mnt(mnt))
return true;
d_op = path->dentry->d_op;
if (d_op == &ns_dentry_operations)
return true;
if (d_op == &pidfs_dentry_operations)
return true;
if (!is_mounted(path->mnt))
return false;
return check_anonymous_mnt(mnt);
}
static struct mount *__do_loopback(struct path *old_path, int recurse)
{
struct mount *mnt = ERR_PTR(-EINVAL), *old = real_mount(old_path->mnt);
@ -2899,13 +3029,8 @@ static struct mount *__do_loopback(struct path *old_path, int recurse)
if (IS_MNT_UNBINDABLE(old))
return mnt;
if (!check_mnt(old)) {
const struct dentry_operations *d_op = old_path->dentry->d_op;
if (d_op != &ns_dentry_operations &&
d_op != &pidfs_dentry_operations)
return mnt;
}
if (!may_copy_tree(old_path))
return mnt;
if (!recurse && has_locked_children(old, old_path->dentry))
return mnt;
@ -2972,15 +3097,30 @@ out:
static struct file *open_detached_copy(struct path *path, bool recursive)
{
struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
struct mnt_namespace *ns = alloc_mnt_ns(user_ns, true);
struct mnt_namespace *ns, *mnt_ns = current->nsproxy->mnt_ns, *src_mnt_ns;
struct user_namespace *user_ns = mnt_ns->user_ns;
struct mount *mnt, *p;
struct file *file;
ns = alloc_mnt_ns(user_ns, true);
if (IS_ERR(ns))
return ERR_CAST(ns);
namespace_lock();
/*
* Record the sequence number of the source mount namespace.
* This needs to hold namespace_sem to ensure that the mount
* doesn't get attached.
*/
if (is_mounted(path->mnt)) {
src_mnt_ns = real_mount(path->mnt)->mnt_ns;
if (is_anon_ns(src_mnt_ns))
ns->seq_origin = src_mnt_ns->seq_origin;
else
ns->seq_origin = src_mnt_ns->seq;
}
mnt = __do_loopback(path, recursive);
if (IS_ERR(mnt)) {
namespace_unlock();
@ -3420,8 +3560,56 @@ static int can_move_mount_beneath(const struct path *from,
return 0;
}
static int do_move_mount(struct path *old_path, struct path *new_path,
bool beneath)
/* may_use_mount() - check if a mount tree can be used
* @mnt: vfsmount to be used
*
* This helper checks if the caller may use the mount tree starting
* from @path->mnt. The caller may use the mount tree under the
* following circumstances:
*
* (1) The caller is located in the mount namespace of the mount tree.
* This also implies that the mount does not belong to an anonymous
* mount namespace.
* (2) The caller is trying to use a mount tree that belongs to an
* anonymous mount namespace.
*
* For that to be safe, this helper enforces that the origin mount
* namespace the anonymous mount namespace was created from is the
* same as the caller's mount namespace by comparing the sequence
* numbers.
*
* The ownership of a non-anonymous mount namespace such as the
* caller's cannot change.
* => We know that the caller's mount namespace is stable.
*
* If the origin sequence number of the anonymous mount namespace is
* the same as the sequence number of the caller's mount namespace.
* => The owning namespaces are the same.
*
* ==> The earlier capability check on the owning namespace of the
* caller's mount namespace ensures that the caller has the
* ability to use the mount tree.
*
* Returns true if the mount tree can be used, false otherwise.
*/
static inline bool may_use_mount(struct mount *mnt)
{
if (check_mnt(mnt))
return true;
/*
* Make sure that noone unmounted the target path or somehow
* managed to get their hands on something purely kernel
* internal.
*/
if (!is_mounted(&mnt->mnt))
return false;
return check_anonymous_mnt(mnt);
}
static int do_move_mount(struct path *old_path,
struct path *new_path, enum mnt_tree_flags_t flags)
{
struct mnt_namespace *ns;
struct mount *p;
@ -3429,8 +3617,7 @@ static int do_move_mount(struct path *old_path, struct path *new_path,
struct mount *parent;
struct mountpoint *mp, *old_mp;
int err;
bool attached;
enum mnt_tree_flags_t flags = 0;
bool attached, beneath = flags & MNT_TREE_BENEATH;
mp = do_lock_mount(new_path, beneath);
if (IS_ERR(mp))
@ -3446,8 +3633,7 @@ static int do_move_mount(struct path *old_path, struct path *new_path,
ns = old->mnt_ns;
err = -EINVAL;
/* The mountpoint must be in our namespace. */
if (!check_mnt(p))
if (!may_use_mount(p))
goto out;
/* The thing moved must be mounted... */
@ -3458,6 +3644,32 @@ static int do_move_mount(struct path *old_path, struct path *new_path,
if (!(attached ? check_mnt(old) : is_anon_ns(ns)))
goto out;
if (is_anon_ns(ns)) {
/*
* Ending up with two files referring to the root of the
* same anonymous mount namespace would cause an error
* as this would mean trying to move the same mount
* twice into the mount tree which would be rejected
* later. But be explicit about it right here.
*/
if ((is_anon_ns(p->mnt_ns) && ns == p->mnt_ns))
goto out;
/*
* If this is an anonymous mount tree ensure that mount
* propagation can detect mounts that were just
* propagated to the target mount tree so we don't
* propagate onto them.
*/
ns->mntns_flags |= MNTNS_PROPAGATING;
} else if (is_anon_ns(p->mnt_ns)) {
/*
* Don't allow moving an attached mount tree to an
* anonymous mount tree.
*/
goto out;
}
if (old->mnt.mnt_flags & MNT_LOCKED)
goto out;
@ -3500,6 +3712,9 @@ static int do_move_mount(struct path *old_path, struct path *new_path,
if (err)
goto out;
if (is_anon_ns(ns))
ns->mntns_flags &= ~MNTNS_PROPAGATING;
/* if the mount is moved, it should no longer be expire
* automatically */
list_del_init(&old->mnt_expire);
@ -3508,10 +3723,13 @@ static int do_move_mount(struct path *old_path, struct path *new_path,
out:
unlock_mount(mp);
if (!err) {
if (attached)
if (attached) {
mntput_no_expire(parent);
else
} else {
/* Make sure we notice when we leak mounts. */
VFS_WARN_ON_ONCE(!mnt_ns_empty(ns));
free_mnt_ns(ns);
}
}
return err;
}
@ -3528,7 +3746,7 @@ static int do_move_mount_old(struct path *path, const char *old_name)
if (err)
return err;
err = do_move_mount(&old_path, path, false);
err = do_move_mount(&old_path, path, 0);
path_put(&old_path);
return err;
}
@ -4369,6 +4587,21 @@ err_unlock:
return ret;
}
static inline int vfs_move_mount(struct path *from_path, struct path *to_path,
enum mnt_tree_flags_t mflags)
{
int ret;
ret = security_move_mount(from_path, to_path);
if (ret)
return ret;
if (mflags & MNT_TREE_PROPAGATION)
return do_set_group(from_path, to_path);
return do_move_mount(from_path, to_path, mflags);
}
/*
* Move a mount from one place to another. In combination with
* fsopen()/fsmount() this is used to install a new mount and in combination
@ -4382,8 +4615,12 @@ SYSCALL_DEFINE5(move_mount,
int, to_dfd, const char __user *, to_pathname,
unsigned int, flags)
{
struct path from_path, to_path;
unsigned int lflags;
struct path to_path __free(path_put) = {};
struct path from_path __free(path_put) = {};
struct filename *to_name __free(putname) = NULL;
struct filename *from_name __free(putname) = NULL;
unsigned int lflags, uflags;
enum mnt_tree_flags_t mflags = 0;
int ret = 0;
if (!may_mount())
@ -4396,43 +4633,53 @@ SYSCALL_DEFINE5(move_mount,
(MOVE_MOUNT_BENEATH | MOVE_MOUNT_SET_GROUP))
return -EINVAL;
/* If someone gives a pathname, they aren't permitted to move
* from an fd that requires unmount as we can't get at the flag
* to clear it afterwards.
*/
if (flags & MOVE_MOUNT_SET_GROUP) mflags |= MNT_TREE_PROPAGATION;
if (flags & MOVE_MOUNT_BENEATH) mflags |= MNT_TREE_BENEATH;
lflags = 0;
if (flags & MOVE_MOUNT_F_SYMLINKS) lflags |= LOOKUP_FOLLOW;
if (flags & MOVE_MOUNT_F_AUTOMOUNTS) lflags |= LOOKUP_AUTOMOUNT;
if (flags & MOVE_MOUNT_F_EMPTY_PATH) lflags |= LOOKUP_EMPTY;
ret = user_path_at(from_dfd, from_pathname, lflags, &from_path);
if (ret < 0)
return ret;
uflags = 0;
if (flags & MOVE_MOUNT_F_EMPTY_PATH) uflags = AT_EMPTY_PATH;
from_name = getname_maybe_null(from_pathname, uflags);
if (IS_ERR(from_name))
return PTR_ERR(from_name);
lflags = 0;
if (flags & MOVE_MOUNT_T_SYMLINKS) lflags |= LOOKUP_FOLLOW;
if (flags & MOVE_MOUNT_T_AUTOMOUNTS) lflags |= LOOKUP_AUTOMOUNT;
if (flags & MOVE_MOUNT_T_EMPTY_PATH) lflags |= LOOKUP_EMPTY;
uflags = 0;
if (flags & MOVE_MOUNT_T_EMPTY_PATH) uflags = AT_EMPTY_PATH;
to_name = getname_maybe_null(to_pathname, uflags);
if (IS_ERR(to_name))
return PTR_ERR(to_name);
ret = user_path_at(to_dfd, to_pathname, lflags, &to_path);
if (ret < 0)
goto out_from;
if (!to_name && to_dfd >= 0) {
CLASS(fd_raw, f_to)(to_dfd);
if (fd_empty(f_to))
return -EBADF;
ret = security_move_mount(&from_path, &to_path);
if (ret < 0)
goto out_to;
to_path = fd_file(f_to)->f_path;
path_get(&to_path);
} else {
ret = filename_lookup(to_dfd, to_name, lflags, &to_path, NULL);
if (ret)
return ret;
}
if (flags & MOVE_MOUNT_SET_GROUP)
ret = do_set_group(&from_path, &to_path);
else
ret = do_move_mount(&from_path, &to_path,
(flags & MOVE_MOUNT_BENEATH));
if (!from_name && from_dfd >= 0) {
CLASS(fd_raw, f_from)(from_dfd);
if (fd_empty(f_from))
return -EBADF;
out_to:
path_put(&to_path);
out_from:
path_put(&from_path);
return ret;
return vfs_move_mount(&fd_file(f_from)->f_path, &to_path, mflags);
}
ret = filename_lookup(from_dfd, from_name, lflags, &from_path, NULL);
if (ret)
return ret;
return vfs_move_mount(&from_path, &to_path, mflags);
}
/*
@ -5516,7 +5763,7 @@ static int grab_requested_root(struct mnt_namespace *ns, struct path *root)
* We have to find the first mount in our ns and use that, however it
* may not exist, so handle that properly.
*/
if (RB_EMPTY_ROOT(&ns->mounts))
if (mnt_ns_empty(ns))
return -ENOENT;
first = child = ns->root;
@ -5556,7 +5803,7 @@ static int do_statmount(struct kstatmount *s, u64 mnt_id, u64 mnt_ns_id,
int err;
/* Has the namespace already been emptied? */
if (mnt_ns_id && RB_EMPTY_ROOT(&ns->mounts))
if (mnt_ns_id && mnt_ns_empty(ns))
return -ENOENT;
s->mnt = lookup_mnt_in_ns(mnt_id, ns);

View File

@ -150,7 +150,7 @@ static struct mount *propagation_next(struct mount *m,
struct mount *origin)
{
/* are there any slaves of this mount? */
if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
if (!IS_MNT_PROPAGATED(m) && !list_empty(&m->mnt_slave_list))
return first_slave(m);
while (1) {
@ -174,7 +174,7 @@ static struct mount *skip_propagation_subtree(struct mount *m,
* Advance m such that propagation_next will not return
* the slaves of m.
*/
if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
if (!IS_MNT_PROPAGATED(m) && !list_empty(&m->mnt_slave_list))
m = last_slave(m);
return m;
@ -185,7 +185,7 @@ static struct mount *next_group(struct mount *m, struct mount *origin)
while (1) {
while (1) {
struct mount *next;
if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
if (!IS_MNT_PROPAGATED(m) && !list_empty(&m->mnt_slave_list))
return first_slave(m);
next = next_peer(m);
if (m->mnt_group_id == origin->mnt_group_id) {
@ -226,7 +226,7 @@ static int propagate_one(struct mount *m, struct mountpoint *dest_mp)
struct mount *child;
int type;
/* skip ones added by this propagate_mnt() */
if (IS_MNT_NEW(m))
if (IS_MNT_PROPAGATED(m))
return 0;
/* skip if mountpoint isn't covered by it */
if (!is_subdir(dest_mp->m_dentry, m->mnt.mnt_root))
@ -380,7 +380,7 @@ bool propagation_would_overmount(const struct mount *from,
if (!IS_MNT_SHARED(from))
return false;
if (IS_MNT_NEW(to))
if (IS_MNT_PROPAGATED(to))
return false;
if (to->mnt.mnt_root != mp->m_dentry)

View File

@ -12,7 +12,7 @@
#define IS_MNT_SHARED(m) ((m)->mnt.mnt_flags & MNT_SHARED)
#define IS_MNT_SLAVE(m) ((m)->mnt_master)
#define IS_MNT_NEW(m) (!(m)->mnt_ns || is_anon_ns((m)->mnt_ns))
#define IS_MNT_PROPAGATED(m) (!(m)->mnt_ns || ((m)->mnt_ns->mntns_flags & MNTNS_PROPAGATING))
#define CLEAR_MNT_SHARED(m) ((m)->mnt.mnt_flags &= ~MNT_SHARED)
#define IS_MNT_UNBINDABLE(m) ((m)->mnt.mnt_flags & MNT_UNBINDABLE)
#define IS_MNT_MARKED(m) ((m)->mnt.mnt_flags & MNT_MARKED)

View File

@ -2854,6 +2854,7 @@ static inline struct filename *getname_maybe_null(const char __user *name, int f
return __getname_maybe_null(name);
}
extern void putname(struct filename *name);
DEFINE_FREE(putname, struct filename *, if (!IS_ERR_OR_NULL(_T)) putname(_T))
static inline struct filename *refname(struct filename *name)
{

View File

@ -20,6 +20,7 @@
#include <stdarg.h>
#include <linux/mount.h>
#include "../filesystems/overlayfs/wrappers.h"
#include "../kselftest_harness.h"
#ifndef CLONE_NEWNS
@ -126,6 +127,26 @@
#endif
#endif
#ifndef __NR_move_mount
#if defined __alpha__
#define __NR_move_mount 539
#elif defined _MIPS_SIM
#if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */
#define __NR_move_mount 4429
#endif
#if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */
#define __NR_move_mount 6429
#endif
#if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */
#define __NR_move_mount 5429
#endif
#elif defined __ia64__
#define __NR_move_mount (428 + 1024)
#else
#define __NR_move_mount 429
#endif
#endif
#ifndef MOUNT_ATTR_IDMAP
#define MOUNT_ATTR_IDMAP 0x00100000
#endif
@ -397,6 +418,10 @@ FIXTURE_SETUP(mount_setattr)
ASSERT_EQ(mkdir("/tmp/B/BB", 0777), 0);
ASSERT_EQ(mkdir("/tmp/target1", 0777), 0);
ASSERT_EQ(mkdir("/tmp/target2", 0777), 0);
ASSERT_EQ(mount("testing", "/tmp/B/BB", "tmpfs", MS_NOATIME | MS_NODEV,
"size=100000,mode=700"), 0);
@ -1506,4 +1531,631 @@ TEST_F(mount_setattr, mount_attr_nosymfollow)
ASSERT_EQ(close(fd), 0);
}
TEST_F(mount_setattr, open_tree_detached)
{
int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF;
struct statx stx;
fd_tree_base = sys_open_tree(-EBADF, "/mnt",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_base, 0);
/*
* /mnt testing tmpfs
* |-/mnt/A testing tmpfs
* | `-/mnt/A/AA testing tmpfs
* | `-/mnt/A/AA/B testing tmpfs
* | `-/mnt/A/AA/B/BB testing tmpfs
* `-/mnt/B testing ramfs
*/
ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA/B", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA/B/BB", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
fd_tree_subdir = sys_open_tree(fd_tree_base, "A/AA",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_subdir, 0);
/*
* /AA testing tmpfs
* `-/AA/B testing tmpfs
* `-/AA/B/BB testing tmpfs
*/
ASSERT_EQ(statx(fd_tree_subdir, "B", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_subdir, "B/BB", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(move_mount(fd_tree_subdir, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0);
/*
* /tmp/target1 testing tmpfs
* `-/tmp/target1/B testing tmpfs
* `-/tmp/target1/B/BB testing tmpfs
*/
ASSERT_EQ(statx(-EBADF, "/tmp/target1", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(-EBADF, "/tmp/target1/B", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(-EBADF, "/tmp/target1/B/BB", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(move_mount(fd_tree_base, "", -EBADF, "/tmp/target2", MOVE_MOUNT_F_EMPTY_PATH), 0);
/*
* /tmp/target2 testing tmpfs
* |-/tmp/target2/A testing tmpfs
* | `-/tmp/target2/A/AA testing tmpfs
* | `-/tmp/target2/A/AA/B testing tmpfs
* | `-/tmp/target2/A/AA/B/BB testing tmpfs
* `-/tmp/target2/B testing ramfs
*/
ASSERT_EQ(statx(-EBADF, "/tmp/target2", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(-EBADF, "/tmp/target2/A", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(-EBADF, "/tmp/target2/A/AA", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(-EBADF, "/tmp/target2/A/AA/B", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(-EBADF, "/tmp/target2/A/AA/B/BB", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(-EBADF, "/tmp/target2/B", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
EXPECT_EQ(close(fd_tree_base), 0);
EXPECT_EQ(close(fd_tree_subdir), 0);
}
TEST_F(mount_setattr, open_tree_detached_fail)
{
int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF;
struct statx stx;
fd_tree_base = sys_open_tree(-EBADF, "/mnt",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_base, 0);
/*
* /mnt testing tmpfs
* |-/mnt/A testing tmpfs
* | `-/mnt/A/AA testing tmpfs
* | `-/mnt/A/AA/B testing tmpfs
* | `-/mnt/A/AA/B/BB testing tmpfs
* `-/mnt/B testing ramfs
*/
ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA/B", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA/B/BB", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
/*
* The origin mount namespace of the anonymous mount namespace
* of @fd_tree_base doesn't match the caller's mount namespace
* anymore so creation of another detached mounts must fail.
*/
fd_tree_subdir = sys_open_tree(fd_tree_base, "A/AA",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_LT(fd_tree_subdir, 0);
ASSERT_EQ(errno, EINVAL);
}
TEST_F(mount_setattr, open_tree_detached_fail2)
{
int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF;
struct statx stx;
fd_tree_base = sys_open_tree(-EBADF, "/mnt",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_base, 0);
/*
* /mnt testing tmpfs
* |-/mnt/A testing tmpfs
* | `-/mnt/A/AA testing tmpfs
* | `-/mnt/A/AA/B testing tmpfs
* | `-/mnt/A/AA/B/BB testing tmpfs
* `-/mnt/B testing ramfs
*/
ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA/B", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA/B/BB", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
EXPECT_EQ(create_and_enter_userns(), 0);
/*
* The caller entered a new user namespace. They will have
* CAP_SYS_ADMIN in this user namespace. However, they're still
* located in a mount namespace that is owned by an ancestor
* user namespace in which they hold no privilege. Creating a
* detached mount must thus fail.
*/
fd_tree_subdir = sys_open_tree(fd_tree_base, "A/AA",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_LT(fd_tree_subdir, 0);
ASSERT_EQ(errno, EPERM);
}
TEST_F(mount_setattr, open_tree_detached_fail3)
{
int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF;
struct statx stx;
fd_tree_base = sys_open_tree(-EBADF, "/mnt",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_base, 0);
/*
* /mnt testing tmpfs
* |-/mnt/A testing tmpfs
* | `-/mnt/A/AA testing tmpfs
* | `-/mnt/A/AA/B testing tmpfs
* | `-/mnt/A/AA/B/BB testing tmpfs
* `-/mnt/B testing ramfs
*/
ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA/B", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(statx(fd_tree_base, "A/AA/B/BB", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
EXPECT_EQ(prepare_unpriv_mountns(), 0);
/*
* The caller entered a new mount namespace. They will have
* CAP_SYS_ADMIN in the owning user namespace of their mount
* namespace.
*
* However, the origin mount namespace of the anonymous mount
* namespace of @fd_tree_base doesn't match the caller's mount
* namespace anymore so creation of another detached mounts must
* fail.
*/
fd_tree_subdir = sys_open_tree(fd_tree_base, "A/AA",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_LT(fd_tree_subdir, 0);
ASSERT_EQ(errno, EINVAL);
}
TEST_F(mount_setattr, open_tree_subfolder)
{
int fd_context, fd_tmpfs, fd_tree;
fd_context = sys_fsopen("tmpfs", 0);
ASSERT_GE(fd_context, 0);
ASSERT_EQ(sys_fsconfig(fd_context, FSCONFIG_CMD_CREATE, NULL, NULL, 0), 0);
fd_tmpfs = sys_fsmount(fd_context, 0, 0);
ASSERT_GE(fd_tmpfs, 0);
EXPECT_EQ(close(fd_context), 0);
ASSERT_EQ(mkdirat(fd_tmpfs, "subdir", 0755), 0);
fd_tree = sys_open_tree(fd_tmpfs, "subdir",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree, 0);
EXPECT_EQ(close(fd_tmpfs), 0);
ASSERT_EQ(mkdirat(-EBADF, "/mnt/open_tree_subfolder", 0755), 0);
ASSERT_EQ(sys_move_mount(fd_tree, "", -EBADF, "/mnt/open_tree_subfolder", MOVE_MOUNT_F_EMPTY_PATH), 0);
EXPECT_EQ(close(fd_tree), 0);
ASSERT_EQ(umount2("/mnt/open_tree_subfolder", 0), 0);
EXPECT_EQ(rmdir("/mnt/open_tree_subfolder"), 0);
}
TEST_F(mount_setattr, mount_detached_mount_on_detached_mount_then_close)
{
int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF;
struct statx stx;
fd_tree_base = sys_open_tree(-EBADF, "/mnt",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_base, 0);
/*
* /mnt testing tmpfs
*/
ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0);
ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
fd_tree_subdir = sys_open_tree(fd_tree_base, "",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_EMPTY_PATH | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_subdir, 0);
/*
* /mnt testing tmpfs
*/
ASSERT_EQ(statx(fd_tree_subdir, "A", 0, 0, &stx), 0);
ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
/*
* /mnt testing tmpfs
* `-/mnt testing tmpfs
*/
ASSERT_EQ(move_mount(fd_tree_subdir, "", fd_tree_base, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0);
ASSERT_EQ(statx(fd_tree_subdir, "", AT_EMPTY_PATH, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_NE(move_mount(fd_tree_subdir, "", fd_tree_base, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0);
EXPECT_EQ(close(fd_tree_base), 0);
EXPECT_EQ(close(fd_tree_subdir), 0);
}
TEST_F(mount_setattr, mount_detached_mount_on_detached_mount_and_attach)
{
int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF;
struct statx stx;
__u64 mnt_id = 0;
fd_tree_base = sys_open_tree(-EBADF, "/mnt",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_base, 0);
/*
* /mnt testing tmpfs
*/
ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0);
ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
fd_tree_subdir = sys_open_tree(fd_tree_base, "",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_EMPTY_PATH | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_subdir, 0);
/*
* /mnt testing tmpfs
*/
ASSERT_EQ(statx(fd_tree_subdir, "A", 0, 0, &stx), 0);
ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
/*
* /mnt testing tmpfs
* `-/mnt testing tmpfs
*/
ASSERT_EQ(move_mount(fd_tree_subdir, "", fd_tree_base, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0);
ASSERT_EQ(statx(fd_tree_subdir, "", AT_EMPTY_PATH, STATX_MNT_ID_UNIQUE, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_TRUE(stx.stx_mask & STATX_MNT_ID_UNIQUE);
mnt_id = stx.stx_mnt_id;
ASSERT_NE(move_mount(fd_tree_subdir, "", fd_tree_base, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0);
ASSERT_EQ(move_mount(fd_tree_base, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0);
ASSERT_EQ(statx(-EBADF, "/tmp/target1", 0, STATX_MNT_ID_UNIQUE, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_TRUE(stx.stx_mask & STATX_MNT_ID_UNIQUE);
ASSERT_EQ(stx.stx_mnt_id, mnt_id);
EXPECT_EQ(close(fd_tree_base), 0);
EXPECT_EQ(close(fd_tree_subdir), 0);
}
TEST_F(mount_setattr, move_mount_detached_fail)
{
int fd_tree_base = -EBADF, fd_tree_subdir = -EBADF;
struct statx stx;
fd_tree_base = sys_open_tree(-EBADF, "/mnt",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_base, 0);
/* Attach the mount to the caller's mount namespace. */
ASSERT_EQ(move_mount(fd_tree_base, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0);
ASSERT_EQ(statx(fd_tree_base, "A", 0, 0, &stx), 0);
ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
fd_tree_subdir = sys_open_tree(-EBADF, "/tmp/B",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE);
ASSERT_GE(fd_tree_subdir, 0);
ASSERT_EQ(statx(fd_tree_subdir, "BB", 0, 0, &stx), 0);
ASSERT_FALSE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
/* Not allowed to move an attached mount to a detached mount. */
ASSERT_NE(move_mount(fd_tree_base, "", fd_tree_subdir, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0);
ASSERT_EQ(errno, EINVAL);
EXPECT_EQ(close(fd_tree_base), 0);
EXPECT_EQ(close(fd_tree_subdir), 0);
}
TEST_F(mount_setattr, attach_detached_mount_then_umount_then_close)
{
int fd_tree = -EBADF;
struct statx stx;
fd_tree = sys_open_tree(-EBADF, "/mnt",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree, 0);
ASSERT_EQ(statx(fd_tree, "A", 0, 0, &stx), 0);
/* We copied with AT_RECURSIVE so /mnt/A must be a mountpoint. */
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
/* Attach the mount to the caller's mount namespace. */
ASSERT_EQ(move_mount(fd_tree, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0);
ASSERT_EQ(statx(-EBADF, "/tmp/target1", 0, 0, &stx), 0);
ASSERT_TRUE(stx.stx_attributes & STATX_ATTR_MOUNT_ROOT);
ASSERT_EQ(umount2("/tmp/target1", MNT_DETACH), 0);
/*
* This tests whether dissolve_on_fput() handles a NULL mount
* namespace correctly, i.e., that it doesn't splat.
*/
EXPECT_EQ(close(fd_tree), 0);
}
TEST_F(mount_setattr, mount_detached1_onto_detached2_then_close_detached1_then_mount_detached2_onto_attached)
{
int fd_tree1 = -EBADF, fd_tree2 = -EBADF;
/*
* |-/mnt/A testing tmpfs
* `-/mnt/A/AA testing tmpfs
* `-/mnt/A/AA/B testing tmpfs
* `-/mnt/A/AA/B/BB testing tmpfs
*/
fd_tree1 = sys_open_tree(-EBADF, "/mnt/A",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree1, 0);
/*
* `-/mnt/B testing ramfs
*/
fd_tree2 = sys_open_tree(-EBADF, "/mnt/B",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_EMPTY_PATH | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree2, 0);
/*
* Move the source detached mount tree to the target detached
* mount tree. This will move all the mounts in the source mount
* tree from the source anonymous mount namespace to the target
* anonymous mount namespace.
*
* The source detached mount tree and the target detached mount
* tree now both refer to the same anonymous mount namespace.
*
* |-"" testing ramfs
* `-"" testing tmpfs
* `-""/AA testing tmpfs
* `-""/AA/B testing tmpfs
* `-""/AA/B/BB testing tmpfs
*/
ASSERT_EQ(move_mount(fd_tree1, "", fd_tree2, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0);
/*
* The source detached mount tree @fd_tree1 is now an attached
* mount, i.e., it has a parent. Specifically, it now has the
* root mount of the mount tree of @fd_tree2 as its parent.
*
* That means we are no longer allowed to attach it as we only
* allow attaching the root of an anonymous mount tree, not
* random bits and pieces. Verify that the kernel enforces this.
*/
ASSERT_NE(move_mount(fd_tree1, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0);
/*
* Closing the source detached mount tree must not unmount and
* free the shared anonymous mount namespace. The kernel will
* quickly yell at us because the anonymous mount namespace
* won't be empty when it's freed.
*/
EXPECT_EQ(close(fd_tree1), 0);
/*
* Attach the mount tree to a non-anonymous mount namespace.
* This can only succeed if closing fd_tree1 had proper
* semantics and didn't cause the anonymous mount namespace to
* be freed. If it did this will trigger a UAF which will be
* visible on any KASAN enabled kernel.
*
* |-/tmp/target1 testing ramfs
* `-/tmp/target1 testing tmpfs
* `-/tmp/target1/AA testing tmpfs
* `-/tmp/target1/AA/B testing tmpfs
* `-/tmp/target1/AA/B/BB testing tmpfs
*/
ASSERT_EQ(move_mount(fd_tree2, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0);
EXPECT_EQ(close(fd_tree2), 0);
}
TEST_F(mount_setattr, two_detached_mounts_referring_to_same_anonymous_mount_namespace)
{
int fd_tree1 = -EBADF, fd_tree2 = -EBADF;
/*
* Copy the following mount tree:
*
* |-/mnt/A testing tmpfs
* `-/mnt/A/AA testing tmpfs
* `-/mnt/A/AA/B testing tmpfs
* `-/mnt/A/AA/B/BB testing tmpfs
*/
fd_tree1 = sys_open_tree(-EBADF, "/mnt/A",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree1, 0);
/*
* Create an O_PATH file descriptors with a separate struct file
* that refers to the same detached mount tree as @fd_tree1
*/
fd_tree2 = sys_open_tree(fd_tree1, "",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_EMPTY_PATH | OPEN_TREE_CLOEXEC);
ASSERT_GE(fd_tree2, 0);
/*
* Copy the following mount tree:
*
* |-/tmp/target1 testing tmpfs
* `-/tmp/target1/AA testing tmpfs
* `-/tmp/target1/AA/B testing tmpfs
* `-/tmp/target1/AA/B/BB testing tmpfs
*/
ASSERT_EQ(move_mount(fd_tree2, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0);
/*
* This must fail as this would mean adding the same mount tree
* into the same mount tree.
*/
ASSERT_NE(move_mount(fd_tree1, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0);
}
TEST_F(mount_setattr, two_detached_subtrees_of_same_anonymous_mount_namespace)
{
int fd_tree1 = -EBADF, fd_tree2 = -EBADF;
/*
* Copy the following mount tree:
*
* |-/mnt/A testing tmpfs
* `-/mnt/A/AA testing tmpfs
* `-/mnt/A/AA/B testing tmpfs
* `-/mnt/A/AA/B/BB testing tmpfs
*/
fd_tree1 = sys_open_tree(-EBADF, "/mnt/A",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree1, 0);
/*
* Create an O_PATH file descriptors with a separate struct file that
* refers to a subtree of the same detached mount tree as @fd_tree1
*/
fd_tree2 = sys_open_tree(fd_tree1, "AA",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_EMPTY_PATH | OPEN_TREE_CLOEXEC);
ASSERT_GE(fd_tree2, 0);
/*
* This must fail as it is only possible to attach the root of a
* detached mount tree.
*/
ASSERT_NE(move_mount(fd_tree2, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0);
ASSERT_EQ(move_mount(fd_tree1, "", -EBADF, "/tmp/target1", MOVE_MOUNT_F_EMPTY_PATH), 0);
}
TEST_F(mount_setattr, detached_tree_propagation)
{
int fd_tree = -EBADF;
struct statx stx1, stx2, stx3, stx4;
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
ASSERT_EQ(mount(NULL, "/mnt", NULL, MS_REC | MS_SHARED, NULL), 0);
/*
* Copy the following mount tree:
*
* /mnt testing tmpfs
* |-/mnt/A testing tmpfs
* | `-/mnt/A/AA testing tmpfs
* | `-/mnt/A/AA/B testing tmpfs
* | `-/mnt/A/AA/B/BB testing tmpfs
* `-/mnt/B testing ramfs
*/
fd_tree = sys_open_tree(-EBADF, "/mnt",
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
AT_RECURSIVE | OPEN_TREE_CLOEXEC |
OPEN_TREE_CLONE);
ASSERT_GE(fd_tree, 0);
ASSERT_EQ(statx(-EBADF, "/mnt/A", 0, 0, &stx1), 0);
ASSERT_EQ(statx(fd_tree, "A", 0, 0, &stx2), 0);
/*
* Copying the mount namespace like done above doesn't alter the
* mounts in any way so the filesystem mounted on /mnt must be
* identical even though the mounts will differ. Use the device
* information to verify that. Note that tmpfs will have a 0
* major number so comparing the major number is misleading.
*/
ASSERT_EQ(stx1.stx_dev_minor, stx2.stx_dev_minor);
/* Mount a tmpfs filesystem over /mnt/A. */
ASSERT_EQ(mount(NULL, "/mnt/A", "tmpfs", 0, NULL), 0);
ASSERT_EQ(statx(-EBADF, "/mnt/A", 0, 0, &stx3), 0);
ASSERT_EQ(statx(fd_tree, "A", 0, 0, &stx4), 0);
/*
* A new filesystem has been mounted on top of /mnt/A which
* means that the device information will be different for any
* statx() that was taken from /mnt/A before the mount compared
* to one after the mount.
*
* Since we already now that the device information between the
* stx1 and stx2 samples are identical we also now that stx2 and
* stx3 device information will necessarily differ.
*/
ASSERT_NE(stx1.stx_dev_minor, stx3.stx_dev_minor);
/*
* If mount propagation worked correctly then the tmpfs mount
* that was created after the mount namespace was unshared will
* have propagated onto /mnt/A in the detached mount tree.
*
* Verify that the device information for stx3 and stx4 are
* identical. It is already established that stx3 is different
* from both stx1 and stx2 sampled before the tmpfs mount was
* done so if stx3 and stx4 are identical the proof is done.
*/
ASSERT_EQ(stx3.stx_dev_minor, stx4.stx_dev_minor);
EXPECT_EQ(close(fd_tree), 0);
}
TEST_HARNESS_MAIN