All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1 1/2] vfs: Add new setgid_create_umask test
@ 2022-05-20 16:04 Yang Xu
  2022-05-20 16:04 ` [PATCH v1 2/2] vfs: Add new setgid_create_acl test Yang Xu
                   ` (2 more replies)
  0 siblings, 3 replies; 15+ messages in thread
From: Yang Xu @ 2022-05-20 16:04 UTC (permalink / raw)
  To: fstests; +Cc: Yang Xu

The current_umask() is stripped from the mode directly in the vfs if the
filesystem either doesn't support acls or the filesystem has been
mounted without posic acl support.

If the filesystem does support acls then current_umask() stripping is
deferred to posix_acl_create(). So when the filesystem calls
posix_acl_create() and there are no acls set or not supported then
current_umask() will be stripped.

This patch is also designed to test kernel patchset behaviour
"move S_ISGID stripping into the vfs"
https://patchwork.kernel.org/project/linux-fsdevel/list/?series=635692

Here we only use umask(S_IXGRP) to check whether inode strip
S_ISGID works correctly and check whether S_IXGRP mode exists.

Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
---
 src/vfs/idmapped-mounts.c | 429 ++++++++++++++++++++++++++++++++++++++
 src/vfs/idmapped-mounts.h |   1 +
 src/vfs/utils.c           |  14 ++
 src/vfs/utils.h           |   1 +
 src/vfs/vfstest.c         | 205 +++++++++++++++++-
 tests/generic/691         |  40 ++++
 tests/generic/691.out     |   2 +
 7 files changed, 691 insertions(+), 1 deletion(-)
 create mode 100755 tests/generic/691
 create mode 100644 tests/generic/691.out

diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
index 63297d5f..72de52cc 100644
--- a/src/vfs/idmapped-mounts.c
+++ b/src/vfs/idmapped-mounts.c
@@ -7664,6 +7664,425 @@ out:
 	return fret;
 }
 
+static int setgid_create_umask_idmapped(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF, open_tree_fd = -EBADF;
+	struct mount_attr attr = {
+		.attr_set = MOUNT_ATTR_IDMAP,
+	};
+	pid_t pid;
+	int tmpfile_fd = -EBADF;
+	bool supported = false;
+	char path[PATH_MAX];
+	mode_t mode;
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the sid bits got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	/* Changing mount properties on a detached mount. */
+	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
+	if (attr.userns_fd < 0) {
+		log_stderr("failure: get_userns_fd");
+		goto out;
+	}
+
+	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+				     AT_EMPTY_PATH |
+				     AT_NO_AUTOMOUNT |
+				     AT_SYMLINK_NOFOLLOW |
+				     OPEN_TREE_CLOEXEC |
+				     OPEN_TREE_CLONE);
+	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;
+	}
+
+	supported = openat_tmpfile_supported(open_tree_fd);
+
+	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
+	 * whether has group execute or search permission.
+	 */
+	umask(S_IXGRP);
+	mode = umask(S_IXGRP);
+	if (!(mode & S_IXGRP))
+		die("failure: umask");
+
+		pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		if (!switch_ids(10000, 11000))
+			die("failure: switch fsids");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(attr.userns_fd);
+	safe_close(file1_fd);
+	safe_close(open_tree_fd);
+
+	return fret;
+}
+
+static int setgid_create_umask_idmapped_in_userns(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF, open_tree_fd = -EBADF;
+	struct mount_attr attr = {
+		.attr_set = MOUNT_ATTR_IDMAP,
+	};
+	pid_t pid;
+	int tmpfile_fd = -EBADF;
+	bool supported = false;
+	char path[PATH_MAX];
+	mode_t mode;
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the sid bits got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	/* Changing mount properties on a detached mount. */
+	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
+	if (attr.userns_fd < 0) {
+		log_stderr("failure: get_userns_fd");
+		goto out;
+	}
+
+	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+				     AT_EMPTY_PATH |
+				     AT_NO_AUTOMOUNT |
+				     AT_SYMLINK_NOFOLLOW |
+				     OPEN_TREE_CLOEXEC |
+				     OPEN_TREE_CLONE);
+	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;
+	}
+
+	supported = openat_tmpfile_supported(open_tree_fd);
+
+	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
+	 * whether has group execute or search permission.
+	 */
+	umask(S_IXGRP);
+	mode = umask(S_IXGRP);
+	if (!(mode & S_IXGRP))
+		die("failure: umask");
+
+	/*
+	 * Below we verify that setgid inheritance for a newly created file or
+	 * directory works correctly. As part of this we need to verify that
+	 * newly created files or directories inherit their gid from their
+	 * parent directory. So we change the parent directorie's gid to 1000
+	 * and create a file with fs{g,u}id 0 and verify that the newly created
+	 * file and directory inherit gid 1000, not 0.
+	 */
+	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
+		log_stderr("failure: fchownat");
+		goto out;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		if (!caps_supported()) {
+			log_debug("skip: capability library not installed");
+			exit(EXIT_SUCCESS);
+		}
+
+		if (!switch_userns(attr.userns_fd, 0, 0, false))
+			die("failure: switch_userns");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(attr.userns_fd);
+	safe_close(file1_fd);
+	safe_close(open_tree_fd);
+
+	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",                                                          },
@@ -7745,3 +8164,13 @@ const struct test_suite s_setxattr_fix_705191b03d50 = {
 	.tests = t_setxattr_fix_705191b03d50,
 	.nr_tests = ARRAY_SIZE(t_setxattr_fix_705191b03d50),
 };
+
+static const struct test_struct t_setgid_create_umask_idmapped[] = {
+	{ setgid_create_umask_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount",		},
+	{ setgid_create_umask_idmapped_in_userns,			T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount inside userns",	},
+};
+
+const struct test_suite s_setgid_create_umask_idmapped = {
+	.tests = t_setgid_create_umask_idmapped,
+	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped),
+};
diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
index ff21ea2c..a332439f 100644
--- a/src/vfs/idmapped-mounts.h
+++ b/src/vfs/idmapped-mounts.h
@@ -14,5 +14,6 @@ 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;
 extern const struct test_suite s_setxattr_fix_705191b03d50;
+extern const struct test_suite s_setgid_create_umask_idmapped;
 
 #endif /* __IDMAPPED_MOUNTS_H */
diff --git a/src/vfs/utils.c b/src/vfs/utils.c
index 1388edda..6db7a11d 100644
--- a/src/vfs/utils.c
+++ b/src/vfs/utils.c
@@ -809,6 +809,20 @@ bool is_sticky(int dfd, const char *path, int flags)
 	return (st.st_mode & S_ISVTX) > 0;
 }
 
