]> git.apps.os.sepia.ceph.com Git - xfstests-dev.git/commitdiff
generic: add nested user namespace tests
authorChristian Brauner <christian.brauner@ubuntu.com>
Sat, 14 Aug 2021 10:48:04 +0000 (12:48 +0200)
committerEryu Guan <guaneryu@gmail.com>
Sun, 22 Aug 2021 11:52:17 +0000 (19:52 +0800)
Test ownership and ownership changes in a complex user namespace
hierarchy.

Cc: fstests@vger.kernel.org
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
src/idmapped-mounts/idmapped-mounts.c
src/idmapped-mounts/utils.h
tests/generic/645 [new file with mode: 0755]
tests/generic/645.out [new file with mode: 0644]

index fdbdd8271680de1d7dac03259f5b7f3b66379d34..1e7c25aa99029be97f373ad0815c8f5cc8bd6b99 100644 (file)
@@ -8812,6 +8812,714 @@ out:
        return fret;
 }
 
+static int nested_userns(void)
+{
+       int fret = -1;
+       int ret;
+       pid_t pid;
+       struct list *it, *next;
+       struct userns_hierarchy hierarchy[] = {
+               { .level = 1, .fd_userns = -EBADF, },
+               { .level = 2, .fd_userns = -EBADF, },
+               { .level = 3, .fd_userns = -EBADF, },
+               { .level = 4, .fd_userns = -EBADF, },
+               /* Dummy entry that marks the end. */
+               { .level = MAX_USERNS_LEVEL, .fd_userns = -EBADF, },
+       };
+       struct mount_attr attr_level1 = {
+               .attr_set       = MOUNT_ATTR_IDMAP,
+               .userns_fd      = -EBADF,
+       };
+       struct mount_attr attr_level2 = {
+               .attr_set       = MOUNT_ATTR_IDMAP,
+               .userns_fd      = -EBADF,
+       };
+       struct mount_attr attr_level3 = {
+               .attr_set       = MOUNT_ATTR_IDMAP,
+               .userns_fd      = -EBADF,
+       };
+       struct mount_attr attr_level4 = {
+               .attr_set       = MOUNT_ATTR_IDMAP,
+               .userns_fd      = -EBADF,
+       };
+       int fd_dir1 = -EBADF,
+           fd_open_tree_level1 = -EBADF,
+           fd_open_tree_level2 = -EBADF,
+           fd_open_tree_level3 = -EBADF,
+           fd_open_tree_level4 = -EBADF;
+       const unsigned int id_file_range = 10000;
+
+       list_init(&hierarchy[0].id_map);
+       list_init(&hierarchy[1].id_map);
+       list_init(&hierarchy[2].id_map);
+       list_init(&hierarchy[3].id_map);
+
+       /*
+        * Give a large map to the outermost user namespace so we can create
+        * comfortable nested maps.
+        */
+       ret = add_map_entry(&hierarchy[0].id_map, 1000000, 0, 1000000000, ID_TYPE_UID);
+       if (ret) {
+               log_stderr("failure: adding uidmap for userns at level 1");
+               goto out;
+       }
+
+       ret = add_map_entry(&hierarchy[0].id_map, 1000000, 0, 1000000000, ID_TYPE_GID);
+       if (ret) {
+               log_stderr("failure: adding gidmap for userns at level 1");
+               goto out;
+       }
+
+       /* This is uid:0->2000000:100000000 in init userns. */
+       ret = add_map_entry(&hierarchy[1].id_map, 1000000, 0, 100000000, ID_TYPE_UID);
+       if (ret) {
+               log_stderr("failure: adding uidmap for userns at level 2");
+               goto out;
+       }
+
+       /* This is gid:0->2000000:100000000 in init userns. */
+       ret = add_map_entry(&hierarchy[1].id_map, 1000000, 0, 100000000, ID_TYPE_GID);
+       if (ret) {
+               log_stderr("failure: adding gidmap for userns at level 2");
+               goto out;
+       }
+
+       /* This is uid:0->3000000:999 in init userns. */
+       ret = add_map_entry(&hierarchy[2].id_map, 1000000, 0, 999, ID_TYPE_UID);
+       if (ret) {
+               log_stderr("failure: adding uidmap for userns at level 3");
+               goto out;
+       }
+
+       /* This is gid:0->3000000:999 in the init userns. */
+       ret = add_map_entry(&hierarchy[2].id_map, 1000000, 0, 999, ID_TYPE_GID);
+       if (ret) {
+               log_stderr("failure: adding gidmap for userns at level 3");
+               goto out;
+       }
+
+       /* id 999 will remain unmapped. */
+
+       /* This is uid:1000->2001000:1 in init userns. */
+       ret = add_map_entry(&hierarchy[2].id_map, 1000, 1000, 1, ID_TYPE_UID);
+       if (ret) {
+               log_stderr("failure: adding uidmap for userns at level 3");
+               goto out;
+       }
+
+       /* This is gid:1000->2001000:1 in init userns. */
+       ret = add_map_entry(&hierarchy[2].id_map, 1000, 1000, 1, ID_TYPE_GID);
+       if (ret) {
+               log_stderr("failure: adding gidmap for userns at level 3");
+               goto out;
+       }
+
+       /* This is uid:1001->3001001:10000 in init userns. */
+       ret = add_map_entry(&hierarchy[2].id_map, 1001001, 1001, 10000000, ID_TYPE_UID);
+       if (ret) {
+               log_stderr("failure: adding uidmap for userns at level 3");
+               goto out;
+       }
+
+       /* This is gid:1001->3001001:10000 in init userns. */
+       ret = add_map_entry(&hierarchy[2].id_map, 1001001, 1001, 10000000, ID_TYPE_GID);
+       if (ret) {
+               log_stderr("failure: adding gidmap for userns at level 3");
+               goto out;
+       }
+
+       /* Don't write a mapping in the 4th userns. */
+       list_empty(&hierarchy[4].id_map);
+
+       /* Create the actual userns hierarchy. */
+       ret = create_userns_hierarchy(hierarchy);
+       if (ret) {
+               log_stderr("failure: create userns hierarchy");
+               goto out;
+       }
+
+       attr_level1.userns_fd = hierarchy[0].fd_userns;
+       attr_level2.userns_fd = hierarchy[1].fd_userns;
+       attr_level3.userns_fd = hierarchy[2].fd_userns;
+       attr_level4.userns_fd = hierarchy[3].fd_userns;
+
+       /*
+        * Create one directory where we create files for each uid/gid within
+        * the first userns.
+        */
+       if (mkdirat(t_dir1_fd, DIR1, 0777)) {
+               log_stderr("failure: mkdirat");
+               goto out;
+       }
+
+       fd_dir1 = openat(t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC);
+       if (fd_dir1 < 0) {
+               log_stderr("failure: openat");
+               goto out;
+       }
+
+       for (unsigned int id = 0; id <= id_file_range; id++) {
+               char file[256];
+
+               snprintf(file, sizeof(file), DIR1 "/" FILE1 "_%u", id);
+
+               if (mknodat(t_dir1_fd, file, S_IFREG | 0644, 0)) {
+                       log_stderr("failure: create %s", file);
+                       goto out;
+               }
+
+               if (fchownat(t_dir1_fd, file, id, id, AT_SYMLINK_NOFOLLOW)) {
+                       log_stderr("failure: fchownat %s", file);
+                       goto out;
+               }
+
+               if (!expected_uid_gid(t_dir1_fd, file, 0, id, id)) {
+                       log_stderr("failure: check ownership %s", file);
+                       goto out;
+               }
+       }
+
+       /* Create detached mounts for all the user namespaces. */
+       fd_open_tree_level1 = sys_open_tree(t_dir1_fd, DIR1,
+                                           AT_NO_AUTOMOUNT |
+                                           AT_SYMLINK_NOFOLLOW |
+                                           OPEN_TREE_CLOEXEC |
+                                           OPEN_TREE_CLONE);
+       if (fd_open_tree_level1 < 0) {
+               log_stderr("failure: sys_open_tree");
+               goto out;
+       }
+
+       fd_open_tree_level2 = sys_open_tree(t_dir1_fd, DIR1,
+                                           AT_NO_AUTOMOUNT |
+                                           AT_SYMLINK_NOFOLLOW |
+                                           OPEN_TREE_CLOEXEC |
+                                           OPEN_TREE_CLONE);
+       if (fd_open_tree_level2 < 0) {
+               log_stderr("failure: sys_open_tree");
+               goto out;
+       }
+
+       fd_open_tree_level3 = sys_open_tree(t_dir1_fd, DIR1,
+                                           AT_NO_AUTOMOUNT |
+                                           AT_SYMLINK_NOFOLLOW |
+                                           OPEN_TREE_CLOEXEC |
+                                           OPEN_TREE_CLONE);
+       if (fd_open_tree_level3 < 0) {
+               log_stderr("failure: sys_open_tree");
+               goto out;
+       }
+
+       fd_open_tree_level4 = sys_open_tree(t_dir1_fd, DIR1,
+                                           AT_NO_AUTOMOUNT |
+                                           AT_SYMLINK_NOFOLLOW |
+                                           OPEN_TREE_CLOEXEC |
+                                           OPEN_TREE_CLONE);
+       if (fd_open_tree_level4 < 0) {
+               log_stderr("failure: sys_open_tree");
+               goto out;
+       }
+
+       /* Turn detached mounts into detached idmapped mounts. */
+       if (sys_mount_setattr(fd_open_tree_level1, "", AT_EMPTY_PATH,
+                             &attr_level1, sizeof(attr_level1))) {
+               log_stderr("failure: sys_mount_setattr");
+               goto out;
+       }
+
+       if (sys_mount_setattr(fd_open_tree_level2, "", AT_EMPTY_PATH,
+                             &attr_level2, sizeof(attr_level2))) {
+               log_stderr("failure: sys_mount_setattr");
+               goto out;
+       }
+
+       if (sys_mount_setattr(fd_open_tree_level3, "", AT_EMPTY_PATH,
+                             &attr_level3, sizeof(attr_level3))) {
+               log_stderr("failure: sys_mount_setattr");
+               goto out;
+       }
+
+       if (sys_mount_setattr(fd_open_tree_level4, "", AT_EMPTY_PATH,
+                             &attr_level4, sizeof(attr_level4))) {
+               log_stderr("failure: sys_mount_setattr");
+               goto out;
+       }
+
+       /* Verify that ownership looks correct for callers in the init userns. */
+       for (unsigned int id = 0; id <= id_file_range; id++) {
+               bool bret;
+               unsigned int id_level1, id_level2, id_level3;
+               char file[256];
+
+               snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+               id_level1 = id + 1000000;
+               if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1)) {
+                       log_stderr("failure: check ownership %s", file);
+                       goto out;
+               }
+
+               id_level2 = id + 2000000;
+               if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) {
+                       log_stderr("failure: check ownership %s", file);
+                       goto out;
+               }
+
+               if (id == 999) {
+                       /* This id is unmapped. */
+                       bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+               } else if (id == 1000) {
+                       id_level3 = id + 2000000; /* We punched a hole in the map at 1000. */
+                       bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+               } else {
+                       id_level3 = id + 3000000; /* Rest is business as usual. */
+                       bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+               }
+               if (!bret) {
+                       log_stderr("failure: check ownership %s", file);
+                       goto out;
+               }
+
+               if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) {
+                       log_stderr("failure: check ownership %s", file);
+                       goto out;
+               }
+       }
+
+       /* Verify that ownership looks correct for callers in the first userns. */
+       pid = fork();
+       if (pid < 0) {
+               log_stderr("failure: fork");
+               goto out;
+       }
+       if (pid == 0) {
+               if (!switch_userns(attr_level1.userns_fd, 0, 0, false))
+                       die("failure: switch_userns");
+
+               for (unsigned int id = 0; id <= id_file_range; id++) {
+                       bool bret;
+                       unsigned int id_level1, id_level2, id_level3;
+                       char file[256];
+
+                       snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+                       id_level1 = id;
+                       if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1))
+                               die("failure: check ownership %s", file);
+
+                       id_level2 = id + 1000000;
+                       if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2))
+                               die("failure: check ownership %s", file);
+
+                       if (id == 999) {
+                               /* This id is unmapped. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+                       } else if (id == 1000) {
+                               id_level3 = id + 1000000; /* We punched a hole in the map at 1000. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+                       } else {
+                               id_level3 = id + 2000000; /* Rest is business as usual. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+                       }
+                       if (!bret)
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+               }
+
+               exit(EXIT_SUCCESS);
+       }
+       if (wait_for_pid(pid))
+               goto out;
+
+       /* Verify that ownership looks correct for callers in the second userns. */
+       pid = fork();
+       if (pid < 0) {
+               log_stderr("failure: fork");
+               goto out;
+       }
+       if (pid == 0) {
+               if (!switch_userns(attr_level2.userns_fd, 0, 0, false))
+                       die("failure: switch_userns");
+
+               for (unsigned int id = 0; id <= id_file_range; id++) {
+                       bool bret;
+                       unsigned int id_level2, id_level3;
+                       char file[256];
+
+                       snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+                       if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       id_level2 = id;
+                       if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2))
+                               die("failure: check ownership %s", file);
+
+                       if (id == 999) {
+                               /* This id is unmapped. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+                       } else if (id == 1000) {
+                               id_level3 = id; /* We punched a hole in the map at 1000. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+                       } else {
+                               id_level3 = id + 1000000; /* Rest is business as usual. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+                       }
+                       if (!bret)
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+               }
+
+               exit(EXIT_SUCCESS);
+       }
+       if (wait_for_pid(pid))
+               goto out;
+
+       /* Verify that ownership looks correct for callers in the third userns. */
+       pid = fork();
+       if (pid < 0) {
+               log_stderr("failure: fork");
+               goto out;
+       }
+       if (pid == 0) {
+               if (!switch_userns(attr_level3.userns_fd, 0, 0, false))
+                       die("failure: switch_userns");
+
+               for (unsigned int id = 0; id <= id_file_range; id++) {
+                       bool bret;
+                       unsigned int id_level2, id_level3;
+                       char file[256];
+
+                       snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+                       if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       if (id == 1000) {
+                               /*
+                                * The idmapping of the third userns has a hole
+                                * at uid/gid 1000. That means:
+                                * - 1000->userns_0(2000000) // init userns
+                                * - 1000->userns_1(2000000) // level 1
+                                * - 1000->userns_2(1000000) // level 2
+                                * - 1000->userns_3(1000)    // level 3 (because level 3 has a hole)
+                                */
+                               id_level2 = id;
+                               bret = expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2);
+                       } else {
+                               bret = expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid);
+                       }
+                       if (!bret)
+                               die("failure: check ownership %s", file);
+
+
+                       if (id == 999) {
+                               /* This id is unmapped. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+                       } else {
+                               id_level3 = id; /* Rest is business as usual. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+                       }
+                       if (!bret)
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+               }
+
+               exit(EXIT_SUCCESS);
+       }
+       if (wait_for_pid(pid))
+               goto out;
+
+       /* Verify that ownership looks correct for callers in the fourth userns. */
+       pid = fork();
+       if (pid < 0) {
+               log_stderr("failure: fork");
+               goto out;
+       }
+       if (pid == 0) {
+               if (setns(attr_level4.userns_fd, CLONE_NEWUSER))
+                       die("failure: switch_userns");
+
+               for (unsigned int id = 0; id <= id_file_range; id++) {
+                       char file[256];
+
+                       snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+                       if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+               }
+
+               exit(EXIT_SUCCESS);
+       }
+       if (wait_for_pid(pid))
+               goto out;
+
+       /* Verify that chown works correctly for callers in the first userns. */
+       pid = fork();
+       if (pid < 0) {
+               log_stderr("failure: fork");
+               goto out;
+       }
+       if (pid == 0) {
+               if (!switch_userns(attr_level1.userns_fd, 0, 0, false))
+                       die("failure: switch_userns");
+
+               for (unsigned int id = 0; id <= id_file_range; id++) {
+                       bool bret;
+                       unsigned int id_level1, id_level2, id_level3, id_new;
+                       char file[256];
+
+                       snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+                       id_new = id + 1;
+                       if (fchownat(fd_open_tree_level1, file, id_new, id_new, AT_SYMLINK_NOFOLLOW))
+                               die("failure: fchownat %s", file);
+
+                       id_level1 = id_new;
+                       if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1))
+                               die("failure: check ownership %s", file);
+
+                       id_level2 = id_new + 1000000;
+                       if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2))
+                               die("failure: check ownership %s", file);
+
+                       if (id_new == 999) {
+                               /* This id is unmapped. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+                       } else if (id_new == 1000) {
+                               id_level3 = id_new + 1000000; /* We punched a hole in the map at 1000. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+                       } else {
+                               id_level3 = id_new + 2000000; /* Rest is business as usual. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+                       }
+                       if (!bret)
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       /* Revert ownership. */
+                       if (fchownat(fd_open_tree_level1, file, id, id, AT_SYMLINK_NOFOLLOW))
+                               die("failure: fchownat %s", file);
+               }
+
+               exit(EXIT_SUCCESS);
+       }
+       if (wait_for_pid(pid))
+               goto out;
+
+       /* Verify that chown works correctly for callers in the second userns. */
+       pid = fork();
+       if (pid < 0) {
+               log_stderr("failure: fork");
+               goto out;
+       }
+       if (pid == 0) {
+               if (!switch_userns(attr_level2.userns_fd, 0, 0, false))
+                       die("failure: switch_userns");
+
+               for (unsigned int id = 0; id <= id_file_range; id++) {
+                       bool bret;
+                       unsigned int id_level2, id_level3, id_new;
+                       char file[256];
+
+                       snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+                       id_new = id + 1;
+                       if (fchownat(fd_open_tree_level2, file, id_new, id_new, AT_SYMLINK_NOFOLLOW))
+                               die("failure: fchownat %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       id_level2 = id_new;
+                       if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2))
+                               die("failure: check ownership %s", file);
+
+                       if (id_new == 999) {
+                               /* This id is unmapped. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+                       } else if (id_new == 1000) {
+                               id_level3 = id_new; /* We punched a hole in the map at 1000. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+                       } else {
+                               id_level3 = id_new + 1000000; /* Rest is business as usual. */
+                               bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+                       }
+                       if (!bret)
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       /* Revert ownership. */
+                       if (fchownat(fd_open_tree_level2, file, id, id, AT_SYMLINK_NOFOLLOW))
+                               die("failure: fchownat %s", file);
+               }
+
+               exit(EXIT_SUCCESS);
+       }
+       if (wait_for_pid(pid))
+               goto out;
+
+       /* Verify that chown works correctly for callers in the third userns. */
+       pid = fork();
+       if (pid < 0) {
+               log_stderr("failure: fork");
+               goto out;
+       }
+       if (pid == 0) {
+               if (!switch_userns(attr_level3.userns_fd, 0, 0, false))
+                       die("failure: switch_userns");
+
+               for (unsigned int id = 0; id <= id_file_range; id++) {
+                       unsigned int id_new;
+                       char file[256];
+
+                       snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+                       id_new = id + 1;
+                       if (id_new == 999 || id_new == 1000) {
+                               /*
+                                * We can't change ownership as we can't
+                                * chown from or to an unmapped id.
+                                */
+                               if (!fchownat(fd_open_tree_level3, file, id_new, id_new, AT_SYMLINK_NOFOLLOW))
+                                       die("failure: fchownat %s", file);
+                       } else {
+                               if (fchownat(fd_open_tree_level3, file, id_new, id_new, AT_SYMLINK_NOFOLLOW))
+                                       die("failure: fchownat %s", file);
+                       }
+
+                       if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       /* There's no id 1000 anymore as we changed ownership for id 1000 to 1001 above. */
+                       if (!expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       if (id_new == 999) {
+                               /*
+                                * We did not change ownership as we can't
+                                * chown to an unmapped id.
+                                */
+                               if (!expected_uid_gid(fd_open_tree_level3, file, 0, id, id))
+                                       die("failure: check ownership %s", file);
+                       } else if (id_new == 1000) {
+                               /*
+                                * We did not change ownership as we can't
+                                * chown from an unmapped id.
+                                */
+                               if (!expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid))
+                                       die("failure: check ownership %s", file);
+                       } else {
+                               if (!expected_uid_gid(fd_open_tree_level3, file, 0, id_new, id_new))
+                                       die("failure: check ownership %s", file);
+                       }
+
+                       if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       /* Revert ownership. */
+                       if (id_new != 999 && id_new != 1000) {
+                               if (fchownat(fd_open_tree_level3, file, id, id, AT_SYMLINK_NOFOLLOW))
+                                       die("failure: fchownat %s", file);
+                       }
+               }
+
+               exit(EXIT_SUCCESS);
+       }
+       if (wait_for_pid(pid))
+               goto out;
+
+       /* Verify that chown works correctly for callers in the fourth userns. */
+       pid = fork();
+       if (pid < 0) {
+               log_stderr("failure: fork");
+               goto out;
+       }
+       if (pid == 0) {
+               if (setns(attr_level4.userns_fd, CLONE_NEWUSER))
+                       die("failure: switch_userns");
+
+               for (unsigned int id = 0; id <= id_file_range; id++) {
+                       char file[256];
+                       unsigned long id_new;
+
+                       snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+                       id_new = id + 1;
+                       if (!fchownat(fd_open_tree_level4, file, id_new, id_new, AT_SYMLINK_NOFOLLOW))
+                               die("failure: fchownat %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+                       if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+                               die("failure: check ownership %s", file);
+
+               }
+
+               exit(EXIT_SUCCESS);
+       }
+       if (wait_for_pid(pid))
+               goto out;
+
+       fret = 0;
+       log_debug("Ran test");
+
+out:
+       list_for_each_safe(it, &hierarchy[0].id_map, next) {
+               list_del(it);
+               free(it->elem);
+               free(it);
+       }
+
+       list_for_each_safe(it, &hierarchy[1].id_map, next) {
+               list_del(it);
+               free(it->elem);
+               free(it);
+       }
+
+       list_for_each_safe(it, &hierarchy[2].id_map, next) {
+               list_del(it);
+               free(it->elem);
+               free(it);
+       }
+
+       safe_close(hierarchy[0].fd_userns);
+       safe_close(hierarchy[1].fd_userns);
+       safe_close(hierarchy[2].fd_userns);
+       safe_close(fd_dir1);
+       safe_close(fd_open_tree_level1);
+       safe_close(fd_open_tree_level2);
+       safe_close(fd_open_tree_level3);
+       safe_close(fd_open_tree_level4);
+       return fret;
+}
+
 static void usage(void)
 {
        fprintf(stderr, "Description:\n");
@@ -8825,6 +9533,7 @@ static void usage(void)
        fprintf(stderr, "--supported                  Test whether idmapped mounts are supported on this filesystem\n");
        fprintf(stderr, "--test-core                  Run core idmapped mount testsuite\n");
        fprintf(stderr, "--test-fscaps-regression     Run fscap regression tests\n");
+       fprintf(stderr, "--test-nested-userns         Run nested userns idmapped mount testsuite\n");
 
        _exit(EXIT_SUCCESS);
 }
