mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-09 19:51:25 -04:00
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 commit7a54947e72('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 commit2462651ffa("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: Christian Brauner <brauner@kernel.org>
This commit is contained in:
@@ -5176,7 +5176,8 @@ SYSCALL_DEFINE5(open_tree_attr, int, dfd, const char __user *, filename,
|
||||
int ret;
|
||||
struct mount_kattr kattr = {};
|
||||
|
||||
kattr.kflags = MOUNT_KATTR_IDMAP_REPLACE;
|
||||
if (flags & OPEN_TREE_CLONE)
|
||||
kattr.kflags = MOUNT_KATTR_IDMAP_REPLACE;
|
||||
if (flags & AT_RECURSIVE)
|
||||
kattr.kflags |= MOUNT_KATTR_RECURSE;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user