+/*is_ixgrp - check whether file or directory is S_IXGRP */
+bool is_ixgrp(int dfd, const char *path, int flags)
+{
+	int ret;
+	struct stat st;
+
+	ret = fstatat(dfd, path, &st, flags);
+	if (ret < 0)
+		return false;
+
+	errno = 0; /* Don't report misleading errno. */
+	return (st.st_mode & S_IXGRP);
+}
+
 bool switch_resids(uid_t uid, gid_t gid)
 {
 	if (setresgid(gid, gid, gid))
diff --git a/src/vfs/utils.h b/src/vfs/utils.h
index 7fb702fd..c0dbe370 100644
--- a/src/vfs/utils.h
+++ b/src/vfs/utils.h
@@ -368,6 +368,7 @@ extern bool expected_file_size(int dfd, const char *path, int flags,
 extern bool is_setid(int dfd, const char *path, int flags);
 extern bool is_setgid(int dfd, const char *path, int flags);
 extern bool is_sticky(int dfd, const char *path, int flags);
+extern bool is_ixgrp(int dfd, const char *path, int flags);
 extern bool openat_tmpfile_supported(int dirfd);
 
 #endif /* __IDMAP_UTILS_H */
diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
index 29ac0bec..8448362e 100644
--- a/src/vfs/vfstest.c
+++ b/src/vfs/vfstest.c
@@ -1733,6 +1733,186 @@ out:
 	return fret;
 }
 
+/* The current_umask() is stripped from the mode directly in the vfs if the
+ * filesystem either doesn't support acls or the filesystem has been
+ * mounted without posic acl support.
+ *
+ * If the filesystem does support acls then current_umask() stripping is
+ * deferred to posix_acl_create(). So when the filesystem calls
+ * posix_acl_create() and there are no acls set or not supported then
+ * current_umask() will be stripped.
+ *
+ * Use umask(S_IXGRP) to check whether inode strip S_ISGID works correctly.
+ */
+
+static int setgid_create_umask(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF;
+	int tmpfile_fd = -EBADF;
+	pid_t pid;
+	bool supported = false;
+	mode_t mode;
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the setgid bit got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	supported = openat_tmpfile_supported(info->t_dir1_fd);
+
+	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
+	 * whether has group execute or search permission.
+	 */
+	umask(S_IXGRP);
+	mode = umask(S_IXGRP);
+	if (!(mode & S_IXGRP))
+		die("failure: umask");
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		if (!switch_ids(0, 10000))
+			die("failure: switch_ids");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a character device via mknodat() vfs_mknod */
+		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (unlinkat(info->t_dir1_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (unlinkat(info->t_dir1_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(file1_fd);
+	return fret;
+}
+
 static int setattr_truncate(const struct vfstest_info *info)
 {
 	int fret = -1;
@@ -1807,6 +1987,7 @@ static void usage(void)
 	fprintf(stderr, "--test-btrfs                        Run btrfs specific idmapped mount testsuite\n");
 	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
 	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
+	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
 
 	_exit(EXIT_SUCCESS);
 }
@@ -1825,6 +2006,7 @@ static const struct option longopts[] = {
 	{"test-btrfs",				no_argument,		0,	'b'},
 	{"test-setattr-fix-968219708108",	no_argument,		0,	'i'},
 	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
+	{"test-setgid-create-umask",		no_argument,		0,	'u'},
 	{NULL,					0,			0,	  0},
 };
 
@@ -1850,6 +2032,15 @@ static const struct test_suite s_basic = {
 	.nr_tests = ARRAY_SIZE(t_basic),
 };
 
+static const struct test_struct t_setgid_create_umask[] = {
+	{ setgid_create_umask,						0,			"create operations in directories with setgid bit set under umask",				},
+};
+
+static const struct test_suite s_setgid_create_umask = {
+	.tests = t_setgid_create_umask,
+	.nr_tests = ARRAY_SIZE(t_setgid_create_umask),
+};
+
 static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size)
 {
 	int i;
@@ -1947,7 +2138,8 @@ int main(int argc, char *argv[])
 	bool idmapped_mounts_supported = false, test_btrfs = false,
 	     test_core = false, test_fscaps_regression = false,
 	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
-	     test_setxattr_fix_705191b03d50 = false;
+	     test_setxattr_fix_705191b03d50 = false,
+	     test_setgid_create_umask = false;
 
 	init_vfstest_info(&info);
 
@@ -1989,6 +2181,9 @@ int main(int argc, char *argv[])
 		case 'j':
 			test_setxattr_fix_705191b03d50 = true;
 			break;
+		case 'u':
+			test_setgid_create_umask = true;
+			break;
 		case 'h':
 			/* fallthrough */
 		default:
@@ -2066,6 +2261,14 @@ int main(int argc, char *argv[])
 	    !run_suite(&info, &s_setxattr_fix_705191b03d50))
 		goto out;
 
+	if (test_setgid_create_umask) {
+		if (!run_suite(&info, &s_setgid_create_umask))
+			goto out;
+
+		if (!run_suite(&info, &s_setgid_create_umask_idmapped))
+			goto out;
+	}
+
 	fret = EXIT_SUCCESS;
 
 out:
diff --git a/tests/generic/691 b/tests/generic/691
new file mode 100755
index 00000000..d4875854
--- /dev/null
+++ b/tests/generic/691
@@ -0,0 +1,40 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2022 Fujitsu Limited. All Rights Reserved.
+#
+# FS QA Test No. 691
+#
+# Test that idmapped mounts setgid's behave correctly when using
+# umask(S_IXGRP)
+#
+. ./common/preamble
+_begin_fstest auto quick cap idmapped mount perms rw unlink
+
+# Import common functions.
+. ./common/filter
+
+# real QA test starts here
+
+_supported_fs generic
+_require_test
+_require_scratch
+
+echo "Silence is golden"
+
+$here/src/vfs/vfstest --test-setgid-create-umask \
+        --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
+
+_scratch_mkfs > "$seqres.full" 2>&1
+export MOUNT_OPTIONS="-o noacl"
+
+# If filesystem supports noacl mount option, also test setgid bit whether
+# was stripped correctly.
+# noacl will earse acl flag in superblock, so kernel will use current_umask
+# in vfs directly instead of calling posix_acl_create on underflying
+# filesystem.
+_try_scratch_mount >>$seqres.full 2>&1 && \
+	$here/src/vfs/vfstest --test-setgid-create-umask \
+        --device "$SCRATCH_DEV" --mount "$SCRATCH_MNT" --fstype "$FSTYP"
+
+status=0
+exit
diff --git a/tests/generic/691.out b/tests/generic/691.out
new file mode 100644
index 00000000..006aef43
--- /dev/null
+++ b/tests/generic/691.out
@@ -0,0 +1,2 @@
+QA output created by 691
+Silence is golden
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v1 2/2] vfs: Add new setgid_create_acl test
  2022-05-20 16:04 [PATCH v1 1/2] vfs: Add new setgid_create_umask test Yang Xu
@ 2022-05-20 16:04 ` Yang Xu
  2022-07-16 16:12 ` [PATCH v1 1/2] vfs: Add new setgid_create_umask test Zorro Lang
  2022-07-25  7:55 ` xuyang2018.jy
  2 siblings, 0 replies; 15+ messages in thread
From: Yang Xu @ 2022-05-20 16:04 UTC (permalink / raw)
  To: fstests; +Cc: Yang Xu

The current_umask() is stripped from the mode directly in the vfs if the
filesystem either doesn't support acls or the filesystem has been
mounted without posic acl support.

If the filesystem does support acls then current_umask() stripping is
deferred to posix_acl_create(). So when the filesystem calls
posix_acl_create() and there are no acls set or not supported then
current_umask() will be stripped.

If the parent directory has a default acl then permissions are based off
of that and current_umask() is ignored. Specifically, if the ACL has an
ACL_MASK entry, the group permissions correspond to the permissions of
the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
group permissions correspond to the permissions of the ACL_GROUP_OBJ
entry.

Here we only use setfacl to set default acl or add ACL_MASK to check
whether inode strip  S_ISGID works correctly.

Like umask test, this patch is also designed to test kernel patchset behaviour
"move S_ISGID stripping into the vfs"
https://patchwork.kernel.org/project/linux-fsdevel/list/?series=635692

Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
---
 src/vfs/idmapped-mounts.c | 702 ++++++++++++++++++++++++++++++++++++++
 src/vfs/idmapped-mounts.h |   1 +
 src/vfs/vfstest.c         | 344 ++++++++++++++++++-
 tests/generic/692         |  29 ++
 tests/generic/692.out     |   2 +
 5 files changed, 1077 insertions(+), 1 deletion(-)
 create mode 100755 tests/generic/692
 create mode 100644 tests/generic/692.out

diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
index 72de52cc..a61a18a7 100644
--- a/src/vfs/idmapped-mounts.c
+++ b/src/vfs/idmapped-mounts.c
@@ -8083,6 +8083,698 @@ out:
 	return fret;
 }
 
+static int setgid_create_acl_idmapped(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF, open_tree_fd = -EBADF;
+	struct mount_attr attr = {
+		.attr_set = MOUNT_ATTR_IDMAP,
+	};
+	pid_t pid;
+	int tmpfile_fd = -EBADF;
+	bool supported = false;
+	char path[PATH_MAX];
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the sid bits got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	/* Changing mount properties on a detached mount. */
+	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
+	if (attr.userns_fd < 0) {
+		log_stderr("failure: get_userns_fd");
+		goto out;
+	}
+
+	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+				     AT_EMPTY_PATH |
+				     AT_NO_AUTOMOUNT |
+				     AT_SYMLINK_NOFOLLOW |
+				     OPEN_TREE_CLOEXEC |
+				     OPEN_TREE_CLONE);
+	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;
+	}
+
+	supported = openat_tmpfile_supported(open_tree_fd);
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* If the parent directory has a default acl then permissions are based off
+		 * of that and current_umask() is ignored. Specifically, if the ACL has an
+		 * ACL_MASK entry, the group permissions correspond to the permissions of
+		 * the ACL_MASK entry.
+		 */
+		umask(S_IXGRP);
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!switch_ids(10000, 11000))
+			die("failure: switch fsids");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* If the parent directory has a default acl then permissions are based off
+		 * of that and current_umask() is ignored. Specifically, if the ACL has an
+		 * ACL_MASK entry, the group permissions correspond to the permissions of
+		 * the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
+		 * group permissions correspond to the permissions of the ACL_GROUP_OBJ
+		 * entry.
+		 */
+		umask(S_IXGRP);
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!switch_ids(10000, 11000))
+			die("failure: switch fsids");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (!is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(attr.userns_fd);
+	safe_close(file1_fd);
+	safe_close(open_tree_fd);
+
+	return fret;
+}
+
+static int setgid_create_acl_idmapped_in_userns(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF, open_tree_fd = -EBADF;
+	struct mount_attr attr = {
+		.attr_set = MOUNT_ATTR_IDMAP,
+	};
+	pid_t pid;
+	int tmpfile_fd = -EBADF;
+	bool supported = false;
+	char path[PATH_MAX];
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the sid bits got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	/* Changing mount properties on a detached mount. */
+	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
+	if (attr.userns_fd < 0) {
+		log_stderr("failure: get_userns_fd");
+		goto out;
+	}
+
+	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+				     AT_EMPTY_PATH |
+				     AT_NO_AUTOMOUNT |
+				     AT_SYMLINK_NOFOLLOW |
+				     OPEN_TREE_CLOEXEC |
+				     OPEN_TREE_CLONE);
+	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;
+	}
+
+	supported = openat_tmpfile_supported(open_tree_fd);
+
+	/*
+	 * Below we verify that setgid inheritance for a newly created file or
+	 * directory works correctly. As part of this we need to verify that
+	 * newly created files or directories inherit their gid from their
+	 * parent directory. So we change the parent directorie's gid to 1000
+	 * and create a file with fs{g,u}id 0 and verify that the newly created
+	 * file and directory inherit gid 1000, not 0.
+	 */
+	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
+		log_stderr("failure: fchownat");
+		goto out;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* If the parent directory has a default acl then permissions are based off
+		 * of that and current_umask() is ignored. Specifically, if the ACL has an
+		 * ACL_MASK entry, the group permissions correspond to the permissions of
+		 * the ACL_MASK entry.
+		 */
+		umask(S_IXGRP);
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!caps_supported()) {
+			log_debug("skip: capability library not installed");
+			exit(EXIT_SUCCESS);
+		}
+
+		if (!switch_userns(attr.userns_fd, 0, 0, false))
+			die("failure: switch_userns");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* If the parent directory has a default acl then permissions are based off
+		 * of that and current_umask() is ignored. Specifically, if the ACL has an
+		 * ACL_MASK entry, the group permissions correspond to the permissions of
+		 * the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
+		 * group permissions correspond to the permissions of the ACL_GROUP_OBJ
+		 * entry.
+		 */
+		umask(S_IXGRP);
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!caps_supported()) {
+			log_debug("skip: capability library not installed");
+			exit(EXIT_SUCCESS);
+		}
+
+		if (!switch_userns(attr.userns_fd, 0, 0, false))
+			die("failure: switch_userns");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, FILE2, 0))
+			 die("failure: is_ixgrp");
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			 die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (!is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(attr.userns_fd);
+	safe_close(file1_fd);
+	safe_close(open_tree_fd);
+
+	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",                                                          },
@@ -8174,3 +8866,13 @@ const struct test_suite s_setgid_create_umask_idmapped = {
 	.tests = t_setgid_create_umask_idmapped,
 	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped),
 };
+
+static const struct test_struct t_setgid_create_acl_idmapped[] = {
+	{ setgid_create_acl_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using acl in directories with setgid bit set on idmapped mount",                },
+	{ setgid_create_acl_idmapped_in_userns,				T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using acl in directories with setgid bit set on idmapped mount inside userns",  },
+};
+
+const struct test_suite s_setgid_create_acl_idmapped = {
+	.tests = t_setgid_create_acl_idmapped,
+	.nr_tests = ARRAY_SIZE(t_setgid_create_acl_idmapped),
+};
diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
index a332439f..5256ed4a 100644
--- a/src/vfs/idmapped-mounts.h
+++ b/src/vfs/idmapped-mounts.h
@@ -15,5 +15,6 @@ extern const struct test_suite s_nested_userns;
 extern const struct test_suite s_setattr_fix_968219708108;
 extern const struct test_suite s_setxattr_fix_705191b03d50;
 extern const struct test_suite s_setgid_create_umask_idmapped;
+extern const struct test_suite s_setgid_create_acl_idmapped;
 
 #endif /* __IDMAPPED_MOUNTS_H */
diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
index 8448362e..a7f02986 100644
--- a/src/vfs/vfstest.c
+++ b/src/vfs/vfstest.c
@@ -27,6 +27,8 @@
 #include "missing.h"
 #include "utils.h"
 
+static char t_buf[PATH_MAX];
+
 static void init_vfstest_info(struct vfstest_info *info)
 {
 	info->t_overflowuid		= 65534;
@@ -1913,6 +1915,324 @@ out:
 	return fret;
 }
 
+/*
+ * If the parent directory has a default acl then permissions are based off
+ * of that and current_umask() is ignored. Specifically, if the ACL has an
+ * ACL_MASK entry, the group permissions correspond to the permissions of
+ * the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
+ * group permissions correspond to the permissions of the ACL_GROUP_OBJ
+ * entry.
+ *
+ * Use setfacl to check whether inode strip S_ISGID works correctly.
+ */
+
+static int setgid_create_acl(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF;
+	int tmpfile_fd = -EBADF;
+	pid_t pid;
+	bool supported = false;
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the setgid bit got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	supported = openat_tmpfile_supported(info->t_dir1_fd);
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* If the parent directory has a default acl then permissions are based off
+		 * of that and current_umask() is ignored. Specifically, if the ACL has an
+		 * ACL_MASK entry, the group permissions correspond to the permissions of
+		 * the ACL_MASK entry.
+		 */
+		umask(S_IXGRP);
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!switch_ids(0, 10000))
+			die("failure: switch_ids");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a character device via mknodat() vfs_mknod */
+		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (unlinkat(info->t_dir1_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(info->t_dir1_fd, FILE3, 0, 0, 0))
+				die("failure: check ownership");
+			if (unlinkat(info->t_dir1_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* If the parent directory has a default acl then permissions are based off
+		 * of that and current_umask() is ignored. Specifically, if the ACL has an
+		 * ACL_MASK entry, the group permissions correspond to the permissions of
+		 * the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
+		 * group permissions correspond to the permissions of the ACL_GROUP_OBJ
+		 * entry.
+		 */
+		umask(S_IXGRP);
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!switch_ids(0, 10000))
+			die("failure: switch_ids");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a character device via mknodat() vfs_mknod */
+		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (unlinkat(info->t_dir1_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (!is_ixgrp(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(info->t_dir1_fd, FILE3, 0, 0, 0))
+				die("failure: check ownership");
+			if (unlinkat(info->t_dir1_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(file1_fd);
+
+	return fret;
+}
+
 static int setattr_truncate(const struct vfstest_info *info)
 {
 	int fret = -1;
@@ -1988,6 +2308,7 @@ static void usage(void)
 	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
 	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
 	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
+	fprintf(stderr, "--test-setgid-create-acl            Run setgid with acl tests\n");
 
 	_exit(EXIT_SUCCESS);
 }
@@ -2007,6 +2328,7 @@ static const struct option longopts[] = {
 	{"test-setattr-fix-968219708108",	no_argument,		0,	'i'},
 	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
 	{"test-setgid-create-umask",		no_argument,		0,	'u'},
+	{"test-setgid-create-acl",		no_argument,		0,	'l'},
 	{NULL,					0,			0,	  0},
 };
 
@@ -2041,6 +2363,15 @@ static const struct test_suite s_setgid_create_umask = {
 	.nr_tests = ARRAY_SIZE(t_setgid_create_umask),
 };
 
+static const struct test_struct t_setgid_create_acl[] = {
+	{ setgid_create_acl,						0,			"create operations in directories with setgid bit set under posix acl",				},
+};
+
+static const struct test_suite s_setgid_create_acl = {
+	.tests = t_setgid_create_acl,
+	.nr_tests = ARRAY_SIZE(t_setgid_create_acl),
+};
+
 static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size)
 {
 	int i;
@@ -2139,7 +2470,7 @@ int main(int argc, char *argv[])
 	     test_core = false, test_fscaps_regression = false,
 	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
 	     test_setxattr_fix_705191b03d50 = false,
-	     test_setgid_create_umask = false;
+	     test_setgid_create_umask = false, test_setgid_create_acl = false;
 
 	init_vfstest_info(&info);
 
@@ -2184,6 +2515,9 @@ int main(int argc, char *argv[])
 		case 'u':
 			test_setgid_create_umask = true;
 			break;
+		case 'l':
+			test_setgid_create_acl = true;
+			break;
 		case 'h':
 			/* fallthrough */
 		default:
@@ -2269,6 +2603,14 @@ int main(int argc, char *argv[])
 			goto out;
 	}
 
+	if (test_setgid_create_acl) {
+		if (!run_suite(&info, &s_setgid_create_acl))
+			goto out;
+
+		if (!run_suite(&info, &s_setgid_create_acl_idmapped))
+			goto out;
+	}
+
 	fret = EXIT_SUCCESS;
 
 out:
diff --git a/tests/generic/692 b/tests/generic/692
new file mode 100755
index 00000000..cd7439e4
--- /dev/null
+++ b/tests/generic/692
@@ -0,0 +1,29 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2022 Fujitsu Limited. All Rights Reserved.
+#
+# FS QA Test No. 692
+#
+# Test that idmapped mounts setgid's behave correctly when using
+# acl
+#
+. ./common/preamble
+_begin_fstest auto quick cap acl idmapped mount perms rw unlink
+
+# Import common functions.
+. ./common/filter
+. ./common/attr
+
+# real QA test starts here
+
+_supported_fs generic
+_require_test
+_require_acls
+
+echo "Silence is golden"
+
+$here/src/vfs/vfstest --test-setgid-create-acl \
+        --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
+
+status=$?
+exit
diff --git a/tests/generic/692.out b/tests/generic/692.out
new file mode 100644
index 00000000..d7521a9f
--- /dev/null
+++ b/tests/generic/692.out
@@ -0,0 +1,2 @@
+QA output created by 692
+Silence is golden
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH v1 1/2] vfs: Add new setgid_create_umask test
  2022-05-20 16:04 [PATCH v1 1/2] vfs: Add new setgid_create_umask test Yang Xu
  2022-05-20 16:04 ` [PATCH v1 2/2] vfs: Add new setgid_create_acl test Yang Xu
@ 2022-07-16 16:12 ` Zorro Lang
  2022-07-19  7:20   ` xuyang2018.jy
  2022-07-25  7:55 ` xuyang2018.jy
  2 siblings, 1 reply; 15+ messages in thread
From: Zorro Lang @ 2022-07-16 16:12 UTC (permalink / raw)
  To: Yang Xu; +Cc: fstests, Christian Brauner, linux-fsdevel

On Sat, May 21, 2022 at 12:04:23AM +0800, Yang Xu wrote:
> The current_umask() is stripped from the mode directly in the vfs if the
> filesystem either doesn't support acls or the filesystem has been
> mounted without posic acl support.
> 
> If the filesystem does support acls then current_umask() stripping is
> deferred to posix_acl_create(). So when the filesystem calls
> posix_acl_create() and there are no acls set or not supported then
> current_umask() will be stripped.
> 
> This patch is also designed to test kernel patchset behaviour
> "move S_ISGID stripping into the vfs"
> https://patchwork.kernel.org/project/linux-fsdevel/list/?series=635692
> 
> Here we only use umask(S_IXGRP) to check whether inode strip
> S_ISGID works correctly and check whether S_IXGRP mode exists.
> 
> Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
> ---

This patchset has been stuck at here more than one month. As VFS guys gave it
many review points last time [1]. So cc vfs list to make sure if they have
more concern.

Thanks,
Zorro

[1]
https://lore.kernel.org/fstests/1649763226-2329-4-git-send-email-xuyang2018.jy@fujitsu.com/

>  src/vfs/idmapped-mounts.c | 429 ++++++++++++++++++++++++++++++++++++++
>  src/vfs/idmapped-mounts.h |   1 +
>  src/vfs/utils.c           |  14 ++
>  src/vfs/utils.h           |   1 +
>  src/vfs/vfstest.c         | 205 +++++++++++++++++-
>  tests/generic/691         |  40 ++++
>  tests/generic/691.out     |   2 +
>  7 files changed, 691 insertions(+), 1 deletion(-)
>  create mode 100755 tests/generic/691
>  create mode 100644 tests/generic/691.out
> 
> diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
> index 63297d5f..72de52cc 100644
> --- a/src/vfs/idmapped-mounts.c
> +++ b/src/vfs/idmapped-mounts.c
> @@ -7664,6 +7664,425 @@ out:
>  	return fret;
>  }
>  
> +static int setgid_create_umask_idmapped(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
> +	struct mount_attr attr = {
> +		.attr_set = MOUNT_ATTR_IDMAP,
> +	};
> +	pid_t pid;
> +	int tmpfile_fd = -EBADF;
> +	bool supported = false;
> +	char path[PATH_MAX];
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the sid bits got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	/* Changing mount properties on a detached mount. */
> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
> +	if (attr.userns_fd < 0) {
> +		log_stderr("failure: get_userns_fd");
> +		goto out;
> +	}
> +
> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
> +				     AT_EMPTY_PATH |
> +				     AT_NO_AUTOMOUNT |
> +				     AT_SYMLINK_NOFOLLOW |
> +				     OPEN_TREE_CLOEXEC |
> +				     OPEN_TREE_CLONE);
> +	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;
> +	}
> +
> +	supported = openat_tmpfile_supported(open_tree_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +		pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		if (!switch_ids(10000, 11000))
> +			die("failure: switch fsids");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(open_tree_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create directory */
> +		if (mkdirat(open_tree_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a whiteout device via mknodat() vfs_mknod */
> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 10000, not by gid 11000.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 10000, not by gid 11000.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(open_tree_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(open_tree_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
> +				die("failure: check ownership");
> +			if (unlinkat(open_tree_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(attr.userns_fd);
> +	safe_close(file1_fd);
> +	safe_close(open_tree_fd);
> +
> +	return fret;
> +}
> +
> +static int setgid_create_umask_idmapped_in_userns(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
> +	struct mount_attr attr = {
> +		.attr_set = MOUNT_ATTR_IDMAP,
> +	};
> +	pid_t pid;
> +	int tmpfile_fd = -EBADF;
> +	bool supported = false;
> +	char path[PATH_MAX];
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the sid bits got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	/* Changing mount properties on a detached mount. */
> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
> +	if (attr.userns_fd < 0) {
> +		log_stderr("failure: get_userns_fd");
> +		goto out;
> +	}
> +
> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
> +				     AT_EMPTY_PATH |
> +				     AT_NO_AUTOMOUNT |
> +				     AT_SYMLINK_NOFOLLOW |
> +				     OPEN_TREE_CLOEXEC |
> +				     OPEN_TREE_CLONE);
> +	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;
> +	}
> +
> +	supported = openat_tmpfile_supported(open_tree_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +	/*
> +	 * Below we verify that setgid inheritance for a newly created file or
> +	 * directory works correctly. As part of this we need to verify that
> +	 * newly created files or directories inherit their gid from their
> +	 * parent directory. So we change the parent directorie's gid to 1000
> +	 * and create a file with fs{g,u}id 0 and verify that the newly created
> +	 * file and directory inherit gid 1000, not 0.
> +	 */
> +	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
> +		log_stderr("failure: fchownat");
> +		goto out;
> +	}
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		if (!caps_supported()) {
> +			log_debug("skip: capability library not installed");
> +			exit(EXIT_SUCCESS);
> +		}
> +
> +		if (!switch_userns(attr.userns_fd, 0, 0, false))
> +			die("failure: switch_userns");
> +
> +		if (!caps_down_fsetid())
> +			die("failure: caps_down_fsetid");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(open_tree_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create directory */
> +		if (mkdirat(open_tree_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a whiteout device via mknodat() vfs_mknod */
> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 1000, not by gid 0.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 1000, not by gid 0.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(open_tree_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(open_tree_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
> +				die("failure: check ownership");
> +			if (unlinkat(open_tree_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(attr.userns_fd);
> +	safe_close(file1_fd);
> +	safe_close(open_tree_fd);
> +
> +	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",                                                          },
> @@ -7745,3 +8164,13 @@ const struct test_suite s_setxattr_fix_705191b03d50 = {
>  	.tests = t_setxattr_fix_705191b03d50,
>  	.nr_tests = ARRAY_SIZE(t_setxattr_fix_705191b03d50),
>  };
> +
> +static const struct test_struct t_setgid_create_umask_idmapped[] = {
> +	{ setgid_create_umask_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount",		},
> +	{ setgid_create_umask_idmapped_in_userns,			T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount inside userns",	},
> +};
> +
> +const struct test_suite s_setgid_create_umask_idmapped = {
> +	.tests = t_setgid_create_umask_idmapped,
> +	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped),
> +};
> diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
> index ff21ea2c..a332439f 100644
> --- a/src/vfs/idmapped-mounts.h
> +++ b/src/vfs/idmapped-mounts.h
> @@ -14,5 +14,6 @@ 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;
>  extern const struct test_suite s_setxattr_fix_705191b03d50;
> +extern const struct test_suite s_setgid_create_umask_idmapped;
>  
>  #endif /* __IDMAPPED_MOUNTS_H */
> diff --git a/src/vfs/utils.c b/src/vfs/utils.c
> index 1388edda..6db7a11d 100644
> --- a/src/vfs/utils.c
> +++ b/src/vfs/utils.c
> @@ -809,6 +809,20 @@ bool is_sticky(int dfd, const char *path, int flags)
>  	return (st.st_mode & S_ISVTX) > 0;
>  }
>  
> +/*is_ixgrp - check whether file or directory is S_IXGRP */
> +bool is_ixgrp(int dfd, const char *path, int flags)
> +{
> +	int ret;
> +	struct stat st;
> +
> +	ret = fstatat(dfd, path, &st, flags);
> +	if (ret < 0)
> +		return false;
> +
> +	errno = 0; /* Don't report misleading errno. */
> +	return (st.st_mode & S_IXGRP);
> +}
> +
>  bool switch_resids(uid_t uid, gid_t gid)
>  {
>  	if (setresgid(gid, gid, gid))
> diff --git a/src/vfs/utils.h b/src/vfs/utils.h
> index 7fb702fd..c0dbe370 100644
> --- a/src/vfs/utils.h
> +++ b/src/vfs/utils.h
> @@ -368,6 +368,7 @@ extern bool expected_file_size(int dfd, const char *path, int flags,
>  extern bool is_setid(int dfd, const char *path, int flags);
>  extern bool is_setgid(int dfd, const char *path, int flags);
>  extern bool is_sticky(int dfd, const char *path, int flags);
> +extern bool is_ixgrp(int dfd, const char *path, int flags);
>  extern bool openat_tmpfile_supported(int dirfd);
>  
>  #endif /* __IDMAP_UTILS_H */
> diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
> index 29ac0bec..8448362e 100644
> --- a/src/vfs/vfstest.c
> +++ b/src/vfs/vfstest.c
> @@ -1733,6 +1733,186 @@ out:
>  	return fret;
>  }
>  
> +/* The current_umask() is stripped from the mode directly in the vfs if the
> + * filesystem either doesn't support acls or the filesystem has been
> + * mounted without posic acl support.
> + *
> + * If the filesystem does support acls then current_umask() stripping is
> + * deferred to posix_acl_create(). So when the filesystem calls
> + * posix_acl_create() and there are no acls set or not supported then
> + * current_umask() will be stripped.
> + *
> + * Use umask(S_IXGRP) to check whether inode strip S_ISGID works correctly.
> + */
> +
> +static int setgid_create_umask(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF;
> +	int tmpfile_fd = -EBADF;
> +	pid_t pid;
> +	bool supported = false;
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the setgid bit got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	supported = openat_tmpfile_supported(info->t_dir1_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		if (!switch_ids(0, 10000))
> +			die("failure: switch_ids");
> +
> +		if (!caps_down_fsetid())
> +			die("failure: caps_down_fsetid");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(info->t_dir1_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(info->t_dir1_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(info->t_dir1_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(info->t_dir1_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(info->t_dir1_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a character device via mknodat() vfs_mknod */
> +		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 0, not by gid 10000.
> +		 */
> +		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 0, not by gid 10000.
> +		 */
> +		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(info->t_dir1_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(info->t_dir1_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(info->t_dir1_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(info->t_dir1_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (unlinkat(info->t_dir1_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(file1_fd);
> +	return fret;
> +}
> +
>  static int setattr_truncate(const struct vfstest_info *info)
>  {
>  	int fret = -1;
> @@ -1807,6 +1987,7 @@ static void usage(void)
>  	fprintf(stderr, "--test-btrfs                        Run btrfs specific idmapped mount testsuite\n");
>  	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
>  	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
> +	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
>  
>  	_exit(EXIT_SUCCESS);
>  }
> @@ -1825,6 +2006,7 @@ static const struct option longopts[] = {
>  	{"test-btrfs",				no_argument,		0,	'b'},
>  	{"test-setattr-fix-968219708108",	no_argument,		0,	'i'},
>  	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
> +	{"test-setgid-create-umask",		no_argument,		0,	'u'},
>  	{NULL,					0,			0,	  0},
>  };
>  
> @@ -1850,6 +2032,15 @@ static const struct test_suite s_basic = {
>  	.nr_tests = ARRAY_SIZE(t_basic),
>  };
>  
> +static const struct test_struct t_setgid_create_umask[] = {
> +	{ setgid_create_umask,						0,			"create operations in directories with setgid bit set under umask",				},
> +};
> +
> +static const struct test_suite s_setgid_create_umask = {
> +	.tests = t_setgid_create_umask,
> +	.nr_tests = ARRAY_SIZE(t_setgid_create_umask),
> +};
> +
>  static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size)
>  {
>  	int i;
> @@ -1947,7 +2138,8 @@ int main(int argc, char *argv[])
>  	bool idmapped_mounts_supported = false, test_btrfs = false,
>  	     test_core = false, test_fscaps_regression = false,
>  	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
> -	     test_setxattr_fix_705191b03d50 = false;
> +	     test_setxattr_fix_705191b03d50 = false,
> +	     test_setgid_create_umask = false;
>  
>  	init_vfstest_info(&info);
>  
> @@ -1989,6 +2181,9 @@ int main(int argc, char *argv[])
>  		case 'j':
>  			test_setxattr_fix_705191b03d50 = true;
>  			break;
> +		case 'u':
> +			test_setgid_create_umask = true;
> +			break;
>  		case 'h':
>  			/* fallthrough */
>  		default:
> @@ -2066,6 +2261,14 @@ int main(int argc, char *argv[])
>  	    !run_suite(&info, &s_setxattr_fix_705191b03d50))
>  		goto out;
>  
> +	if (test_setgid_create_umask) {
> +		if (!run_suite(&info, &s_setgid_create_umask))
> +			goto out;
> +
> +		if (!run_suite(&info, &s_setgid_create_umask_idmapped))
> +			goto out;
> +	}
> +
>  	fret = EXIT_SUCCESS;
>  
>  out:
> diff --git a/tests/generic/691 b/tests/generic/691
> new file mode 100755
> index 00000000..d4875854
> --- /dev/null
> +++ b/tests/generic/691
> @@ -0,0 +1,40 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2022 Fujitsu Limited. All Rights Reserved.
> +#
> +# FS QA Test No. 691
> +#
> +# Test that idmapped mounts setgid's behave correctly when using
> +# umask(S_IXGRP)
> +#
> +. ./common/preamble
> +_begin_fstest auto quick cap idmapped mount perms rw unlink
> +
> +# Import common functions.
> +. ./common/filter
> +
> +# real QA test starts here
> +
> +_supported_fs generic
> +_require_test
> +_require_scratch
> +
> +echo "Silence is golden"
> +
> +$here/src/vfs/vfstest --test-setgid-create-umask \
> +        --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
> +
> +_scratch_mkfs > "$seqres.full" 2>&1
> +export MOUNT_OPTIONS="-o noacl"
> +
> +# If filesystem supports noacl mount option, also test setgid bit whether
> +# was stripped correctly.
> +# noacl will earse acl flag in superblock, so kernel will use current_umask
> +# in vfs directly instead of calling posix_acl_create on underflying
> +# filesystem.
> +_try_scratch_mount >>$seqres.full 2>&1 && \
> +	$here/src/vfs/vfstest --test-setgid-create-umask \
> +        --device "$SCRATCH_DEV" --mount "$SCRATCH_MNT" --fstype "$FSTYP"
> +
> +status=0
> +exit
> diff --git a/tests/generic/691.out b/tests/generic/691.out
> new file mode 100644
> index 00000000..006aef43
> --- /dev/null
> +++ b/tests/generic/691.out
> @@ -0,0 +1,2 @@
> +QA output created by 691
> +Silence is golden
> -- 
> 2.27.0
> 


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v1 1/2] vfs: Add new setgid_create_umask test
  2022-07-16 16:12 ` [PATCH v1 1/2] vfs: Add new setgid_create_umask test Zorro Lang
@ 2022-07-19  7:20   ` xuyang2018.jy
  0 siblings, 0 replies; 15+ messages in thread
From: xuyang2018.jy @ 2022-07-19  7:20 UTC (permalink / raw)
  To: Zorro Lang; +Cc: fstests, Christian Brauner, linux-fsdevel


on 2022/07/17 0:12, Zorro Lang wrote:
> On Sat, May 21, 2022 at 12:04:23AM +0800, Yang Xu wrote:
>> The current_umask() is stripped from the mode directly in the vfs if the
>> filesystem either doesn't support acls or the filesystem has been
>> mounted without posic acl support.
>>
>> If the filesystem does support acls then current_umask() stripping is
>> deferred to posix_acl_create(). So when the filesystem calls
>> posix_acl_create() and there are no acls set or not supported then
>> current_umask() will be stripped.
>>
>> This patch is also designed to test kernel patchset behaviour
>> "move S_ISGID stripping into the vfs"
>> https://patchwork.kernel.org/project/linux-fsdevel/list/?series=635692
>>
>> Here we only use umask(S_IXGRP) to check whether inode strip
>> S_ISGID works correctly and check whether S_IXGRP mode exists.
>>
>> Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
>> ---
> 
> This patchset has been stuck at here more than one month. As VFS guys gave it
> many review points last time [1]. So cc vfs list to make sure if they have
> more concern.
> 

Yes.

I don't plan to update these two tests until kernel patch set[1] is 
merged at next merge window.

ps: I may also refer to Christian 31c01ce18 ("generic: add test for 
tmpfs POSIX ACLs") format and only add  regression test in normal 
situation instead of in additional idmapped mount and userns situation.

[1]https://patchwork.kernel.org/project/linux-fsdevel/list/?series=659534

Best Regards
Yang Xu

> Thanks,
> Zorro
> 
> [1]
> https://lore.kernel.org/fstests/1649763226-2329-4-git-send-email-xuyang2018.jy@fujitsu.com/
> 
>>   src/vfs/idmapped-mounts.c | 429 ++++++++++++++++++++++++++++++++++++++
>>   src/vfs/idmapped-mounts.h |   1 +
>>   src/vfs/utils.c           |  14 ++
>>   src/vfs/utils.h           |   1 +
>>   src/vfs/vfstest.c         | 205 +++++++++++++++++-
>>   tests/generic/691         |  40 ++++
>>   tests/generic/691.out     |   2 +
>>   7 files changed, 691 insertions(+), 1 deletion(-)
>>   create mode 100755 tests/generic/691
>>   create mode 100644 tests/generic/691.out
>>
>> diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
>> index 63297d5f..72de52cc 100644
>> --- a/src/vfs/idmapped-mounts.c
>> +++ b/src/vfs/idmapped-mounts.c
>> @@ -7664,6 +7664,425 @@ out:
>>   	return fret;
>>   }
>>   
>> +static int setgid_create_umask_idmapped(const struct vfstest_info *info)
>> +{
>> +	int fret = -1;
>> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
>> +	struct mount_attr attr = {
>> +		.attr_set = MOUNT_ATTR_IDMAP,
>> +	};
>> +	pid_t pid;
>> +	int tmpfile_fd = -EBADF;
>> +	bool supported = false;
>> +	char path[PATH_MAX];
>> +	mode_t mode;
>> +
>> +	if (!caps_supported())
>> +		return 0;
>> +
>> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
>> +			      S_IWUSR |
>> +			      S_IRGRP |
>> +			      S_IWGRP |
>> +			      S_IROTH |
>> +			      S_IWOTH |
>> +			      S_IXUSR |
>> +			      S_IXGRP |
>> +			      S_IXOTH |
>> +			      S_ISGID), 0) {
>> +		log_stderr("failure: fchmod");
>> +		goto out;
>> +	}
>> +
>> +	/* Verify that the sid bits got raised. */
>> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
>> +		log_stderr("failure: is_setgid");
>> +		goto out;
>> +	}
>> +
>> +	/* Changing mount properties on a detached mount. */
>> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
>> +	if (attr.userns_fd < 0) {
>> +		log_stderr("failure: get_userns_fd");
>> +		goto out;
>> +	}
>> +
>> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
>> +				     AT_EMPTY_PATH |
>> +				     AT_NO_AUTOMOUNT |
>> +				     AT_SYMLINK_NOFOLLOW |
>> +				     OPEN_TREE_CLOEXEC |
>> +				     OPEN_TREE_CLONE);
>> +	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;
>> +	}
>> +
>> +	supported = openat_tmpfile_supported(open_tree_fd);
>> +
>> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
>> +	 * whether has group execute or search permission.
>> +	 */
>> +	umask(S_IXGRP);
>> +	mode = umask(S_IXGRP);
>> +	if (!(mode & S_IXGRP))
>> +		die("failure: umask");
>> +
>> +		pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		if (!switch_ids(10000, 11000))
>> +			die("failure: switch fsids");
>> +
>> +		/* create regular file via open() */
>> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
>> +		if (file1_fd < 0)
>> +			die("failure: create");
>> +
>> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
>> +		 * bit needs to be stripped.
>> +		 */
>> +		if (is_setgid(open_tree_fd, FILE1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create directory */
>> +		if (mkdirat(open_tree_fd, DIR1, 0000))
>> +			die("failure: create");
>> +
>> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
>> +			/* We're not in_group_p(). */
>> +			if (is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		} else {
>> +			/* Directories always inherit the setgid bit. */
>> +			if (!is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		}
>> +
>> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a special file via mknodat() vfs_create */
>> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, FILE2, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a whiteout device via mknodat() vfs_mknod */
>> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/*
>> +		 * In setgid directories newly created files always inherit the
>> +		 * gid from the parent directory. Verify that the file is owned
>> +		 * by gid 10000, not by gid 11000.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		/*
>> +		 * In setgid directories newly created directories always
>> +		 * inherit the gid from the parent directory. Verify that the
>> +		 * directory is owned by gid 10000, not by gid 11000.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (unlinkat(open_tree_fd, FILE1, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, FILE2, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: delete");
>> +
>> +		/* create tmpfile via filesystem tmpfile api */
>> +		if (supported) {
>> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
>> +			if (tmpfile_fd < 0)
>> +				die("failure: create");
>> +			/* link the temporary file into the filesystem, making it permanent */
>> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
>> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
>> +				die("failure: linkat");
>> +			if (close(tmpfile_fd))
>> +				die("failure: close");
>> +			if (is_setgid(open_tree_fd, FILE3, 0))
>> +				die("failure: is_setgid");
>> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
>> +				die("failure: is_ixgrp");
>> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
>> +				die("failure: check ownership");
>> +			if (unlinkat(open_tree_fd, FILE3, 0))
>> +				die("failure: delete");
>> +		}
>> +
>> +		exit(EXIT_SUCCESS);
>> +	}
>> +	if (wait_for_pid(pid))
>> +		goto out;
>> +
>> +	fret = 0;
>> +	log_debug("Ran test");
>> +out:
>> +	safe_close(attr.userns_fd);
>> +	safe_close(file1_fd);
>> +	safe_close(open_tree_fd);
>> +
>> +	return fret;
>> +}
>> +
>> +static int setgid_create_umask_idmapped_in_userns(const struct vfstest_info *info)
>> +{
>> +	int fret = -1;
>> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
>> +	struct mount_attr attr = {
>> +		.attr_set = MOUNT_ATTR_IDMAP,
>> +	};
>> +	pid_t pid;
>> +	int tmpfile_fd = -EBADF;
>> +	bool supported = false;
>> +	char path[PATH_MAX];
>> +	mode_t mode;
>> +
>> +	if (!caps_supported())
>> +		return 0;
>> +
>> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
>> +			      S_IWUSR |
>> +			      S_IRGRP |
>> +			      S_IWGRP |
>> +			      S_IROTH |
>> +			      S_IWOTH |
>> +			      S_IXUSR |
>> +			      S_IXGRP |
>> +			      S_IXOTH |
>> +			      S_ISGID), 0) {
>> +		log_stderr("failure: fchmod");
>> +		goto out;
>> +	}
>> +
>> +	/* Verify that the sid bits got raised. */
>> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
>> +		log_stderr("failure: is_setgid");
>> +		goto out;
>> +	}
>> +
>> +	/* Changing mount properties on a detached mount. */
>> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
>> +	if (attr.userns_fd < 0) {
>> +		log_stderr("failure: get_userns_fd");
>> +		goto out;
>> +	}
>> +
>> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
>> +				     AT_EMPTY_PATH |
>> +				     AT_NO_AUTOMOUNT |
>> +				     AT_SYMLINK_NOFOLLOW |
>> +				     OPEN_TREE_CLOEXEC |
>> +				     OPEN_TREE_CLONE);
>> +	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;
>> +	}
>> +
>> +	supported = openat_tmpfile_supported(open_tree_fd);
>> +
>> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
>> +	 * whether has group execute or search permission.
>> +	 */
>> +	umask(S_IXGRP);
>> +	mode = umask(S_IXGRP);
>> +	if (!(mode & S_IXGRP))
>> +		die("failure: umask");
>> +
>> +	/*
>> +	 * Below we verify that setgid inheritance for a newly created file or
>> +	 * directory works correctly. As part of this we need to verify that
>> +	 * newly created files or directories inherit their gid from their
>> +	 * parent directory. So we change the parent directorie's gid to 1000
>> +	 * and create a file with fs{g,u}id 0 and verify that the newly created
>> +	 * file and directory inherit gid 1000, not 0.
>> +	 */
>> +	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
>> +		log_stderr("failure: fchownat");
>> +		goto out;
>> +	}
>> +
>> +	pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		if (!caps_supported()) {
>> +			log_debug("skip: capability library not installed");
>> +			exit(EXIT_SUCCESS);
>> +		}
>> +
>> +		if (!switch_userns(attr.userns_fd, 0, 0, false))
>> +			die("failure: switch_userns");
>> +
>> +		if (!caps_down_fsetid())
>> +			die("failure: caps_down_fsetid");
>> +
>> +		/* create regular file via open() */
>> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
>> +		if (file1_fd < 0)
>> +			die("failure: create");
>> +
>> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
>> +		 * bit needs to be stripped.
>> +		 */
>> +		if (is_setgid(open_tree_fd, FILE1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create directory */
>> +		if (mkdirat(open_tree_fd, DIR1, 0000))
>> +			die("failure: create");
>> +
>> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
>> +			/* We're not in_group_p(). */
>> +			if (is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		} else {
>> +			/* Directories always inherit the setgid bit. */
>> +			if (!is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		}
>> +
>> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a special file via mknodat() vfs_create */
>> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, FILE2, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a whiteout device via mknodat() vfs_mknod */
>> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/*
>> +		 * In setgid directories newly created files always inherit the
>> +		 * gid from the parent directory. Verify that the file is owned
>> +		 * by gid 1000, not by gid 0.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		/*
>> +		 * In setgid directories newly created directories always
>> +		 * inherit the gid from the parent directory. Verify that the
>> +		 * directory is owned by gid 1000, not by gid 0.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (unlinkat(open_tree_fd, FILE1, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, FILE2, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: delete");
>> +
>> +		/* create tmpfile via filesystem tmpfile api */
>> +		if (supported) {
>> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
>> +			if (tmpfile_fd < 0)
>> +				die("failure: create");
>> +			/* link the temporary file into the filesystem, making it permanent */
>> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
>> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
>> +				die("failure: linkat");
>> +			if (close(tmpfile_fd))
>> +				die("failure: close");
>> +			if (is_setgid(open_tree_fd, FILE3, 0))
>> +				die("failure: is_setgid");
>> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
>> +				die("failure: is_ixgrp");
>> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
>> +				die("failure: check ownership");
>> +			if (unlinkat(open_tree_fd, FILE3, 0))
>> +				die("failure: delete");
>> +		}
>> +
>> +		exit(EXIT_SUCCESS);
>> +	}
>> +	if (wait_for_pid(pid))
>> +		goto out;
>> +
>> +	fret = 0;
>> +	log_debug("Ran test");
>> +out:
>> +	safe_close(attr.userns_fd);
>> +	safe_close(file1_fd);
>> +	safe_close(open_tree_fd);
>> +
>> +	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",                                                          },
>> @@ -7745,3 +8164,13 @@ const struct test_suite s_setxattr_fix_705191b03d50 = {
>>   	.tests = t_setxattr_fix_705191b03d50,
>>   	.nr_tests = ARRAY_SIZE(t_setxattr_fix_705191b03d50),
>>   };
>> +
>> +static const struct test_struct t_setgid_create_umask_idmapped[] = {
>> +	{ setgid_create_umask_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount",		},
>> +	{ setgid_create_umask_idmapped_in_userns,			T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount inside userns",	},
>> +};
>> +
>> +const struct test_suite s_setgid_create_umask_idmapped = {
>> +	.tests = t_setgid_create_umask_idmapped,
>> +	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped),
>> +};
>> diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
>> index ff21ea2c..a332439f 100644
>> --- a/src/vfs/idmapped-mounts.h
>> +++ b/src/vfs/idmapped-mounts.h
>> @@ -14,5 +14,6 @@ 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;
>>   extern const struct test_suite s_setxattr_fix_705191b03d50;
>> +extern const struct test_suite s_setgid_create_umask_idmapped;
>>   
>>   #endif /* __IDMAPPED_MOUNTS_H */
>> diff --git a/src/vfs/utils.c b/src/vfs/utils.c
>> index 1388edda..6db7a11d 100644
>> --- a/src/vfs/utils.c
>> +++ b/src/vfs/utils.c
>> @@ -809,6 +809,20 @@ bool is_sticky(int dfd, const char *path, int flags)
>>   	return (st.st_mode & S_ISVTX) > 0;
>>   }
>>   
>> +/*is_ixgrp - check whether file or directory is S_IXGRP */
>> +bool is_ixgrp(int dfd, const char *path, int flags)
>> +{
>> +	int ret;
>> +	struct stat st;
>> +
>> +	ret = fstatat(dfd, path, &st, flags);
>> +	if (ret < 0)
>> +		return false;
>> +
>> +	errno = 0; /* Don't report misleading errno. */
>> +	return (st.st_mode & S_IXGRP);
>> +}
>> +
>>   bool switch_resids(uid_t uid, gid_t gid)
>>   {
>>   	if (setresgid(gid, gid, gid))
>> diff --git a/src/vfs/utils.h b/src/vfs/utils.h
>> index 7fb702fd..c0dbe370 100644
>> --- a/src/vfs/utils.h
>> +++ b/src/vfs/utils.h
>> @@ -368,6 +368,7 @@ extern bool expected_file_size(int dfd, const char *path, int flags,
>>   extern bool is_setid(int dfd, const char *path, int flags);
>>   extern bool is_setgid(int dfd, const char *path, int flags);
>>   extern bool is_sticky(int dfd, const char *path, int flags);
>> +extern bool is_ixgrp(int dfd, const char *path, int flags);
>>   extern bool openat_tmpfile_supported(int dirfd);
>>   
>>   #endif /* __IDMAP_UTILS_H */
>> diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
>> index 29ac0bec..8448362e 100644
>> --- a/src/vfs/vfstest.c
>> +++ b/src/vfs/vfstest.c
>> @@ -1733,6 +1733,186 @@ out:
>>   	return fret;
>>   }
>>   
>> +/* The current_umask() is stripped from the mode directly in the vfs if the
>> + * filesystem either doesn't support acls or the filesystem has been
>> + * mounted without posic acl support.
>> + *
>> + * If the filesystem does support acls then current_umask() stripping is
>> + * deferred to posix_acl_create(). So when the filesystem calls
>> + * posix_acl_create() and there are no acls set or not supported then
>> + * current_umask() will be stripped.
>> + *
>> + * Use umask(S_IXGRP) to check whether inode strip S_ISGID works correctly.
>> + */
>> +
>> +static int setgid_create_umask(const struct vfstest_info *info)
>> +{
>> +	int fret = -1;
>> +	int file1_fd = -EBADF;
>> +	int tmpfile_fd = -EBADF;
>> +	pid_t pid;
>> +	bool supported = false;
>> +	mode_t mode;
>> +
>> +	if (!caps_supported())
>> +		return 0;
>> +
>> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
>> +			      S_IWUSR |
>> +			      S_IRGRP |
>> +			      S_IWGRP |
>> +			      S_IROTH |
>> +			      S_IWOTH |
>> +			      S_IXUSR |
>> +			      S_IXGRP |
>> +			      S_IXOTH |
>> +			      S_ISGID), 0) {
>> +		log_stderr("failure: fchmod");
>> +		goto out;
>> +	}
>> +
>> +	/* Verify that the setgid bit got raised. */
>> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
>> +		log_stderr("failure: is_setgid");
>> +		goto out;
>> +	}
>> +
>> +	supported = openat_tmpfile_supported(info->t_dir1_fd);
>> +
>> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
>> +	 * whether has group execute or search permission.
>> +	 */
>> +	umask(S_IXGRP);
>> +	mode = umask(S_IXGRP);
>> +	if (!(mode & S_IXGRP))
>> +		die("failure: umask");
>> +
>> +	pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		if (!switch_ids(0, 10000))
>> +			die("failure: switch_ids");
>> +
>> +		if (!caps_down_fsetid())
>> +			die("failure: caps_down_fsetid");
>> +
>> +		/* create regular file via open() */
>> +		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
>> +		if (file1_fd < 0)
>> +			die("failure: create");
>> +
>> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
>> +		 * bit needs to be stripped.
>> +		 */
>> +		if (is_setgid(info->t_dir1_fd, FILE1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(info->t_dir1_fd, FILE1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
>> +			die("failure: create");
>> +
>> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
>> +			/* We're not in_group_p(). */
>> +			if (is_setgid(info->t_dir1_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		} else {
>> +			/* Directories always inherit the setgid bit. */
>> +			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		}
>> +
>> +		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a special file via mknodat() vfs_create */
>> +		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(info->t_dir1_fd, FILE2, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(info->t_dir1_fd, FILE2, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a character device via mknodat() vfs_mknod */
>> +		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/*
>> +		 * In setgid directories newly created files always inherit the
>> +		 * gid from the parent directory. Verify that the file is owned
>> +		 * by gid 0, not by gid 10000.
>> +		 */
>> +		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
>> +			die("failure: check ownership");
>> +
>> +		/*
>> +		 * In setgid directories newly created directories always
>> +		 * inherit the gid from the parent directory. Verify that the
>> +		 * directory is owned by gid 0, not by gid 10000.
>> +		 */
>> +		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
>> +			die("failure: check ownership");
>> +
>> +		if (unlinkat(info->t_dir1_fd, FILE1, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(info->t_dir1_fd, FILE2, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
>> +			die("failure: delete");
>> +
>> +		/* create tmpfile via filesystem tmpfile api */
>> +		if (supported) {
>> +			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
>> +			if (tmpfile_fd < 0)
>> +				die("failure: create");
>> +			/* link the temporary file into the filesystem, making it permanent */
>> +			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
>> +				die("failure: linkat");
>> +			if (close(tmpfile_fd))
>> +				die("failure: close");
>> +			if (is_setgid(info->t_dir1_fd, FILE3, 0))
>> +				die("failure: is_setgid");
>> +			if (is_ixgrp(info->t_dir1_fd, FILE3, 0))
>> +				die("failure: is_ixgrp");
>> +			if (unlinkat(info->t_dir1_fd, FILE3, 0))
>> +				die("failure: delete");
>> +		}
>> +
>> +		exit(EXIT_SUCCESS);
>> +	}
>> +	if (wait_for_pid(pid))
>> +		goto out;
>> +
>> +	fret = 0;
>> +	log_debug("Ran test");
>> +out:
>> +	safe_close(file1_fd);
>> +	return fret;
>> +}
>> +
>>   static int setattr_truncate(const struct vfstest_info *info)
>>   {
>>   	int fret = -1;
>> @@ -1807,6 +1987,7 @@ static void usage(void)
>>   	fprintf(stderr, "--test-btrfs                        Run btrfs specific idmapped mount testsuite\n");
>>   	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
>>   	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
>> +	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
>>   
>>   	_exit(EXIT_SUCCESS);
>>   }
>> @@ -1825,6 +2006,7 @@ static const struct option longopts[] = {
>>   	{"test-btrfs",				no_argument,		0,	'b'},
>>   	{"test-setattr-fix-968219708108",	no_argument,		0,	'i'},
>>   	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
>> +	{"test-setgid-create-umask",		no_argument,		0,	'u'},
>>   	{NULL,					0,			0,	  0},
>>   };
>>   
>> @@ -1850,6 +2032,15 @@ static const struct test_suite s_basic = {
>>   	.nr_tests = ARRAY_SIZE(t_basic),
>>   };
>>   
>> +static const struct test_struct t_setgid_create_umask[] = {
>> +	{ setgid_create_umask,						0,			"create operations in directories with setgid bit set under umask",				},
>> +};
>> +
>> +static const struct test_suite s_setgid_create_umask = {
>> +	.tests = t_setgid_create_umask,
>> +	.nr_tests = ARRAY_SIZE(t_setgid_create_umask),
>> +};
>> +
>>   static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size)
>>   {
>>   	int i;
>> @@ -1947,7 +2138,8 @@ int main(int argc, char *argv[])
>>   	bool idmapped_mounts_supported = false, test_btrfs = false,
>>   	     test_core = false, test_fscaps_regression = false,
>>   	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
>> -	     test_setxattr_fix_705191b03d50 = false;
>> +	     test_setxattr_fix_705191b03d50 = false,
>> +	     test_setgid_create_umask = false;
>>   
>>   	init_vfstest_info(&info);
>>   
>> @@ -1989,6 +2181,9 @@ int main(int argc, char *argv[])
>>   		case 'j':
>>   			test_setxattr_fix_705191b03d50 = true;
>>   			break;
>> +		case 'u':
>> +			test_setgid_create_umask = true;
>> +			break;
>>   		case 'h':
>>   			/* fallthrough */
>>   		default:
>> @@ -2066,6 +2261,14 @@ int main(int argc, char *argv[])
>>   	    !run_suite(&info, &s_setxattr_fix_705191b03d50))
>>   		goto out;
>>   
>> +	if (test_setgid_create_umask) {
>> +		if (!run_suite(&info, &s_setgid_create_umask))
>> +			goto out;
>> +
>> +		if (!run_suite(&info, &s_setgid_create_umask_idmapped))
>> +			goto out;
>> +	}
>> +
>>   	fret = EXIT_SUCCESS;
>>   
>>   out:
>> diff --git a/tests/generic/691 b/tests/generic/691
>> new file mode 100755
>> index 00000000..d4875854
>> --- /dev/null
>> +++ b/tests/generic/691
>> @@ -0,0 +1,40 @@
>> +#! /bin/bash
>> +# SPDX-License-Identifier: GPL-2.0
>> +# Copyright (c) 2022 Fujitsu Limited. All Rights Reserved.
>> +#
>> +# FS QA Test No. 691
>> +#
>> +# Test that idmapped mounts setgid's behave correctly when using
>> +# umask(S_IXGRP)
>> +#
>> +. ./common/preamble
>> +_begin_fstest auto quick cap idmapped mount perms rw unlink
>> +
>> +# Import common functions.
>> +. ./common/filter
>> +
>> +# real QA test starts here
>> +
>> +_supported_fs generic
>> +_require_test
>> +_require_scratch
>> +
>> +echo "Silence is golden"
>> +
>> +$here/src/vfs/vfstest --test-setgid-create-umask \
>> +        --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
>> +
>> +_scratch_mkfs > "$seqres.full" 2>&1
>> +export MOUNT_OPTIONS="-o noacl"
>> +
>> +# If filesystem supports noacl mount option, also test setgid bit whether
>> +# was stripped correctly.
>> +# noacl will earse acl flag in superblock, so kernel will use current_umask
>> +# in vfs directly instead of calling posix_acl_create on underflying
>> +# filesystem.
>> +_try_scratch_mount >>$seqres.full 2>&1 && \
>> +	$here/src/vfs/vfstest --test-setgid-create-umask \
>> +        --device "$SCRATCH_DEV" --mount "$SCRATCH_MNT" --fstype "$FSTYP"
>> +
>> +status=0
>> +exit
>> diff --git a/tests/generic/691.out b/tests/generic/691.out
>> new file mode 100644
>> index 00000000..006aef43
>> --- /dev/null
>> +++ b/tests/generic/691.out
>> @@ -0,0 +1,2 @@
>> +QA output created by 691
>> +Silence is golden
>> -- 
>> 2.27.0
>>
> 

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v1 1/2] vfs: Add new setgid_create_umask test
  2022-05-20 16:04 [PATCH v1 1/2] vfs: Add new setgid_create_umask test Yang Xu
  2022-05-20 16:04 ` [PATCH v1 2/2] vfs: Add new setgid_create_acl test Yang Xu
  2022-07-16 16:12 ` [PATCH v1 1/2] vfs: Add new setgid_create_umask test Zorro Lang
@ 2022-07-25  7:55 ` xuyang2018.jy
  2022-07-25 14:20   ` Christian Brauner
  2 siblings, 1 reply; 15+ messages in thread
From: xuyang2018.jy @ 2022-07-25  7:55 UTC (permalink / raw)
  To: fstests; +Cc: Christian Brauner


on  2022/05/21 0:04, Yang Xu wrote:
> The current_umask() is stripped from the mode directly in the vfs if the
> filesystem either doesn't support acls or the filesystem has been
> mounted without posic acl support.
> 
> If the filesystem does support acls then current_umask() stripping is
> deferred to posix_acl_create(). So when the filesystem calls
> posix_acl_create() and there are no acls set or not supported then
> current_umask() will be stripped.
> 
> This patch is also designed to test kernel patchset behaviour
> "move S_ISGID stripping into the vfs"
> https://patchwork.kernel.org/project/linux-fsdevel/list/?series=635692

The kernel patch has been merged into linux-next branch[1].

Does anyone review this fstests patch or give some comment?

I plan to remove setgid_create_umask_idmapped_in_userns and 
setgid_create_umask_idmapped cases because they doesn't trigger bug.

Just treat it  as a kernel regression test ie 31c01ce18 ("generic: add 
test for tmpfs POSIX ACLs") format.

CC Christian to confirm that whether need to test idmapped and 
idmapped_in_userbs situation .

[1]https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/commit/?h=next-20220722&id=1639a49ccdce

ps: this patch still can be applied by change seq number to an unused 
number ie 693.

Best Regards
Yang Xu

> 
> Here we only use umask(S_IXGRP) to check whether inode strip
> S_ISGID works correctly and check whether S_IXGRP mode exists.
> 
> Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
> ---
>   src/vfs/idmapped-mounts.c | 429 ++++++++++++++++++++++++++++++++++++++
>   src/vfs/idmapped-mounts.h |   1 +
>   src/vfs/utils.c           |  14 ++
>   src/vfs/utils.h           |   1 +
>   src/vfs/vfstest.c         | 205 +++++++++++++++++-
>   tests/generic/691         |  40 ++++
>   tests/generic/691.out     |   2 +
>   7 files changed, 691 insertions(+), 1 deletion(-)
>   create mode 100755 tests/generic/691
>   create mode 100644 tests/generic/691.out
> 
> diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
> index 63297d5f..72de52cc 100644
> --- a/src/vfs/idmapped-mounts.c
> +++ b/src/vfs/idmapped-mounts.c
> @@ -7664,6 +7664,425 @@ out:
>   	return fret;
>   }
>   
> +static int setgid_create_umask_idmapped(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
> +	struct mount_attr attr = {
> +		.attr_set = MOUNT_ATTR_IDMAP,
> +	};
> +	pid_t pid;
> +	int tmpfile_fd = -EBADF;
> +	bool supported = false;
> +	char path[PATH_MAX];
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the sid bits got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	/* Changing mount properties on a detached mount. */
> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
> +	if (attr.userns_fd < 0) {
> +		log_stderr("failure: get_userns_fd");
> +		goto out;
> +	}
> +
> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
> +				     AT_EMPTY_PATH |
> +				     AT_NO_AUTOMOUNT |
> +				     AT_SYMLINK_NOFOLLOW |
> +				     OPEN_TREE_CLOEXEC |
> +				     OPEN_TREE_CLONE);
> +	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;
> +	}
> +
> +	supported = openat_tmpfile_supported(open_tree_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +		pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		if (!switch_ids(10000, 11000))
> +			die("failure: switch fsids");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(open_tree_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create directory */
> +		if (mkdirat(open_tree_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a whiteout device via mknodat() vfs_mknod */
> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 10000, not by gid 11000.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 10000, not by gid 11000.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(open_tree_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(open_tree_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
> +				die("failure: check ownership");
> +			if (unlinkat(open_tree_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(attr.userns_fd);
> +	safe_close(file1_fd);
> +	safe_close(open_tree_fd);
> +
> +	return fret;
> +}
> +
> +static int setgid_create_umask_idmapped_in_userns(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
> +	struct mount_attr attr = {
> +		.attr_set = MOUNT_ATTR_IDMAP,
> +	};
> +	pid_t pid;
> +	int tmpfile_fd = -EBADF;
> +	bool supported = false;
> +	char path[PATH_MAX];
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the sid bits got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	/* Changing mount properties on a detached mount. */
> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
> +	if (attr.userns_fd < 0) {
> +		log_stderr("failure: get_userns_fd");
> +		goto out;
> +	}
> +
> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
> +				     AT_EMPTY_PATH |
> +				     AT_NO_AUTOMOUNT |
> +				     AT_SYMLINK_NOFOLLOW |
> +				     OPEN_TREE_CLOEXEC |
> +				     OPEN_TREE_CLONE);
> +	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;
> +	}
> +
> +	supported = openat_tmpfile_supported(open_tree_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +	/*
> +	 * Below we verify that setgid inheritance for a newly created file or
> +	 * directory works correctly. As part of this we need to verify that
> +	 * newly created files or directories inherit their gid from their
> +	 * parent directory. So we change the parent directorie's gid to 1000
> +	 * and create a file with fs{g,u}id 0 and verify that the newly created
> +	 * file and directory inherit gid 1000, not 0.
> +	 */
> +	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
> +		log_stderr("failure: fchownat");
> +		goto out;
> +	}
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		if (!caps_supported()) {
> +			log_debug("skip: capability library not installed");
> +			exit(EXIT_SUCCESS);
> +		}
> +
> +		if (!switch_userns(attr.userns_fd, 0, 0, false))
> +			die("failure: switch_userns");
> +
> +		if (!caps_down_fsetid())
> +			die("failure: caps_down_fsetid");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(open_tree_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create directory */
> +		if (mkdirat(open_tree_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a whiteout device via mknodat() vfs_mknod */
> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 1000, not by gid 0.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 1000, not by gid 0.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(open_tree_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(open_tree_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
> +				die("failure: check ownership");
> +			if (unlinkat(open_tree_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(attr.userns_fd);
> +	safe_close(file1_fd);
> +	safe_close(open_tree_fd);
> +
> +	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",                                                          },
> @@ -7745,3 +8164,13 @@ const struct test_suite s_setxattr_fix_705191b03d50 = {
>   	.tests = t_setxattr_fix_705191b03d50,
>   	.nr_tests = ARRAY_SIZE(t_setxattr_fix_705191b03d50),
>   };
> +
> +static const struct test_struct t_setgid_create_umask_idmapped[] = {
> +	{ setgid_create_umask_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount",		},
> +	{ setgid_create_umask_idmapped_in_userns,			T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount inside userns",	},
> +};
> +
> +const struct test_suite s_setgid_create_umask_idmapped = {
> +	.tests = t_setgid_create_umask_idmapped,
> +	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped),
> +};
> diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
> index ff21ea2c..a332439f 100644
> --- a/src/vfs/idmapped-mounts.h
> +++ b/src/vfs/idmapped-mounts.h
> @@ -14,5 +14,6 @@ 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;
>   extern const struct test_suite s_setxattr_fix_705191b03d50;
> +extern const struct test_suite s_setgid_create_umask_idmapped;
>   
>   #endif /* __IDMAPPED_MOUNTS_H */
> diff --git a/src/vfs/utils.c b/src/vfs/utils.c
> index 1388edda..6db7a11d 100644
> --- a/src/vfs/utils.c
> +++ b/src/vfs/utils.c
> @@ -809,6 +809,20 @@ bool is_sticky(int dfd, const char *path, int flags)
>   	return (st.st_mode & S_ISVTX) > 0;
>   }
>   
> +/*is_ixgrp - check whether file or directory is S_IXGRP */
> +bool is_ixgrp(int dfd, const char *path, int flags)
> +{
> +	int ret;
> +	struct stat st;
> +
> +	ret = fstatat(dfd, path, &st, flags);
> +	if (ret < 0)
> +		return false;
> +
> +	errno = 0; /* Don't report misleading errno. */
> +	return (st.st_mode & S_IXGRP);
> +}
> +
>   bool switch_resids(uid_t uid, gid_t gid)
>   {
>   	if (setresgid(gid, gid, gid))
> diff --git a/src/vfs/utils.h b/src/vfs/utils.h
> index 7fb702fd..c0dbe370 100644
> --- a/src/vfs/utils.h
> +++ b/src/vfs/utils.h
> @@ -368,6 +368,7 @@ extern bool expected_file_size(int dfd, const char *path, int flags,
>   extern bool is_setid(int dfd, const char *path, int flags);
>   extern bool is_setgid(int dfd, const char *path, int flags);
>   extern bool is_sticky(int dfd, const char *path, int flags);
> +extern bool is_ixgrp(int dfd, const char *path, int flags);
>   extern bool openat_tmpfile_supported(int dirfd);
>   
>   #endif /* __IDMAP_UTILS_H */
> diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
> index 29ac0bec..8448362e 100644
> --- a/src/vfs/vfstest.c
> +++ b/src/vfs/vfstest.c
> @@ -1733,6 +1733,186 @@ out:
>   	return fret;
>   }
>   
> +/* The current_umask() is stripped from the mode directly in the vfs if the
> + * filesystem either doesn't support acls or the filesystem has been
> + * mounted without posic acl support.
> + *
> + * If the filesystem does support acls then current_umask() stripping is
> + * deferred to posix_acl_create(). So when the filesystem calls
> + * posix_acl_create() and there are no acls set or not supported then
> + * current_umask() will be stripped.
> + *
> + * Use umask(S_IXGRP) to check whether inode strip S_ISGID works correctly.
> + */
> +
> +static int setgid_create_umask(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF;
> +	int tmpfile_fd = -EBADF;
> +	pid_t pid;
> +	bool supported = false;
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the setgid bit got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	supported = openat_tmpfile_supported(info->t_dir1_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		if (!switch_ids(0, 10000))
> +			die("failure: switch_ids");
> +
> +		if (!caps_down_fsetid())
> +			die("failure: caps_down_fsetid");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(info->t_dir1_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(info->t_dir1_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(info->t_dir1_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(info->t_dir1_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(info->t_dir1_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a character device via mknodat() vfs_mknod */
> +		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 0, not by gid 10000.
> +		 */
> +		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 0, not by gid 10000.
> +		 */
> +		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(info->t_dir1_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(info->t_dir1_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(info->t_dir1_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(info->t_dir1_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (unlinkat(info->t_dir1_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(file1_fd);
> +	return fret;
> +}
> +
>   static int setattr_truncate(const struct vfstest_info *info)
>   {
>   	int fret = -1;
> @@ -1807,6 +1987,7 @@ static void usage(void)
>   	fprintf(stderr, "--test-btrfs                        Run btrfs specific idmapped mount testsuite\n");
>   	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
>   	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
> +	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
>   
>   	_exit(EXIT_SUCCESS);
>   }
> @@ -1825,6 +2006,7 @@ static const struct option longopts[] = {
>   	{"test-btrfs",				no_argument,		0,	'b'},
>   	{"test-setattr-fix-968219708108",	no_argument,		0,	'i'},
>   	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
> +	{"test-setgid-create-umask",		no_argument,		0,	'u'},
>   	{NULL,					0,			0,	  0},
>   };
>   
> @@ -1850,6 +2032,15 @@ static const struct test_suite s_basic = {
>   	.nr_tests = ARRAY_SIZE(t_basic),
>   };
>   
> +static const struct test_struct t_setgid_create_umask[] = {
> +	{ setgid_create_umask,						0,			"create operations in directories with setgid bit set under umask",				},
> +};
> +
> +static const struct test_suite s_setgid_create_umask = {
> +	.tests = t_setgid_create_umask,
> +	.nr_tests = ARRAY_SIZE(t_setgid_create_umask),
> +};
> +
>   static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size)
>   {
>   	int i;
> @@ -1947,7 +2138,8 @@ int main(int argc, char *argv[])
>   	bool idmapped_mounts_supported = false, test_btrfs = false,
>   	     test_core = false, test_fscaps_regression = false,
>   	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
> -	     test_setxattr_fix_705191b03d50 = false;
> +	     test_setxattr_fix_705191b03d50 = false,
> +	     test_setgid_create_umask = false;
>   
>   	init_vfstest_info(&info);
>   
> @@ -1989,6 +2181,9 @@ int main(int argc, char *argv[])
>   		case 'j':
>   			test_setxattr_fix_705191b03d50 = true;
>   			break;
> +		case 'u':
> +			test_setgid_create_umask = true;
> +			break;
>   		case 'h':
>   			/* fallthrough */
>   		default:
> @@ -2066,6 +2261,14 @@ int main(int argc, char *argv[])
>   	    !run_suite(&info, &s_setxattr_fix_705191b03d50))
>   		goto out;
>   
> +	if (test_setgid_create_umask) {
> +		if (!run_suite(&info, &s_setgid_create_umask))
> +			goto out;
> +
> +		if (!run_suite(&info, &s_setgid_create_umask_idmapped))
> +			goto out;
> +	}
> +
>   	fret = EXIT_SUCCESS;
>   
>   out:
> diff --git a/tests/generic/691 b/tests/generic/691
> new file mode 100755
> index 00000000..d4875854
> --- /dev/null
> +++ b/tests/generic/691
> @@ -0,0 +1,40 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2022 Fujitsu Limited. All Rights Reserved.
> +#
> +# FS QA Test No. 691
> +#
> +# Test that idmapped mounts setgid's behave correctly when using
> +# umask(S_IXGRP)
> +#
> +. ./common/preamble
> +_begin_fstest auto quick cap idmapped mount perms rw unlink
> +
> +# Import common functions.
> +. ./common/filter
> +
> +# real QA test starts here
> +
> +_supported_fs generic
> +_require_test
> +_require_scratch
> +
> +echo "Silence is golden"
> +
> +$here/src/vfs/vfstest --test-setgid-create-umask \
> +        --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
> +
> +_scratch_mkfs > "$seqres.full" 2>&1
> +export MOUNT_OPTIONS="-o noacl"
> +
> +# If filesystem supports noacl mount option, also test setgid bit whether
> +# was stripped correctly.
> +# noacl will earse acl flag in superblock, so kernel will use current_umask
> +# in vfs directly instead of calling posix_acl_create on underflying
> +# filesystem.
> +_try_scratch_mount >>$seqres.full 2>&1 && \
> +	$here/src/vfs/vfstest --test-setgid-create-umask \
> +        --device "$SCRATCH_DEV" --mount "$SCRATCH_MNT" --fstype "$FSTYP"
> +
> +status=0
> +exit
> diff --git a/tests/generic/691.out b/tests/generic/691.out
> new file mode 100644
> index 00000000..006aef43
> --- /dev/null
> +++ b/tests/generic/691.out
> @@ -0,0 +1,2 @@
> +QA output created by 691
> +Silence is golden

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v1 1/2] vfs: Add new setgid_create_umask test
  2022-07-25  7:55 ` xuyang2018.jy
@ 2022-07-25 14:20   ` Christian Brauner
  2022-07-26  8:20     ` xuyang2018.jy
  2022-07-26  9:31     ` [PATCH v2 " Yang Xu
  0 siblings, 2 replies; 15+ messages in thread
From: Christian Brauner @ 2022-07-25 14:20 UTC (permalink / raw)
  To: xuyang2018.jy; +Cc: fstests

On Mon, Jul 25, 2022 at 07:55:23AM +0000, xuyang2018.jy@fujitsu.com wrote:
> 
> on  2022/05/21 0:04, Yang Xu wrote:
> > The current_umask() is stripped from the mode directly in the vfs if the
> > filesystem either doesn't support acls or the filesystem has been
> > mounted without posic acl support.
> > 
> > If the filesystem does support acls then current_umask() stripping is
> > deferred to posix_acl_create(). So when the filesystem calls
> > posix_acl_create() and there are no acls set or not supported then
> > current_umask() will be stripped.
> > 
> > This patch is also designed to test kernel patchset behaviour
> > "move S_ISGID stripping into the vfs"
> > https://patchwork.kernel.org/project/linux-fsdevel/list/?series=635692
> 
> The kernel patch has been merged into linux-next branch[1].
> 
> Does anyone review this fstests patch or give some comment?
> 
> I plan to remove setgid_create_umask_idmapped_in_userns and 
> setgid_create_umask_idmapped cases because they doesn't trigger bug.
> 
> Just treat it  as a kernel regression test ie 31c01ce18 ("generic: add 
> test for tmpfs POSIX ACLs") format.
> 
> CC Christian to confirm that whether need to test idmapped and 
> idmapped_in_userbs situation .

You've also sent a patch to LTP I saw which is good. I'm not completely
sure why you want to remove setgid_create_umask_idmapped{_in_userns}.
But as long as we have tests for setgid stripping on idmapped mounts I'm
fine with this.

Christian

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v1 1/2] vfs: Add new setgid_create_umask test
  2022-07-25 14:20   ` Christian Brauner
@ 2022-07-26  8:20     ` xuyang2018.jy
  2022-07-26  9:31     ` [PATCH v2 " Yang Xu
  1 sibling, 0 replies; 15+ messages in thread
From: xuyang2018.jy @ 2022-07-26  8:20 UTC (permalink / raw)
  To: Christian Brauner; +Cc: fstests

on 2022/07/25 22:20, Christian Brauner wrote:
> On Mon, Jul 25, 2022 at 07:55:23AM +0000, xuyang2018.jy@fujitsu.com wrote:
>>
>> on  2022/05/21 0:04, Yang Xu wrote:
>>> The current_umask() is stripped from the mode directly in the vfs if the
>>> filesystem either doesn't support acls or the filesystem has been
>>> mounted without posic acl support.
>>>
>>> If the filesystem does support acls then current_umask() stripping is
>>> deferred to posix_acl_create(). So when the filesystem calls
>>> posix_acl_create() and there are no acls set or not supported then
>>> current_umask() will be stripped.
>>>
>>> This patch is also designed to test kernel patchset behaviour
>>> "move S_ISGID stripping into the vfs"
>>> https://patchwork.kernel.org/project/linux-fsdevel/list/?series=635692
>>
>> The kernel patch has been merged into linux-next branch[1].
>>
>> Does anyone review this fstests patch or give some comment?
>>
>> I plan to remove setgid_create_umask_idmapped_in_userns and
>> setgid_create_umask_idmapped cases because they doesn't trigger bug.
>>
>> Just treat it  as a kernel regression test ie 31c01ce18 ("generic: add
>> test for tmpfs POSIX ACLs") format.
>>
>> CC Christian to confirm that whether need to test idmapped and
>> idmapped_in_userbs situation .
> 
> You've also sent a patch to LTP I saw which is good. I'm not completely
> sure why you want to remove setgid_create_umask_idmapped{_in_userns}.
> But as long as we have tests for setgid stripping on idmapped mounts I'm
> fine with this.

I think about it again today, maybe still have  some meaningful. I will 
keep it on v2. v2 only updates a small, include add kernel commit id and 
remove duplicated comment.

Best Regards
Yang Xu
> 
> Christian

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v2 1/2] vfs: Add new setgid_create_umask test
  2022-07-25 14:20   ` Christian Brauner
  2022-07-26  8:20     ` xuyang2018.jy
@ 2022-07-26  9:31     ` Yang Xu
  2022-07-26  9:31       ` [PATCH v2 2/2] vfs: Add new setgid_create_acl test Yang Xu
  2022-08-15 10:02       ` [PATCH v2 1/2] vfs: Add new setgid_create_umask test xuyang2018.jy
  1 sibling, 2 replies; 15+ messages in thread
From: Yang Xu @ 2022-07-26  9:31 UTC (permalink / raw)
  To: fstests; +Cc: brauner, Yang Xu

The current_umask() is stripped from the mode directly in the vfs if the
filesystem either doesn't support acls or the filesystem has been
mounted without posic acl support.

If the filesystem does support acls then current_umask() stripping is
deferred to posix_acl_create(). So when the filesystem calls
posix_acl_create() and there are no acls set or not supported then
current_umask() will be stripped.

This patch is also designed to test kernel patch behaviour
"move S_ISGID stripping into the vfs* helper"

Here we only use umask(S_IXGRP) to check S_ISGID stripping whether works
correctly and S_IXGRP mode still exists.

Also use noacl mount option to test another kernel bug
fs: Add missing umask strip in vfs_tmpfile

Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
---
1.add kernel commit id
2.use s_setgid_create_umask_idmapped_mounts instead of confused s_setgid_create_umask_idmapped
 src/vfs/idmapped-mounts.c | 429 ++++++++++++++++++++++++++++++++++++++
 src/vfs/idmapped-mounts.h |   1 +
 src/vfs/utils.c           |  14 ++
 src/vfs/utils.h           |   1 +
 src/vfs/vfstest.c         | 205 +++++++++++++++++-
 tests/generic/998         |  46 ++++
 tests/generic/998.out     |   2 +
 7 files changed, 697 insertions(+), 1 deletion(-)
 create mode 100755 tests/generic/998
 create mode 100644 tests/generic/998.out

diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
index 63297d5f..f63ac44b 100644
--- a/src/vfs/idmapped-mounts.c
+++ b/src/vfs/idmapped-mounts.c
@@ -7664,6 +7664,425 @@ out:
 	return fret;
 }
 
+static int setgid_create_umask_idmapped(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF, open_tree_fd = -EBADF;
+	struct mount_attr attr = {
+		.attr_set = MOUNT_ATTR_IDMAP,
+	};
+	pid_t pid;
+	int tmpfile_fd = -EBADF;
+	bool supported = false;
+	char path[PATH_MAX];
+	mode_t mode;
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the sid bits got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	/* Changing mount properties on a detached mount. */
+	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
+	if (attr.userns_fd < 0) {
+		log_stderr("failure: get_userns_fd");
+		goto out;
+	}
+
+	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+				     AT_EMPTY_PATH |
+				     AT_NO_AUTOMOUNT |
+				     AT_SYMLINK_NOFOLLOW |
+				     OPEN_TREE_CLOEXEC |
+				     OPEN_TREE_CLONE);
+	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;
+	}
+
+	supported = openat_tmpfile_supported(open_tree_fd);
+
+	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
+	 * whether has group execute or search permission.
+	 */
+	umask(S_IXGRP);
+	mode = umask(S_IXGRP);
+	if (!(mode & S_IXGRP))
+		die("failure: umask");
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		if (!switch_ids(10000, 11000))
+			die("failure: switch fsids");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(attr.userns_fd);
+	safe_close(file1_fd);
+	safe_close(open_tree_fd);
+
+	return fret;
+}
+
+static int setgid_create_umask_idmapped_in_userns(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF, open_tree_fd = -EBADF;
+	struct mount_attr attr = {
+		.attr_set = MOUNT_ATTR_IDMAP,
+	};
+	pid_t pid;
+	int tmpfile_fd = -EBADF;
+	bool supported = false;
+	char path[PATH_MAX];
+	mode_t mode;
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the sid bits got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	/* Changing mount properties on a detached mount. */
+	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
+	if (attr.userns_fd < 0) {
+		log_stderr("failure: get_userns_fd");
+		goto out;
+	}
+
+	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+				     AT_EMPTY_PATH |
+				     AT_NO_AUTOMOUNT |
+				     AT_SYMLINK_NOFOLLOW |
+				     OPEN_TREE_CLOEXEC |
+				     OPEN_TREE_CLONE);
+	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;
+	}
+
+	supported = openat_tmpfile_supported(open_tree_fd);
+
+	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
+	 * whether has group execute or search permission.
+	 */
+	umask(S_IXGRP);
+	mode = umask(S_IXGRP);
+	if (!(mode & S_IXGRP))
+		die("failure: umask");
+
+	/*
+	 * Below we verify that setgid inheritance for a newly created file or
+	 * directory works correctly. As part of this we need to verify that
+	 * newly created files or directories inherit their gid from their
+	 * parent directory. So we change the parent directorie's gid to 1000
+	 * and create a file with fs{g,u}id 0 and verify that the newly created
+	 * file and directory inherit gid 1000, not 0.
+	 */
+	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
+		log_stderr("failure: fchownat");
+		goto out;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		if (!caps_supported()) {
+			log_debug("skip: capability library not installed");
+			exit(EXIT_SUCCESS);
+		}
+
+		if (!switch_userns(attr.userns_fd, 0, 0, false))
+			die("failure: switch_userns");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(attr.userns_fd);
+	safe_close(file1_fd);
+	safe_close(open_tree_fd);
+
+	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",                                                          },
@@ -7745,3 +8164,13 @@ const struct test_suite s_setxattr_fix_705191b03d50 = {
 	.tests = t_setxattr_fix_705191b03d50,
 	.nr_tests = ARRAY_SIZE(t_setxattr_fix_705191b03d50),
 };
+
+static const struct test_struct t_setgid_create_umask_idmapped_mounts[] = {
+	{ setgid_create_umask_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount",		},
+	{ setgid_create_umask_idmapped_in_userns,			T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount inside userns",	},
+};
+
+const struct test_suite s_setgid_create_umask_idmapped_mounts = {
+	.tests = t_setgid_create_umask_idmapped_mounts,
+	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped_mounts),
+};
diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
index ff21ea2c..a9fb31ea 100644
--- a/src/vfs/idmapped-mounts.h
+++ b/src/vfs/idmapped-mounts.h
@@ -14,5 +14,6 @@ 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;
 extern const struct test_suite s_setxattr_fix_705191b03d50;
+extern const struct test_suite s_setgid_create_umask_idmapped_mounts;
 
 #endif /* __IDMAPPED_MOUNTS_H */
diff --git a/src/vfs/utils.c b/src/vfs/utils.c
index 1388edda..6db7a11d 100644
--- a/src/vfs/utils.c
+++ b/src/vfs/utils.c
@@ -809,6 +809,20 @@ bool is_sticky(int dfd, const char *path, int flags)
 	return (st.st_mode & S_ISVTX) > 0;
 }
 
+/*is_ixgrp - check whether file or directory is S_IXGRP */
+bool is_ixgrp(int dfd, const char *path, int flags)
+{
+	int ret;
+	struct stat st;
+
+	ret = fstatat(dfd, path, &st, flags);
+	if (ret < 0)
+		return false;
+
+	errno = 0; /* Don't report misleading errno. */
+	return (st.st_mode & S_IXGRP);
+}
+
 bool switch_resids(uid_t uid, gid_t gid)
 {
 	if (setresgid(gid, gid, gid))
diff --git a/src/vfs/utils.h b/src/vfs/utils.h
index 7fb702fd..c0dbe370 100644
--- a/src/vfs/utils.h
+++ b/src/vfs/utils.h
@@ -368,6 +368,7 @@ extern bool expected_file_size(int dfd, const char *path, int flags,
 extern bool is_setid(int dfd, const char *path, int flags);
 extern bool is_setgid(int dfd, const char *path, int flags);
 extern bool is_sticky(int dfd, const char *path, int flags);
+extern bool is_ixgrp(int dfd, const char *path, int flags);
 extern bool openat_tmpfile_supported(int dirfd);
 
 #endif /* __IDMAP_UTILS_H */
diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
index 29ac0bec..e928a1f5 100644
--- a/src/vfs/vfstest.c
+++ b/src/vfs/vfstest.c
@@ -1733,6 +1733,186 @@ out:
 	return fret;
 }
 
+/* The current_umask() is stripped from the mode directly in the vfs if the
+ * filesystem either doesn't support acls or the filesystem has been
+ * mounted without posic acl support.
+ *
+ * If the filesystem does support acls then current_umask() stripping is
+ * deferred to posix_acl_create(). So when the filesystem calls
+ * posix_acl_create() and there are no acls set or not supported then
+ * current_umask() will be stripped.
+ *
+ * Use umask(S_IXGRP) to check whether inode strip S_ISGID works correctly.
+ *
+ * test for commit ac6800e279a2 ("fs: Add missing umask strip in vfs_tmpfile")
+ * and 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers").
+ */
+
+static int setgid_create_umask(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF;
+	int tmpfile_fd = -EBADF;
+	pid_t pid;
+	bool supported = false;
+	mode_t mode;
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the setgid bit got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	supported = openat_tmpfile_supported(info->t_dir1_fd);
+
+	umask(S_IXGRP);
+	mode = umask(S_IXGRP);
+	if (!(mode & S_IXGRP))
+		die("failure: umask");
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		if (!switch_ids(0, 10000))
+			die("failure: switch_ids");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a character device via mknodat() vfs_mknod */
+		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (unlinkat(info->t_dir1_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (unlinkat(info->t_dir1_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(file1_fd);
+	return fret;
+}
+
 static int setattr_truncate(const struct vfstest_info *info)
 {
 	int fret = -1;
@@ -1807,6 +1987,7 @@ static void usage(void)
 	fprintf(stderr, "--test-btrfs                        Run btrfs specific idmapped mount testsuite\n");
 	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
 	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
+	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
 
 	_exit(EXIT_SUCCESS);
 }
@@ -1825,6 +2006,7 @@ static const struct option longopts[] = {
 	{"test-btrfs",				no_argument,		0,	'b'},
 	{"test-setattr-fix-968219708108",	no_argument,		0,	'i'},
 	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
+	{"test-setgid-create-umask",		no_argument,		0,	'u'},
 	{NULL,					0,			0,	  0},
 };
 
@@ -1850,6 +2032,15 @@ static const struct test_suite s_basic = {
 	.nr_tests = ARRAY_SIZE(t_basic),
 };
 
+static const struct test_struct t_setgid_create_umask[] = {
+	{ setgid_create_umask,						0,			"create operations in directories with setgid bit set under umask",				},
+};
+
+static const struct test_suite s_setgid_create_umask = {
+	.tests = t_setgid_create_umask,
+	.nr_tests = ARRAY_SIZE(t_setgid_create_umask),
+};
+
 static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size)
 {
 	int i;
@@ -1947,7 +2138,8 @@ int main(int argc, char *argv[])
 	bool idmapped_mounts_supported = false, test_btrfs = false,
 	     test_core = false, test_fscaps_regression = false,
 	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
-	     test_setxattr_fix_705191b03d50 = false;
+	     test_setxattr_fix_705191b03d50 = false,
+	     test_setgid_create_umask = false;
 
 	init_vfstest_info(&info);
 
@@ -1989,6 +2181,9 @@ int main(int argc, char *argv[])
 		case 'j':
 			test_setxattr_fix_705191b03d50 = true;
 			break;
+		case 'u':
+			test_setgid_create_umask = true;
+			break;
 		case 'h':
 			/* fallthrough */
 		default:
@@ -2066,6 +2261,14 @@ int main(int argc, char *argv[])
 	    !run_suite(&info, &s_setxattr_fix_705191b03d50))
 		goto out;
 
+	if (test_setgid_create_umask) {
+		if (!run_suite(&info, &s_setgid_create_umask))
+			goto out;
+
+		if (!run_suite(&info, &s_setgid_create_umask_idmapped_mounts))
+			goto out;
+	}
+
 	fret = EXIT_SUCCESS;
 
 out:
diff --git a/tests/generic/998 b/tests/generic/998
new file mode 100755
index 00000000..5f736b6d
--- /dev/null
+++ b/tests/generic/998
@@ -0,0 +1,46 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2022 Fujitsu Limited. All Rights Reserved.
+#
+# FS QA Test No. 998
+#
+# Test S_ISGID stripping whether works correctly when call process
+# uses umask(S_IXGRP).
+#
+# It is also a regression test for
+# commit ac6800e279a2 ("fs: Add missing umask strip in vfs_tmpfile")
+# commit 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers")
+
+. ./common/preamble
+_begin_fstest auto quick cap idmapped mount perms rw unlink
+
+# Import common functions.
+. ./common/filter
+
+# real QA test starts here
+
+_supported_fs generic
+_require_test
+_require_scratch
+_fixed_by_kernel_commit ac6800e279a2 \
+	"fs: Add missing umask strip in vfs_tmpfile" \
+1639a49ccdce "fs: move S_ISGID stripping into the vfs_*() helpers"
+
+_scratch_mkfs >$seqres.full 2>&1
+
+$here/src/vfs/vfstest --test-setgid-create-umask \
+        --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
+
+export MOUNT_OPTIONS="-o noacl"
+
+# Also test S_ISGID stripping whether works correctly on underflying filesystem
+# that supports noacl feature.
+# noacl will earse acl flag in superblock, so kernel will use current_umask in
+# vfs directly instead of calling posix_acl_create on underflying filesystem.
+_try_scratch_mount >>$seqres.full 2>&1 && \
+	$here/src/vfs/vfstest --test-setgid-create-umask \
+        --device "$SCRATCH_DEV" --mount "$SCRATCH_MNT" --fstype "$FSTYP"
+
+echo "Silence is golden"
+status=0
+exit
diff --git a/tests/generic/998.out b/tests/generic/998.out
new file mode 100644
index 00000000..d2679ae0
--- /dev/null
+++ b/tests/generic/998.out
@@ -0,0 +1,2 @@
+QA output created by 998
+Silence is golden
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 2/2] vfs: Add new setgid_create_acl test
  2022-07-26  9:31     ` [PATCH v2 " Yang Xu
@ 2022-07-26  9:31       ` Yang Xu
  2022-08-29 13:20         ` Christian Brauner
  2022-08-29 13:52         ` Christian Brauner
  2022-08-15 10:02       ` [PATCH v2 1/2] vfs: Add new setgid_create_umask test xuyang2018.jy
  1 sibling, 2 replies; 15+ messages in thread
From: Yang Xu @ 2022-07-26  9:31 UTC (permalink / raw)
  To: fstests; +Cc: brauner, Yang Xu

The current_umask() is stripped from the mode directly in the vfs if the
filesystem either doesn't support acls or the filesystem has been
mounted without posic acl support.

If the filesystem does support acls then current_umask() stripping is
deferred to posix_acl_create(). So when the filesystem calls
posix_acl_create() and there are no acls set or not supported then
current_umask() will be stripped.

If the parent directory has a default acl then permissions are based off
of that and current_umask() is ignored. Specifically, if the ACL has an
ACL_MASK entry, the group permissions correspond to the permissions of
the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
group permissions correspond to the permissions of the ACL_GROUP_OBJ
entry.

Here we only use setfacl to set default acl or add ACL_MASK to check
whether inode strip  S_ISGID works correctly.

Like umask test, this patch is also designed to test kernel patchset behaviour
"move S_ISGID stripping into the vfs* helper"

Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
---
1.add kernel commit id
2.move umask into parent process intead of child process
3.remove duplicated comment for ACL_GROUP_OBJ and ACL_MASK because
we have mentioned it before the function
 src/vfs/idmapped-mounts.c | 704 ++++++++++++++++++++++++++++++++++++++
 src/vfs/idmapped-mounts.h |   1 +
 src/vfs/vfstest.c         | 345 ++++++++++++++++++-
 tests/generic/999         |  33 ++
 tests/generic/999.out     |   2 +
 5 files changed, 1084 insertions(+), 1 deletion(-)
 create mode 100755 tests/generic/999
 create mode 100644 tests/generic/999.out

diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
index f63ac44b..f934cf4a 100644
--- a/src/vfs/idmapped-mounts.c
+++ b/src/vfs/idmapped-mounts.c
@@ -8083,6 +8083,700 @@ out:
 	return fret;
 }
 
+static int setgid_create_acl_idmapped(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF, open_tree_fd = -EBADF;
+	struct mount_attr attr = {
+		.attr_set = MOUNT_ATTR_IDMAP,
+	};
+	pid_t pid;
+	int tmpfile_fd = -EBADF;
+	bool supported = false;
+	char path[PATH_MAX];
+	mode_t mode;
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the sid bits got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	/* Changing mount properties on a detached mount. */
+	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
+	if (attr.userns_fd < 0) {
+		log_stderr("failure: get_userns_fd");
+		goto out;
+	}
+
+	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+				     AT_EMPTY_PATH |
+				     AT_NO_AUTOMOUNT |
+				     AT_SYMLINK_NOFOLLOW |
+				     OPEN_TREE_CLOEXEC |
+				     OPEN_TREE_CLONE);
+	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;
+	}
+
+	supported = openat_tmpfile_supported(open_tree_fd);
+
+	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
+	 * whether has group execute or search permission.
+	 */
+	umask(S_IXGRP);
+	mode = umask(S_IXGRP);
+	if (!(mode & S_IXGRP))
+		die("failure: umask");
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* The group permissions correspond to the permissions of the
+		 * ACL_MASK entry.
+		 */
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!switch_ids(10000, 11000))
+			die("failure: switch fsids");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* The group permissions correspond to the permissions of the
+		 * ACL_GROUP_OBJ entry.
+		 */
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!switch_ids(10000, 11000))
+			die("failure: switch fsids");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 10000, not by gid 11000.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (!is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(attr.userns_fd);
+	safe_close(file1_fd);
+	safe_close(open_tree_fd);
+
+	return fret;
+}
+
+static int setgid_create_acl_idmapped_in_userns(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF, open_tree_fd = -EBADF;
+	struct mount_attr attr = {
+		.attr_set = MOUNT_ATTR_IDMAP,
+	};
+	pid_t pid;
+	int tmpfile_fd = -EBADF;
+	bool supported = false;
+	char path[PATH_MAX];
+	mode_t mode;
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the sid bits got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	/* Changing mount properties on a detached mount. */
+	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
+	if (attr.userns_fd < 0) {
+		log_stderr("failure: get_userns_fd");
+		goto out;
+	}
+
+	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+				     AT_EMPTY_PATH |
+				     AT_NO_AUTOMOUNT |
+				     AT_SYMLINK_NOFOLLOW |
+				     OPEN_TREE_CLOEXEC |
+				     OPEN_TREE_CLONE);
+	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;
+	}
+
+	supported = openat_tmpfile_supported(open_tree_fd);
+
+	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
+	 * whether has group execute or search permission.
+	 */
+	umask(S_IXGRP);
+	mode = umask(S_IXGRP);
+	if (!(mode & S_IXGRP))
+		die("failure: umask");
+
+	/*
+	 * Below we verify that setgid inheritance for a newly created file or
+	 * directory works correctly. As part of this we need to verify that
+	 * newly created files or directories inherit their gid from their
+	 * parent directory. So we change the parent directorie's gid to 1000
+	 * and create a file with fs{g,u}id 0 and verify that the newly created
+	 * file and directory inherit gid 1000, not 0.
+	 */
+	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
+		log_stderr("failure: fchownat");
+		goto out;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* The group permissions correspond to the permissions of the
+		 * ACL_MASK entry.
+		 */
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!caps_supported()) {
+			log_debug("skip: capability library not installed");
+			exit(EXIT_SUCCESS);
+		}
+
+		if (!switch_userns(attr.userns_fd, 0, 0, false))
+			die("failure: switch_userns");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* The group permissions correspond to the permissions of the
+		 * ACL_GROUP_OBJ entry.
+		 */
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!caps_supported()) {
+			log_debug("skip: capability library not installed");
+			exit(EXIT_SUCCESS);
+		}
+
+		if (!switch_userns(attr.userns_fd, 0, 0, false))
+			die("failure: switch_userns");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(open_tree_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(open_tree_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(open_tree_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(open_tree_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, FILE2, 0))
+			 die("failure: is_ixgrp");
+		/* create a whiteout device via mknodat() vfs_mknod */
+		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(open_tree_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(open_tree_fd, CHRDEV1, 0))
+			 die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 1000, not by gid 0.
+		 */
+		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
+			die("failure: check ownership");
+
+		if (unlinkat(open_tree_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(open_tree_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
+			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(open_tree_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (!is_ixgrp(open_tree_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
+				die("failure: check ownership");
+			if (unlinkat(open_tree_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(attr.userns_fd);
+	safe_close(file1_fd);
+	safe_close(open_tree_fd);
+
+	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",                                                          },
@@ -8174,3 +8868,13 @@ const struct test_suite s_setgid_create_umask_idmapped_mounts = {
 	.tests = t_setgid_create_umask_idmapped_mounts,
 	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped_mounts),
 };
+
+static const struct test_struct t_setgid_create_acl_idmapped_mounts[] = {
+	{ setgid_create_acl_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using acl in directories with setgid bit set on idmapped mount",                },
+	{ setgid_create_acl_idmapped_in_userns,				T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using acl in directories with setgid bit set on idmapped mount inside userns",  },
+};
+
+const struct test_suite s_setgid_create_acl_idmapped_mounts = {
+	.tests = t_setgid_create_acl_idmapped_mounts,
+	.nr_tests = ARRAY_SIZE(t_setgid_create_acl_idmapped_mounts),
+};
diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
index a9fb31ea..3b0f0825 100644
--- a/src/vfs/idmapped-mounts.h
+++ b/src/vfs/idmapped-mounts.h
@@ -15,5 +15,6 @@ extern const struct test_suite s_nested_userns;
 extern const struct test_suite s_setattr_fix_968219708108;
 extern const struct test_suite s_setxattr_fix_705191b03d50;
 extern const struct test_suite s_setgid_create_umask_idmapped_mounts;
+extern const struct test_suite s_setgid_create_acl_idmapped_mounts;
 
 #endif /* __IDMAPPED_MOUNTS_H */
diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
index e928a1f5..f7639de3 100644
--- a/src/vfs/vfstest.c
+++ b/src/vfs/vfstest.c
@@ -27,6 +27,8 @@
 #include "missing.h"
 #include "utils.h"
 
+static char t_buf[PATH_MAX];
+
 static void init_vfstest_info(struct vfstest_info *info)
 {
 	info->t_overflowuid		= 65534;
@@ -1913,6 +1915,325 @@ out:
 	return fret;
 }
 
+/*
+ * If the parent directory has a default acl then permissions are based off
+ * of that and current_umask() is ignored. Specifically, if the ACL has an
+ * ACL_MASK entry, the group permissions correspond to the permissions of
+ * the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
+ * group permissions correspond to the permissions of the ACL_GROUP_OBJ
+ * entry.
+ *
+ * Use setfacl to check whether inode strip S_ISGID works correctly.
+ *
+ * Test for commit
+ * 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers").
+ */
+
+static int setgid_create_acl(const struct vfstest_info *info)
+{
+	int fret = -1;
+	int file1_fd = -EBADF;
+	int tmpfile_fd = -EBADF;
+	pid_t pid;
+	bool supported = false;
+	mode_t mode;
+
+	if (!caps_supported())
+		return 0;
+
+	if (fchmod(info->t_dir1_fd, S_IRUSR |
+			      S_IWUSR |
+			      S_IRGRP |
+			      S_IWGRP |
+			      S_IROTH |
+			      S_IWOTH |
+			      S_IXUSR |
+			      S_IXGRP |
+			      S_IXOTH |
+			      S_ISGID), 0) {
+		log_stderr("failure: fchmod");
+		goto out;
+	}
+
+	/* Verify that the setgid bit got raised. */
+	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
+		log_stderr("failure: is_setgid");
+		goto out;
+	}
+
+	supported = openat_tmpfile_supported(info->t_dir1_fd);
+
+	umask(S_IXGRP);
+	mode = umask(S_IXGRP);
+	if (!(mode & S_IXGRP))
+		die("failure: umask");
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* The group permissions correspond to the permissions of the
+		 * ACL_MASK entry.
+		 */
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!switch_ids(0, 10000))
+			die("failure: switch_ids");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a character device via mknodat() vfs_mknod */
+		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (unlinkat(info->t_dir1_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (is_ixgrp(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(info->t_dir1_fd, FILE3, 0, 0, 0))
+				die("failure: check ownership");
+			if (unlinkat(info->t_dir1_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	pid = fork();
+	if (pid < 0) {
+		log_stderr("failure: fork");
+		goto out;
+	}
+	if (pid == 0) {
+		/* The group permissions correspond to the permissions of the
+		 * ACL_GROUP_OBJ entry.
+		 */
+		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1);
+		if (system(t_buf))
+			die("failure: system");
+
+		if (!switch_ids(0, 10000))
+			die("failure: switch_ids");
+
+		if (!caps_down_fsetid())
+			die("failure: caps_down_fsetid");
+
+		/* create regular file via open() */
+		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
+		if (file1_fd < 0)
+			die("failure: create");
+
+		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
+		 * bit needs to be stripped.
+		 */
+		if (is_setgid(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(info->t_dir1_fd, FILE1, 0))
+			die("failure: is_ixgrp");
+
+		/* create directory */
+		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
+			die("failure: create");
+
+		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
+			/* We're not in_group_p(). */
+			if (is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		} else {
+			/* Directories always inherit the setgid bit. */
+			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
+				die("failure: is_setgid");
+		}
+
+		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
+			die("failure: is_ixgrp");
+
+		/* create a special file via mknodat() vfs_create */
+		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(info->t_dir1_fd, FILE2, 0))
+			die("failure: is_ixgrp");
+
+		/* create a character device via mknodat() vfs_mknod */
+		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
+			die("failure: mknodat");
+
+		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_setgid");
+
+		if (!is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: is_ixgrp");
+
+		/*
+		 * In setgid directories newly created files always inherit the
+		 * gid from the parent directory. Verify that the file is owned
+		 * by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
+			die("failure: check ownership");
+
+		/*
+		 * In setgid directories newly created directories always
+		 * inherit the gid from the parent directory. Verify that the
+		 * directory is owned by gid 0, not by gid 10000.
+		 */
+		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
+			die("failure: check ownership");
+
+		if (unlinkat(info->t_dir1_fd, FILE1, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, FILE2, 0))
+			die("failure: delete");
+
+		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
+			die("failure: delete");
+
+		/* create tmpfile via filesystem tmpfile api */
+		if (supported) {
+			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
+			if (tmpfile_fd < 0)
+				die("failure: create");
+			/* link the temporary file into the filesystem, making it permanent */
+			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
+				die("failure: linkat");
+			if (close(tmpfile_fd))
+				die("failure: close");
+			if (is_setgid(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_setgid");
+			if (!is_ixgrp(info->t_dir1_fd, FILE3, 0))
+				die("failure: is_ixgrp");
+			if (!expected_uid_gid(info->t_dir1_fd, FILE3, 0, 0, 0))
+				die("failure: check ownership");
+			if (unlinkat(info->t_dir1_fd, FILE3, 0))
+				die("failure: delete");
+		}
+
+		exit(EXIT_SUCCESS);
+	}
+	if (wait_for_pid(pid))
+		goto out;
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	safe_close(file1_fd);
+
+	return fret;
+}
+
 static int setattr_truncate(const struct vfstest_info *info)
 {
 	int fret = -1;
@@ -1988,6 +2309,7 @@ static void usage(void)
 	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
 	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
 	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
+	fprintf(stderr, "--test-setgid-create-acl            Run setgid with acl tests\n");
 
 	_exit(EXIT_SUCCESS);
 }
@@ -2007,6 +2329,7 @@ static const struct option longopts[] = {
 	{"test-setattr-fix-968219708108",	no_argument,		0,	'i'},
 	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
 	{"test-setgid-create-umask",		no_argument,		0,	'u'},
+	{"test-setgid-create-acl",		no_argument,		0,	'l'},
 	{NULL,					0,			0,	  0},
 };
 
@@ -2041,6 +2364,15 @@ static const struct test_suite s_setgid_create_umask = {
 	.nr_tests = ARRAY_SIZE(t_setgid_create_umask),
 };
 
+static const struct test_struct t_setgid_create_acl[] = {
+	{ setgid_create_acl,						0,			"create operations in directories with setgid bit set under posix acl",				},
+};
+
+static const struct test_suite s_setgid_create_acl = {
+	.tests = t_setgid_create_acl,
+	.nr_tests = ARRAY_SIZE(t_setgid_create_acl),
+};
+
 static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size)
 {
 	int i;
@@ -2139,7 +2471,7 @@ int main(int argc, char *argv[])
 	     test_core = false, test_fscaps_regression = false,
 	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
 	     test_setxattr_fix_705191b03d50 = false,
-	     test_setgid_create_umask = false;
+	     test_setgid_create_umask = false, test_setgid_create_acl = false;
 
 	init_vfstest_info(&info);
 
@@ -2184,6 +2516,9 @@ int main(int argc, char *argv[])
 		case 'u':
 			test_setgid_create_umask = true;
 			break;
+		case 'l':
+			test_setgid_create_acl = true;
+			break;
 		case 'h':
 			/* fallthrough */
 		default:
@@ -2269,6 +2604,14 @@ int main(int argc, char *argv[])
 			goto out;
 	}
 
+	if (test_setgid_create_acl) {
+		if (!run_suite(&info, &s_setgid_create_acl))
+			goto out;
+
+		if (!run_suite(&info, &s_setgid_create_acl_idmapped_mounts))
+			goto out;
+	}
+
 	fret = EXIT_SUCCESS;
 
 out:
diff --git a/tests/generic/999 b/tests/generic/999
new file mode 100755
index 00000000..0c73e90e
--- /dev/null
+++ b/tests/generic/999
@@ -0,0 +1,33 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2022 Fujitsu Limited. All Rights Reserved.
+#
+# FS QA Test No. 999
+#
+# Test S_ISGID stripping whether works correctly when call process
+# uses posix acl.
+#
+# It is also a regression test for
+# commit 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers")
+
+. ./common/preamble
+_begin_fstest auto quick cap acl idmapped mount perms rw unlink
+
+# Import common functions.
+. ./common/filter
+. ./common/attr
+
+# real QA test starts here
+
+_supported_fs generic
+_require_test
+_require_acls
+_fixed_by_kernel_commit 1639a49ccdce \
+	"fs: move S_ISGID stripping into the vfs_*() helpers"
+
+$here/src/vfs/vfstest --test-setgid-create-acl \
+        --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
+
+echo "Silence is golden"
+status=0
+exit
diff --git a/tests/generic/999.out b/tests/generic/999.out
new file mode 100644
index 00000000..3b276ca8
--- /dev/null
+++ b/tests/generic/999.out
@@ -0,0 +1,2 @@
+QA output created by 999
+Silence is golden
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 1/2] vfs: Add new setgid_create_umask test
  2022-07-26  9:31     ` [PATCH v2 " Yang Xu
  2022-07-26  9:31       ` [PATCH v2 2/2] vfs: Add new setgid_create_acl test Yang Xu
@ 2022-08-15 10:02       ` xuyang2018.jy
  2022-08-29  1:14         ` xuyang2018.jy
  1 sibling, 1 reply; 15+ messages in thread
From: xuyang2018.jy @ 2022-08-15 10:02 UTC (permalink / raw)
  To: fstests; +Cc: brauner



on 2022/07/26 17:31, Yang Xu wrote:

> The current_umask() is stripped from the mode directly in the vfs if the
> filesystem either doesn't support acls or the filesystem has been
> mounted without posic acl support.
> 
> If the filesystem does support acls then current_umask() stripping is
> deferred to posix_acl_create(). So when the filesystem calls
> posix_acl_create() and there are no acls set or not supported then
> current_umask() will be stripped.
> 
> This patch is also designed to test kernel patch behaviour
> "move S_ISGID stripping into the vfs* helper"
> 
The kernel patchset[1] has been merged into 6.0-rc1 kernel, I guess we 
can review this patch.

[1]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1639a49ccdce58ea248841ed9b23babcce6dbb0b
Best Regards
Yang Xu
> Here we only use umask(S_IXGRP) to check S_ISGID stripping whether works
> correctly and S_IXGRP mode still exists.
> 
> Also use noacl mount option to test another kernel bug
> fs: Add missing umask strip in vfs_tmpfile
> 
> Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
> ---
> 1.add kernel commit id
> 2.use s_setgid_create_umask_idmapped_mounts instead of confused s_setgid_create_umask_idmapped
>   src/vfs/idmapped-mounts.c | 429 ++++++++++++++++++++++++++++++++++++++
>   src/vfs/idmapped-mounts.h |   1 +
>   src/vfs/utils.c           |  14 ++
>   src/vfs/utils.h           |   1 +
>   src/vfs/vfstest.c         | 205 +++++++++++++++++-
>   tests/generic/998         |  46 ++++
>   tests/generic/998.out     |   2 +
>   7 files changed, 697 insertions(+), 1 deletion(-)
>   create mode 100755 tests/generic/998
>   create mode 100644 tests/generic/998.out
> 
> diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
> index 63297d5f..f63ac44b 100644
> --- a/src/vfs/idmapped-mounts.c
> +++ b/src/vfs/idmapped-mounts.c
> @@ -7664,6 +7664,425 @@ out:
>   	return fret;
>   }
>   
> +static int setgid_create_umask_idmapped(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
> +	struct mount_attr attr = {
> +		.attr_set = MOUNT_ATTR_IDMAP,
> +	};
> +	pid_t pid;
> +	int tmpfile_fd = -EBADF;
> +	bool supported = false;
> +	char path[PATH_MAX];
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the sid bits got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	/* Changing mount properties on a detached mount. */
> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
> +	if (attr.userns_fd < 0) {
> +		log_stderr("failure: get_userns_fd");
> +		goto out;
> +	}
> +
> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
> +				     AT_EMPTY_PATH |
> +				     AT_NO_AUTOMOUNT |
> +				     AT_SYMLINK_NOFOLLOW |
> +				     OPEN_TREE_CLOEXEC |
> +				     OPEN_TREE_CLONE);
> +	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;
> +	}
> +
> +	supported = openat_tmpfile_supported(open_tree_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		if (!switch_ids(10000, 11000))
> +			die("failure: switch fsids");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(open_tree_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create directory */
> +		if (mkdirat(open_tree_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a whiteout device via mknodat() vfs_mknod */
> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 10000, not by gid 11000.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 10000, not by gid 11000.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(open_tree_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(open_tree_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
> +				die("failure: check ownership");
> +			if (unlinkat(open_tree_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(attr.userns_fd);
> +	safe_close(file1_fd);
> +	safe_close(open_tree_fd);
> +
> +	return fret;
> +}
> +
> +static int setgid_create_umask_idmapped_in_userns(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
> +	struct mount_attr attr = {
> +		.attr_set = MOUNT_ATTR_IDMAP,
> +	};
> +	pid_t pid;
> +	int tmpfile_fd = -EBADF;
> +	bool supported = false;
> +	char path[PATH_MAX];
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the sid bits got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	/* Changing mount properties on a detached mount. */
> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
> +	if (attr.userns_fd < 0) {
> +		log_stderr("failure: get_userns_fd");
> +		goto out;
> +	}
> +
> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
> +				     AT_EMPTY_PATH |
> +				     AT_NO_AUTOMOUNT |
> +				     AT_SYMLINK_NOFOLLOW |
> +				     OPEN_TREE_CLOEXEC |
> +				     OPEN_TREE_CLONE);
> +	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;
> +	}
> +
> +	supported = openat_tmpfile_supported(open_tree_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +	/*
> +	 * Below we verify that setgid inheritance for a newly created file or
> +	 * directory works correctly. As part of this we need to verify that
> +	 * newly created files or directories inherit their gid from their
> +	 * parent directory. So we change the parent directorie's gid to 1000
> +	 * and create a file with fs{g,u}id 0 and verify that the newly created
> +	 * file and directory inherit gid 1000, not 0.
> +	 */
> +	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
> +		log_stderr("failure: fchownat");
> +		goto out;
> +	}
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		if (!caps_supported()) {
> +			log_debug("skip: capability library not installed");
> +			exit(EXIT_SUCCESS);
> +		}
> +
> +		if (!switch_userns(attr.userns_fd, 0, 0, false))
> +			die("failure: switch_userns");
> +
> +		if (!caps_down_fsetid())
> +			die("failure: caps_down_fsetid");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(open_tree_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create directory */
> +		if (mkdirat(open_tree_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a whiteout device via mknodat() vfs_mknod */
> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 1000, not by gid 0.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 1000, not by gid 0.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(open_tree_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(open_tree_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
> +				die("failure: check ownership");
> +			if (unlinkat(open_tree_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(attr.userns_fd);
> +	safe_close(file1_fd);
> +	safe_close(open_tree_fd);
> +
> +	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",                                                          },
> @@ -7745,3 +8164,13 @@ const struct test_suite s_setxattr_fix_705191b03d50 = {
>   	.tests = t_setxattr_fix_705191b03d50,
>   	.nr_tests = ARRAY_SIZE(t_setxattr_fix_705191b03d50),
>   };
> +
> +static const struct test_struct t_setgid_create_umask_idmapped_mounts[] = {
> +	{ setgid_create_umask_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount",		},
> +	{ setgid_create_umask_idmapped_in_userns,			T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount inside userns",	},
> +};
> +
> +const struct test_suite s_setgid_create_umask_idmapped_mounts = {
> +	.tests = t_setgid_create_umask_idmapped_mounts,
> +	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped_mounts),
> +};
> diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
> index ff21ea2c..a9fb31ea 100644
> --- a/src/vfs/idmapped-mounts.h
> +++ b/src/vfs/idmapped-mounts.h
> @@ -14,5 +14,6 @@ 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;
>   extern const struct test_suite s_setxattr_fix_705191b03d50;
> +extern const struct test_suite s_setgid_create_umask_idmapped_mounts;
>   
>   #endif /* __IDMAPPED_MOUNTS_H */
> diff --git a/src/vfs/utils.c b/src/vfs/utils.c
> index 1388edda..6db7a11d 100644
> --- a/src/vfs/utils.c
> +++ b/src/vfs/utils.c
> @@ -809,6 +809,20 @@ bool is_sticky(int dfd, const char *path, int flags)
>   	return (st.st_mode & S_ISVTX) > 0;
>   }
>   
> +/*is_ixgrp - check whether file or directory is S_IXGRP */
> +bool is_ixgrp(int dfd, const char *path, int flags)
> +{
> +	int ret;
> +	struct stat st;
> +
> +	ret = fstatat(dfd, path, &st, flags);
> +	if (ret < 0)
> +		return false;
> +
> +	errno = 0; /* Don't report misleading errno. */
> +	return (st.st_mode & S_IXGRP);
> +}
> +
>   bool switch_resids(uid_t uid, gid_t gid)
>   {
>   	if (setresgid(gid, gid, gid))
> diff --git a/src/vfs/utils.h b/src/vfs/utils.h
> index 7fb702fd..c0dbe370 100644
> --- a/src/vfs/utils.h
> +++ b/src/vfs/utils.h
> @@ -368,6 +368,7 @@ extern bool expected_file_size(int dfd, const char *path, int flags,
>   extern bool is_setid(int dfd, const char *path, int flags);
>   extern bool is_setgid(int dfd, const char *path, int flags);
>   extern bool is_sticky(int dfd, const char *path, int flags);
> +extern bool is_ixgrp(int dfd, const char *path, int flags);
>   extern bool openat_tmpfile_supported(int dirfd);
>   
>   #endif /* __IDMAP_UTILS_H */
> diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
> index 29ac0bec..e928a1f5 100644
> --- a/src/vfs/vfstest.c
> +++ b/src/vfs/vfstest.c
> @@ -1733,6 +1733,186 @@ out:
>   	return fret;
>   }
>   
> +/* The current_umask() is stripped from the mode directly in the vfs if the
> + * filesystem either doesn't support acls or the filesystem has been
> + * mounted without posic acl support.
> + *
> + * If the filesystem does support acls then current_umask() stripping is
> + * deferred to posix_acl_create(). So when the filesystem calls
> + * posix_acl_create() and there are no acls set or not supported then
> + * current_umask() will be stripped.
> + *
> + * Use umask(S_IXGRP) to check whether inode strip S_ISGID works correctly.
> + *
> + * test for commit ac6800e279a2 ("fs: Add missing umask strip in vfs_tmpfile")
> + * and 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers").
> + */
> +
> +static int setgid_create_umask(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF;
> +	int tmpfile_fd = -EBADF;
> +	pid_t pid;
> +	bool supported = false;
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the setgid bit got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	supported = openat_tmpfile_supported(info->t_dir1_fd);
> +
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		if (!switch_ids(0, 10000))
> +			die("failure: switch_ids");
> +
> +		if (!caps_down_fsetid())
> +			die("failure: caps_down_fsetid");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(info->t_dir1_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(info->t_dir1_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(info->t_dir1_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(info->t_dir1_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(info->t_dir1_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a character device via mknodat() vfs_mknod */
> +		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 0, not by gid 10000.
> +		 */
> +		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 0, not by gid 10000.
> +		 */
> +		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(info->t_dir1_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(info->t_dir1_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(info->t_dir1_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(info->t_dir1_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (unlinkat(info->t_dir1_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(file1_fd);
> +	return fret;
> +}
> +
>   static int setattr_truncate(const struct vfstest_info *info)
>   {
>   	int fret = -1;
> @@ -1807,6 +1987,7 @@ static void usage(void)
>   	fprintf(stderr, "--test-btrfs                        Run btrfs specific idmapped mount testsuite\n");
>   	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
>   	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
> +	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
>   
>   	_exit(EXIT_SUCCESS);
>   }
> @@ -1825,6 +2006,7 @@ static const struct option longopts[] = {
>   	{"test-btrfs",				no_argument,		0,	'b'},
>   	{"test-setattr-fix-968219708108",	no_argument,		0,	'i'},
>   	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
> +	{"test-setgid-create-umask",		no_argument,		0,	'u'},
>   	{NULL,					0,			0,	  0},
>   };
>   
> @@ -1850,6 +2032,15 @@ static const struct test_suite s_basic = {
>   	.nr_tests = ARRAY_SIZE(t_basic),
>   };
>   
> +static const struct test_struct t_setgid_create_umask[] = {
> +	{ setgid_create_umask,						0,			"create operations in directories with setgid bit set under umask",				},
> +};
> +
> +static const struct test_suite s_setgid_create_umask = {
> +	.tests = t_setgid_create_umask,
> +	.nr_tests = ARRAY_SIZE(t_setgid_create_umask),
> +};
> +
>   static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size)
>   {
>   	int i;
> @@ -1947,7 +2138,8 @@ int main(int argc, char *argv[])
>   	bool idmapped_mounts_supported = false, test_btrfs = false,
>   	     test_core = false, test_fscaps_regression = false,
>   	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
> -	     test_setxattr_fix_705191b03d50 = false;
> +	     test_setxattr_fix_705191b03d50 = false,
> +	     test_setgid_create_umask = false;
>   
>   	init_vfstest_info(&info);
>   
> @@ -1989,6 +2181,9 @@ int main(int argc, char *argv[])
>   		case 'j':
>   			test_setxattr_fix_705191b03d50 = true;
>   			break;
> +		case 'u':
> +			test_setgid_create_umask = true;
> +			break;
>   		case 'h':
>   			/* fallthrough */
>   		default:
> @@ -2066,6 +2261,14 @@ int main(int argc, char *argv[])
>   	    !run_suite(&info, &s_setxattr_fix_705191b03d50))
>   		goto out;
>   
> +	if (test_setgid_create_umask) {
> +		if (!run_suite(&info, &s_setgid_create_umask))
> +			goto out;
> +
> +		if (!run_suite(&info, &s_setgid_create_umask_idmapped_mounts))
> +			goto out;
> +	}
> +
>   	fret = EXIT_SUCCESS;
>   
>   out:
> diff --git a/tests/generic/998 b/tests/generic/998
> new file mode 100755
> index 00000000..5f736b6d
> --- /dev/null
> +++ b/tests/generic/998
> @@ -0,0 +1,46 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2022 Fujitsu Limited. All Rights Reserved.
> +#
> +# FS QA Test No. 998
> +#
> +# Test S_ISGID stripping whether works correctly when call process
> +# uses umask(S_IXGRP).
> +#
> +# It is also a regression test for
> +# commit ac6800e279a2 ("fs: Add missing umask strip in vfs_tmpfile")
> +# commit 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers")
> +
> +. ./common/preamble
> +_begin_fstest auto quick cap idmapped mount perms rw unlink
> +
> +# Import common functions.
> +. ./common/filter
> +
> +# real QA test starts here
> +
> +_supported_fs generic
> +_require_test
> +_require_scratch
> +_fixed_by_kernel_commit ac6800e279a2 \
> +	"fs: Add missing umask strip in vfs_tmpfile" \
> +1639a49ccdce "fs: move S_ISGID stripping into the vfs_*() helpers"
> +
> +_scratch_mkfs >$seqres.full 2>&1
> +
> +$here/src/vfs/vfstest --test-setgid-create-umask \
> +        --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
> +
> +export MOUNT_OPTIONS="-o noacl"
> +
> +# Also test S_ISGID stripping whether works correctly on underflying filesystem
> +# that supports noacl feature.
> +# noacl will earse acl flag in superblock, so kernel will use current_umask in
> +# vfs directly instead of calling posix_acl_create on underflying filesystem.
> +_try_scratch_mount >>$seqres.full 2>&1 && \
> +	$here/src/vfs/vfstest --test-setgid-create-umask \
> +        --device "$SCRATCH_DEV" --mount "$SCRATCH_MNT" --fstype "$FSTYP"
> +
> +echo "Silence is golden"
> +status=0
> +exit
> diff --git a/tests/generic/998.out b/tests/generic/998.out
> new file mode 100644
> index 00000000..d2679ae0
> --- /dev/null
> +++ b/tests/generic/998.out
> @@ -0,0 +1,2 @@
> +QA output created by 998
> +Silence is golden

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 1/2] vfs: Add new setgid_create_umask test
  2022-08-15 10:02       ` [PATCH v2 1/2] vfs: Add new setgid_create_umask test xuyang2018.jy
@ 2022-08-29  1:14         ` xuyang2018.jy
  0 siblings, 0 replies; 15+ messages in thread
From: xuyang2018.jy @ 2022-08-29  1:14 UTC (permalink / raw)
  To: fstests; +Cc: brauner, Zorro Lang


on 2022/08/15 18:02, xuyang2018.jy@fujitsu.com wrote:
> 
> 
> on 2022/07/26 17:31, Yang Xu wrote:
> 
>> The current_umask() is stripped from the mode directly in the vfs if the
>> filesystem either doesn't support acls or the filesystem has been
>> mounted without posic acl support.
>>
>> If the filesystem does support acls then current_umask() stripping is
>> deferred to posix_acl_create(). So when the filesystem calls
>> posix_acl_create() and there are no acls set or not supported then
>> current_umask() will be stripped.
>>
>> This patch is also designed to test kernel patch behaviour
>> "move S_ISGID stripping into the vfs* helper"
>>
> The kernel patchset[1] has been merged into 6.0-rc1 kernel, I guess we
> can review this patch.

Ping again. Any comment?

Best Regards
Yang Xu
> 
> [1]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=1639a49ccdce58ea248841ed9b23babcce6dbb0b
> Best Regards
> Yang Xu
>> Here we only use umask(S_IXGRP) to check S_ISGID stripping whether works
>> correctly and S_IXGRP mode still exists.
>>
>> Also use noacl mount option to test another kernel bug
>> fs: Add missing umask strip in vfs_tmpfile
>>
>> Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
>> ---
>> 1.add kernel commit id
>> 2.use s_setgid_create_umask_idmapped_mounts instead of confused s_setgid_create_umask_idmapped
>>    src/vfs/idmapped-mounts.c | 429 ++++++++++++++++++++++++++++++++++++++
>>    src/vfs/idmapped-mounts.h |   1 +
>>    src/vfs/utils.c           |  14 ++
>>    src/vfs/utils.h           |   1 +
>>    src/vfs/vfstest.c         | 205 +++++++++++++++++-
>>    tests/generic/998         |  46 ++++
>>    tests/generic/998.out     |   2 +
>>    7 files changed, 697 insertions(+), 1 deletion(-)
>>    create mode 100755 tests/generic/998
>>    create mode 100644 tests/generic/998.out
>>
>> diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
>> index 63297d5f..f63ac44b 100644
>> --- a/src/vfs/idmapped-mounts.c
>> +++ b/src/vfs/idmapped-mounts.c
>> @@ -7664,6 +7664,425 @@ out:
>>    	return fret;
>>    }
>>    
>> +static int setgid_create_umask_idmapped(const struct vfstest_info *info)
>> +{
>> +	int fret = -1;
>> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
>> +	struct mount_attr attr = {
>> +		.attr_set = MOUNT_ATTR_IDMAP,
>> +	};
>> +	pid_t pid;
>> +	int tmpfile_fd = -EBADF;
>> +	bool supported = false;
>> +	char path[PATH_MAX];
>> +	mode_t mode;
>> +
>> +	if (!caps_supported())
>> +		return 0;
>> +
>> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
>> +			      S_IWUSR |
>> +			      S_IRGRP |
>> +			      S_IWGRP |
>> +			      S_IROTH |
>> +			      S_IWOTH |
>> +			      S_IXUSR |
>> +			      S_IXGRP |
>> +			      S_IXOTH |
>> +			      S_ISGID), 0) {
>> +		log_stderr("failure: fchmod");
>> +		goto out;
>> +	}
>> +
>> +	/* Verify that the sid bits got raised. */
>> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
>> +		log_stderr("failure: is_setgid");
>> +		goto out;
>> +	}
>> +
>> +	/* Changing mount properties on a detached mount. */
>> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
>> +	if (attr.userns_fd < 0) {
>> +		log_stderr("failure: get_userns_fd");
>> +		goto out;
>> +	}
>> +
>> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
>> +				     AT_EMPTY_PATH |
>> +				     AT_NO_AUTOMOUNT |
>> +				     AT_SYMLINK_NOFOLLOW |
>> +				     OPEN_TREE_CLOEXEC |
>> +				     OPEN_TREE_CLONE);
>> +	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;
>> +	}
>> +
>> +	supported = openat_tmpfile_supported(open_tree_fd);
>> +
>> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
>> +	 * whether has group execute or search permission.
>> +	 */
>> +	umask(S_IXGRP);
>> +	mode = umask(S_IXGRP);
>> +	if (!(mode & S_IXGRP))
>> +		die("failure: umask");
>> +
>> +	pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		if (!switch_ids(10000, 11000))
>> +			die("failure: switch fsids");
>> +
>> +		/* create regular file via open() */
>> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
>> +		if (file1_fd < 0)
>> +			die("failure: create");
>> +
>> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
>> +		 * bit needs to be stripped.
>> +		 */
>> +		if (is_setgid(open_tree_fd, FILE1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create directory */
>> +		if (mkdirat(open_tree_fd, DIR1, 0000))
>> +			die("failure: create");
>> +
>> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
>> +			/* We're not in_group_p(). */
>> +			if (is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		} else {
>> +			/* Directories always inherit the setgid bit. */
>> +			if (!is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		}
>> +
>> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a special file via mknodat() vfs_create */
>> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, FILE2, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a whiteout device via mknodat() vfs_mknod */
>> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/*
>> +		 * In setgid directories newly created files always inherit the
>> +		 * gid from the parent directory. Verify that the file is owned
>> +		 * by gid 10000, not by gid 11000.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		/*
>> +		 * In setgid directories newly created directories always
>> +		 * inherit the gid from the parent directory. Verify that the
>> +		 * directory is owned by gid 10000, not by gid 11000.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (unlinkat(open_tree_fd, FILE1, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, FILE2, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: delete");
>> +
>> +		/* create tmpfile via filesystem tmpfile api */
>> +		if (supported) {
>> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
>> +			if (tmpfile_fd < 0)
>> +				die("failure: create");
>> +			/* link the temporary file into the filesystem, making it permanent */
>> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
>> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
>> +				die("failure: linkat");
>> +			if (close(tmpfile_fd))
>> +				die("failure: close");
>> +			if (is_setgid(open_tree_fd, FILE3, 0))
>> +				die("failure: is_setgid");
>> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
>> +				die("failure: is_ixgrp");
>> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
>> +				die("failure: check ownership");
>> +			if (unlinkat(open_tree_fd, FILE3, 0))
>> +				die("failure: delete");
>> +		}
>> +
>> +		exit(EXIT_SUCCESS);
>> +	}
>> +	if (wait_for_pid(pid))
>> +		goto out;
>> +
>> +	fret = 0;
>> +	log_debug("Ran test");
>> +out:
>> +	safe_close(attr.userns_fd);
>> +	safe_close(file1_fd);
>> +	safe_close(open_tree_fd);
>> +
>> +	return fret;
>> +}
>> +
>> +static int setgid_create_umask_idmapped_in_userns(const struct vfstest_info *info)
>> +{
>> +	int fret = -1;
>> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
>> +	struct mount_attr attr = {
>> +		.attr_set = MOUNT_ATTR_IDMAP,
>> +	};
>> +	pid_t pid;
>> +	int tmpfile_fd = -EBADF;
>> +	bool supported = false;
>> +	char path[PATH_MAX];
>> +	mode_t mode;
>> +
>> +	if (!caps_supported())
>> +		return 0;
>> +
>> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
>> +			      S_IWUSR |
>> +			      S_IRGRP |
>> +			      S_IWGRP |
>> +			      S_IROTH |
>> +			      S_IWOTH |
>> +			      S_IXUSR |
>> +			      S_IXGRP |
>> +			      S_IXOTH |
>> +			      S_ISGID), 0) {
>> +		log_stderr("failure: fchmod");
>> +		goto out;
>> +	}
>> +
>> +	/* Verify that the sid bits got raised. */
>> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
>> +		log_stderr("failure: is_setgid");
>> +		goto out;
>> +	}
>> +
>> +	/* Changing mount properties on a detached mount. */
>> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
>> +	if (attr.userns_fd < 0) {
>> +		log_stderr("failure: get_userns_fd");
>> +		goto out;
>> +	}
>> +
>> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
>> +				     AT_EMPTY_PATH |
>> +				     AT_NO_AUTOMOUNT |
>> +				     AT_SYMLINK_NOFOLLOW |
>> +				     OPEN_TREE_CLOEXEC |
>> +				     OPEN_TREE_CLONE);
>> +	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;
>> +	}
>> +
>> +	supported = openat_tmpfile_supported(open_tree_fd);
>> +
>> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
>> +	 * whether has group execute or search permission.
>> +	 */
>> +	umask(S_IXGRP);
>> +	mode = umask(S_IXGRP);
>> +	if (!(mode & S_IXGRP))
>> +		die("failure: umask");
>> +
>> +	/*
>> +	 * Below we verify that setgid inheritance for a newly created file or
>> +	 * directory works correctly. As part of this we need to verify that
>> +	 * newly created files or directories inherit their gid from their
>> +	 * parent directory. So we change the parent directorie's gid to 1000
>> +	 * and create a file with fs{g,u}id 0 and verify that the newly created
>> +	 * file and directory inherit gid 1000, not 0.
>> +	 */
>> +	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
>> +		log_stderr("failure: fchownat");
>> +		goto out;
>> +	}
>> +
>> +	pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		if (!caps_supported()) {
>> +			log_debug("skip: capability library not installed");
>> +			exit(EXIT_SUCCESS);
>> +		}
>> +
>> +		if (!switch_userns(attr.userns_fd, 0, 0, false))
>> +			die("failure: switch_userns");
>> +
>> +		if (!caps_down_fsetid())
>> +			die("failure: caps_down_fsetid");
>> +
>> +		/* create regular file via open() */
>> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
>> +		if (file1_fd < 0)
>> +			die("failure: create");
>> +
>> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
>> +		 * bit needs to be stripped.
>> +		 */
>> +		if (is_setgid(open_tree_fd, FILE1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create directory */
>> +		if (mkdirat(open_tree_fd, DIR1, 0000))
>> +			die("failure: create");
>> +
>> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
>> +			/* We're not in_group_p(). */
>> +			if (is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		} else {
>> +			/* Directories always inherit the setgid bit. */
>> +			if (!is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		}
>> +
>> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a special file via mknodat() vfs_create */
>> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, FILE2, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a whiteout device via mknodat() vfs_mknod */
>> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/*
>> +		 * In setgid directories newly created files always inherit the
>> +		 * gid from the parent directory. Verify that the file is owned
>> +		 * by gid 1000, not by gid 0.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		/*
>> +		 * In setgid directories newly created directories always
>> +		 * inherit the gid from the parent directory. Verify that the
>> +		 * directory is owned by gid 1000, not by gid 0.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (unlinkat(open_tree_fd, FILE1, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, FILE2, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: delete");
>> +
>> +		/* create tmpfile via filesystem tmpfile api */
>> +		if (supported) {
>> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
>> +			if (tmpfile_fd < 0)
>> +				die("failure: create");
>> +			/* link the temporary file into the filesystem, making it permanent */
>> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
>> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
>> +				die("failure: linkat");
>> +			if (close(tmpfile_fd))
>> +				die("failure: close");
>> +			if (is_setgid(open_tree_fd, FILE3, 0))
>> +				die("failure: is_setgid");
>> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
>> +				die("failure: is_ixgrp");
>> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
>> +				die("failure: check ownership");
>> +			if (unlinkat(open_tree_fd, FILE3, 0))
>> +				die("failure: delete");
>> +		}
>> +
>> +		exit(EXIT_SUCCESS);
>> +	}
>> +	if (wait_for_pid(pid))
>> +		goto out;
>> +
>> +	fret = 0;
>> +	log_debug("Ran test");
>> +out:
>> +	safe_close(attr.userns_fd);
>> +	safe_close(file1_fd);
>> +	safe_close(open_tree_fd);
>> +
>> +	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",                                                          },
>> @@ -7745,3 +8164,13 @@ const struct test_suite s_setxattr_fix_705191b03d50 = {
>>    	.tests = t_setxattr_fix_705191b03d50,
>>    	.nr_tests = ARRAY_SIZE(t_setxattr_fix_705191b03d50),
>>    };
>> +
>> +static const struct test_struct t_setgid_create_umask_idmapped_mounts[] = {
>> +	{ setgid_create_umask_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount",		},
>> +	{ setgid_create_umask_idmapped_in_userns,			T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using umask in directories with setgid bit set on idmapped mount inside userns",	},
>> +};
>> +
>> +const struct test_suite s_setgid_create_umask_idmapped_mounts = {
>> +	.tests = t_setgid_create_umask_idmapped_mounts,
>> +	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped_mounts),
>> +};
>> diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
>> index ff21ea2c..a9fb31ea 100644
>> --- a/src/vfs/idmapped-mounts.h
>> +++ b/src/vfs/idmapped-mounts.h
>> @@ -14,5 +14,6 @@ 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;
>>    extern const struct test_suite s_setxattr_fix_705191b03d50;
>> +extern const struct test_suite s_setgid_create_umask_idmapped_mounts;
>>    
>>    #endif /* __IDMAPPED_MOUNTS_H */
>> diff --git a/src/vfs/utils.c b/src/vfs/utils.c
>> index 1388edda..6db7a11d 100644
>> --- a/src/vfs/utils.c
>> +++ b/src/vfs/utils.c
>> @@ -809,6 +809,20 @@ bool is_sticky(int dfd, const char *path, int flags)
>>    	return (st.st_mode & S_ISVTX) > 0;
>>    }
>>    
>> +/*is_ixgrp - check whether file or directory is S_IXGRP */
>> +bool is_ixgrp(int dfd, const char *path, int flags)
>> +{
>> +	int ret;
>> +	struct stat st;
>> +
>> +	ret = fstatat(dfd, path, &st, flags);
>> +	if (ret < 0)
>> +		return false;
>> +
>> +	errno = 0; /* Don't report misleading errno. */
>> +	return (st.st_mode & S_IXGRP);
>> +}
>> +
>>    bool switch_resids(uid_t uid, gid_t gid)
>>    {
>>    	if (setresgid(gid, gid, gid))
>> diff --git a/src/vfs/utils.h b/src/vfs/utils.h
>> index 7fb702fd..c0dbe370 100644
>> --- a/src/vfs/utils.h
>> +++ b/src/vfs/utils.h
>> @@ -368,6 +368,7 @@ extern bool expected_file_size(int dfd, const char *path, int flags,
>>    extern bool is_setid(int dfd, const char *path, int flags);
>>    extern bool is_setgid(int dfd, const char *path, int flags);
>>    extern bool is_sticky(int dfd, const char *path, int flags);
>> +extern bool is_ixgrp(int dfd, const char *path, int flags);
>>    extern bool openat_tmpfile_supported(int dirfd);
>>    
>>    #endif /* __IDMAP_UTILS_H */
>> diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
>> index 29ac0bec..e928a1f5 100644
>> --- a/src/vfs/vfstest.c
>> +++ b/src/vfs/vfstest.c
>> @@ -1733,6 +1733,186 @@ out:
>>    	return fret;
>>    }
>>    
>> +/* The current_umask() is stripped from the mode directly in the vfs if the
>> + * filesystem either doesn't support acls or the filesystem has been
>> + * mounted without posic acl support.
>> + *
>> + * If the filesystem does support acls then current_umask() stripping is
>> + * deferred to posix_acl_create(). So when the filesystem calls
>> + * posix_acl_create() and there are no acls set or not supported then
>> + * current_umask() will be stripped.
>> + *
>> + * Use umask(S_IXGRP) to check whether inode strip S_ISGID works correctly.
>> + *
>> + * test for commit ac6800e279a2 ("fs: Add missing umask strip in vfs_tmpfile")
>> + * and 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers").
>> + */
>> +
>> +static int setgid_create_umask(const struct vfstest_info *info)
>> +{
>> +	int fret = -1;
>> +	int file1_fd = -EBADF;
>> +	int tmpfile_fd = -EBADF;
>> +	pid_t pid;
>> +	bool supported = false;
>> +	mode_t mode;
>> +
>> +	if (!caps_supported())
>> +		return 0;
>> +
>> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
>> +			      S_IWUSR |
>> +			      S_IRGRP |
>> +			      S_IWGRP |
>> +			      S_IROTH |
>> +			      S_IWOTH |
>> +			      S_IXUSR |
>> +			      S_IXGRP |
>> +			      S_IXOTH |
>> +			      S_ISGID), 0) {
>> +		log_stderr("failure: fchmod");
>> +		goto out;
>> +	}
>> +
>> +	/* Verify that the setgid bit got raised. */
>> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
>> +		log_stderr("failure: is_setgid");
>> +		goto out;
>> +	}
>> +
>> +	supported = openat_tmpfile_supported(info->t_dir1_fd);
>> +
>> +	umask(S_IXGRP);
>> +	mode = umask(S_IXGRP);
>> +	if (!(mode & S_IXGRP))
>> +		die("failure: umask");
>> +
>> +	pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		if (!switch_ids(0, 10000))
>> +			die("failure: switch_ids");
>> +
>> +		if (!caps_down_fsetid())
>> +			die("failure: caps_down_fsetid");
>> +
>> +		/* create regular file via open() */
>> +		file1_fd = openat(info->t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
>> +		if (file1_fd < 0)
>> +			die("failure: create");
>> +
>> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
>> +		 * bit needs to be stripped.
>> +		 */
>> +		if (is_setgid(info->t_dir1_fd, FILE1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(info->t_dir1_fd, FILE1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		if (mkdirat(info->t_dir1_fd, DIR1, 0000))
>> +			die("failure: create");
>> +
>> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
>> +			/* We're not in_group_p(). */
>> +			if (is_setgid(info->t_dir1_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		} else {
>> +			/* Directories always inherit the setgid bit. */
>> +			if (!is_setgid(info->t_dir1_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		}
>> +
>> +		if (is_ixgrp(info->t_dir1_fd, DIR1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a special file via mknodat() vfs_create */
>> +		if (mknodat(info->t_dir1_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(info->t_dir1_fd, FILE2, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(info->t_dir1_fd, FILE2, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a character device via mknodat() vfs_mknod */
>> +		if (mknodat(info->t_dir1_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, makedev(5, 1)))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(info->t_dir1_fd, CHRDEV1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(info->t_dir1_fd, CHRDEV1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/*
>> +		 * In setgid directories newly created files always inherit the
>> +		 * gid from the parent directory. Verify that the file is owned
>> +		 * by gid 0, not by gid 10000.
>> +		 */
>> +		if (!expected_uid_gid(info->t_dir1_fd, FILE1, 0, 0, 0))
>> +			die("failure: check ownership");
>> +
>> +		/*
>> +		 * In setgid directories newly created directories always
>> +		 * inherit the gid from the parent directory. Verify that the
>> +		 * directory is owned by gid 0, not by gid 10000.
>> +		 */
>> +		if (!expected_uid_gid(info->t_dir1_fd, DIR1, 0, 0, 0))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(info->t_dir1_fd, FILE2, 0, 0, 0))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(info->t_dir1_fd, CHRDEV1, 0, 0, 0))
>> +			die("failure: check ownership");
>> +
>> +		if (unlinkat(info->t_dir1_fd, FILE1, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(info->t_dir1_fd, DIR1, AT_REMOVEDIR))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(info->t_dir1_fd, FILE2, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(info->t_dir1_fd, CHRDEV1, 0))
>> +			die("failure: delete");
>> +
>> +		/* create tmpfile via filesystem tmpfile api */
>> +		if (supported) {
>> +			tmpfile_fd = openat(info->t_dir1_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
>> +			if (tmpfile_fd < 0)
>> +				die("failure: create");
>> +			/* link the temporary file into the filesystem, making it permanent */
>> +			if (linkat(tmpfile_fd, "", info->t_dir1_fd, FILE3, AT_EMPTY_PATH))
>> +				die("failure: linkat");
>> +			if (close(tmpfile_fd))
>> +				die("failure: close");
>> +			if (is_setgid(info->t_dir1_fd, FILE3, 0))
>> +				die("failure: is_setgid");
>> +			if (is_ixgrp(info->t_dir1_fd, FILE3, 0))
>> +				die("failure: is_ixgrp");
>> +			if (unlinkat(info->t_dir1_fd, FILE3, 0))
>> +				die("failure: delete");
>> +		}
>> +
>> +		exit(EXIT_SUCCESS);
>> +	}
>> +	if (wait_for_pid(pid))
>> +		goto out;
>> +
>> +	fret = 0;
>> +	log_debug("Ran test");
>> +out:
>> +	safe_close(file1_fd);
>> +	return fret;
>> +}
>> +
>>    static int setattr_truncate(const struct vfstest_info *info)
>>    {
>>    	int fret = -1;
>> @@ -1807,6 +1987,7 @@ static void usage(void)
>>    	fprintf(stderr, "--test-btrfs                        Run btrfs specific idmapped mount testsuite\n");
>>    	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
>>    	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
>> +	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
>>    
>>    	_exit(EXIT_SUCCESS);
>>    }
>> @@ -1825,6 +2006,7 @@ static const struct option longopts[] = {
>>    	{"test-btrfs",				no_argument,		0,	'b'},
>>    	{"test-setattr-fix-968219708108",	no_argument,		0,	'i'},
>>    	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
>> +	{"test-setgid-create-umask",		no_argument,		0,	'u'},
>>    	{NULL,					0,			0,	  0},
>>    };
>>    
>> @@ -1850,6 +2032,15 @@ static const struct test_suite s_basic = {
>>    	.nr_tests = ARRAY_SIZE(t_basic),
>>    };
>>    
>> +static const struct test_struct t_setgid_create_umask[] = {
>> +	{ setgid_create_umask,						0,			"create operations in directories with setgid bit set under umask",				},
>> +};
>> +
>> +static const struct test_suite s_setgid_create_umask = {
>> +	.tests = t_setgid_create_umask,
>> +	.nr_tests = ARRAY_SIZE(t_setgid_create_umask),
>> +};
>> +
>>    static bool run_test(struct vfstest_info *info, const struct test_struct suite[], size_t suite_size)
>>    {
>>    	int i;
>> @@ -1947,7 +2138,8 @@ int main(int argc, char *argv[])
>>    	bool idmapped_mounts_supported = false, test_btrfs = false,
>>    	     test_core = false, test_fscaps_regression = false,
>>    	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
>> -	     test_setxattr_fix_705191b03d50 = false;
>> +	     test_setxattr_fix_705191b03d50 = false,
>> +	     test_setgid_create_umask = false;
>>    
>>    	init_vfstest_info(&info);
>>    
>> @@ -1989,6 +2181,9 @@ int main(int argc, char *argv[])
>>    		case 'j':
>>    			test_setxattr_fix_705191b03d50 = true;
>>    			break;
>> +		case 'u':
>> +			test_setgid_create_umask = true;
>> +			break;
>>    		case 'h':
>>    			/* fallthrough */
>>    		default:
>> @@ -2066,6 +2261,14 @@ int main(int argc, char *argv[])
>>    	    !run_suite(&info, &s_setxattr_fix_705191b03d50))
>>    		goto out;
>>    
>> +	if (test_setgid_create_umask) {
>> +		if (!run_suite(&info, &s_setgid_create_umask))
>> +			goto out;
>> +
>> +		if (!run_suite(&info, &s_setgid_create_umask_idmapped_mounts))
>> +			goto out;
>> +	}
>> +
>>    	fret = EXIT_SUCCESS;
>>    
>>    out:
>> diff --git a/tests/generic/998 b/tests/generic/998
>> new file mode 100755
>> index 00000000..5f736b6d
>> --- /dev/null
>> +++ b/tests/generic/998
>> @@ -0,0 +1,46 @@
>> +#! /bin/bash
>> +# SPDX-License-Identifier: GPL-2.0
>> +# Copyright (c) 2022 Fujitsu Limited. All Rights Reserved.
>> +#
>> +# FS QA Test No. 998
>> +#
>> +# Test S_ISGID stripping whether works correctly when call process
>> +# uses umask(S_IXGRP).
>> +#
>> +# It is also a regression test for
>> +# commit ac6800e279a2 ("fs: Add missing umask strip in vfs_tmpfile")
>> +# commit 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers")
>> +
>> +. ./common/preamble
>> +_begin_fstest auto quick cap idmapped mount perms rw unlink
>> +
>> +# Import common functions.
>> +. ./common/filter
>> +
>> +# real QA test starts here
>> +
>> +_supported_fs generic
>> +_require_test
>> +_require_scratch
>> +_fixed_by_kernel_commit ac6800e279a2 \
>> +	"fs: Add missing umask strip in vfs_tmpfile" \
>> +1639a49ccdce "fs: move S_ISGID stripping into the vfs_*() helpers"
>> +
>> +_scratch_mkfs >$seqres.full 2>&1
>> +
>> +$here/src/vfs/vfstest --test-setgid-create-umask \
>> +        --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
>> +
>> +export MOUNT_OPTIONS="-o noacl"
>> +
>> +# Also test S_ISGID stripping whether works correctly on underflying filesystem
>> +# that supports noacl feature.
>> +# noacl will earse acl flag in superblock, so kernel will use current_umask in
>> +# vfs directly instead of calling posix_acl_create on underflying filesystem.
>> +_try_scratch_mount >>$seqres.full 2>&1 && \
>> +	$here/src/vfs/vfstest --test-setgid-create-umask \
>> +        --device "$SCRATCH_DEV" --mount "$SCRATCH_MNT" --fstype "$FSTYP"
>> +
>> +echo "Silence is golden"
>> +status=0
>> +exit
>> diff --git a/tests/generic/998.out b/tests/generic/998.out
>> new file mode 100644
>> index 00000000..d2679ae0
>> --- /dev/null
>> +++ b/tests/generic/998.out
>> @@ -0,0 +1,2 @@
>> +QA output created by 998
>> +Silence is golden

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 2/2] vfs: Add new setgid_create_acl test
  2022-07-26  9:31       ` [PATCH v2 2/2] vfs: Add new setgid_create_acl test Yang Xu
@ 2022-08-29 13:20         ` Christian Brauner
  2022-08-30  9:03           ` xuyang2018.jy
  2022-08-29 13:52         ` Christian Brauner
  1 sibling, 1 reply; 15+ messages in thread
From: Christian Brauner @ 2022-08-29 13:20 UTC (permalink / raw)
  To: Yang Xu; +Cc: fstests

On Tue, Jul 26, 2022 at 05:31:21PM +0800, Yang Xu wrote:
> The current_umask() is stripped from the mode directly in the vfs if the
> filesystem either doesn't support acls or the filesystem has been
> mounted without posic acl support.
> 
> If the filesystem does support acls then current_umask() stripping is
> deferred to posix_acl_create(). So when the filesystem calls
> posix_acl_create() and there are no acls set or not supported then
> current_umask() will be stripped.
> 
> If the parent directory has a default acl then permissions are based off
> of that and current_umask() is ignored. Specifically, if the ACL has an
> ACL_MASK entry, the group permissions correspond to the permissions of
> the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
> group permissions correspond to the permissions of the ACL_GROUP_OBJ
> entry.
> 
> Here we only use setfacl to set default acl or add ACL_MASK to check
> whether inode strip  S_ISGID works correctly.
> 
> Like umask test, this patch is also designed to test kernel patchset behaviour
> "move S_ISGID stripping into the vfs* helper"
> 
> Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
> ---

Hey Yang,

Thanks for your patches! Just a minor comment below.

> 1.add kernel commit id
> 2.move umask into parent process intead of child process
> 3.remove duplicated comment for ACL_GROUP_OBJ and ACL_MASK because
> we have mentioned it before the function
>  src/vfs/idmapped-mounts.c | 704 ++++++++++++++++++++++++++++++++++++++
>  src/vfs/idmapped-mounts.h |   1 +
>  src/vfs/vfstest.c         | 345 ++++++++++++++++++-
>  tests/generic/999         |  33 ++
>  tests/generic/999.out     |   2 +
>  5 files changed, 1084 insertions(+), 1 deletion(-)
>  create mode 100755 tests/generic/999
>  create mode 100644 tests/generic/999.out
> 
> diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
> index f63ac44b..f934cf4a 100644
> --- a/src/vfs/idmapped-mounts.c
> +++ b/src/vfs/idmapped-mounts.c
> @@ -8083,6 +8083,700 @@ out:
>  	return fret;
>  }
>  
> +static int setgid_create_acl_idmapped(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
> +	struct mount_attr attr = {
> +		.attr_set = MOUNT_ATTR_IDMAP,
> +	};
> +	pid_t pid;
> +	int tmpfile_fd = -EBADF;
> +	bool supported = false;
> +	char path[PATH_MAX];
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the sid bits got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	/* Changing mount properties on a detached mount. */
> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
> +	if (attr.userns_fd < 0) {
> +		log_stderr("failure: get_userns_fd");
> +		goto out;
> +	}
> +
> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
> +				     AT_EMPTY_PATH |
> +				     AT_NO_AUTOMOUNT |
> +				     AT_SYMLINK_NOFOLLOW |
> +				     OPEN_TREE_CLOEXEC |
> +				     OPEN_TREE_CLONE);
> +	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;
> +	}
> +
> +	supported = openat_tmpfile_supported(open_tree_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");

All the calls to umask() in this patch should be after the fork() in the
child process. The die() helper is only supposed to be used in child
processes anyway. The rest looks fine to me. So with the umask() moved
you can add,
Tested-by: Christian Brauner (Microsoft) <brauner@kernel.org>
Reviewed-by: Christian Brauner (Microsoft) <brauner@kernel.org>

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 2/2] vfs: Add new setgid_create_acl test
  2022-07-26  9:31       ` [PATCH v2 2/2] vfs: Add new setgid_create_acl test Yang Xu
  2022-08-29 13:20         ` Christian Brauner
@ 2022-08-29 13:52         ` Christian Brauner
  2022-08-30  9:06           ` xuyang2018.jy
  1 sibling, 1 reply; 15+ messages in thread
From: Christian Brauner @ 2022-08-29 13:52 UTC (permalink / raw)
  To: Yang Xu; +Cc: fstests

On Tue, Jul 26, 2022 at 05:31:21PM +0800, Yang Xu wrote:
> The current_umask() is stripped from the mode directly in the vfs if the
> filesystem either doesn't support acls or the filesystem has been
> mounted without posic acl support.
> 
> If the filesystem does support acls then current_umask() stripping is
> deferred to posix_acl_create(). So when the filesystem calls
> posix_acl_create() and there are no acls set or not supported then
> current_umask() will be stripped.
> 
> If the parent directory has a default acl then permissions are based off
> of that and current_umask() is ignored. Specifically, if the ACL has an
> ACL_MASK entry, the group permissions correspond to the permissions of
> the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
> group permissions correspond to the permissions of the ACL_GROUP_OBJ
> entry.
> 
> Here we only use setfacl to set default acl or add ACL_MASK to check
> whether inode strip  S_ISGID works correctly.
> 
> Like umask test, this patch is also designed to test kernel patchset behaviour
> "move S_ISGID stripping into the vfs* helper"
> 
> Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
> ---

Hey Yang,

Thank your for all that work! Just a few nits.

> 1.add kernel commit id
> 2.move umask into parent process intead of child process
> 3.remove duplicated comment for ACL_GROUP_OBJ and ACL_MASK because
> we have mentioned it before the function
>  src/vfs/idmapped-mounts.c | 704 ++++++++++++++++++++++++++++++++++++++
>  src/vfs/idmapped-mounts.h |   1 +
>  src/vfs/vfstest.c         | 345 ++++++++++++++++++-
>  tests/generic/999         |  33 ++
>  tests/generic/999.out     |   2 +
>  5 files changed, 1084 insertions(+), 1 deletion(-)
>  create mode 100755 tests/generic/999
>  create mode 100644 tests/generic/999.out
> 
> diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
> index f63ac44b..f934cf4a 100644
> --- a/src/vfs/idmapped-mounts.c
> +++ b/src/vfs/idmapped-mounts.c
> @@ -8083,6 +8083,700 @@ out:
>  	return fret;
>  }
>  
> +static int setgid_create_acl_idmapped(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
> +	struct mount_attr attr = {
> +		.attr_set = MOUNT_ATTR_IDMAP,
> +	};
> +	pid_t pid;
> +	int tmpfile_fd = -EBADF;
> +	bool supported = false;
> +	char path[PATH_MAX];
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the sid bits got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	/* Changing mount properties on a detached mount. */
> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
> +	if (attr.userns_fd < 0) {
> +		log_stderr("failure: get_userns_fd");
> +		goto out;
> +	}
> +
> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
> +				     AT_EMPTY_PATH |
> +				     AT_NO_AUTOMOUNT |
> +				     AT_SYMLINK_NOFOLLOW |
> +				     OPEN_TREE_CLOEXEC |
> +				     OPEN_TREE_CLONE);
> +	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;
> +	}
> +
> +	supported = openat_tmpfile_supported(open_tree_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");

Please move all the umask() calls after the fork() into the child.

> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		/* The group permissions correspond to the permissions of the
> +		 * ACL_MASK entry.
> +		 */
> +		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
> +		if (system(t_buf))
> +			die("failure: system");
> +
> +		if (!switch_ids(10000, 11000))
> +			die("failure: switch fsids");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(open_tree_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create directory */
> +		if (mkdirat(open_tree_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a whiteout device via mknodat() vfs_mknod */
> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 10000, not by gid 11000.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 10000, not by gid 11000.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(open_tree_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(open_tree_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
> +				die("failure: check ownership");
> +			if (unlinkat(open_tree_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		/* The group permissions correspond to the permissions of the
> +		 * ACL_GROUP_OBJ entry.
> +		 */
> +		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1);
> +		if (system(t_buf))
> +			die("failure: system");
> +
> +		if (!switch_ids(10000, 11000))
> +			die("failure: switch fsids");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(open_tree_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (!is_ixgrp(open_tree_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create directory */
> +		if (mkdirat(open_tree_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (!is_ixgrp(open_tree_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a whiteout device via mknodat() vfs_mknod */
> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (!is_ixgrp(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 10000, not by gid 11000.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 10000, not by gid 11000.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(open_tree_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(open_tree_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (!is_ixgrp(open_tree_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
> +				die("failure: check ownership");
> +			if (unlinkat(open_tree_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(attr.userns_fd);
> +	safe_close(file1_fd);
> +	safe_close(open_tree_fd);
> +
> +	return fret;
> +}
> +
> +static int setgid_create_acl_idmapped_in_userns(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
> +	struct mount_attr attr = {
> +		.attr_set = MOUNT_ATTR_IDMAP,
> +	};
> +	pid_t pid;
> +	int tmpfile_fd = -EBADF;
> +	bool supported = false;
> +	char path[PATH_MAX];
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the sid bits got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	/* Changing mount properties on a detached mount. */
> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
> +	if (attr.userns_fd < 0) {
> +		log_stderr("failure: get_userns_fd");
> +		goto out;
> +	}
> +
> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
> +				     AT_EMPTY_PATH |
> +				     AT_NO_AUTOMOUNT |
> +				     AT_SYMLINK_NOFOLLOW |
> +				     OPEN_TREE_CLOEXEC |
> +				     OPEN_TREE_CLONE);
> +	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;
> +	}
> +
> +	supported = openat_tmpfile_supported(open_tree_fd);
> +
> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
> +	 * whether has group execute or search permission.
> +	 */
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +	/*
> +	 * Below we verify that setgid inheritance for a newly created file or
> +	 * directory works correctly. As part of this we need to verify that
> +	 * newly created files or directories inherit their gid from their
> +	 * parent directory. So we change the parent directorie's gid to 1000
> +	 * and create a file with fs{g,u}id 0 and verify that the newly created
> +	 * file and directory inherit gid 1000, not 0.
> +	 */
> +	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
> +		log_stderr("failure: fchownat");
> +		goto out;
> +	}
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		/* The group permissions correspond to the permissions of the
> +		 * ACL_MASK entry.
> +		 */
> +		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
> +		if (system(t_buf))
> +			die("failure: system");
> +
> +		if (!caps_supported()) {
> +			log_debug("skip: capability library not installed");
> +			exit(EXIT_SUCCESS);
> +		}
> +
> +		if (!switch_userns(attr.userns_fd, 0, 0, false))
> +			die("failure: switch_userns");
> +
> +		if (!caps_down_fsetid())
> +			die("failure: caps_down_fsetid");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(open_tree_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create directory */
> +		if (mkdirat(open_tree_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a whiteout device via mknodat() vfs_mknod */
> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 1000, not by gid 0.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 1000, not by gid 0.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(open_tree_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(open_tree_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
> +				die("failure: check ownership");
> +			if (unlinkat(open_tree_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		/* The group permissions correspond to the permissions of the
> +		 * ACL_GROUP_OBJ entry.
> +		 */
> +		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1);
> +		if (system(t_buf))
> +			die("failure: system");
> +
> +		if (!caps_supported()) {
> +			log_debug("skip: capability library not installed");
> +			exit(EXIT_SUCCESS);
> +		}
> +
> +		if (!switch_userns(attr.userns_fd, 0, 0, false))
> +			die("failure: switch_userns");
> +
> +		if (!caps_down_fsetid())
> +			die("failure: caps_down_fsetid");
> +
> +		/* create regular file via open() */
> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
> +		if (file1_fd < 0)
> +			die("failure: create");
> +
> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
> +		 * bit needs to be stripped.
> +		 */
> +		if (is_setgid(open_tree_fd, FILE1, 0))
> +			die("failure: is_setgid");
> +
> +		if (!is_ixgrp(open_tree_fd, FILE1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create directory */
> +		if (mkdirat(open_tree_fd, DIR1, 0000))
> +			die("failure: create");
> +
> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
> +			/* We're not in_group_p(). */
> +			if (is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		} else {
> +			/* Directories always inherit the setgid bit. */
> +			if (!is_setgid(open_tree_fd, DIR1, 0))
> +				die("failure: is_setgid");
> +		}
> +
> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
> +			die("failure: is_ixgrp");
> +
> +		/* create a special file via mknodat() vfs_create */
> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, FILE2, 0))
> +			die("failure: is_setgid");
> +
> +		if (!is_ixgrp(open_tree_fd, FILE2, 0))
> +			 die("failure: is_ixgrp");
> +		/* create a whiteout device via mknodat() vfs_mknod */
> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
> +			die("failure: mknodat");
> +
> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
> +			die("failure: is_setgid");
> +
> +		if (!is_ixgrp(open_tree_fd, CHRDEV1, 0))
> +			 die("failure: is_ixgrp");
> +
> +		/*
> +		 * In setgid directories newly created files always inherit the
> +		 * gid from the parent directory. Verify that the file is owned
> +		 * by gid 1000, not by gid 0.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		/*
> +		 * In setgid directories newly created directories always
> +		 * inherit the gid from the parent directory. Verify that the
> +		 * directory is owned by gid 1000, not by gid 0.
> +		 */
> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
> +			die("failure: check ownership");
> +
> +		if (unlinkat(open_tree_fd, FILE1, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, FILE2, 0))
> +			die("failure: delete");
> +
> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
> +			die("failure: delete");
> +
> +		/* create tmpfile via filesystem tmpfile api */
> +		if (supported) {
> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
> +			if (tmpfile_fd < 0)
> +				die("failure: create");
> +			/* link the temporary file into the filesystem, making it permanent */
> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
> +				die("failure: linkat");
> +			if (close(tmpfile_fd))
> +				die("failure: close");
> +			if (is_setgid(open_tree_fd, FILE3, 0))
> +				die("failure: is_setgid");
> +			if (!is_ixgrp(open_tree_fd, FILE3, 0))
> +				die("failure: is_ixgrp");
> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
> +				die("failure: check ownership");
> +			if (unlinkat(open_tree_fd, FILE3, 0))
> +				die("failure: delete");
> +		}
> +
> +		exit(EXIT_SUCCESS);
> +	}
> +	if (wait_for_pid(pid))
> +		goto out;
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	safe_close(attr.userns_fd);
> +	safe_close(file1_fd);
> +	safe_close(open_tree_fd);
> +
> +	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",                                                          },
> @@ -8174,3 +8868,13 @@ const struct test_suite s_setgid_create_umask_idmapped_mounts = {
>  	.tests = t_setgid_create_umask_idmapped_mounts,
>  	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped_mounts),
>  };
> +
> +static const struct test_struct t_setgid_create_acl_idmapped_mounts[] = {
> +	{ setgid_create_acl_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using acl in directories with setgid bit set on idmapped mount",                },
> +	{ setgid_create_acl_idmapped_in_userns,				T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using acl in directories with setgid bit set on idmapped mount inside userns",  },
> +};
> +
> +const struct test_suite s_setgid_create_acl_idmapped_mounts = {
> +	.tests = t_setgid_create_acl_idmapped_mounts,
> +	.nr_tests = ARRAY_SIZE(t_setgid_create_acl_idmapped_mounts),
> +};
> diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
> index a9fb31ea..3b0f0825 100644
> --- a/src/vfs/idmapped-mounts.h
> +++ b/src/vfs/idmapped-mounts.h
> @@ -15,5 +15,6 @@ extern const struct test_suite s_nested_userns;
>  extern const struct test_suite s_setattr_fix_968219708108;
>  extern const struct test_suite s_setxattr_fix_705191b03d50;
>  extern const struct test_suite s_setgid_create_umask_idmapped_mounts;
> +extern const struct test_suite s_setgid_create_acl_idmapped_mounts;
>  
>  #endif /* __IDMAPPED_MOUNTS_H */
> diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
> index e928a1f5..f7639de3 100644
> --- a/src/vfs/vfstest.c
> +++ b/src/vfs/vfstest.c
> @@ -27,6 +27,8 @@
>  #include "missing.h"
>  #include "utils.h"
>  
> +static char t_buf[PATH_MAX];
> +
>  static void init_vfstest_info(struct vfstest_info *info)
>  {
>  	info->t_overflowuid		= 65534;
> @@ -1913,6 +1915,325 @@ out:
>  	return fret;
>  }
>  
> +/*
> + * If the parent directory has a default acl then permissions are based off
> + * of that and current_umask() is ignored. Specifically, if the ACL has an
> + * ACL_MASK entry, the group permissions correspond to the permissions of
> + * the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
> + * group permissions correspond to the permissions of the ACL_GROUP_OBJ
> + * entry.
> + *
> + * Use setfacl to check whether inode strip S_ISGID works correctly.
> + *
> + * Test for commit
> + * 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers").
> + */

This comment is pretty helpful and I'd like to see similar ones on top
of all the other tests you're adding. That would make it easier for
other to understand what's going on.

> +
> +static int setgid_create_acl(const struct vfstest_info *info)
> +{
> +	int fret = -1;
> +	int file1_fd = -EBADF;
> +	int tmpfile_fd = -EBADF;
> +	pid_t pid;
> +	bool supported = false;
> +	mode_t mode;
> +
> +	if (!caps_supported())
> +		return 0;
> +
> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
> +			      S_IWUSR |
> +			      S_IRGRP |
> +			      S_IWGRP |
> +			      S_IROTH |
> +			      S_IWOTH |
> +			      S_IXUSR |
> +			      S_IXGRP |
> +			      S_IXOTH |
> +			      S_ISGID), 0) {
> +		log_stderr("failure: fchmod");
> +		goto out;
> +	}
> +
> +	/* Verify that the setgid bit got raised. */
> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
> +		log_stderr("failure: is_setgid");
> +		goto out;
> +	}
> +
> +	supported = openat_tmpfile_supported(info->t_dir1_fd);
> +
> +	umask(S_IXGRP);
> +	mode = umask(S_IXGRP);
> +	if (!(mode & S_IXGRP))
> +		die("failure: umask");
> +
> +	pid = fork();
> +	if (pid < 0) {
> +		log_stderr("failure: fork");
> +		goto out;
> +	}
> +	if (pid == 0) {
> +		/* The group permissions correspond to the permissions of the
> +		 * ACL_MASK entry.
> +		 */

It wouldn't hurt to add a longer comment to these setfacl calls
detailing what you're trying to test exactly otherwise it may be hard
for other who are not familiar with ACLs what this does. :)

> +		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
> +		if (system(t_buf))
> +			die("failure: system");

Other than that this looks good!
Christian

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 2/2] vfs: Add new setgid_create_acl test
  2022-08-29 13:20         ` Christian Brauner
@ 2022-08-30  9:03           ` xuyang2018.jy
  0 siblings, 0 replies; 15+ messages in thread
From: xuyang2018.jy @ 2022-08-30  9:03 UTC (permalink / raw)
  To: Christian Brauner; +Cc: fstests


on 2022/08/29 21:20, Christian Brauner wrote:
> On Tue, Jul 26, 2022 at 05:31:21PM +0800, Yang Xu wrote:
>> The current_umask() is stripped from the mode directly in the vfs if the
>> filesystem either doesn't support acls or the filesystem has been
>> mounted without posic acl support.
>>
>> If the filesystem does support acls then current_umask() stripping is
>> deferred to posix_acl_create(). So when the filesystem calls
>> posix_acl_create() and there are no acls set or not supported then
>> current_umask() will be stripped.
>>
>> If the parent directory has a default acl then permissions are based off
>> of that and current_umask() is ignored. Specifically, if the ACL has an
>> ACL_MASK entry, the group permissions correspond to the permissions of
>> the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
>> group permissions correspond to the permissions of the ACL_GROUP_OBJ
>> entry.
>>
>> Here we only use setfacl to set default acl or add ACL_MASK to check
>> whether inode strip  S_ISGID works correctly.
>>
>> Like umask test, this patch is also designed to test kernel patchset behaviour
>> "move S_ISGID stripping into the vfs* helper"
>>
>> Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
>> ---
> 
> Hey Yang,
> 
> Thanks for your patches! Just a minor comment below.
> 
>> 1.add kernel commit id
>> 2.move umask into parent process intead of child process
>> 3.remove duplicated comment for ACL_GROUP_OBJ and ACL_MASK because
>> we have mentioned it before the function
>>   src/vfs/idmapped-mounts.c | 704 ++++++++++++++++++++++++++++++++++++++
>>   src/vfs/idmapped-mounts.h |   1 +
>>   src/vfs/vfstest.c         | 345 ++++++++++++++++++-
>>   tests/generic/999         |  33 ++
>>   tests/generic/999.out     |   2 +
>>   5 files changed, 1084 insertions(+), 1 deletion(-)
>>   create mode 100755 tests/generic/999
>>   create mode 100644 tests/generic/999.out
>>
>> diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
>> index f63ac44b..f934cf4a 100644
>> --- a/src/vfs/idmapped-mounts.c
>> +++ b/src/vfs/idmapped-mounts.c
>> @@ -8083,6 +8083,700 @@ out:
>>   	return fret;
>>   }
>>   
>> +static int setgid_create_acl_idmapped(const struct vfstest_info *info)
>> +{
>> +	int fret = -1;
>> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
>> +	struct mount_attr attr = {
>> +		.attr_set = MOUNT_ATTR_IDMAP,
>> +	};
>> +	pid_t pid;
>> +	int tmpfile_fd = -EBADF;
>> +	bool supported = false;
>> +	char path[PATH_MAX];
>> +	mode_t mode;
>> +
>> +	if (!caps_supported())
>> +		return 0;
>> +
>> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
>> +			      S_IWUSR |
>> +			      S_IRGRP |
>> +			      S_IWGRP |
>> +			      S_IROTH |
>> +			      S_IWOTH |
>> +			      S_IXUSR |
>> +			      S_IXGRP |
>> +			      S_IXOTH |
>> +			      S_ISGID), 0) {
>> +		log_stderr("failure: fchmod");
>> +		goto out;
>> +	}
>> +
>> +	/* Verify that the sid bits got raised. */
>> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
>> +		log_stderr("failure: is_setgid");
>> +		goto out;
>> +	}
>> +
>> +	/* Changing mount properties on a detached mount. */
>> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
>> +	if (attr.userns_fd < 0) {
>> +		log_stderr("failure: get_userns_fd");
>> +		goto out;
>> +	}
>> +
>> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
>> +				     AT_EMPTY_PATH |
>> +				     AT_NO_AUTOMOUNT |
>> +				     AT_SYMLINK_NOFOLLOW |
>> +				     OPEN_TREE_CLOEXEC |
>> +				     OPEN_TREE_CLONE);
>> +	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;
>> +	}
>> +
>> +	supported = openat_tmpfile_supported(open_tree_fd);
>> +
>> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
>> +	 * whether has group execute or search permission.
>> +	 */
>> +	umask(S_IXGRP);
>> +	mode = umask(S_IXGRP);
>> +	if (!(mode & S_IXGRP))
>> +		die("failure: umask");
> 
> All the calls to umask() in this patch should be after the fork() in the
> child process. The die() helper is only supposed to be used in child
> processes anyway.

Oh, yes, I did't notice it before. Will move it into child process on v3.

Best Regards
Yang Xu

> The rest looks fine to me. So with the umask() moved
> you can add,
> Tested-by: Christian Brauner (Microsoft) <brauner@kernel.org>
> Reviewed-by: Christian Brauner (Microsoft) <brauner@kernel.org>

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 2/2] vfs: Add new setgid_create_acl test
  2022-08-29 13:52         ` Christian Brauner
@ 2022-08-30  9:06           ` xuyang2018.jy
  0 siblings, 0 replies; 15+ messages in thread
From: xuyang2018.jy @ 2022-08-30  9:06 UTC (permalink / raw)
  To: Christian Brauner; +Cc: fstests



on 2022/08/29 21:52, Christian Brauner wrote:
> On Tue, Jul 26, 2022 at 05:31:21PM +0800, Yang Xu wrote:
>> The current_umask() is stripped from the mode directly in the vfs if the
>> filesystem either doesn't support acls or the filesystem has been
>> mounted without posic acl support.
>>
>> If the filesystem does support acls then current_umask() stripping is
>> deferred to posix_acl_create(). So when the filesystem calls
>> posix_acl_create() and there are no acls set or not supported then
>> current_umask() will be stripped.
>>
>> If the parent directory has a default acl then permissions are based off
>> of that and current_umask() is ignored. Specifically, if the ACL has an
>> ACL_MASK entry, the group permissions correspond to the permissions of
>> the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
>> group permissions correspond to the permissions of the ACL_GROUP_OBJ
>> entry.
>>
>> Here we only use setfacl to set default acl or add ACL_MASK to check
>> whether inode strip  S_ISGID works correctly.
>>
>> Like umask test, this patch is also designed to test kernel patchset behaviour
>> "move S_ISGID stripping into the vfs* helper"
>>
>> Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
>> ---
> 
> Hey Yang,
> 
> Thank your for all that work! Just a few nits.
> 
>> 1.add kernel commit id
>> 2.move umask into parent process intead of child process
>> 3.remove duplicated comment for ACL_GROUP_OBJ and ACL_MASK because
>> we have mentioned it before the function
>>   src/vfs/idmapped-mounts.c | 704 ++++++++++++++++++++++++++++++++++++++
>>   src/vfs/idmapped-mounts.h |   1 +
>>   src/vfs/vfstest.c         | 345 ++++++++++++++++++-
>>   tests/generic/999         |  33 ++
>>   tests/generic/999.out     |   2 +
>>   5 files changed, 1084 insertions(+), 1 deletion(-)
>>   create mode 100755 tests/generic/999
>>   create mode 100644 tests/generic/999.out
>>
>> diff --git a/src/vfs/idmapped-mounts.c b/src/vfs/idmapped-mounts.c
>> index f63ac44b..f934cf4a 100644
>> --- a/src/vfs/idmapped-mounts.c
>> +++ b/src/vfs/idmapped-mounts.c
>> @@ -8083,6 +8083,700 @@ out:
>>   	return fret;
>>   }
>>   
>> +static int setgid_create_acl_idmapped(const struct vfstest_info *info)
>> +{
>> +	int fret = -1;
>> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
>> +	struct mount_attr attr = {
>> +		.attr_set = MOUNT_ATTR_IDMAP,
>> +	};
>> +	pid_t pid;
>> +	int tmpfile_fd = -EBADF;
>> +	bool supported = false;
>> +	char path[PATH_MAX];
>> +	mode_t mode;
>> +
>> +	if (!caps_supported())
>> +		return 0;
>> +
>> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
>> +			      S_IWUSR |
>> +			      S_IRGRP |
>> +			      S_IWGRP |
>> +			      S_IROTH |
>> +			      S_IWOTH |
>> +			      S_IXUSR |
>> +			      S_IXGRP |
>> +			      S_IXOTH |
>> +			      S_ISGID), 0) {
>> +		log_stderr("failure: fchmod");
>> +		goto out;
>> +	}
>> +
>> +	/* Verify that the sid bits got raised. */
>> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
>> +		log_stderr("failure: is_setgid");
>> +		goto out;
>> +	}
>> +
>> +	/* Changing mount properties on a detached mount. */
>> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
>> +	if (attr.userns_fd < 0) {
>> +		log_stderr("failure: get_userns_fd");
>> +		goto out;
>> +	}
>> +
>> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
>> +				     AT_EMPTY_PATH |
>> +				     AT_NO_AUTOMOUNT |
>> +				     AT_SYMLINK_NOFOLLOW |
>> +				     OPEN_TREE_CLOEXEC |
>> +				     OPEN_TREE_CLONE);
>> +	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;
>> +	}
>> +
>> +	supported = openat_tmpfile_supported(open_tree_fd);
>> +
>> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
>> +	 * whether has group execute or search permission.
>> +	 */
>> +	umask(S_IXGRP);
>> +	mode = umask(S_IXGRP);
>> +	if (!(mode & S_IXGRP))
>> +		die("failure: umask");
> 
> Please move all the umask() calls after the fork() into the child.

OK.
> 
>> +
>> +	pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		/* The group permissions correspond to the permissions of the
>> +		 * ACL_MASK entry.
>> +		 */
>> +		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
>> +		if (system(t_buf))
>> +			die("failure: system");
>> +
>> +		if (!switch_ids(10000, 11000))
>> +			die("failure: switch fsids");
>> +
>> +		/* create regular file via open() */
>> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
>> +		if (file1_fd < 0)
>> +			die("failure: create");
>> +
>> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
>> +		 * bit needs to be stripped.
>> +		 */
>> +		if (is_setgid(open_tree_fd, FILE1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create directory */
>> +		if (mkdirat(open_tree_fd, DIR1, 0000))
>> +			die("failure: create");
>> +
>> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
>> +			/* We're not in_group_p(). */
>> +			if (is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		} else {
>> +			/* Directories always inherit the setgid bit. */
>> +			if (!is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		}
>> +
>> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a special file via mknodat() vfs_create */
>> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, FILE2, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a whiteout device via mknodat() vfs_mknod */
>> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/*
>> +		 * In setgid directories newly created files always inherit the
>> +		 * gid from the parent directory. Verify that the file is owned
>> +		 * by gid 10000, not by gid 11000.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		/*
>> +		 * In setgid directories newly created directories always
>> +		 * inherit the gid from the parent directory. Verify that the
>> +		 * directory is owned by gid 10000, not by gid 11000.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (unlinkat(open_tree_fd, FILE1, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, FILE2, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: delete");
>> +
>> +		/* create tmpfile via filesystem tmpfile api */
>> +		if (supported) {
>> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
>> +			if (tmpfile_fd < 0)
>> +				die("failure: create");
>> +			/* link the temporary file into the filesystem, making it permanent */
>> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
>> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
>> +				die("failure: linkat");
>> +			if (close(tmpfile_fd))
>> +				die("failure: close");
>> +			if (is_setgid(open_tree_fd, FILE3, 0))
>> +				die("failure: is_setgid");
>> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
>> +				die("failure: is_ixgrp");
>> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
>> +				die("failure: check ownership");
>> +			if (unlinkat(open_tree_fd, FILE3, 0))
>> +				die("failure: delete");
>> +		}
>> +
>> +		exit(EXIT_SUCCESS);
>> +	}
>> +	if (wait_for_pid(pid))
>> +		goto out;
>> +
>> +	pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		/* The group permissions correspond to the permissions of the
>> +		 * ACL_GROUP_OBJ entry.
>> +		 */
>> +		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1);
>> +		if (system(t_buf))
>> +			die("failure: system");
>> +
>> +		if (!switch_ids(10000, 11000))
>> +			die("failure: switch fsids");
>> +
>> +		/* create regular file via open() */
>> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
>> +		if (file1_fd < 0)
>> +			die("failure: create");
>> +
>> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
>> +		 * bit needs to be stripped.
>> +		 */
>> +		if (is_setgid(open_tree_fd, FILE1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (!is_ixgrp(open_tree_fd, FILE1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create directory */
>> +		if (mkdirat(open_tree_fd, DIR1, 0000))
>> +			die("failure: create");
>> +
>> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
>> +			/* We're not in_group_p(). */
>> +			if (is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		} else {
>> +			/* Directories always inherit the setgid bit. */
>> +			if (!is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		}
>> +
>> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a special file via mknodat() vfs_create */
>> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, FILE2, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (!is_ixgrp(open_tree_fd, FILE2, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a whiteout device via mknodat() vfs_mknod */
>> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (!is_ixgrp(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/*
>> +		 * In setgid directories newly created files always inherit the
>> +		 * gid from the parent directory. Verify that the file is owned
>> +		 * by gid 10000, not by gid 11000.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		/*
>> +		 * In setgid directories newly created directories always
>> +		 * inherit the gid from the parent directory. Verify that the
>> +		 * directory is owned by gid 10000, not by gid 11000.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 10000, 10000))
>> +			die("failure: check ownership");
>> +
>> +		if (unlinkat(open_tree_fd, FILE1, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, FILE2, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: delete");
>> +
>> +		/* create tmpfile via filesystem tmpfile api */
>> +		if (supported) {
>> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
>> +			if (tmpfile_fd < 0)
>> +				die("failure: create");
>> +			/* link the temporary file into the filesystem, making it permanent */
>> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
>> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
>> +				die("failure: linkat");
>> +			if (close(tmpfile_fd))
>> +				die("failure: close");
>> +			if (is_setgid(open_tree_fd, FILE3, 0))
>> +				die("failure: is_setgid");
>> +			if (!is_ixgrp(open_tree_fd, FILE3, 0))
>> +				die("failure: is_ixgrp");
>> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 10000, 10000))
>> +				die("failure: check ownership");
>> +			if (unlinkat(open_tree_fd, FILE3, 0))
>> +				die("failure: delete");
>> +		}
>> +
>> +		exit(EXIT_SUCCESS);
>> +	}
>> +	if (wait_for_pid(pid))
>> +		goto out;
>> +
>> +	fret = 0;
>> +	log_debug("Ran test");
>> +out:
>> +	safe_close(attr.userns_fd);
>> +	safe_close(file1_fd);
>> +	safe_close(open_tree_fd);
>> +
>> +	return fret;
>> +}
>> +
>> +static int setgid_create_acl_idmapped_in_userns(const struct vfstest_info *info)
>> +{
>> +	int fret = -1;
>> +	int file1_fd = -EBADF, open_tree_fd = -EBADF;
>> +	struct mount_attr attr = {
>> +		.attr_set = MOUNT_ATTR_IDMAP,
>> +	};
>> +	pid_t pid;
>> +	int tmpfile_fd = -EBADF;
>> +	bool supported = false;
>> +	char path[PATH_MAX];
>> +	mode_t mode;
>> +
>> +	if (!caps_supported())
>> +		return 0;
>> +
>> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
>> +			      S_IWUSR |
>> +			      S_IRGRP |
>> +			      S_IWGRP |
>> +			      S_IROTH |
>> +			      S_IWOTH |
>> +			      S_IXUSR |
>> +			      S_IXGRP |
>> +			      S_IXOTH |
>> +			      S_ISGID), 0) {
>> +		log_stderr("failure: fchmod");
>> +		goto out;
>> +	}
>> +
>> +	/* Verify that the sid bits got raised. */
>> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
>> +		log_stderr("failure: is_setgid");
>> +		goto out;
>> +	}
>> +
>> +	/* Changing mount properties on a detached mount. */
>> +	attr.userns_fd	= get_userns_fd(0, 10000, 10000);
>> +	if (attr.userns_fd < 0) {
>> +		log_stderr("failure: get_userns_fd");
>> +		goto out;
>> +	}
>> +
>> +	open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
>> +				     AT_EMPTY_PATH |
>> +				     AT_NO_AUTOMOUNT |
>> +				     AT_SYMLINK_NOFOLLOW |
>> +				     OPEN_TREE_CLOEXEC |
>> +				     OPEN_TREE_CLONE);
>> +	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;
>> +	}
>> +
>> +	supported = openat_tmpfile_supported(open_tree_fd);
>> +
>> +	/* Only umask with S_IXGRP because inode strip S_ISGID will check mode
>> +	 * whether has group execute or search permission.
>> +	 */
>> +	umask(S_IXGRP);
>> +	mode = umask(S_IXGRP);
>> +	if (!(mode & S_IXGRP))
>> +		die("failure: umask");
>> +
>> +	/*
>> +	 * Below we verify that setgid inheritance for a newly created file or
>> +	 * directory works correctly. As part of this we need to verify that
>> +	 * newly created files or directories inherit their gid from their
>> +	 * parent directory. So we change the parent directorie's gid to 1000
>> +	 * and create a file with fs{g,u}id 0 and verify that the newly created
>> +	 * file and directory inherit gid 1000, not 0.
>> +	 */
>> +	if (fchownat(info->t_dir1_fd, "", -1, 1000, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) {
>> +		log_stderr("failure: fchownat");
>> +		goto out;
>> +	}
>> +
>> +	pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		/* The group permissions correspond to the permissions of the
>> +		 * ACL_MASK entry.
>> +		 */
>> +		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
>> +		if (system(t_buf))
>> +			die("failure: system");
>> +
>> +		if (!caps_supported()) {
>> +			log_debug("skip: capability library not installed");
>> +			exit(EXIT_SUCCESS);
>> +		}
>> +
>> +		if (!switch_userns(attr.userns_fd, 0, 0, false))
>> +			die("failure: switch_userns");
>> +
>> +		if (!caps_down_fsetid())
>> +			die("failure: caps_down_fsetid");
>> +
>> +		/* create regular file via open() */
>> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
>> +		if (file1_fd < 0)
>> +			die("failure: create");
>> +
>> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
>> +		 * bit needs to be stripped.
>> +		 */
>> +		if (is_setgid(open_tree_fd, FILE1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create directory */
>> +		if (mkdirat(open_tree_fd, DIR1, 0000))
>> +			die("failure: create");
>> +
>> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
>> +			/* We're not in_group_p(). */
>> +			if (is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		} else {
>> +			/* Directories always inherit the setgid bit. */
>> +			if (!is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		}
>> +
>> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a special file via mknodat() vfs_create */
>> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, FILE2, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, FILE2, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a whiteout device via mknodat() vfs_mknod */
>> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (is_ixgrp(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/*
>> +		 * In setgid directories newly created files always inherit the
>> +		 * gid from the parent directory. Verify that the file is owned
>> +		 * by gid 1000, not by gid 0.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		/*
>> +		 * In setgid directories newly created directories always
>> +		 * inherit the gid from the parent directory. Verify that the
>> +		 * directory is owned by gid 1000, not by gid 0.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (unlinkat(open_tree_fd, FILE1, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, FILE2, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: delete");
>> +
>> +		/* create tmpfile via filesystem tmpfile api */
>> +		if (supported) {
>> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
>> +			if (tmpfile_fd < 0)
>> +				die("failure: create");
>> +			/* link the temporary file into the filesystem, making it permanent */
>> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
>> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
>> +				die("failure: linkat");
>> +			if (close(tmpfile_fd))
>> +				die("failure: close");
>> +			if (is_setgid(open_tree_fd, FILE3, 0))
>> +				die("failure: is_setgid");
>> +			if (is_ixgrp(open_tree_fd, FILE3, 0))
>> +				die("failure: is_ixgrp");
>> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
>> +				die("failure: check ownership");
>> +			if (unlinkat(open_tree_fd, FILE3, 0))
>> +				die("failure: delete");
>> +		}
>> +
>> +		exit(EXIT_SUCCESS);
>> +	}
>> +	if (wait_for_pid(pid))
>> +		goto out;
>> +
>> +	pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		/* The group permissions correspond to the permissions of the
>> +		 * ACL_GROUP_OBJ entry.
>> +		 */
>> +		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rwx,o::rwx, %s/%s", info->t_mountpoint, T_DIR1);
>> +		if (system(t_buf))
>> +			die("failure: system");
>> +
>> +		if (!caps_supported()) {
>> +			log_debug("skip: capability library not installed");
>> +			exit(EXIT_SUCCESS);
>> +		}
>> +
>> +		if (!switch_userns(attr.userns_fd, 0, 0, false))
>> +			die("failure: switch_userns");
>> +
>> +		if (!caps_down_fsetid())
>> +			die("failure: caps_down_fsetid");
>> +
>> +		/* create regular file via open() */
>> +		file1_fd = openat(open_tree_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, S_IXGRP | S_ISGID);
>> +		if (file1_fd < 0)
>> +			die("failure: create");
>> +
>> +		/* Neither in_group_p() nor capable_wrt_inode_uidgid() so setgid
>> +		 * bit needs to be stripped.
>> +		 */
>> +		if (is_setgid(open_tree_fd, FILE1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (!is_ixgrp(open_tree_fd, FILE1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create directory */
>> +		if (mkdirat(open_tree_fd, DIR1, 0000))
>> +			die("failure: create");
>> +
>> +		if (xfs_irix_sgid_inherit_enabled(info->t_fstype)) {
>> +			/* We're not in_group_p(). */
>> +			if (is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		} else {
>> +			/* Directories always inherit the setgid bit. */
>> +			if (!is_setgid(open_tree_fd, DIR1, 0))
>> +				die("failure: is_setgid");
>> +		}
>> +
>> +		if (is_ixgrp(open_tree_fd, DIR1, 0))
>> +			die("failure: is_ixgrp");
>> +
>> +		/* create a special file via mknodat() vfs_create */
>> +		if (mknodat(open_tree_fd, FILE2, S_IFREG | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, FILE2, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (!is_ixgrp(open_tree_fd, FILE2, 0))
>> +			 die("failure: is_ixgrp");
>> +		/* create a whiteout device via mknodat() vfs_mknod */
>> +		if (mknodat(open_tree_fd, CHRDEV1, S_IFCHR | S_ISGID | S_IXGRP, 0))
>> +			die("failure: mknodat");
>> +
>> +		if (is_setgid(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: is_setgid");
>> +
>> +		if (!is_ixgrp(open_tree_fd, CHRDEV1, 0))
>> +			 die("failure: is_ixgrp");
>> +
>> +		/*
>> +		 * In setgid directories newly created files always inherit the
>> +		 * gid from the parent directory. Verify that the file is owned
>> +		 * by gid 1000, not by gid 0.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, FILE1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		/*
>> +		 * In setgid directories newly created directories always
>> +		 * inherit the gid from the parent directory. Verify that the
>> +		 * directory is owned by gid 1000, not by gid 0.
>> +		 */
>> +		if (!expected_uid_gid(open_tree_fd, DIR1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, FILE2, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (!expected_uid_gid(open_tree_fd, CHRDEV1, 0, 0, 1000))
>> +			die("failure: check ownership");
>> +
>> +		if (unlinkat(open_tree_fd, FILE1, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, DIR1, AT_REMOVEDIR))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, FILE2, 0))
>> +			die("failure: delete");
>> +
>> +		if (unlinkat(open_tree_fd, CHRDEV1, 0))
>> +			die("failure: delete");
>> +
>> +		/* create tmpfile via filesystem tmpfile api */
>> +		if (supported) {
>> +			tmpfile_fd = openat(open_tree_fd, ".", O_TMPFILE | O_RDWR, S_IXGRP | S_ISGID);
>> +			if (tmpfile_fd < 0)
>> +				die("failure: create");
>> +			/* link the temporary file into the filesystem, making it permanent */
>> +			snprintf(path, PATH_MAX,  "/proc/self/fd/%d", tmpfile_fd);
>> +			if (linkat(AT_FDCWD, path, open_tree_fd, FILE3, AT_SYMLINK_FOLLOW))
>> +				die("failure: linkat");
>> +			if (close(tmpfile_fd))
>> +				die("failure: close");
>> +			if (is_setgid(open_tree_fd, FILE3, 0))
>> +				die("failure: is_setgid");
>> +			if (!is_ixgrp(open_tree_fd, FILE3, 0))
>> +				die("failure: is_ixgrp");
>> +			if (!expected_uid_gid(open_tree_fd, FILE3, 0, 0, 1000))
>> +				die("failure: check ownership");
>> +			if (unlinkat(open_tree_fd, FILE3, 0))
>> +				die("failure: delete");
>> +		}
>> +
>> +		exit(EXIT_SUCCESS);
>> +	}
>> +	if (wait_for_pid(pid))
>> +		goto out;
>> +
>> +	fret = 0;
>> +	log_debug("Ran test");
>> +out:
>> +	safe_close(attr.userns_fd);
>> +	safe_close(file1_fd);
>> +	safe_close(open_tree_fd);
>> +
>> +	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",                                                          },
>> @@ -8174,3 +8868,13 @@ const struct test_suite s_setgid_create_umask_idmapped_mounts = {
>>   	.tests = t_setgid_create_umask_idmapped_mounts,
>>   	.nr_tests = ARRAY_SIZE(t_setgid_create_umask_idmapped_mounts),
>>   };
>> +
>> +static const struct test_struct t_setgid_create_acl_idmapped_mounts[] = {
>> +	{ setgid_create_acl_idmapped,					T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using acl in directories with setgid bit set on idmapped mount",                },
>> +	{ setgid_create_acl_idmapped_in_userns,				T_REQUIRE_IDMAPPED_MOUNTS,	"create operations by using acl in directories with setgid bit set on idmapped mount inside userns",  },
>> +};
>> +
>> +const struct test_suite s_setgid_create_acl_idmapped_mounts = {
>> +	.tests = t_setgid_create_acl_idmapped_mounts,
>> +	.nr_tests = ARRAY_SIZE(t_setgid_create_acl_idmapped_mounts),
>> +};
>> diff --git a/src/vfs/idmapped-mounts.h b/src/vfs/idmapped-mounts.h
>> index a9fb31ea..3b0f0825 100644
>> --- a/src/vfs/idmapped-mounts.h
>> +++ b/src/vfs/idmapped-mounts.h
>> @@ -15,5 +15,6 @@ extern const struct test_suite s_nested_userns;
>>   extern const struct test_suite s_setattr_fix_968219708108;
>>   extern const struct test_suite s_setxattr_fix_705191b03d50;
>>   extern const struct test_suite s_setgid_create_umask_idmapped_mounts;
>> +extern const struct test_suite s_setgid_create_acl_idmapped_mounts;
>>   
>>   #endif /* __IDMAPPED_MOUNTS_H */
>> diff --git a/src/vfs/vfstest.c b/src/vfs/vfstest.c
>> index e928a1f5..f7639de3 100644
>> --- a/src/vfs/vfstest.c
>> +++ b/src/vfs/vfstest.c
>> @@ -27,6 +27,8 @@
>>   #include "missing.h"
>>   #include "utils.h"
>>   
>> +static char t_buf[PATH_MAX];
>> +
>>   static void init_vfstest_info(struct vfstest_info *info)
>>   {
>>   	info->t_overflowuid		= 65534;
>> @@ -1913,6 +1915,325 @@ out:
>>   	return fret;
>>   }
>>   
>> +/*
>> + * If the parent directory has a default acl then permissions are based off
>> + * of that and current_umask() is ignored. Specifically, if the ACL has an
>> + * ACL_MASK entry, the group permissions correspond to the permissions of
>> + * the ACL_MASK entry. Otherwise, if the ACL has no ACL_MASK entry, the
>> + * group permissions correspond to the permissions of the ACL_GROUP_OBJ
>> + * entry.
>> + *
>> + * Use setfacl to check whether inode strip S_ISGID works correctly.
>> + *
>> + * Test for commit
>> + * 1639a49ccdce ("fs: move S_ISGID stripping into the vfs_*() helpers").
>> + */
> 
> This comment is pretty helpful and I'd like to see similar ones on top
> of all the other tests you're adding. That would make it easier for
> other to understand what's going on.

OK.
> 
>> +
>> +static int setgid_create_acl(const struct vfstest_info *info)
>> +{
>> +	int fret = -1;
>> +	int file1_fd = -EBADF;
>> +	int tmpfile_fd = -EBADF;
>> +	pid_t pid;
>> +	bool supported = false;
>> +	mode_t mode;
>> +
>> +	if (!caps_supported())
>> +		return 0;
>> +
>> +	if (fchmod(info->t_dir1_fd, S_IRUSR |
>> +			      S_IWUSR |
>> +			      S_IRGRP |
>> +			      S_IWGRP |
>> +			      S_IROTH |
>> +			      S_IWOTH |
>> +			      S_IXUSR |
>> +			      S_IXGRP |
>> +			      S_IXOTH |
>> +			      S_ISGID), 0) {
>> +		log_stderr("failure: fchmod");
>> +		goto out;
>> +	}
>> +
>> +	/* Verify that the setgid bit got raised. */
>> +	if (!is_setgid(info->t_dir1_fd, "", AT_EMPTY_PATH)) {
>> +		log_stderr("failure: is_setgid");
>> +		goto out;
>> +	}
>> +
>> +	supported = openat_tmpfile_supported(info->t_dir1_fd);
>> +
>> +	umask(S_IXGRP);
>> +	mode = umask(S_IXGRP);
>> +	if (!(mode & S_IXGRP))
>> +		die("failure: umask");
>> +
>> +	pid = fork();
>> +	if (pid < 0) {
>> +		log_stderr("failure: fork");
>> +		goto out;
>> +	}
>> +	if (pid == 0) {
>> +		/* The group permissions correspond to the permissions of the
>> +		 * ACL_MASK entry.
>> +		 */
> 
> It wouldn't hurt to add a longer comment to these setfacl calls
> detailing what you're trying to test exactly otherwise it may be hard
> for other who are not familiar with ACLs what this does. :)

Yes, will add a longer comment.

Best Regards
Yang Xu
> 
>> +		snprintf(t_buf, sizeof(t_buf), "setfacl -d -m u::rwx,g::rw,o::rwx,m:rw %s/%s", info->t_mountpoint, T_DIR1);
>> +		if (system(t_buf))
>> +			die("failure: system");
> 
> Other than that this looks good!
> Christian

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2022-08-30  9:06 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-05-20 16:04 [PATCH v1 1/2] vfs: Add new setgid_create_umask test Yang Xu
2022-05-20 16:04 ` [PATCH v1 2/2] vfs: Add new setgid_create_acl test Yang Xu
2022-07-16 16:12 ` [PATCH v1 1/2] vfs: Add new setgid_create_umask test Zorro Lang
2022-07-19  7:20   ` xuyang2018.jy
2022-07-25  7:55 ` xuyang2018.jy
2022-07-25 14:20   ` Christian Brauner
2022-07-26  8:20     ` xuyang2018.jy
2022-07-26  9:31     ` [PATCH v2 " Yang Xu
2022-07-26  9:31       ` [PATCH v2 2/2] vfs: Add new setgid_create_acl test Yang Xu
2022-08-29 13:20         ` Christian Brauner
2022-08-30  9:03           ` xuyang2018.jy
2022-08-29 13:52         ` Christian Brauner
2022-08-30  9:06           ` xuyang2018.jy
2022-08-15 10:02       ` [PATCH v2 1/2] vfs: Add new setgid_create_umask test xuyang2018.jy
2022-08-29  1:14         ` xuyang2018.jy

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.