[PATCH v6 7/8] vfs: Replace security_sb_mount/security_move_mount with granular hooks

Song Liu <[email protected]>
Newsgroups org.kernel.vger.linux-security-module,org.kernel.vger.linux-fsdevel,org.kernel.vger.selinux
Message-ID <[email protected]>
Replace the monolithic security_sb_mount() call in path_mount() and
the security_move_mount() call on the mount-move paths with the new
granular mount hooks:

- do_loopback(): call security_mount_bind()
- do_new_mount(): call security_mount_new()
- do_remount(): call security_mount_remount()
- do_reconfigure_mnt(): call security_mount_reconfigure()
- do_change_type(): call security_mount_change_type()
- do_move_mount(): call security_mount_move()
- do_set_group(): call security_mount_move()

The new hooks are called at the individual operation level with
appropriate context (resolved paths, fs_context), rather than at
the top of path_mount() with raw string arguments.

security_mount_bind() and security_mount_move() are called under the
namespace semaphore, after the mountpoint has been resolved and pinned
by LOCK_MOUNT(). This ensures the LSM inspects the mount that will
actually be used, instead of a mountpoint that could be overmounted
between the check and the operation. Placing the move hook in
do_move_mount() and do_set_group() also unifies the old mount(2)
MS_MOVE path (do_move_mount_old()) and the move_mount(2) path
(vfs_move_mount()) on a single call site.

For MOVE_MOUNT_BENEATH, security_mount_move() additionally receives the
mount that ends up on top, so an LSM can tell a plain move (target ==
top) apart from a mount-beneath operation.

Code generated with the assistance of Claude, reviewed by human.

Signed-off-by: Song Liu <[email protected]>
---
 fs/namespace.c | 77 +++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 61 insertions(+), 16 deletions(-)

diff --git a/fs/namespace.c b/fs/namespace.c
index 3d5cd5bf3b05..bc9401a2efac 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -2893,6 +2893,10 @@ static int do_change_type(const struct path *path, int ms_flags)
 	if (!type)
 		return -EINVAL;
 
+	err = security_mount_change_type(path, ms_flags);
+	if (err)
+		return err;
+
 	guard(namespace_excl)();
 
 	err = may_change_propagation(mnt);
