aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Brauner <brauner@kernel.org>2022-04-28 15:09:21 +0200
committerChristian Brauner (Microsoft) <brauner@kernel.org>2022-05-12 11:01:21 +0200
commit72ef930a6700b3aef36a80b4388670a757c2261d (patch)
tree60406125c1077fb191126956c9c4b0d97d1274f3
parent47de1ea0cf79fad8f20b04a68ccaf00eec18da0f (diff)
downloadxfstests-dev-fs.idmapped.rename.tar.gz
vfstest: split out remaining idmapped mount testsfs.idmapped.rename
Split out all the remaining idmapped mount tests into the idmapped mounts source file. Cc: Dave Chinner <david@fromorbit.com> Cc: Amir Goldstein <amir73il@gmail.com> Cc: Eryu Guan <guaneryu@gmail.com> Cc: Christoph Hellwig <hch@lst.de> Cc: Zorro Lang <zlang@redhat.com> Cc: "Darrick J. Wong" <djwong@kernel.org> Cc: fstests <fstests@vger.kernel.org> Signed-off-by: Christian Brauner (Microsoft) <brauner@kernel.org>
-rw-r--r--src/vfs/idmapped-mounts.c1123
-rw-r--r--src/vfs/idmapped-mounts.h2
-rw-r--r--src/vfs/utils.c130
-rw-r--r--src/vfs/utils.h5
-rw-r--r--src/vfs/vfstest.c1260
5 files changed, 1261 insertions, 1259 deletions
diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
index d935e4c814..8c9b03da00 100644
--- a/src/vfs/idmapped-mounts.c
+++ b/src/vfs/idmapped-mounts.c
@@ -6470,6 +6470,1110 @@ out:
return fret;
}
+static int nested_userns(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int ret;
+ pid_t pid;
+ unsigned int id;
+ 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(info->t_dir1_fd, DIR1, 0777)) {
+ log_stderr("failure: mkdirat");
+ goto out;
+ }
+
+ fd_dir1 = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC);
+ if (fd_dir1 < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ for (id = 0; id <= id_file_range; id++) {
+ char file[256];
+
+ snprintf(file, sizeof(file), DIR1 "/" FILE1 "_%u", id);
+
+ if (mknodat(info->t_dir1_fd, file, S_IFREG | 0644, 0)) {
+ log_stderr("failure: create %s", file);
+ goto out;
+ }
+
+ if (fchownat(info->t_dir1_fd, file, id, id, AT_SYMLINK_NOFOLLOW)) {
+ log_stderr("failure: fchownat %s", file);
+ goto out;
+ }
+
+ if (!expected_uid_gid(info->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(info->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(info->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(info->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(info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level2, file, 0, info->t_overflowuid, info->t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level2, file, 0, info->t_overflowuid, info->t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->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;
+}
+
+#define USER1 "fsgqa"
+#define USER2 "fsgqa2"
+
+/**
+ * lookup_ids - lookup uid and gid for a username
+ * @name: [in] name of the user
+ * @uid: [out] pointer to the user-ID
+ * @gid: [out] pointer to the group-ID
+ *
+ * Lookup the uid and gid of a user.
+ *
+ * Return: On success, true is returned.
+ * On error, false is returned.
+ */
+static bool lookup_ids(const char *name, uid_t *uid, gid_t *gid)
+{
+ bool bret = false;
+ struct passwd *pwentp = NULL;
+ struct passwd pwent;
+ char *buf;
+ ssize_t bufsize;
+ int ret;
+
+ bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (bufsize < 0)
+ bufsize = 1024;
+
+ buf = malloc(bufsize);
+ if (!buf)
+ return bret;
+
+ ret = getpwnam_r(name, &pwent, buf, bufsize, &pwentp);
+ if (!ret && pwentp) {
+ *uid = pwent.pw_uid;
+ *gid = pwent.pw_gid;
+ bret = true;
+ }
+
+ free(buf);
+ return bret;
+}
+
+/**
+ * setattr_fix_968219708108 - test for commit 968219708108 ("fs: handle circular mappings correctly")
+ *
+ * Test that ->setattr() works correctly for idmapped mounts with circular
+ * idmappings such as:
+ *
+ * b:1000:1001:1
+ * b:1001:1000:1
+ *
+ * Assume a directory /source with two files:
+ *
+ * /source/file1 | 1000:1000
+ * /source/file2 | 1001:1001
+ *
+ * and we create an idmapped mount of /source at /target with an idmapped of:
+ *
+ * mnt_userns: 1000:1001:1
+ * 1001:1000:1
+ *
+ * In the idmapped mount file1 will be owned by uid 1001 and file2 by uid 1000:
+ *
+ * /target/file1 | 1001:1001
+ * /target/file2 | 1000:1000
+ *
+ * Because in essence the idmapped mount switches ownership for {g,u}id 1000
+ * and {g,u}id 1001.
+ *
+ * 1. A user with fs{g,u}id 1000 must be allowed to setattr /target/file2 from
+ * {g,u}id 1000 in the idmapped mount to {g,u}id 1000.
+ * 2. A user with fs{g,u}id 1001 must be allowed to setattr /target/file1 from
+ * {g,u}id 1001 in the idmapped mount to {g,u}id 1001.
+ * 3. A user with fs{g,u}id 1000 must fail to setattr /target/file1 from
+ * {g,u}id 1001 in the idmapped mount to {g,u}id 1000.
+ * This must fail with EPERM. The caller's fs{g,u}id doesn't match the
+ * {g,u}id of the file.
+ * 4. A user with fs{g,u}id 1001 must fail to setattr /target/file2 from
+ * {g,u}id 1000 in the idmapped mount to {g,u}id 1000.
+ * This must fail with EPERM. The caller's fs{g,u}id doesn't match the
+ * {g,u}id of the file.
+ * 5. Both, a user with fs{g,u}id 1000 and a user with fs{g,u}id 1001, must
+ * fail to setattr /target/file1 owned by {g,u}id 1001 in the idmapped mount
+ * and /target/file2 owned by {g,u}id 1000 in the idmapped mount to any
+ * {g,u}id apart from {g,u}id 1000 or 1001 with EINVAL.
+ * Only {g,u}id 1000 and 1001 have a mapping in the idmapped mount. Other
+ * {g,u}id are unmapped.
+ */
+static int setattr_fix_968219708108(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ .userns_fd = -EBADF,
+ };
+ int ret;
+ uid_t user1_uid, user2_uid;
+ gid_t user1_gid, user2_gid;
+ pid_t pid;
+ struct list idmap;
+ struct list *it_cur, *it_next;
+
+ if (!caps_supported())
+ return 0;
+
+ list_init(&idmap);
+
+ if (!lookup_ids(USER1, &user1_uid, &user1_gid)) {
+ log_stderr("failure: lookup_user");
+ goto out;
+ }
+
+ if (!lookup_ids(USER2, &user2_uid, &user2_gid)) {
+ log_stderr("failure: lookup_user");
+ goto out;
+ }
+
+ log_debug("Found " USER1 " with uid(%d) and gid(%d) and " USER2 " with uid(%d) and gid(%d)",
+ user1_uid, user1_gid, user2_uid, user2_gid);
+
+ if (mkdirat(info->t_dir1_fd, DIR1, 0777)) {
+ log_stderr("failure: mkdirat");
+ goto out;
+ }
+
+ if (mknodat(info->t_dir1_fd, DIR1 "/" FILE1, S_IFREG | 0644, 0)) {
+ log_stderr("failure: mknodat");
+ goto out;
+ }
+
+ if (chown_r(info->t_mnt_fd, T_DIR1, user1_uid, user1_gid)) {
+ log_stderr("failure: chown_r");
+ goto out;
+ }
+
+ if (mknodat(info->t_dir1_fd, DIR1 "/" FILE2, S_IFREG | 0644, 0)) {
+ log_stderr("failure: mknodat");
+ goto out;
+ }
+
+ if (fchownat(info->t_dir1_fd, DIR1 "/" FILE2, user2_uid, user2_gid, AT_SYMLINK_NOFOLLOW)) {
+ log_stderr("failure: fchownat");
+ goto out;
+ }
+
+ print_r(info->t_mnt_fd, T_DIR1);
+
+ /* u:1000:1001:1 */
+ ret = add_map_entry(&idmap, user1_uid, user2_uid, 1, ID_TYPE_UID);
+ if (ret) {
+ log_stderr("failure: add_map_entry");
+ goto out;
+ }
+
+ /* u:1001:1000:1 */
+ ret = add_map_entry(&idmap, user2_uid, user1_uid, 1, ID_TYPE_UID);
+ if (ret) {
+ log_stderr("failure: add_map_entry");
+ goto out;
+ }
+
+ /* g:1000:1001:1 */
+ ret = add_map_entry(&idmap, user1_gid, user2_gid, 1, ID_TYPE_GID);
+ if (ret) {
+ log_stderr("failure: add_map_entry");
+ goto out;
+ }
+
+ /* g:1001:1000:1 */
+ ret = add_map_entry(&idmap, user2_gid, user1_gid, 1, ID_TYPE_GID);
+ if (ret) {
+ log_stderr("failure: add_map_entry");
+ goto out;
+ }
+
+ attr.userns_fd = get_userns_fd_from_idmap(&idmap);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, DIR1,
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE |
+ AT_RECURSIVE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ print_r(open_tree_fd, "");
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ /* switch to {g,u}id 1001 */
+ if (!switch_resids(user2_uid, user2_gid))
+ die("failure: switch_resids");
+
+ /* drop all capabilities */
+ if (!caps_down())
+ die("failure: caps_down");
+
+ /*
+ * The {g,u}id 0 is not mapped in this idmapped mount so this
+ * needs to fail with EINVAL.
+ */
+ if (!fchownat(open_tree_fd, FILE1, 0, 0, AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+ if (errno != EINVAL)
+ die("failure: errno");
+
+ /*
+ * A user with fs{g,u}id 1001 must be allowed to change
+ * ownership of /target/file1 owned by {g,u}id 1001 in this
+ * idmapped mount to {g,u}id 1001.
+ */
+ if (fchownat(open_tree_fd, FILE1, user2_uid, user2_gid,
+ AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+
+ /* Verify that the ownership is still {g,u}id 1001. */
+ if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW,
+ user2_uid, user2_gid))
+ die("failure: check ownership");
+
+ /*
+ * A user with fs{g,u}id 1001 must not be allowed to change
+ * ownership of /target/file1 owned by {g,u}id 1001 in this
+ * idmapped mount to {g,u}id 1000.
+ */
+ if (!fchownat(open_tree_fd, FILE1, user1_uid, user1_gid,
+ AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ /* Verify that the ownership is still {g,u}id 1001. */
+ if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW,
+ user2_uid, user2_gid))
+ die("failure: check ownership");
+
+ /*
+ * A user with fs{g,u}id 1001 must not be allowed to change
+ * ownership of /target/file2 owned by {g,u}id 1000 in this
+ * idmapped mount to {g,u}id 1000.
+ */
+ if (!fchownat(open_tree_fd, FILE2, user1_uid, user1_gid,
+ AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ /* Verify that the ownership is still {g,u}id 1000. */
+ if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW,
+ user1_uid, user1_gid))
+ die("failure: check ownership");
+
+ /*
+ * A user with fs{g,u}id 1001 must not be allowed to change
+ * ownership of /target/file2 owned by {g,u}id 1000 in this
+ * idmapped mount to {g,u}id 1001.
+ */
+ if (!fchownat(open_tree_fd, FILE2, user2_uid, user2_gid,
+ AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ /* Verify that the ownership is still {g,u}id 1000. */
+ if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW,
+ user1_uid, user1_gid))
+ die("failure: check ownership");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ /* switch to {g,u}id 1000 */
+ if (!switch_resids(user1_uid, user1_gid))
+ die("failure: switch_resids");
+
+ /* drop all capabilities */
+ if (!caps_down())
+ die("failure: caps_down");
+
+ /*
+ * The {g,u}id 0 is not mapped in this idmapped mount so this
+ * needs to fail with EINVAL.
+ */
+ if (!fchownat(open_tree_fd, FILE1, 0, 0, AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+ if (errno != EINVAL)
+ die("failure: errno");
+
+ /*
+ * A user with fs{g,u}id 1000 must be allowed to change
+ * ownership of /target/file2 owned by {g,u}id 1000 in this
+ * idmapped mount to {g,u}id 1000.
+ */
+ if (fchownat(open_tree_fd, FILE2, user1_uid, user1_gid,
+ AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+
+ /* Verify that the ownership is still {g,u}id 1000. */
+ if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW,
+ user1_uid, user1_gid))
+ die("failure: check ownership");
+
+ /*
+ * A user with fs{g,u}id 1000 must not be allowed to change
+ * ownership of /target/file2 owned by {g,u}id 1000 in this
+ * idmapped mount to {g,u}id 1001.
+ */
+ if (!fchownat(open_tree_fd, FILE2, user2_uid, user2_gid,
+ AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ /* Verify that the ownership is still {g,u}id 1000. */
+ if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW,
+ user1_uid, user1_gid))
+ die("failure: check ownership");
+
+ /*
+ * A user with fs{g,u}id 1000 must not be allowed to change
+ * ownership of /target/file1 owned by {g,u}id 1001 in this
+ * idmapped mount to {g,u}id 1000.
+ */
+ if (!fchownat(open_tree_fd, FILE1, user1_uid, user1_gid,
+ AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ /* Verify that the ownership is still {g,u}id 1001. */
+ if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW,
+ user2_uid, user2_gid))
+ die("failure: check ownership");
+
+ /*
+ * A user with fs{g,u}id 1000 must not be allowed to change
+ * ownership of /target/file1 owned by {g,u}id 1001 in this
+ * idmapped mount to {g,u}id 1001.
+ */
+ if (!fchownat(open_tree_fd, FILE1, user2_uid, user2_gid,
+ AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ /* Verify that the ownership is still {g,u}id 1001. */
+ if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW,
+ user2_uid, user2_gid))
+ die("failure: check ownership");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+
+ list_for_each_safe(it_cur, &idmap, it_next) {
+ list_del(it_cur);
+ free(it_cur->elem);
+ free(it_cur);
+ }
+
+ return fret;
+}
+
static const struct test_struct t_idmapped_mounts[] = {
{ acls, true, "posix acls on regular mounts", },
{ create_in_userns, true, "create operations in user namespace", },
@@ -6523,3 +7627,22 @@ const struct test_suite s_fscaps_in_ancestor_userns = {
.tests = t_fscaps_in_ancestor_userns,
.nr_tests = ARRAY_SIZE(t_fscaps_in_ancestor_userns),
};
+
+static const struct test_struct t_nested_userns[] = {
+ { nested_userns, true, "test that nested user namespaces behave correctly when attached to idmapped mounts", },
+};
+
+const struct test_suite s_nested_userns = {
+ .tests = t_nested_userns,
+ .nr_tests = ARRAY_SIZE(t_nested_userns),
+};
+
+/* Test for commit 968219708108 ("fs: handle circular mappings correctly"). */
+static const struct test_struct t_setattr_fix_968219708108[] = {
+ { setattr_fix_968219708108, true, "test that setattr works correctly", },
+};
+
+const struct test_suite s_setattr_fix_968219708108 = {
+ .tests = t_setattr_fix_968219708108,
+ .nr_tests = ARRAY_SIZE(t_setattr_fix_968219708108),
+};
diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
index 37c8886d02..9febecd3be 100644
--- a/src/vfs/idmapped-mounts.h
+++ b/src/vfs/idmapped-mounts.h
@@ -11,5 +11,7 @@
extern const struct test_suite s_idmapped_mounts;
extern const struct test_suite s_fscaps_in_ancestor_userns;
+extern const struct test_suite s_nested_userns;
+extern const struct test_suite s_setattr_fix_968219708108;
#endif /* __IDMAPPED_MOUNTS_H */
diff --git a/src/vfs/utils.c b/src/vfs/utils.c
index 28944b706a..089ac34e12 100644
--- a/src/vfs/utils.c
+++ b/src/vfs/utils.c
@@ -871,3 +871,133 @@ int fd_to_fd(int from, int to)
return 0;
}
+
+/*
+ * There'll be scenarios where you'll want to see the attributes associated with
+ * a directory tree during debugging or just to make sure things look correct.
+ * Simply uncomment and place the print_r() helper where you need it.
+ */
+#ifdef DEBUG_TRACE
+static int fd_cloexec(int fd, bool cloexec)
+{
+ int oflags, nflags;
+
+ oflags = fcntl(fd, F_GETFD, 0);
+ if (oflags < 0)
+ return -errno;
+
+ if (cloexec)
+ nflags = oflags | FD_CLOEXEC;
+ else
+ nflags = oflags & ~FD_CLOEXEC;
+
+ if (nflags == oflags)
+ return 0;
+
+ if (fcntl(fd, F_SETFD, nflags) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static inline int dup_cloexec(int fd)
+{
+ int fd_dup;
+
+ fd_dup = dup(fd);
+ if (fd_dup < 0)
+ return -errno;
+
+ if (fd_cloexec(fd_dup, true)) {
+ close(fd_dup);
+ return -errno;
+ }
+
+ return fd_dup;
+}
+
+int print_r(int fd, const char *path)
+{
+ int ret = 0;
+ int dfd, dfd_dup;
+ DIR *dir;
+ struct dirent *direntp;
+ struct stat st;
+
+ if (!path || *path == '\0') {
+ char buf[sizeof("/proc/self/fd/") + 30];
+
+ ret = snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
+ if (ret < 0 || (size_t)ret >= sizeof(buf))
+ return -1;
+
+ /*
+ * O_PATH file descriptors can't be used so we need to re-open
+ * just in case.
+ */
+ dfd = openat(-EBADF, buf, O_CLOEXEC | O_DIRECTORY, 0);
+ } else {
+ dfd = openat(fd, path, O_CLOEXEC | O_DIRECTORY, 0);
+ }
+ if (dfd < 0)
+ return -1;
+
+ /*
+ * When fdopendir() below succeeds it assumes ownership of the fd so we
+ * to make sure we always have an fd that fdopendir() can own which is
+ * why we dup() in the case where the caller wants us to operate on the
+ * fd directly.
+ */
+ dfd_dup = dup_cloexec(dfd);
+ if (dfd_dup < 0) {
+ close(dfd);
+ return -1;
+ }
+
+ dir = fdopendir(dfd);
+ if (!dir) {
+ close(dfd);
+ close(dfd_dup);
+ return -1;
+ }
+ /* Transfer ownership to fdopendir(). */
+ dfd = -EBADF;
+
+ while ((direntp = readdir(dir))) {
+ if (!strcmp(direntp->d_name, ".") ||
+ !strcmp(direntp->d_name, ".."))
+ continue;
+
+ ret = fstatat(dfd_dup, direntp->d_name, &st, AT_SYMLINK_NOFOLLOW);
+ if (ret < 0 && errno != ENOENT)
+ break;
+
+ ret = 0;
+ if (S_ISDIR(st.st_mode))
+ ret = print_r(dfd_dup, direntp->d_name);
+ else
+ fprintf(stderr, "mode(%o):uid(%d):gid(%d) -> %d/%s\n",
+ (st.st_mode & ~S_IFMT), st.st_uid, st.st_gid,
+ dfd_dup, direntp->d_name);
+ if (ret < 0 && errno != ENOENT)
+ break;
+ }
+
+ if (!path || *path == '\0')
+ ret = fstatat(fd, "", &st,
+ AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
+ AT_EMPTY_PATH);
+ else
+ ret = fstatat(fd, path, &st,
+ AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW);
+ if (!ret)
+ fprintf(stderr, "mode(%o):uid(%d):gid(%d) -> %s\n",
+ (st.st_mode & ~S_IFMT), st.st_uid, st.st_gid,
+ (path && *path) ? path : "(null)");
+
+ close(dfd_dup);
+ closedir(dir);
+
+ return ret;
+}
+#endif
diff --git a/src/vfs/utils.h b/src/vfs/utils.h
index a13efabb67..07aae675fd 100644
--- a/src/vfs/utils.h
+++ b/src/vfs/utils.h
@@ -347,6 +347,11 @@ extern int io_uring_openat_with_creds(struct io_uring *ring, int dfd,
extern int chown_r(int fd, const char *path, uid_t uid, gid_t gid);
extern int rm_r(int fd, const char *path);
+#ifdef DEBUG_TRACE
+extern int print_r(int fd, const char *path);
+#else
+static inline int print_r(int fd, const char *path) { return 0; }
+#endif
extern int fd_to_fd(int from, int to);
extern bool protected_symlinks_enabled(void);
extern bool xfs_irix_sgid_inherit_enabled(const char *fstype);
diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
index dadf1a0b9f..4567e95f5c 100644
--- a/src/vfs/vfstest.c
+++ b/src/vfs/vfstest.c
@@ -79,141 +79,6 @@ static void stash_overflowgid(struct vfstest_info *info)
info->t_overflowgid = atoi(buf);
}
-/*
- * There'll be scenarios where you'll want to see the attributes associated with
- * a directory tree during debugging or just to make sure things look correct.
- * Simply uncomment and place the print_r() helper where you need it.
- */
-#ifdef DEBUG_TRACE
-static int fd_cloexec(int fd, bool cloexec)
-{
- int oflags, nflags;
-
- oflags = fcntl(fd, F_GETFD, 0);
- if (oflags < 0)
- return -errno;
-
- if (cloexec)
- nflags = oflags | FD_CLOEXEC;
- else
- nflags = oflags & ~FD_CLOEXEC;
-
- if (nflags == oflags)
- return 0;
-
- if (fcntl(fd, F_SETFD, nflags) < 0)
- return -errno;
-
- return 0;
-}
-
-static inline int dup_cloexec(int fd)
-{
- int fd_dup;
-
- fd_dup = dup(fd);
- if (fd_dup < 0)
- return -errno;
-
- if (fd_cloexec(fd_dup, true)) {
- close(fd_dup);
- return -errno;
- }
-
- return fd_dup;
-}
-
-__attribute__((unused)) static int print_r(int fd, const char *path)
-{
- int ret = 0;
- int dfd, dfd_dup;
- DIR *dir;
- struct dirent *direntp;
- struct stat st;
-
- if (!path || *path == '\0') {
- char buf[sizeof("/proc/self/fd/") + 30];
-
- ret = snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
- if (ret < 0 || (size_t)ret >= sizeof(buf))
- return -1;
-
- /*
- * O_PATH file descriptors can't be used so we need to re-open
- * just in case.
- */
- dfd = openat(-EBADF, buf, O_CLOEXEC | O_DIRECTORY, 0);
- } else {
- dfd = openat(fd, path, O_CLOEXEC | O_DIRECTORY, 0);
- }
- if (dfd < 0)
- return -1;
-
- /*
- * When fdopendir() below succeeds it assumes ownership of the fd so we
- * to make sure we always have an fd that fdopendir() can own which is
- * why we dup() in the case where the caller wants us to operate on the
- * fd directly.
- */
- dfd_dup = dup_cloexec(dfd);
- if (dfd_dup < 0) {
- close(dfd);
- return -1;
- }
-
- dir = fdopendir(dfd);
- if (!dir) {
- close(dfd);
- close(dfd_dup);
- return -1;
- }
- /* Transfer ownership to fdopendir(). */
- dfd = -EBADF;
-
- while ((direntp = readdir(dir))) {
- if (!strcmp(direntp->d_name, ".") ||
- !strcmp(direntp->d_name, ".."))
- continue;
-
- ret = fstatat(dfd_dup, direntp->d_name, &st, AT_SYMLINK_NOFOLLOW);
- if (ret < 0 && errno != ENOENT)
- break;
-
- ret = 0;
- if (S_ISDIR(st.st_mode))
- ret = print_r(dfd_dup, direntp->d_name);
- else
- fprintf(stderr, "mode(%o):uid(%d):gid(%d) -> %d/%s\n",
- (st.st_mode & ~S_IFMT), st.st_uid, st.st_gid,
- dfd_dup, direntp->d_name);
- if (ret < 0 && errno != ENOENT)
- break;
- }
-
- if (!path || *path == '\0')
- ret = fstatat(fd, "", &st,
- AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW |
- AT_EMPTY_PATH);
- else
- ret = fstatat(fd, path, &st,
- AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW);
- if (!ret)
- fprintf(stderr, "mode(%o):uid(%d):gid(%d) -> %s\n",
- (st.st_mode & ~S_IFMT), st.st_uid, st.st_gid,
- (path && *path) ? path : "(null)");
-
- close(dfd_dup);
- closedir(dir);
-
- return ret;
-}
-#else
-__attribute__((unused)) static int print_r(int fd, const char *path)
-{
- return 0;
-}
-#endif
-
static void test_setup(struct vfstest_info *info)
{
if (mkdirat(info->t_mnt_fd, T_DIR1, 0777))
@@ -1827,1110 +1692,6 @@ out:
return fret;
}
-static int nested_userns(const struct vfstest_info *info)
-{
- int fret = -1;
- int ret;
- pid_t pid;
- unsigned int id;
- 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(info->t_dir1_fd, DIR1, 0777)) {
- log_stderr("failure: mkdirat");
- goto out;
- }
-
- fd_dir1 = openat(info->t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC);
- if (fd_dir1 < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- for (id = 0; id <= id_file_range; id++) {
- char file[256];
-
- snprintf(file, sizeof(file), DIR1 "/" FILE1 "_%u", id);
-
- if (mknodat(info->t_dir1_fd, file, S_IFREG | 0644, 0)) {
- log_stderr("failure: create %s", file);
- goto out;
- }
-
- if (fchownat(info->t_dir1_fd, file, id, id, AT_SYMLINK_NOFOLLOW)) {
- log_stderr("failure: fchownat %s", file);
- goto out;
- }
-
- if (!expected_uid_gid(info->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(info->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(info->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(info->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(info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->t_overflowgid))
- die("failure: check ownership %s", file);
-
- if (!expected_uid_gid(fd_open_tree_level2, file, 0, info->t_overflowuid, info->t_overflowgid))
- die("failure: check ownership %s", file);
-
- if (!expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid))
- die("failure: check ownership %s", file);
-
- if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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, info->t_overflowuid, info->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 (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, info->t_overflowuid, info->t_overflowgid))
- die("failure: check ownership %s", file);
-
- if (!expected_uid_gid(fd_open_tree_level2, file, 0, info->t_overflowuid, info->t_overflowgid))
- die("failure: check ownership %s", file);
-
- if (!expected_uid_gid(fd_open_tree_level3, file, 0, info->t_overflowuid, info->t_overflowgid))
- die("failure: check ownership %s", file);
-
- if (!expected_uid_gid(fd_open_tree_level4, file, 0, info->t_overflowuid, info->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;
-}
-
-#define USER1 "fsgqa"
-#define USER2 "fsgqa2"
-
-/**
- * lookup_ids - lookup uid and gid for a username
- * @name: [in] name of the user
- * @uid: [out] pointer to the user-ID
- * @gid: [out] pointer to the group-ID
- *
- * Lookup the uid and gid of a user.
- *
- * Return: On success, true is returned.
- * On error, false is returned.
- */
-static bool lookup_ids(const char *name, uid_t *uid, gid_t *gid)
-{
- bool bret = false;
- struct passwd *pwentp = NULL;
- struct passwd pwent;
- char *buf;
- ssize_t bufsize;
- int ret;
-
- bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
- if (bufsize < 0)
- bufsize = 1024;
-
- buf = malloc(bufsize);
- if (!buf)
- return bret;
-
- ret = getpwnam_r(name, &pwent, buf, bufsize, &pwentp);
- if (!ret && pwentp) {
- *uid = pwent.pw_uid;
- *gid = pwent.pw_gid;
- bret = true;
- }
-
- free(buf);
- return bret;
-}
-
-/**
- * setattr_fix_968219708108 - test for commit 968219708108 ("fs: handle circular mappings correctly")
- *
- * Test that ->setattr() works correctly for idmapped mounts with circular
- * idmappings such as:
- *
- * b:1000:1001:1
- * b:1001:1000:1
- *
- * Assume a directory /source with two files:
- *
- * /source/file1 | 1000:1000
- * /source/file2 | 1001:1001
- *
- * and we create an idmapped mount of /source at /target with an idmapped of:
- *
- * mnt_userns: 1000:1001:1
- * 1001:1000:1
- *
- * In the idmapped mount file1 will be owned by uid 1001 and file2 by uid 1000:
- *
- * /target/file1 | 1001:1001
- * /target/file2 | 1000:1000
- *
- * Because in essence the idmapped mount switches ownership for {g,u}id 1000
- * and {g,u}id 1001.
- *
- * 1. A user with fs{g,u}id 1000 must be allowed to setattr /target/file2 from
- * {g,u}id 1000 in the idmapped mount to {g,u}id 1000.
- * 2. A user with fs{g,u}id 1001 must be allowed to setattr /target/file1 from
- * {g,u}id 1001 in the idmapped mount to {g,u}id 1001.
- * 3. A user with fs{g,u}id 1000 must fail to setattr /target/file1 from
- * {g,u}id 1001 in the idmapped mount to {g,u}id 1000.
- * This must fail with EPERM. The caller's fs{g,u}id doesn't match the
- * {g,u}id of the file.
- * 4. A user with fs{g,u}id 1001 must fail to setattr /target/file2 from
- * {g,u}id 1000 in the idmapped mount to {g,u}id 1000.
- * This must fail with EPERM. The caller's fs{g,u}id doesn't match the
- * {g,u}id of the file.
- * 5. Both, a user with fs{g,u}id 1000 and a user with fs{g,u}id 1001, must
- * fail to setattr /target/file1 owned by {g,u}id 1001 in the idmapped mount
- * and /target/file2 owned by {g,u}id 1000 in the idmapped mount to any
- * {g,u}id apart from {g,u}id 1000 or 1001 with EINVAL.
- * Only {g,u}id 1000 and 1001 have a mapping in the idmapped mount. Other
- * {g,u}id are unmapped.
- */
-static int setattr_fix_968219708108(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- .userns_fd = -EBADF,
- };
- int ret;
- uid_t user1_uid, user2_uid;
- gid_t user1_gid, user2_gid;
- pid_t pid;
- struct list idmap;
- struct list *it_cur, *it_next;
-
- if (!caps_supported())
- return 0;
-
- list_init(&idmap);
-
- if (!lookup_ids(USER1, &user1_uid, &user1_gid)) {
- log_stderr("failure: lookup_user");
- goto out;
- }
-
- if (!lookup_ids(USER2, &user2_uid, &user2_gid)) {
- log_stderr("failure: lookup_user");
- goto out;
- }
-
- log_debug("Found " USER1 " with uid(%d) and gid(%d) and " USER2 " with uid(%d) and gid(%d)",
- user1_uid, user1_gid, user2_uid, user2_gid);
-
- if (mkdirat(info->t_dir1_fd, DIR1, 0777)) {
- log_stderr("failure: mkdirat");
- goto out;
- }
-
- if (mknodat(info->t_dir1_fd, DIR1 "/" FILE1, S_IFREG | 0644, 0)) {
- log_stderr("failure: mknodat");
- goto out;
- }
-
- if (chown_r(info->t_mnt_fd, T_DIR1, user1_uid, user1_gid)) {
- log_stderr("failure: chown_r");
- goto out;
- }
-
- if (mknodat(info->t_dir1_fd, DIR1 "/" FILE2, S_IFREG | 0644, 0)) {
- log_stderr("failure: mknodat");
- goto out;
- }
-
- if (fchownat(info->t_dir1_fd, DIR1 "/" FILE2, user2_uid, user2_gid, AT_SYMLINK_NOFOLLOW)) {
- log_stderr("failure: fchownat");
- goto out;
- }
-
- print_r(info->t_mnt_fd, T_DIR1);
-
- /* u:1000:1001:1 */
- ret = add_map_entry(&idmap, user1_uid, user2_uid, 1, ID_TYPE_UID);
- if (ret) {
- log_stderr("failure: add_map_entry");
- goto out;
- }
-
- /* u:1001:1000:1 */
- ret = add_map_entry(&idmap, user2_uid, user1_uid, 1, ID_TYPE_UID);
- if (ret) {
- log_stderr("failure: add_map_entry");
- goto out;
- }
-
- /* g:1000:1001:1 */
- ret = add_map_entry(&idmap, user1_gid, user2_gid, 1, ID_TYPE_GID);
- if (ret) {
- log_stderr("failure: add_map_entry");
- goto out;
- }
-
- /* g:1001:1000:1 */
- ret = add_map_entry(&idmap, user2_gid, user1_gid, 1, ID_TYPE_GID);
- if (ret) {
- log_stderr("failure: add_map_entry");
- goto out;
- }
-
- attr.userns_fd = get_userns_fd_from_idmap(&idmap);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, DIR1,
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE |
- AT_RECURSIVE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- print_r(open_tree_fd, "");
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- /* switch to {g,u}id 1001 */
- if (!switch_resids(user2_uid, user2_gid))
- die("failure: switch_resids");
-
- /* drop all capabilities */
- if (!caps_down())
- die("failure: caps_down");
-
- /*
- * The {g,u}id 0 is not mapped in this idmapped mount so this
- * needs to fail with EINVAL.
- */
- if (!fchownat(open_tree_fd, FILE1, 0, 0, AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
- if (errno != EINVAL)
- die("failure: errno");
-
- /*
- * A user with fs{g,u}id 1001 must be allowed to change
- * ownership of /target/file1 owned by {g,u}id 1001 in this
- * idmapped mount to {g,u}id 1001.
- */
- if (fchownat(open_tree_fd, FILE1, user2_uid, user2_gid,
- AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
-
- /* Verify that the ownership is still {g,u}id 1001. */
- if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW,
- user2_uid, user2_gid))
- die("failure: check ownership");
-
- /*
- * A user with fs{g,u}id 1001 must not be allowed to change
- * ownership of /target/file1 owned by {g,u}id 1001 in this
- * idmapped mount to {g,u}id 1000.
- */
- if (!fchownat(open_tree_fd, FILE1, user1_uid, user1_gid,
- AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
- if (errno != EPERM)
- die("failure: errno");
-
- /* Verify that the ownership is still {g,u}id 1001. */
- if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW,
- user2_uid, user2_gid))
- die("failure: check ownership");
-
- /*
- * A user with fs{g,u}id 1001 must not be allowed to change
- * ownership of /target/file2 owned by {g,u}id 1000 in this
- * idmapped mount to {g,u}id 1000.
- */
- if (!fchownat(open_tree_fd, FILE2, user1_uid, user1_gid,
- AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
- if (errno != EPERM)
- die("failure: errno");
-
- /* Verify that the ownership is still {g,u}id 1000. */
- if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW,
- user1_uid, user1_gid))
- die("failure: check ownership");
-
- /*
- * A user with fs{g,u}id 1001 must not be allowed to change
- * ownership of /target/file2 owned by {g,u}id 1000 in this
- * idmapped mount to {g,u}id 1001.
- */
- if (!fchownat(open_tree_fd, FILE2, user2_uid, user2_gid,
- AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
- if (errno != EPERM)
- die("failure: errno");
-
- /* Verify that the ownership is still {g,u}id 1000. */
- if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW,
- user1_uid, user1_gid))
- die("failure: check ownership");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- /* switch to {g,u}id 1000 */
- if (!switch_resids(user1_uid, user1_gid))
- die("failure: switch_resids");
-
- /* drop all capabilities */
- if (!caps_down())
- die("failure: caps_down");
-
- /*
- * The {g,u}id 0 is not mapped in this idmapped mount so this
- * needs to fail with EINVAL.
- */
- if (!fchownat(open_tree_fd, FILE1, 0, 0, AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
- if (errno != EINVAL)
- die("failure: errno");
-
- /*
- * A user with fs{g,u}id 1000 must be allowed to change
- * ownership of /target/file2 owned by {g,u}id 1000 in this
- * idmapped mount to {g,u}id 1000.
- */
- if (fchownat(open_tree_fd, FILE2, user1_uid, user1_gid,
- AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
-
- /* Verify that the ownership is still {g,u}id 1000. */
- if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW,
- user1_uid, user1_gid))
- die("failure: check ownership");
-
- /*
- * A user with fs{g,u}id 1000 must not be allowed to change
- * ownership of /target/file2 owned by {g,u}id 1000 in this
- * idmapped mount to {g,u}id 1001.
- */
- if (!fchownat(open_tree_fd, FILE2, user2_uid, user2_gid,
- AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
- if (errno != EPERM)
- die("failure: errno");
-
- /* Verify that the ownership is still {g,u}id 1000. */
- if (!expected_uid_gid(open_tree_fd, FILE2, AT_SYMLINK_NOFOLLOW,
- user1_uid, user1_gid))
- die("failure: check ownership");
-
- /*
- * A user with fs{g,u}id 1000 must not be allowed to change
- * ownership of /target/file1 owned by {g,u}id 1001 in this
- * idmapped mount to {g,u}id 1000.
- */
- if (!fchownat(open_tree_fd, FILE1, user1_uid, user1_gid,
- AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
- if (errno != EPERM)
- die("failure: errno");
-
- /* Verify that the ownership is still {g,u}id 1001. */
- if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW,
- user2_uid, user2_gid))
- die("failure: check ownership");
-
- /*
- * A user with fs{g,u}id 1000 must not be allowed to change
- * ownership of /target/file1 owned by {g,u}id 1001 in this
- * idmapped mount to {g,u}id 1001.
- */
- if (!fchownat(open_tree_fd, FILE1, user2_uid, user2_gid,
- AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
- if (errno != EPERM)
- die("failure: errno");
-
- /* Verify that the ownership is still {g,u}id 1001. */
- if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW,
- user2_uid, user2_gid))
- die("failure: check ownership");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
-
- list_for_each_safe(it_cur, &idmap, it_next) {
- list_del(it_cur);
- free(it_cur->elem);
- free(it_cur);
- }
-
- return fret;
-}
-
static void usage(void)
{
fprintf(stderr, "Description:\n");
@@ -2941,7 +1702,7 @@ static void usage(void)
fprintf(stderr, "--fstype Filesystem type used in the tests\n");
fprintf(stderr, "--help Print help\n");
fprintf(stderr, "--mountpoint Mountpoint of device\n");
- fprintf(stderr, "--idmapped-mounts-supported Test whether idmapped mounts are supported on this filesystem\n");
+ fprintf(stderr, "--idmapped-mounts-supported Test whether idmapped mounts are supported on this filesystem\n");
fprintf(stderr, "--scratch-mountpoint Mountpoint of scratch device used in the tests\n");
fprintf(stderr, "--scratch-device Scratch device used in the tests\n");
fprintf(stderr, "--test-core Run core idmapped mount testsuite\n");
@@ -2991,25 +1752,6 @@ static const struct test_suite s_basic = {
.nr_tests = ARRAY_SIZE(t_basic),
};
-static const struct test_struct t_nested_userns[] = {
- { nested_userns, true, "test that nested user namespaces behave correctly when attached to idmapped mounts", },
-};
-
-static const struct test_suite s_nested_userns = {
- .tests = t_nested_userns,
- .nr_tests = ARRAY_SIZE(t_nested_userns),
-};
-
-/* Test for commit 968219708108 ("fs: handle circular mappings correctly"). */
-static const struct test_struct t_setattr_fix_968219708108[] = {
- { setattr_fix_968219708108, true, "test that setattr works correctly", },
-};
-
-static const struct test_suite s_setattr_fix_968219708108 = {
- .tests = t_setattr_fix_968219708108,
- .nr_tests = ARRAY_SIZE(t_setattr_fix_968219708108),
-};
-
static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size)
{
int i;