Unverified Commit bef2981b authored by Christian Brauner's avatar Christian Brauner
Browse files

Merge patch series "open_tree_attr: do not allow id-mapping changes without OPEN_TREE_CLONE"

Aleksa Sarai <cyphar@cyphar.com> says:

As described in commit 7a54947e ('Merge patch series "fs: allow
changing idmappings"'), open_tree_attr(2) was necessary in order to
allow for a detached mount to be created and have its idmappings changed
without the risk of any racing threads operating on it. For this reason,
mount_setattr(2) still does not allow for id-mappings to be changed.

However, there was a bug in commit 2462651f ("fs: allow changing
idmappings") which allowed users to bypass this restriction by calling
open_tree_attr(2) *without* OPEN_TREE_CLONE.

can_idmap_mount() prevented this bug from allowing an attached
mountpoint's id-mapping from being modified (thanks to an is_anon_ns()
check), but this still allows for detached (but visible) mounts to have
their be id-mapping changed. This risks the same UAF and locking issues
as described in the merge commit, and was likely unintentional.

For what it's worth, I found this while working on the open_tree_attr(2)
man page, and was trying to figure out what open_tree_attr(2)'s
behaviour was in the (slightly fruity) ~OPEN_TREE_CLONE case.

* patches from https://lore.kernel.org/20250808-open_tree_attr-bugfix-idmap-v1-0-0ec7bc05646c@cyphar.com:
  selftests/mount_setattr: add smoke tests for open_tree_attr(2) bug
  open_tree_attr: do not allow id-mapping changes without OPEN_TREE_CLONE

Link: https://lore.kernel.org/20250808-open_tree_attr-bugfix-idmap-v1-0-0ec7bc05646c@cyphar.com


Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parents 6b65028e 81e4b9cf
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -5176,6 +5176,7 @@ SYSCALL_DEFINE5(open_tree_attr, int, dfd, const char __user *, filename,
		int ret;
		struct mount_kattr kattr = {};

		if (flags & OPEN_TREE_CLONE)
			kattr.kflags = MOUNT_KATTR_IDMAP_REPLACE;
		if (flags & AT_RECURSIVE)
			kattr.kflags |= MOUNT_KATTR_RECURSE;
+64 −13
Original line number Diff line number Diff line
@@ -107,6 +107,26 @@
	#endif
#endif

#ifndef __NR_open_tree_attr
	#if defined __alpha__
		#define __NR_open_tree_attr 577
	#elif defined _MIPS_SIM
		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
			#define __NR_open_tree_attr (467 + 4000)
		#endif
		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
			#define __NR_open_tree_attr (467 + 6000)
		#endif
		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
			#define __NR_open_tree_attr (467 + 5000)
		#endif
	#elif defined __ia64__
		#define __NR_open_tree_attr (467 + 1024)
	#else
		#define __NR_open_tree_attr 467
	#endif
#endif

#ifndef MOUNT_ATTR_IDMAP
#define MOUNT_ATTR_IDMAP 0x00100000
#endif
@@ -121,6 +141,12 @@ static inline int sys_mount_setattr(int dfd, const char *path, unsigned int flag
	return syscall(__NR_mount_setattr, dfd, path, flags, attr, size);
}

static inline int sys_open_tree_attr(int dfd, const char *path, unsigned int flags,
				     struct mount_attr *attr, size_t size)
{
	return syscall(__NR_open_tree_attr, dfd, path, flags, attr, size);
}

static ssize_t write_nointr(int fd, const void *buf, size_t count)
{
	ssize_t ret;
@@ -1222,6 +1248,12 @@ TEST_F(mount_setattr_idmapped, attached_mount_inside_current_mount_namespace)
	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
	ASSERT_GE(attr.userns_fd, 0);
	ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
	/*
	 * Make sure that open_tree_attr() without OPEN_TREE_CLONE is not a way
	 * to bypass this mount_setattr() restriction.
	 */
	ASSERT_LT(sys_open_tree_attr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);

	ASSERT_EQ(close(attr.userns_fd), 0);
	ASSERT_EQ(close(open_tree_fd), 0);
}
@@ -1255,6 +1287,12 @@ TEST_F(mount_setattr_idmapped, attached_mount_outside_current_mount_namespace)
	ASSERT_GE(attr.userns_fd, 0);
	ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr,
				    sizeof(attr)), 0);
	/*
	 * Make sure that open_tree_attr() without OPEN_TREE_CLONE is not a way
	 * to bypass this mount_setattr() restriction.
	 */
	ASSERT_LT(sys_open_tree_attr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);

	ASSERT_EQ(close(attr.userns_fd), 0);
	ASSERT_EQ(close(open_tree_fd), 0);
}
@@ -1321,6 +1359,19 @@ TEST_F(mount_setattr_idmapped, detached_mount_outside_current_mount_namespace)
	ASSERT_EQ(close(open_tree_fd), 0);
}

static bool expected_uid_gid(int dfd, const char *path, int flags,
			     uid_t expected_uid, gid_t expected_gid)
{
	int ret;
	struct stat st;

	ret = fstatat(dfd, path, &st, flags);
	if (ret < 0)
		return false;

	return st.st_uid == expected_uid && st.st_gid == expected_gid;
}

/**
 * Validate that currently changing the idmapping of an idmapped mount fails.
 */
@@ -1331,6 +1382,8 @@ TEST_F(mount_setattr_idmapped, change_idmapping)
		.attr_set = MOUNT_ATTR_IDMAP,
	};

	ASSERT_TRUE(expected_uid_gid(-EBADF, "/mnt/D", 0, 0, 0));

	if (!mount_setattr_supported())
		SKIP(return, "mount_setattr syscall not supported");

@@ -1348,27 +1401,25 @@ TEST_F(mount_setattr_idmapped, change_idmapping)
				    AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
	ASSERT_EQ(close(attr.userns_fd), 0);

	EXPECT_FALSE(expected_uid_gid(open_tree_fd, ".", 0, 0, 0));
	EXPECT_TRUE(expected_uid_gid(open_tree_fd, ".", 0, 10000, 10000));

	/* Change idmapping on a detached mount that is already idmapped. */
	attr.userns_fd	= get_userns_fd(0, 20000, 10000);
	ASSERT_GE(attr.userns_fd, 0);
	ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
	/*
	 * Make sure that open_tree_attr() without OPEN_TREE_CLONE is not a way
	 * to bypass this mount_setattr() restriction.
	 */
	EXPECT_LT(sys_open_tree_attr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
	EXPECT_FALSE(expected_uid_gid(open_tree_fd, ".", 0, 20000, 20000));
	EXPECT_TRUE(expected_uid_gid(open_tree_fd, ".", 0, 10000, 10000));

	ASSERT_EQ(close(attr.userns_fd), 0);
	ASSERT_EQ(close(open_tree_fd), 0);
}

static bool expected_uid_gid(int dfd, const char *path, int flags,
			     uid_t expected_uid, gid_t expected_gid)
{
	int ret;
	struct stat st;

	ret = fstatat(dfd, path, &st, flags);
	if (ret < 0)
		return false;

	return st.st_uid == expected_uid && st.st_gid == expected_gid;
}

TEST_F(mount_setattr_idmapped, idmap_mount_tree_invalid)
{
	int open_tree_fd = -EBADF;