@@ -3003,6 +3007,7 @@ static int do_loopback(const struct path *path, const char *old_name,
 {
 	struct path old_path __free(path_put) = {};
 	struct mount *mnt = NULL;
+	struct path dest;
 	int err;
 
 	if (!old_name || !*old_name)
@@ -3021,6 +3026,17 @@ static int do_loopback(const struct path *path, const char *old_name,
 	if (!check_mnt(mp.parent))
 		return -EINVAL;
 
+	/*
+	 * Check permission against the mountpoint that was actually pinned
+	 * under the namespace semaphore, rather than the caller-supplied
+	 * @path which may have been overmounted before the lock was taken.
+	 */
+	dest.mnt = &mp.parent->mnt;
+	dest.dentry = mp.mp->m_dentry;
+	err = security_mount_bind(&old_path, &dest, recurse);
+	if (err)
+		return err;
+
 	mnt = __do_loopback(&old_path, recurse, CL_COPY_MNT_NS_FILE);
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
@@ -3335,7 +3351,8 @@ static void mnt_warn_timestamp_expiry(const struct path *mountpoint,
  * superblock it refers to.  This is triggered by specifying MS_REMOUNT|MS_BIND
  * to mount(2).
  */
-static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
+static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags,
+			      unsigned long flags)
 {
 	struct super_block *sb = path->mnt->mnt_sb;
 	struct mount *mnt = real_mount(path->mnt);
@@ -3350,6 +3367,10 @@ static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
 	if (!can_change_locked_flags(mnt, mnt_flags))
 		return -EPERM;
 
+	ret = security_mount_reconfigure(path, mnt_flags, flags);
+	if (ret)
+		return ret;
+
 	/*
 	 * We're only checking whether the superblock is read-only not
 	 * changing it, so only take down_read(&sb->s_umount).
@@ -3373,7 +3394,7 @@ static int do_reconfigure_mnt(const struct path *path, unsigned int mnt_flags)
  * on it - tough luck.
  */
 static int do_remount(const struct path *path, int sb_flags,
-		      int mnt_flags, void *data)
+		      int mnt_flags, void *data, unsigned long flags)
 {
 	int err;
 	struct super_block *sb = path->mnt->mnt_sb;
@@ -3400,6 +3421,9 @@ static int do_remount(const struct path *path, int sb_flags,
 	fc->oldapi = true;
 
 	err = parse_monolithic_mount_data(fc, data);
+	if (!err)
+		err = security_mount_remount(fc, path, mnt_flags, flags,
+					    data);
 	if (!err) {
 		down_write(&sb->s_umount);
 		err = -EPERM;
@@ -3438,6 +3462,16 @@ static int do_set_group(const struct path *from_path, const struct path *to_path
 
 	guard(namespace_excl)();
 
+	/*
+	 * Setting a sharing group does not overmount anything, so the
+	 * source, target and top mount all refer to @to_path.  The check
+	 * runs under the namespace semaphore for the same reason as the
+	 * move case.
+	 */
+	err = security_mount_move(from_path, to_path, to_path);
+	if (err)
+		return err;
+
 	err = may_change_propagation(from);
 	if (err)
 		return err;
@@ -3634,6 +3668,7 @@ static int do_move_mount(const struct path *old_path,
 			 enum mnt_tree_flags_t flags)
 {
 	struct mount *old = real_mount(old_path->mnt);
+	struct path target, top;
 	int err;
 	bool beneath = flags & MNT_TREE_BENEATH;
 
@@ -3647,6 +3682,17 @@ static int do_move_mount(const struct path *old_path,
 	if (IS_ERR(mp.parent))
 		return PTR_ERR(mp.parent);
 
+	/*
+	 * The destination that was actually pinned under the namespace
+	 * semaphore.  For a plain move the source is attached on top of
+	 * @target, so @target is also the mount that ends up on top; for
+	 * MOVE_MOUNT_BENEATH the source is inserted below the existing top
+	 * mount, which is reported separately below.
+	 */
+	target.mnt = &mp.parent->mnt;
+	target.dentry = mp.mp->m_dentry;
+	top = target;
+
 	if (check_mnt(old)) {
 		/* if the source is in our namespace... */
 		/* ... it should be detachable from parent */
@@ -3687,8 +3733,14 @@ static int do_move_mount(const struct path *old_path,
 		err = can_move_mount_beneath(old, over, &mp);
 		if (err)
 			return err;
+		top.mnt = &over->mnt;
+		top.dentry = over->mnt.mnt_root;
 	}
 
+	err = security_mount_move(old_path, &target, &top);
+	if (err)
+		return err;
+
 	/*
 	 * Don't move a mount tree containing unbindable mounts to a destination
 	 * mount which is shared.
@@ -3793,7 +3845,7 @@ static int do_new_mount_fc(struct fs_context *fc, const struct path *mountpoint,
  */
 static int do_new_mount(const struct path *path, const char *fstype,
 			int sb_flags, int mnt_flags,
-			const char *name, void *data)
+			const char *name, void *data, unsigned long flags)
 {
 	struct file_system_type *type;
 	struct fs_context *fc;
@@ -3837,6 +3889,9 @@ static int do_new_mount(const struct path *path, const char *fstype,
 		err = parse_monolithic_mount_data(fc, data);
 	if (!err && !mount_capable(fc))
 		err = -EPERM;
+
+	if (!err)
+		err = security_mount_new(fc, path, mnt_flags, flags, data);
 	if (!err)
 		err = do_new_mount_fc(fc, path, mnt_flags);
 
@@ -4087,7 +4142,6 @@ int path_mount(const char *dev_name, const struct path *path,
 		const char *type_page, unsigned long flags, void *data_page)
 {
 	unsigned int mnt_flags = 0, sb_flags;
-	int ret;
 
 	/* Discard magic */
 	if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
@@ -4100,9 +4154,6 @@ int path_mount(const char *dev_name, const struct path *path,
 	if (flags & MS_NOUSER)
 		return -EINVAL;
 
-	ret = security_sb_mount(dev_name, path, type_page, flags, data_page);
-	if (ret)
-		return ret;
 	if (!may_mount())
 		return -EPERM;
 	if (flags & SB_MANDLOCK)
@@ -4148,9 +4199,9 @@ int path_mount(const char *dev_name, const struct path *path,
 			    SB_I_VERSION);
 
 	if ((flags & (MS_REMOUNT | MS_BIND)) == (MS_REMOUNT | MS_BIND))
-		return do_reconfigure_mnt(path, mnt_flags);
+		return do_reconfigure_mnt(path, mnt_flags, flags);
 	if (flags & MS_REMOUNT)
-		return do_remount(path, sb_flags, mnt_flags, data_page);
+		return do_remount(path, sb_flags, mnt_flags, data_page, flags);
 	if (flags & MS_BIND)
 		return do_loopback(path, dev_name, flags & MS_REC);
 	if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
@@ -4159,7 +4210,7 @@ int path_mount(const char *dev_name, const struct path *path,
 		return do_move_mount_old(path, dev_name);
 
 	return do_new_mount(path, type_page, sb_flags, mnt_flags, dev_name,
-			    data_page);
+			    data_page, flags);
 }
 
 int do_mount(const char *dev_name, const char __user *dir_name,
@@ -4554,12 +4605,6 @@ static inline int vfs_move_mount(const struct path *from_path,
 				 const 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);
 
-- 
2.53.0-Meta
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.