Merge tag 'fs.move_mount.move_mount_set_group.v5.15' of git://git.kernel.org/pub...
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 31 Aug 2021 18:54:02 +0000 (11:54 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 31 Aug 2021 18:54:02 +0000 (11:54 -0700)
Pull move_mount updates from Christian Brauner:
 "This contains an extension to the move_mount() syscall making it
  possible to add a single private mount into an existing propagation
  tree.

  The use-case comes from the criu folks which have been struggling with
  restoring complex mount trees for a long time. Variations of this work
  have been discussed at Plumbers before, e.g.

      https://www.linuxplumbersconf.org/event/7/contributions/640/

  The extension to move_mount() enables criu to restore any set of mount
  namespaces, mount trees and sharing group trees without introducing
  yet more complexity into mount propagation itself.

  The changes required to criu to make use of this and restore complex
  propagation trees are available at

      https://github.com/Snorch/criu/commits/mount-v2-poc

  A cleaned-up version of this will go up for merging into the main criu
  repo after this lands"

* tag 'fs.move_mount.move_mount_set_group.v5.15' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux:
  tests: add move_mount(MOVE_MOUNT_SET_GROUP) selftest
  move_mount: allow to add a mount into an existing group

1  2 
fs/namespace.c

diff --combined fs/namespace.c
@@@ -1715,14 -1715,18 +1715,14 @@@ static inline bool may_mount(void
        return ns_capable(current->nsproxy->mnt_ns->user_ns, CAP_SYS_ADMIN);
  }
  
 -#ifdef        CONFIG_MANDATORY_FILE_LOCKING
 -static inline bool may_mandlock(void)
 +static void warn_mandlock(void)
  {
 -      return capable(CAP_SYS_ADMIN);
 +      pr_warn_once("=======================================================\n"
 +                   "WARNING: The mand mount option has been deprecated and\n"
 +                   "         and is ignored by this kernel. Remove the mand\n"
 +                   "         option from the mount to silence this warning.\n"
 +                   "=======================================================\n");
  }
 -#else
 -static inline bool may_mandlock(void)
 -{
 -      pr_warn("VFS: \"mand\" mount option not supported");
 -      return false;
 -}
 -#endif
  
  static int can_umount(const struct path *path, int flags)
  {
@@@ -1934,20 -1938,6 +1934,20 @@@ void drop_collected_mounts(struct vfsmo
        namespace_unlock();
  }
  
 +static bool has_locked_children(struct mount *mnt, struct dentry *dentry)
 +{
 +      struct mount *child;
 +
 +      list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
 +              if (!is_subdir(child->mnt_mountpoint, dentry))
 +                      continue;
 +
 +              if (child->mnt.mnt_flags & MNT_LOCKED)
 +                      return true;
 +      }
 +      return false;
 +}
 +
  /**
   * clone_private_mount - create a private clone of a path
   * @path: path to clone
@@@ -1963,19 -1953,10 +1963,19 @@@ struct vfsmount *clone_private_mount(co
        struct mount *old_mnt = real_mount(path->mnt);
        struct mount *new_mnt;
  
 +      down_read(&namespace_sem);
        if (IS_MNT_UNBINDABLE(old_mnt))
 -              return ERR_PTR(-EINVAL);
 +              goto invalid;
 +
 +      if (!check_mnt(old_mnt))
 +              goto invalid;
 +
 +      if (has_locked_children(old_mnt, path->dentry))
 +              goto invalid;
  
        new_mnt = clone_mnt(old_mnt, path->dentry, CL_PRIVATE);
 +      up_read(&namespace_sem);
 +
        if (IS_ERR(new_mnt))
                return ERR_CAST(new_mnt);
  
        new_mnt->mnt_ns = MNT_NS_INTERNAL;
  
        return &new_mnt->mnt;
 +
 +invalid:
 +      up_read(&namespace_sem);
 +      return ERR_PTR(-EINVAL);
  }
  EXPORT_SYMBOL_GPL(clone_private_mount);
  
@@@ -2338,6 -2315,19 +2338,6 @@@ static int do_change_type(struct path *
        return err;
  }
  
 -static bool has_locked_children(struct mount *mnt, struct dentry *dentry)
 -{
 -      struct mount *child;
 -      list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
 -              if (!is_subdir(child->mnt_mountpoint, dentry))
 -                      continue;
 -
 -              if (child->mnt.mnt_flags & MNT_LOCKED)
 -                      return true;
 -      }
 -      return false;
 -}
 -
  static struct mount *__do_loopback(struct path *old_path, int recurse)
  {
        struct mount *mnt = ERR_PTR(-EINVAL), *old = real_mount(old_path->mnt);
        return ret;
  }
  
+ static int do_set_group(struct path *from_path, struct path *to_path)
+ {
+       struct mount *from, *to;
+       int err;
+       from = real_mount(from_path->mnt);
+       to = real_mount(to_path->mnt);
+       namespace_lock();
+       err = -EINVAL;
+       /* To and From must be mounted */
+       if (!is_mounted(&from->mnt))
+               goto out;
+       if (!is_mounted(&to->mnt))
+               goto out;
+       err = -EPERM;
+       /* We should be allowed to modify mount namespaces of both mounts */
+       if (!ns_capable(from->mnt_ns->user_ns, CAP_SYS_ADMIN))
+               goto out;
+       if (!ns_capable(to->mnt_ns->user_ns, CAP_SYS_ADMIN))
+               goto out;
+       err = -EINVAL;
+       /* To and From paths should be mount roots */
+       if (from_path->dentry != from_path->mnt->mnt_root)
+               goto out;
+       if (to_path->dentry != to_path->mnt->mnt_root)
+               goto out;
+       /* Setting sharing groups is only allowed across same superblock */
+       if (from->mnt.mnt_sb != to->mnt.mnt_sb)
+               goto out;
+       /* From mount root should be wider than To mount root */
+       if (!is_subdir(to->mnt.mnt_root, from->mnt.mnt_root))
+               goto out;
+       /* From mount should not have locked children in place of To's root */
+       if (has_locked_children(from, to->mnt.mnt_root))
+               goto out;
+       /* Setting sharing groups is only allowed on private mounts */
+       if (IS_MNT_SHARED(to) || IS_MNT_SLAVE(to))
+               goto out;
+       /* From should not be private */
+       if (!IS_MNT_SHARED(from) && !IS_MNT_SLAVE(from))
+               goto out;
+       if (IS_MNT_SLAVE(from)) {
+               struct mount *m = from->mnt_master;
+               list_add(&to->mnt_slave, &m->mnt_slave_list);
+               to->mnt_master = m;
+       }
+       if (IS_MNT_SHARED(from)) {
+               to->mnt_group_id = from->mnt_group_id;
+               list_add(&to->mnt_share, &from->mnt_share);
+               lock_mount_hash();
+               set_mnt_shared(to);
+               unlock_mount_hash();
+       }
+       err = 0;
+ out:
+       namespace_unlock();
+       return err;
+ }
  static int do_move_mount(struct path *old_path, struct path *new_path)
  {
        struct mnt_namespace *ns;
@@@ -3189,8 -3251,8 +3261,8 @@@ int path_mount(const char *dev_name, st
                return ret;
        if (!may_mount())
                return -EPERM;
 -      if ((flags & SB_MANDLOCK) && !may_mandlock())
 -              return -EPERM;
 +      if (flags & SB_MANDLOCK)
 +              warn_mandlock();
  
        /* Default to relatime unless overriden */
        if (!(flags & MS_NOATIME))
@@@ -3573,8 -3635,9 +3645,8 @@@ SYSCALL_DEFINE3(fsmount, int, fs_fd, un
        if (fc->phase != FS_CONTEXT_AWAITING_MOUNT)
                goto err_unlock;
  
 -      ret = -EPERM;
 -      if ((fc->sb_flags & SB_MANDLOCK) && !may_mandlock())
 -              goto err_unlock;
 +      if (fc->sb_flags & SB_MANDLOCK)
 +              warn_mandlock();
  
        newmount.mnt = vfs_create_mount(fc);
        if (IS_ERR(newmount.mnt)) {
@@@ -3678,7 -3741,10 +3750,10 @@@ SYSCALL_DEFINE5(move_mount
        if (ret < 0)
                goto out_to;
  
-       ret = do_move_mount(&from_path, &to_path);
+       if (flags & MOVE_MOUNT_SET_GROUP)
+               ret = do_set_group(&from_path, &to_path);
+       else
+               ret = do_move_mount(&from_path, &to_path);
  
  out_to:
        path_put(&to_path);