@@ -8837,6 +9546,7 @@ static const struct option longopts[] = {
        {"help",                        no_argument,            0,      'h'},
        {"test-core",                   no_argument,            0,      'c'},
        {"test-fscaps-regression",      no_argument,            0,      'g'},
+       {"test-nested-userns",          no_argument,            0,      'n'},
        {NULL,                          0,                      0,        0},
 };
 
@@ -8899,6 +9609,10 @@ struct t_idmapped_mounts fscaps_in_ancestor_userns[] = {
        { fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns,    "fscaps on idmapped mounts in user namespace writing fscap valid in ancestor userns",           },
 };
 
+struct t_idmapped_mounts t_nested_userns[] = {
+       { nested_userns,                                                "test that nested user namespaces behave correctly when attached to idmapped mounts",           },
+};
+
 static bool run_test(struct t_idmapped_mounts suite[], size_t suite_size)
 {
        int i;
@@ -8936,7 +9650,8 @@ int main(int argc, char *argv[])
 {
        int fret, ret;
        int index = 0;
-       bool supported = false, test_core = false, test_fscaps_regression = false;
+       bool supported = false, test_core = false,
+            test_fscaps_regression = false, test_nested_userns = false;
 
        while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
                switch (ret) {
@@ -8958,6 +9673,9 @@ int main(int argc, char *argv[])
                case 'g':
                        test_fscaps_regression = true;
                        break;
+               case 'n':
+                       test_nested_userns = true;
+                       break;
                case 'h':
                        /* fallthrough */
                default:
@@ -9035,6 +9753,10 @@ int main(int argc, char *argv[])
                      ARRAY_SIZE(fscaps_in_ancestor_userns)))
                goto out;
 
+       if (test_nested_userns &&
+           !run_test(t_nested_userns, ARRAY_SIZE(t_nested_userns)))
+               goto out;
+
        fret = EXIT_SUCCESS;
 
 out:
index 9694980eec260d0a557918744cdd3d5aa6507e92..afb3c2283c1daa8433b972b56cf9c66b508ee0d8 100644 (file)
@@ -77,6 +77,10 @@ struct userns_hierarchy {
 #define list_for_each(__iterator, __list) \
        for (__iterator = (__list)->next; __iterator != __list; __iterator = __iterator->next)
 
+#define list_for_each_safe(__iterator, __list, __next)               \
+       for (__iterator = (__list)->next, __next = __iterator->next; \
+            __iterator != __list; __iterator = __next, __next = __next->next)
+
 static inline void list_init(struct list *list)
 {
        list->elem = NULL;
diff --git a/tests/generic/645 b/tests/generic/645
new file mode 100755 (executable)
index 0000000..ffe30bb
--- /dev/null
@@ -0,0 +1,28 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2021 Christian Brauner.  All Rights Reserved.
+#
+# FS QA Test 645
+#
+# Test that idmapped mounts behave correctly with complex user namespaces.
+#
+. ./common/preamble
+_begin_fstest auto quick idmapped mount
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# real QA test starts here
+
+_supported_fs generic
+_require_idmapped_mounts
+_require_test
+
+echo "Silence is golden"
+
+$here/src/idmapped-mounts/idmapped-mounts --test-nested-userns \
+       --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
+
+status=$?
+exit
diff --git a/tests/generic/645.out b/tests/generic/645.out
new file mode 100644 (file)
index 0000000..f57ae9c
--- /dev/null
@@ -0,0 +1,2 @@
+QA output created by 645
+Silence is golden