Skip to content

Commit bef2981

Browse files
committed
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 7a54947 ('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 2462651 ("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>
2 parents 6b65028 + 81e4b9c commit bef2981

File tree

2 files changed

+66
-14
lines changed

2 files changed

+66
-14
lines changed

fs/namespace.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5176,7 +5176,8 @@ SYSCALL_DEFINE5(open_tree_attr, int, dfd, const char __user *, filename,
51765176
int ret;
51775177
struct mount_kattr kattr = {};
51785178

5179-
kattr.kflags = MOUNT_KATTR_IDMAP_REPLACE;
5179+
if (flags & OPEN_TREE_CLONE)
5180+
kattr.kflags = MOUNT_KATTR_IDMAP_REPLACE;
51805181
if (flags & AT_RECURSIVE)
51815182
kattr.kflags |= MOUNT_KATTR_RECURSE;
51825183

tools/testing/selftests/mount_setattr/mount_setattr_test.c

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,26 @@
107107
#endif
108108
#endif
109109

110+
#ifndef __NR_open_tree_attr
111+
#if defined __alpha__
112+
#define __NR_open_tree_attr 577
113+
#elif defined _MIPS_SIM
114+
#if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */
115+
#define __NR_open_tree_attr (467 + 4000)
116+
#endif
117+
#if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */
118+
#define __NR_open_tree_attr (467 + 6000)
119+
#endif
120+
#if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */
121+
#define __NR_open_tree_attr (467 + 5000)
122+
#endif
123+
#elif defined __ia64__
124+
#define __NR_open_tree_attr (467 + 1024)
125+
#else
126+
#define __NR_open_tree_attr 467
127+
#endif
128+
#endif
129+
110130
#ifndef MOUNT_ATTR_IDMAP
111131
#define MOUNT_ATTR_IDMAP 0x00100000
112132
#endif
@@ -121,6 +141,12 @@ static inline int sys_mount_setattr(int dfd, const char *path, unsigned int flag
121141
return syscall(__NR_mount_setattr, dfd, path, flags, attr, size);
122142
}
123143

144+
static inline int sys_open_tree_attr(int dfd, const char *path, unsigned int flags,
145+
struct mount_attr *attr, size_t size)
146+
{
147+
return syscall(__NR_open_tree_attr, dfd, path, flags, attr, size);
148+
}
149+
124150
static ssize_t write_nointr(int fd, const void *buf, size_t count)
125151
{
126152
ssize_t ret;
@@ -1222,6 +1248,12 @@ TEST_F(mount_setattr_idmapped, attached_mount_inside_current_mount_namespace)
12221248
attr.userns_fd = get_userns_fd(0, 10000, 10000);
12231249
ASSERT_GE(attr.userns_fd, 0);
12241250
ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
1251+
/*
1252+
* Make sure that open_tree_attr() without OPEN_TREE_CLONE is not a way
1253+
* to bypass this mount_setattr() restriction.
1254+
*/
1255+
ASSERT_LT(sys_open_tree_attr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
1256+
12251257
ASSERT_EQ(close(attr.userns_fd), 0);
12261258
ASSERT_EQ(close(open_tree_fd), 0);
12271259
}
@@ -1255,6 +1287,12 @@ TEST_F(mount_setattr_idmapped, attached_mount_outside_current_mount_namespace)
12551287
ASSERT_GE(attr.userns_fd, 0);
12561288
ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr,
12571289
sizeof(attr)), 0);
1290+
/*
1291+
* Make sure that open_tree_attr() without OPEN_TREE_CLONE is not a way
1292+
* to bypass this mount_setattr() restriction.
1293+
*/
1294+
ASSERT_LT(sys_open_tree_attr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
1295+
12581296
ASSERT_EQ(close(attr.userns_fd), 0);
12591297
ASSERT_EQ(close(open_tree_fd), 0);
12601298
}
@@ -1321,6 +1359,19 @@ TEST_F(mount_setattr_idmapped, detached_mount_outside_current_mount_namespace)
13211359
ASSERT_EQ(close(open_tree_fd), 0);
13221360
}
13231361

1362+
static bool expected_uid_gid(int dfd, const char *path, int flags,
1363+
uid_t expected_uid, gid_t expected_gid)
1364+
{
1365+
int ret;
1366+
struct stat st;
1367+
1368+
ret = fstatat(dfd, path, &st, flags);
1369+
if (ret < 0)
1370+
return false;
1371+
1372+
return st.st_uid == expected_uid && st.st_gid == expected_gid;
1373+
}
1374+
13241375
/**
13251376
* Validate that currently changing the idmapping of an idmapped mount fails.
13261377
*/
@@ -1331,6 +1382,8 @@ TEST_F(mount_setattr_idmapped, change_idmapping)
13311382
.attr_set = MOUNT_ATTR_IDMAP,
13321383
};
13331384

1385+
ASSERT_TRUE(expected_uid_gid(-EBADF, "/mnt/D", 0, 0, 0));
1386+
13341387
if (!mount_setattr_supported())
13351388
SKIP(return, "mount_setattr syscall not supported");
13361389

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

1404+
EXPECT_FALSE(expected_uid_gid(open_tree_fd, ".", 0, 0, 0));
1405+
EXPECT_TRUE(expected_uid_gid(open_tree_fd, ".", 0, 10000, 10000));
1406+
13511407
/* Change idmapping on a detached mount that is already idmapped. */
13521408
attr.userns_fd = get_userns_fd(0, 20000, 10000);
13531409
ASSERT_GE(attr.userns_fd, 0);
13541410
ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
1411+
/*
1412+
* Make sure that open_tree_attr() without OPEN_TREE_CLONE is not a way
1413+
* to bypass this mount_setattr() restriction.
1414+
*/
1415+
EXPECT_LT(sys_open_tree_attr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0);
1416+
EXPECT_FALSE(expected_uid_gid(open_tree_fd, ".", 0, 20000, 20000));
1417+
EXPECT_TRUE(expected_uid_gid(open_tree_fd, ".", 0, 10000, 10000));
1418+
13551419
ASSERT_EQ(close(attr.userns_fd), 0);
13561420
ASSERT_EQ(close(open_tree_fd), 0);
13571421
}
13581422

1359-
static bool expected_uid_gid(int dfd, const char *path, int flags,
1360-
uid_t expected_uid, gid_t expected_gid)
1361-
{
1362-
int ret;
1363-
struct stat st;
1364-
1365-
ret = fstatat(dfd, path, &st, flags);
1366-
if (ret < 0)
1367-
return false;
1368-
1369-
return st.st_uid == expected_uid && st.st_gid == expected_gid;
1370-
}
1371-
13721423
TEST_F(mount_setattr_idmapped, idmap_mount_tree_invalid)
13731424
{
13741425
int open_tree_fd = -EBADF;

0 commit comments

Comments
 (0)