selftests/mount_setattr: add smoke tests for open_tree_attr(2) bug
authorAleksa Sarai <cyphar@cyphar.com>
Thu, 7 Aug 2025 17:55:06 +0000 (03:55 +1000)
committerChristian Brauner <brauner@kernel.org>
Mon, 11 Aug 2025 12:51:49 +0000 (14:51 +0200)
There appear to be no other open_tree_attr(2) tests at the moment, but
as a minimal solution just add some additional checks in the existing
MOUNT_ATTR_IDMAP tests to make sure that open_tree_attr(2) cannot be
used to bypass the tested restrictions that apply to mount_setattr(2).

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
Link: https://lore.kernel.org/20250808-open_tree_attr-bugfix-idmap-v1-2-0ec7bc05646c@cyphar.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
tools/testing/selftests/mount_setattr/mount_setattr_test.c

index b1e4618399be6720e92a2dba6e27947d5698dce3..a688871a98eba8639c34ae4ddf9a7fd13f86ff81 100644 (file)
        #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;