* [PATCH 1/7] idmapped-mounts: remove unused set_cloexec() helper
2021-05-07 15:00 [PATCH 0/7] idmapped mounts: extend testsuite and fixes Christian Brauner
@ 2021-05-07 15:00 ` Christian Brauner
2021-05-07 15:00 ` [PATCH 2/7] idmapped-mounts: add missing newline to print_r() Christian Brauner
` (6 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Christian Brauner @ 2021-05-07 15:00 UTC (permalink / raw)
To: Eryu Guan, fstests; +Cc: Christoph Hellwig, Christian Brauner
From: Christian Brauner <christian.brauner@ubuntu.com>
This function has never been used so remove it.
Cc: fstests@vger.kernel.org
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
src/idmapped-mounts/idmapped-mounts.c | 5 -----
1 file changed, 5 deletions(-)
diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c
index 870a8fe7..66bdd817 100644
--- a/src/idmapped-mounts/idmapped-mounts.c
+++ b/src/idmapped-mounts/idmapped-mounts.c
@@ -371,11 +371,6 @@ static bool is_sticky(int dfd, const char *path, int flags)
return (st.st_mode & S_ISVTX) > 0;
}
-static inline int set_cloexec(int fd)
-{
- return fcntl(fd, F_SETFD, FD_CLOEXEC);
-}
-
static inline bool switch_fsids(uid_t fsuid, gid_t fsgid)
{
if (setfsgid(fsgid))
--
2.27.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 2/7] idmapped-mounts: add missing newline to print_r()
2021-05-07 15:00 [PATCH 0/7] idmapped mounts: extend testsuite and fixes Christian Brauner
2021-05-07 15:00 ` [PATCH 1/7] idmapped-mounts: remove unused set_cloexec() helper Christian Brauner
@ 2021-05-07 15:00 ` Christian Brauner
2021-05-07 15:00 ` [PATCH 3/7] idmapped-mounts: split out run_test() function Christian Brauner
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Christian Brauner @ 2021-05-07 15:00 UTC (permalink / raw)
To: Eryu Guan, fstests; +Cc: Christoph Hellwig, Christian Brauner
From: Christian Brauner <christian.brauner@ubuntu.com>
The function missed to print a newline making the output difficult to
read when running with DEBUG_TRACE.
Cc: fstests@vger.kernel.org
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
src/idmapped-mounts/idmapped-mounts.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c
index 66bdd817..59a3daa8 100644
--- a/src/idmapped-mounts/idmapped-mounts.c
+++ b/src/idmapped-mounts/idmapped-mounts.c
@@ -620,7 +620,7 @@ __attribute__((unused)) static int print_r(int fd, const char *path)
ret = fstatat(fd, path, &st,
AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW);
if (!ret)
- fprintf(stderr, "mode(%o):uid(%d):gid(%d) -> %s",
+ fprintf(stderr, "mode(%o):uid(%d):gid(%d) -> %s\n",
(st.st_mode & ~S_IFMT), st.st_uid, st.st_gid,
(path && *path) ? path : "(null)");
--
2.27.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 3/7] idmapped-mounts: split out run_test() function
2021-05-07 15:00 [PATCH 0/7] idmapped mounts: extend testsuite and fixes Christian Brauner
2021-05-07 15:00 ` [PATCH 1/7] idmapped-mounts: remove unused set_cloexec() helper Christian Brauner
2021-05-07 15:00 ` [PATCH 2/7] idmapped-mounts: add missing newline to print_r() Christian Brauner
@ 2021-05-07 15:00 ` Christian Brauner
2021-05-07 15:00 ` [PATCH 4/7] generic/637: add fscaps regression test Christian Brauner
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Christian Brauner @ 2021-05-07 15:00 UTC (permalink / raw)
To: Eryu Guan, fstests; +Cc: Christoph Hellwig, Christian Brauner
From: Christian Brauner <christian.brauner@ubuntu.com>
to make it easier to run subsets of tests.
Cc: fstests@vger.kernel.org
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
src/idmapped-mounts/idmapped-mounts.c | 70 ++++++++++++++++-----------
1 file changed, 41 insertions(+), 29 deletions(-)
diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c
index 59a3daa8..4d93b721 100644
--- a/src/idmapped-mounts/idmapped-mounts.c
+++ b/src/idmapped-mounts/idmapped-mounts.c
@@ -82,6 +82,8 @@
#define die(format, ...) die_errno(errno, format, ##__VA_ARGS__)
+#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
+
uid_t t_overflowuid = 65534;
gid_t t_overflowgid = 65534;
@@ -8735,7 +8737,7 @@ static const struct option longopts[] = {
struct t_idmapped_mounts {
int (*test)(void);
const char *description;
-} t_idmapped_mounts[] = {
+} basic_suite[] = {
{ acls, "posix acls on regular mounts", },
{ create_in_userns, "create operations in user namespace", },
{ device_node_in_userns, "device node in user namespace", },
@@ -8787,9 +8789,44 @@ struct t_idmapped_mounts {
{ threaded_idmapped_mount_interactions, "threaded operations on idmapped mounts", },
};
+static bool run_test(struct t_idmapped_mounts suite[], size_t suite_size)
+{
+ int i;
+
+ for (i = 0; i < suite_size; i++) {
+ struct t_idmapped_mounts *t = &suite[i];
+ int ret;
+ pid_t pid;
+
+ test_setup();
+
+ pid = fork();
+ if (pid < 0)
+ return false;
+
+ if (pid == 0) {
+ ret = t->test();
+ if (ret) {
+ fprintf(stderr, "failure: %s\n", t->description);
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+
+ ret = wait_for_pid(pid);
+ test_cleanup();
+
+ if (ret)
+ return false;
+ }
+
+ return true;
+}
+
int main(int argc, char *argv[])
{
- int i, fret, ret;
+ int fret, ret;
int index = 0;
bool supported = false;
@@ -8876,33 +8913,8 @@ int main(int argc, char *argv[])
fret = EXIT_FAILURE;
- /* Proper test suite run. */
- for (i = 0; i < (sizeof(t_idmapped_mounts) / sizeof(t_idmapped_mounts[0])); i++) {
- struct t_idmapped_mounts *t = &t_idmapped_mounts[i];
- pid_t pid;
-
- test_setup();
-
- pid = fork();
- if (pid < 0)
- goto out;
-
- if (pid == 0) {
- ret = t->test();
- if (ret) {
- fprintf(stderr, "failure: %s\n", t->description);
- exit(EXIT_FAILURE);
- }
-
- exit(EXIT_SUCCESS);
- }
-
- ret = wait_for_pid(pid);
- test_cleanup();
-
- if (ret)
- goto out;
- }
+ if (!run_test(basic_suite, ARRAY_SIZE(basic_suite)))
+ goto out;
fret = EXIT_SUCCESS;
--
2.27.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 4/7] generic/637: add fscaps regression test
2021-05-07 15:00 [PATCH 0/7] idmapped mounts: extend testsuite and fixes Christian Brauner
` (2 preceding siblings ...)
2021-05-07 15:00 ` [PATCH 3/7] idmapped-mounts: split out run_test() function Christian Brauner
@ 2021-05-07 15:00 ` Christian Brauner
2021-05-23 15:07 ` Eryu Guan
2021-05-07 15:00 ` [PATCH 5/7] idmapped-mounts: refactor helpers Christian Brauner
` (3 subsequent siblings)
7 siblings, 1 reply; 10+ messages in thread
From: Christian Brauner @ 2021-05-07 15:00 UTC (permalink / raw)
To: Eryu Guan, fstests; +Cc: Christoph Hellwig, Christian Brauner
From: Christian Brauner <christian.brauner@ubuntu.com>
Add a test to verify that setting a v3 fscap from an idmapped mount
works as expected. This and other related use-cases were regressed by
commit [1] which was reverted in [2] and the proper fix merged right
before v5.12 was released in [3].
[1]: commit 3b0c2d3eaa83 ("Revert 95ebabde382c ("capabilities: Don't allow writing ambiguous v3 file capabilities")")
[2]: commit 95ebabde382c ("capabilities: Don't allow writing ambiguous v3 file capabilities")
[3]: commit db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
Cc: fstests@vger.kernel.org
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
.gitignore | 1 +
src/idmapped-mounts/Makefile | 12 ++-
src/idmapped-mounts/idmapped-mounts.c | 135 +++++++++++++++++++++++++-
tests/generic/637 | 42 ++++++++
tests/generic/637.out | 2 +
tests/generic/group | 1 +
6 files changed, 188 insertions(+), 5 deletions(-)
create mode 100755 tests/generic/637
create mode 100644 tests/generic/637.out
diff --git a/.gitignore b/.gitignore
index 4cc9c807..da48e6f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -180,6 +180,7 @@
/src/aio-dio-regress/aiodio_sparse2
/src/idmapped-mounts/idmapped-mounts
/src/idmapped-mounts/mount-idmapped
+/src/idmapped-mounts/fscaps-in-ancestor-userns
/src/log-writes/replay-log
/src/perf/*.pyc
diff --git a/src/idmapped-mounts/Makefile b/src/idmapped-mounts/Makefile
index ad4ddc99..1bab6471 100644
--- a/src/idmapped-mounts/Makefile
+++ b/src/idmapped-mounts/Makefile
@@ -3,7 +3,9 @@
TOPDIR = ../..
include $(TOPDIR)/include/builddefs
-TARGETS = idmapped-mounts mount-idmapped
+BINS = idmapped-mounts mount-idmapped
+LINKS = fscaps-in-ancestor-userns
+TARGETS = $(BINS) $(LINKS)
CFILES_IDMAPPED_MOUNTS = idmapped-mounts.c utils.c
CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c
@@ -29,12 +31,18 @@ idmapped-mounts: $(CFILES_IDMAPPED_MOUNTS)
@echo " [CC] $@"
$(Q)$(LTLINK) $(CFILES_IDMAPPED_MOUNTS) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
+fscaps-in-ancestor-userns:
+ ln -sf idmapped-mounts fscaps-in-ancestor-userns
+
mount-idmapped: $(CFILES_MOUNT_IDMAPPED)
@echo " [CC] $@"
$(Q)$(LTLINK) $(CFILES_MOUNT_IDMAPPED) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
install:
$(INSTALL) -m 755 -d $(PKG_LIB_DIR)/src/idmapped-mounts
- $(INSTALL) -m 755 $(TARGETS) $(PKG_LIB_DIR)/src/idmapped-mounts
+ $(INSTALL) -m 755 $(BINS) $(PKG_LIB_DIR)/src/idmapped-mounts
+ cd $(PKG_LIB_DIR)/src/idmapped-mounts && \
+ rm -f $(LINKS) && \
+ $(LN_S) idmapped-mounts fscaps-in-ancestor-userns
-include .dep
diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c
index 4d93b721..94b83c01 100644
--- a/src/idmapped-mounts/idmapped-mounts.c
+++ b/src/idmapped-mounts/idmapped-mounts.c
@@ -3201,6 +3201,121 @@ out:
return fret;
}
+static int fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns(void)
+{
+ int fret = -1;
+ int file1_fd = -EBADF, file1_fd2 = -EBADF, open_tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ file1_fd = openat(t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644);
+ if (file1_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /* Skip if vfs caps are unsupported. */
+ if (set_dummy_vfs_caps(file1_fd, 0, 1000))
+ return 0;
+
+ if (fremovexattr(file1_fd, "security.capability")) {
+ log_stderr("failure: fremovexattr");
+ goto out;
+ }
+ if (expected_dummy_vfs_caps_uid(file1_fd, -1)) {
+ log_stderr("failure: expected_dummy_vfs_caps_uid");
+ goto out;
+ }
+ if (errno != ENODATA) {
+ log_stderr("failure: errno");
+ 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(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;
+ }
+
+ file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, 0);
+ if (file1_fd2 < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /*
+ * Verify we can set an v3 fscap for real root this was regressed at
+ * some point. Make sure this doesn't happen again!
+ */
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(attr.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ if (expected_dummy_vfs_caps_uid(file1_fd2, -1))
+ die("failure: expected_dummy_vfs_caps_uid");
+ if (errno != ENODATA)
+ die("failure: errno");
+
+ if (set_dummy_vfs_caps(file1_fd2, 0, 0))
+ die("failure: set_dummy_vfs_caps");
+
+ if (!expected_dummy_vfs_caps_uid(file1_fd2, 0))
+ die("failure: expected_dummy_vfs_caps_uid");
+
+ if (!expected_dummy_vfs_caps_uid(file1_fd, 0) && errno != EOVERFLOW)
+ die("failure: expected_dummy_vfs_caps_uid");
+
+ exit(EXIT_SUCCESS);
+ }
+
+ if (wait_for_pid(pid))
+ goto out;
+
+ if (!expected_dummy_vfs_caps_uid(file1_fd2, 10000)) {
+ log_stderr("failure: expected_dummy_vfs_caps_uid");
+ goto out;
+ }
+
+ if (!expected_dummy_vfs_caps_uid(file1_fd, 0)) {
+ log_stderr("failure: expected_dummy_vfs_caps_uid");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(file1_fd);
+ safe_close(file1_fd2);
+ safe_close(open_tree_fd);
+
+ return fret;
+}
+
static int fscaps_idmapped_mounts_in_userns_separate_userns(void)
{
int fret = -1;
@@ -8745,7 +8860,7 @@ struct t_idmapped_mounts {
{ fscaps, "fscaps on regular mounts", },
{ fscaps_idmapped_mounts, "fscaps on idmapped mounts", },
{ fscaps_idmapped_mounts_in_userns, "fscaps on idmapped mounts in user namespace", },
- { fscaps_idmapped_mounts_in_userns_separate_userns, "fscaps on idmapped mounts in user namespace with different id mappings ", },
+ { fscaps_idmapped_mounts_in_userns_separate_userns, "fscaps on idmapped mounts in user namespace with different id mappings", },
{ fsids_mapped, "mapped fsids", },
{ fsids_unmapped, "unmapped fsids", },
{ hardlink_crossing_mounts, "cross mount hardlink", },
@@ -8789,6 +8904,10 @@ struct t_idmapped_mounts {
{ threaded_idmapped_mount_interactions, "threaded operations on idmapped mounts", },
};
+struct t_idmapped_mounts fscaps_in_ancestor_userns[] = {
+ { fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns, "fscaps on idmapped mounts in user namespace writing fscap valid in ancestor userns", },
+};
+
static bool run_test(struct t_idmapped_mounts suite[], size_t suite_size)
{
int i;
@@ -8826,6 +8945,7 @@ static bool run_test(struct t_idmapped_mounts suite[], size_t suite_size)
int main(int argc, char *argv[])
{
+ const char *invocation_name;
int fret, ret;
int index = 0;
bool supported = false;
@@ -8913,8 +9033,17 @@ int main(int argc, char *argv[])
fret = EXIT_FAILURE;
- if (!run_test(basic_suite, ARRAY_SIZE(basic_suite)))
- goto out;
+ invocation_name = basename(argv[0]);
+ if (strcmp(invocation_name, "idmapped-mounts") == 0) {
+ if (!run_test(basic_suite, ARRAY_SIZE(basic_suite)))
+ goto out;
+ } else if (strcmp(invocation_name, "fscaps-in-ancestor-userns") == 0) {
+ if (!run_test(fscaps_in_ancestor_userns,
+ ARRAY_SIZE(fscaps_in_ancestor_userns)))
+ goto out;
+ } else {
+ die("idmapped mount test suite \"%s\" unknown", invocation_name);
+ }
fret = EXIT_SUCCESS;
diff --git a/tests/generic/637 b/tests/generic/637
new file mode 100755
index 00000000..25c601b5
--- /dev/null
+++ b/tests/generic/637
@@ -0,0 +1,42 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2021 Christian Brauner. All Rights Reserved.
+#
+# FS QA Test 637
+#
+# Test that fscaps on idmapped mounts behave correctly.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1 # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+ cd /
+ rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+
+_supported_fs generic
+_require_idmapped_mounts
+_require_test
+
+echo "Silence is golden"
+
+$here/src/idmapped-mounts/fscaps-in-ancestor-userns --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
+
+status=$?
+exit
diff --git a/tests/generic/637.out b/tests/generic/637.out
new file mode 100644
index 00000000..55a3d825
--- /dev/null
+++ b/tests/generic/637.out
@@ -0,0 +1,2 @@
+QA output created by 637
+Silence is golden
diff --git a/tests/generic/group b/tests/generic/group
index 105763c4..9dfefdf4 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -639,3 +639,4 @@
634 auto quick atime bigtime
635 auto quick atime bigtime shutdown
636 auto quick swap
+637 auto quick idmapped
--
2.27.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 4/7] generic/637: add fscaps regression test
2021-05-07 15:00 ` [PATCH 4/7] generic/637: add fscaps regression test Christian Brauner
@ 2021-05-23 15:07 ` Eryu Guan
0 siblings, 0 replies; 10+ messages in thread
From: Eryu Guan @ 2021-05-23 15:07 UTC (permalink / raw)
To: Christian Brauner; +Cc: fstests, Christoph Hellwig, Christian Brauner
On Fri, May 07, 2021 at 05:00:57PM +0200, Christian Brauner wrote:
> From: Christian Brauner <christian.brauner@ubuntu.com>
>
> Add a test to verify that setting a v3 fscap from an idmapped mount
> works as expected. This and other related use-cases were regressed by
> commit [1] which was reverted in [2] and the proper fix merged right
> before v5.12 was released in [3].
>
> [1]: commit 3b0c2d3eaa83 ("Revert 95ebabde382c ("capabilities: Don't allow writing ambiguous v3 file capabilities")")
> [2]: commit 95ebabde382c ("capabilities: Don't allow writing ambiguous v3 file capabilities")
> [3]: commit db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
> Cc: fstests@vger.kernel.org
> Cc: Christoph Hellwig <hch@lst.de>
> Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
Sorry for the late review..
> ---
> .gitignore | 1 +
> src/idmapped-mounts/Makefile | 12 ++-
> src/idmapped-mounts/idmapped-mounts.c | 135 +++++++++++++++++++++++++-
> tests/generic/637 | 42 ++++++++
> tests/generic/637.out | 2 +
> tests/generic/group | 1 +
> 6 files changed, 188 insertions(+), 5 deletions(-)
> create mode 100755 tests/generic/637
> create mode 100644 tests/generic/637.out
>
> diff --git a/.gitignore b/.gitignore
> index 4cc9c807..da48e6f8 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -180,6 +180,7 @@
> /src/aio-dio-regress/aiodio_sparse2
> /src/idmapped-mounts/idmapped-mounts
> /src/idmapped-mounts/mount-idmapped
> +/src/idmapped-mounts/fscaps-in-ancestor-userns
> /src/log-writes/replay-log
> /src/perf/*.pyc
>
> diff --git a/src/idmapped-mounts/Makefile b/src/idmapped-mounts/Makefile
> index ad4ddc99..1bab6471 100644
> --- a/src/idmapped-mounts/Makefile
> +++ b/src/idmapped-mounts/Makefile
> @@ -3,7 +3,9 @@
> TOPDIR = ../..
> include $(TOPDIR)/include/builddefs
>
> -TARGETS = idmapped-mounts mount-idmapped
> +BINS = idmapped-mounts mount-idmapped
> +LINKS = fscaps-in-ancestor-userns
> +TARGETS = $(BINS) $(LINKS)
> CFILES_IDMAPPED_MOUNTS = idmapped-mounts.c utils.c
> CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c
>
> @@ -29,12 +31,18 @@ idmapped-mounts: $(CFILES_IDMAPPED_MOUNTS)
> @echo " [CC] $@"
> $(Q)$(LTLINK) $(CFILES_IDMAPPED_MOUNTS) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
>
> +fscaps-in-ancestor-userns:
> + ln -sf idmapped-mounts fscaps-in-ancestor-userns
> +
> mount-idmapped: $(CFILES_MOUNT_IDMAPPED)
> @echo " [CC] $@"
> $(Q)$(LTLINK) $(CFILES_MOUNT_IDMAPPED) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
>
> install:
> $(INSTALL) -m 755 -d $(PKG_LIB_DIR)/src/idmapped-mounts
> - $(INSTALL) -m 755 $(TARGETS) $(PKG_LIB_DIR)/src/idmapped-mounts
> + $(INSTALL) -m 755 $(BINS) $(PKG_LIB_DIR)/src/idmapped-mounts
> + cd $(PKG_LIB_DIR)/src/idmapped-mounts && \
> + rm -f $(LINKS) && \
> + $(LN_S) idmapped-mounts fscaps-in-ancestor-userns
>
> -include .dep
> diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c
> index 4d93b721..94b83c01 100644
> --- a/src/idmapped-mounts/idmapped-mounts.c
> +++ b/src/idmapped-mounts/idmapped-mounts.c
> @@ -3201,6 +3201,121 @@ out:
> return fret;
> }
>
> +static int fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns(void)
> +{
> + int fret = -1;
> + int file1_fd = -EBADF, file1_fd2 = -EBADF, open_tree_fd = -EBADF;
> + struct mount_attr attr = {
> + .attr_set = MOUNT_ATTR_IDMAP,
> + };
> + pid_t pid;
> +
> + file1_fd = openat(t_dir1_fd, FILE1, O_CREAT | O_EXCL | O_CLOEXEC, 0644);
> + if (file1_fd < 0) {
> + log_stderr("failure: openat");
> + goto out;
> + }
> +
> + /* Skip if vfs caps are unsupported. */
> + if (set_dummy_vfs_caps(file1_fd, 0, 1000))
> + return 0;
> +
> + if (fremovexattr(file1_fd, "security.capability")) {
> + log_stderr("failure: fremovexattr");
> + goto out;
> + }
> + if (expected_dummy_vfs_caps_uid(file1_fd, -1)) {
> + log_stderr("failure: expected_dummy_vfs_caps_uid");
> + goto out;
> + }
> + if (errno != ENODATA) {
> + log_stderr("failure: errno");
> + 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(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;
> + }
> +
> + file1_fd2 = openat(open_tree_fd, FILE1, O_RDWR | O_CLOEXEC, 0);
> + if (file1_fd2 < 0) {
> + log_stderr("failure: openat");
> + goto out;
> + }
> +
> + /*
> + * Verify we can set an v3 fscap for real root this was regressed at
> + * some point. Make sure this doesn't happen again!
> + */
> + pid = fork();
> + if (pid < 0) {
> + log_stderr("failure: fork");
> + goto out;
> + }
> + if (pid == 0) {
> + if (!switch_userns(attr.userns_fd, 0, 0, false))
> + die("failure: switch_userns");
> +
> + if (expected_dummy_vfs_caps_uid(file1_fd2, -1))
> + die("failure: expected_dummy_vfs_caps_uid");
> + if (errno != ENODATA)
> + die("failure: errno");
> +
> + if (set_dummy_vfs_caps(file1_fd2, 0, 0))
> + die("failure: set_dummy_vfs_caps");
> +
> + if (!expected_dummy_vfs_caps_uid(file1_fd2, 0))
> + die("failure: expected_dummy_vfs_caps_uid");
> +
> + if (!expected_dummy_vfs_caps_uid(file1_fd, 0) && errno != EOVERFLOW)
> + die("failure: expected_dummy_vfs_caps_uid");
> +
> + exit(EXIT_SUCCESS);
> + }
> +
> + if (wait_for_pid(pid))
> + goto out;
> +
> + if (!expected_dummy_vfs_caps_uid(file1_fd2, 10000)) {
> + log_stderr("failure: expected_dummy_vfs_caps_uid");
> + goto out;
> + }
> +
> + if (!expected_dummy_vfs_caps_uid(file1_fd, 0)) {
> + log_stderr("failure: expected_dummy_vfs_caps_uid");
> + goto out;
> + }
> +
> + fret = 0;
> + log_debug("Ran test");
> +out:
> + safe_close(attr.userns_fd);
> + safe_close(file1_fd);
> + safe_close(file1_fd2);
> + safe_close(open_tree_fd);
> +
> + return fret;
> +}
> +
> static int fscaps_idmapped_mounts_in_userns_separate_userns(void)
> {
> int fret = -1;
> @@ -8745,7 +8860,7 @@ struct t_idmapped_mounts {
> { fscaps, "fscaps on regular mounts", },
> { fscaps_idmapped_mounts, "fscaps on idmapped mounts", },
> { fscaps_idmapped_mounts_in_userns, "fscaps on idmapped mounts in user namespace", },
> - { fscaps_idmapped_mounts_in_userns_separate_userns, "fscaps on idmapped mounts in user namespace with different id mappings ", },
> + { fscaps_idmapped_mounts_in_userns_separate_userns, "fscaps on idmapped mounts in user namespace with different id mappings", },
> { fsids_mapped, "mapped fsids", },
> { fsids_unmapped, "unmapped fsids", },
> { hardlink_crossing_mounts, "cross mount hardlink", },
> @@ -8789,6 +8904,10 @@ struct t_idmapped_mounts {
> { threaded_idmapped_mount_interactions, "threaded operations on idmapped mounts", },
> };
>
> +struct t_idmapped_mounts fscaps_in_ancestor_userns[] = {
> + { fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns, "fscaps on idmapped mounts in user namespace writing fscap valid in ancestor userns", },
> +};
> +
> static bool run_test(struct t_idmapped_mounts suite[], size_t suite_size)
> {
> int i;
> @@ -8826,6 +8945,7 @@ static bool run_test(struct t_idmapped_mounts suite[], size_t suite_size)
>
> int main(int argc, char *argv[])
> {
> + const char *invocation_name;
> int fret, ret;
> int index = 0;
> bool supported = false;
> @@ -8913,8 +9033,17 @@ int main(int argc, char *argv[])
>
> fret = EXIT_FAILURE;
>
> - if (!run_test(basic_suite, ARRAY_SIZE(basic_suite)))
> - goto out;
> + invocation_name = basename(argv[0]);
> + if (strcmp(invocation_name, "idmapped-mounts") == 0) {
> + if (!run_test(basic_suite, ARRAY_SIZE(basic_suite)))
> + goto out;
> + } else if (strcmp(invocation_name, "fscaps-in-ancestor-userns") == 0) {
I think there's no need to create symlink for each new test and use
different invocation name to run the new test. How about always using
the idmapped-mounts binary, just introduce a new option to specify which
test group to run? e.g. introduce a "-t" option and run
./idmapped-mounts -t basic_suite # for existing tests, and
./idmapped-mounts -t fscaps_in_ancester_userns # for this new test
Thanks,
Eryu
> + if (!run_test(fscaps_in_ancestor_userns,
> + ARRAY_SIZE(fscaps_in_ancestor_userns)))
> + goto out;
> + } else {
> + die("idmapped mount test suite \"%s\" unknown", invocation_name);
> + }
>
> fret = EXIT_SUCCESS;
>
> diff --git a/tests/generic/637 b/tests/generic/637
> new file mode 100755
> index 00000000..25c601b5
> --- /dev/null
> +++ b/tests/generic/637
> @@ -0,0 +1,42 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2021 Christian Brauner. All Rights Reserved.
> +#
> +# FS QA Test 637
> +#
> +# Test that fscaps on idmapped mounts behave correctly.
> +#
> +seq=`basename $0`
> +seqres=$RESULT_DIR/$seq
> +echo "QA output created by $seq"
> +
> +here=`pwd`
> +tmp=/tmp/$$
> +status=1 # failure is the default!
> +trap "_cleanup; exit \$status" 0 1 2 3 15
> +
> +_cleanup()
> +{
> + cd /
> + rm -f $tmp.*
> +}
> +
> +# get standard environment, filters and checks
> +. ./common/rc
> +. ./common/filter
> +
> +# remove previous $seqres.full before test
> +rm -f $seqres.full
> +
> +# real QA test starts here
> +
> +_supported_fs generic
> +_require_idmapped_mounts
> +_require_test
> +
> +echo "Silence is golden"
> +
> +$here/src/idmapped-mounts/fscaps-in-ancestor-userns --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
> +
> +status=$?
> +exit
> diff --git a/tests/generic/637.out b/tests/generic/637.out
> new file mode 100644
> index 00000000..55a3d825
> --- /dev/null
> +++ b/tests/generic/637.out
> @@ -0,0 +1,2 @@
> +QA output created by 637
> +Silence is golden
> diff --git a/tests/generic/group b/tests/generic/group
> index 105763c4..9dfefdf4 100644
> --- a/tests/generic/group
> +++ b/tests/generic/group
> @@ -639,3 +639,4 @@
> 634 auto quick atime bigtime
> 635 auto quick atime bigtime shutdown
> 636 auto quick swap
> +637 auto quick idmapped
> --
> 2.27.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 5/7] idmapped-mounts: refactor helpers
2021-05-07 15:00 [PATCH 0/7] idmapped mounts: extend testsuite and fixes Christian Brauner
` (3 preceding siblings ...)
2021-05-07 15:00 ` [PATCH 4/7] generic/637: add fscaps regression test Christian Brauner
@ 2021-05-07 15:00 ` Christian Brauner
2021-05-07 15:00 ` [PATCH 6/7] idmapped-mounts: add nested userns creation helpers Christian Brauner
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: Christian Brauner @ 2021-05-07 15:00 UTC (permalink / raw)
To: Eryu Guan, fstests; +Cc: Christoph Hellwig, Christian Brauner
From: Christian Brauner <christian.brauner@ubuntu.com>
Make all userns creation helpers share a commond codebase and move a
bunch of code into utils.{c,h}. This simplifies a bunch of things and
makes it easier to create nested user namespaces in follow up patches.
Cc: fstests@vger.kernel.org
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
src/idmapped-mounts/mount-idmapped.c | 196 -------------------------
src/idmapped-mounts/utils.c | 209 +++++++++++++++++++--------
src/idmapped-mounts/utils.h | 73 +++++++++-
3 files changed, 223 insertions(+), 255 deletions(-)
diff --git a/src/idmapped-mounts/mount-idmapped.c b/src/idmapped-mounts/mount-idmapped.c
index 5f5ba5d2..ee3b4f05 100644
--- a/src/idmapped-mounts/mount-idmapped.c
+++ b/src/idmapped-mounts/mount-idmapped.c
@@ -31,77 +31,6 @@
#include "missing.h"
#include "utils.h"
-/* A few helpful macros. */
-#define STRLITERALLEN(x) (sizeof(""x"") - 1)
-
-#define INTTYPE_TO_STRLEN(type) \
- (2 + (sizeof(type) <= 1 \
- ? 3 \
- : sizeof(type) <= 2 \
- ? 5 \
- : sizeof(type) <= 4 \
- ? 10 \
- : sizeof(type) <= 8 ? 20 : sizeof(int[-2 * (sizeof(type) > 8)])))
-
-#define syserror(format, ...) \
- ({ \
- fprintf(stderr, format, ##__VA_ARGS__); \
- (-errno); \
- })
-
-#define syserror_set(__ret__, format, ...) \
- ({ \
- typeof(__ret__) __internal_ret__ = (__ret__); \
- errno = labs(__ret__); \
- fprintf(stderr, format, ##__VA_ARGS__); \
- __internal_ret__; \
- })
-
-struct list {
- void *elem;
- struct list *next;
- struct list *prev;
-};
-
-#define list_for_each(__iterator, __list) \
- for (__iterator = (__list)->next; __iterator != __list; __iterator = __iterator->next)
-
-static inline void list_init(struct list *list)
-{
- list->elem = NULL;
- list->next = list->prev = list;
-}
-
-static inline int list_empty(const struct list *list)
-{
- return list == list->next;
-}
-
-static inline void __list_add(struct list *new, struct list *prev, struct list *next)
-{
- next->prev = new;
- new->next = next;
- new->prev = prev;
- prev->next = new;
-}
-
-static inline void list_add_tail(struct list *head, struct list *list)
-{
- __list_add(list, head->prev, head);
-}
-
-typedef enum idmap_type_t {
- ID_TYPE_UID,
- ID_TYPE_GID
-} idmap_type_t;
-
-struct id_map {
- idmap_type_t map_type;
- __u32 nsid;
- __u32 hostid;
- __u32 range;
-};
-
static struct list active_map;
static int add_map_entry(__u32 id_host,
@@ -170,131 +99,6 @@ static int parse_map(char *map)
return 0;
}
-static int write_id_mapping(idmap_type_t map_type, pid_t pid, const char *buf, size_t buf_size)
-{
- int fd = -EBADF, setgroups_fd = -EBADF;
- int fret = -1;
- int ret;
- char path[STRLITERALLEN("/proc/") + INTTYPE_TO_STRLEN(pid_t) +
- STRLITERALLEN("/setgroups") + 1];
-
- if (geteuid() != 0 && map_type == ID_TYPE_GID) {
- ret = snprintf(path, sizeof(path), "/proc/%d/setgroups", pid);
- if (ret < 0 || ret >= sizeof(path))
- goto out;
-
- setgroups_fd = open(path, O_WRONLY | O_CLOEXEC);
- if (setgroups_fd < 0 && errno != ENOENT) {
- syserror("Failed to open \"%s\"", path);
- goto out;
- }
-
- if (setgroups_fd >= 0) {
- ret = write_nointr(setgroups_fd, "deny\n", STRLITERALLEN("deny\n"));
- if (ret != STRLITERALLEN("deny\n")) {
- syserror("Failed to write \"deny\" to \"/proc/%d/setgroups\"", pid);
- goto out;
- }
- }
- }
-
- ret = snprintf(path, sizeof(path), "/proc/%d/%cid_map", pid, map_type == ID_TYPE_UID ? 'u' : 'g');
- if (ret < 0 || ret >= sizeof(path))
- goto out;
-
- fd = open(path, O_WRONLY | O_CLOEXEC);
- if (fd < 0) {
- syserror("Failed to open \"%s\"", path);
- goto out;
- }
-
- ret = write_nointr(fd, buf, buf_size);
- if (ret != buf_size) {
- syserror("Failed to write %cid mapping to \"%s\"",
- map_type == ID_TYPE_UID ? 'u' : 'g', path);
- goto out;
- }
-
- fret = 0;
-out:
- if (fd >= 0)
- close(fd);
- if (setgroups_fd >= 0)
- close(setgroups_fd);
-
- return fret;
-}
-
-static int map_ids_from_idmap(struct list *idmap, pid_t pid)
-{
- int fill, left;
- char mapbuf[4096] = {};
- bool had_entry = false;
-
- for (idmap_type_t map_type = ID_TYPE_UID, u_or_g = 'u';
- map_type <= ID_TYPE_GID; map_type++, u_or_g = 'g') {
- char *pos = mapbuf;
- int ret;
- struct list *iterator;
-
-
- list_for_each(iterator, idmap) {
- struct id_map *map = iterator->elem;
- if (map->map_type != map_type)
- continue;
-
- had_entry = true;
-
- left = 4096 - (pos - mapbuf);
- fill = snprintf(pos, left, "%u %u %u\n", map->nsid, map->hostid, map->range);
- /*
- * The kernel only takes <= 4k for writes to
- * /proc/<pid>/{g,u}id_map
- */
- if (fill <= 0 || fill >= left)
- return syserror_set(-E2BIG, "Too many %cid mappings defined", u_or_g);
-
- pos += fill;
- }
- if (!had_entry)
- continue;
-
- ret = write_id_mapping(map_type, pid, mapbuf, pos - mapbuf);
- if (ret < 0)
- return syserror("Failed to write mapping: %s", mapbuf);
-
- memset(mapbuf, 0, sizeof(mapbuf));
- }
-
- return 0;
-}
-
-static int get_userns_fd_from_idmap(struct list *idmap)
-{
- int ret;
- pid_t pid;
- char path_ns[STRLITERALLEN("/proc/") + INTTYPE_TO_STRLEN(pid_t) +
- STRLITERALLEN("/ns/user") + 1];
-
- pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER | CLONE_NEWNS);
- if (pid < 0)
- return -errno;
-
- ret = map_ids_from_idmap(idmap, pid);
- if (ret < 0)
- return ret;
-
- ret = snprintf(path_ns, sizeof(path_ns), "/proc/%d/ns/user", pid);
- if (ret < 0 || (size_t)ret >= sizeof(path_ns))
- ret = -EIO;
- else
- ret = open(path_ns, O_RDONLY | O_CLOEXEC | O_NOCTTY);
-
- (void)kill(pid, SIGKILL);
- (void)wait_for_pid(pid);
- return ret;
-}
-
static inline bool strnequal(const char *str, const char *eq, size_t len)
{
return strncmp(str, eq, len) == 0;
diff --git a/src/idmapped-mounts/utils.c b/src/idmapped-mounts/utils.c
index 977443f1..e54f481d 100644
--- a/src/idmapped-mounts/utils.c
+++ b/src/idmapped-mounts/utils.c
@@ -36,99 +36,192 @@ ssize_t write_nointr(int fd, const void *buf, size_t count)
return ret;
}
-static int write_file(const char *path, const void *buf, size_t count)
+#define __STACK_SIZE (8 * 1024 * 1024)
+pid_t do_clone(int (*fn)(void *), void *arg, int flags)
{
- int fd;
- ssize_t ret;
+ void *stack;
- fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW);
- if (fd < 0)
- return -1;
+ stack = malloc(__STACK_SIZE);
+ if (!stack)
+ return -ENOMEM;
- ret = write_nointr(fd, buf, count);
- close(fd);
- if (ret < 0 || (size_t)ret != count)
- return -1;
+#ifdef __ia64__
+ return __clone2(fn, stack, __STACK_SIZE, flags | SIGCHLD, arg, NULL);
+#else
+ return clone(fn, stack + __STACK_SIZE, flags | SIGCHLD, arg, NULL);
+#endif
+}
+static int get_userns_fd_cb(void *data)
+{
return 0;
}
-static int map_ids(pid_t pid, unsigned long nsid, unsigned long hostid,
- unsigned long range)
+int wait_for_pid(pid_t pid)
{
- char map[100], procfile[256];
+ int status, ret;
- snprintf(procfile, sizeof(procfile), "/proc/%d/uid_map", pid);
- snprintf(map, sizeof(map), "%lu %lu %lu", nsid, hostid, range);
- if (write_file(procfile, map, strlen(map)))
- return -1;
+again:
+ ret = waitpid(pid, &status, 0);
+ if (ret == -1) {
+ if (errno == EINTR)
+ goto again;
+ return -1;
+ }
- snprintf(procfile, sizeof(procfile), "/proc/%d/gid_map", pid);
- snprintf(map, sizeof(map), "%lu %lu %lu", nsid, hostid, range);
- if (write_file(procfile, map, strlen(map)))
+ if (!WIFEXITED(status))
return -1;
- return 0;
+ return WEXITSTATUS(status);
}
-#define __STACK_SIZE (8 * 1024 * 1024)
-pid_t do_clone(int (*fn)(void *), void *arg, int flags)
+static int write_id_mapping(idmap_type_t map_type, pid_t pid, const char *buf, size_t buf_size)
{
- void *stack;
+ int fd = -EBADF, setgroups_fd = -EBADF;
+ int fret = -1;
+ int ret;
+ char path[STRLITERALLEN("/proc/") + INTTYPE_TO_STRLEN(pid_t) +
+ STRLITERALLEN("/setgroups") + 1];
+
+ if (geteuid() != 0 && map_type == ID_TYPE_GID) {
+ ret = snprintf(path, sizeof(path), "/proc/%d/setgroups", pid);
+ if (ret < 0 || ret >= sizeof(path))
+ goto out;
+
+ setgroups_fd = open(path, O_WRONLY | O_CLOEXEC);
+ if (setgroups_fd < 0 && errno != ENOENT) {
+ syserror("Failed to open \"%s\"", path);
+ goto out;
+ }
+
+ if (setgroups_fd >= 0) {
+ ret = write_nointr(setgroups_fd, "deny\n", STRLITERALLEN("deny\n"));
+ if (ret != STRLITERALLEN("deny\n")) {
+ syserror("Failed to write \"deny\" to \"/proc/%d/setgroups\"", pid);
+ goto out;
+ }
+ }
+ }
- stack = malloc(__STACK_SIZE);
- if (!stack)
- return -ENOMEM;
+ ret = snprintf(path, sizeof(path), "/proc/%d/%cid_map", pid, map_type == ID_TYPE_UID ? 'u' : 'g');
+ if (ret < 0 || ret >= sizeof(path))
+ goto out;
-#ifdef __ia64__
- return __clone2(fn, stack, __STACK_SIZE, flags | SIGCHLD, arg, NULL);
-#else
- return clone(fn, stack + __STACK_SIZE, flags | SIGCHLD, arg, NULL);
-#endif
+ fd = open(path, O_WRONLY | O_CLOEXEC);
+ if (fd < 0) {
+ syserror("Failed to open \"%s\"", path);
+ goto out;
+ }
+
+ ret = write_nointr(fd, buf, buf_size);
+ if (ret != buf_size) {
+ syserror("Failed to write %cid mapping to \"%s\"",
+ map_type == ID_TYPE_UID ? 'u' : 'g', path);
+ goto out;
+ }
+
+ fret = 0;
+out:
+ if (fd >= 0)
+ close(fd);
+ if (setgroups_fd >= 0)
+ close(setgroups_fd);
+
+ return fret;
}
-int get_userns_fd_cb(void *data)
+static int map_ids_from_idmap(struct list *idmap, pid_t pid)
{
- return kill(getpid(), SIGSTOP);
+ int fill, left;
+ char mapbuf[4096] = {};
+ bool had_entry = false;
+
+ for (idmap_type_t map_type = ID_TYPE_UID, u_or_g = 'u';
+ map_type <= ID_TYPE_GID; map_type++, u_or_g = 'g') {
+ char *pos = mapbuf;
+ int ret;
+ struct list *iterator;
+
+
+ list_for_each(iterator, idmap) {
+ struct id_map *map = iterator->elem;
+ if (map->map_type != map_type)
+ continue;
+
+ had_entry = true;
+
+ left = 4096 - (pos - mapbuf);
+ fill = snprintf(pos, left, "%u %u %u\n", map->nsid, map->hostid, map->range);
+ /*
+ * The kernel only takes <= 4k for writes to
+ * /proc/<pid>/{g,u}id_map
+ */
+ if (fill <= 0 || fill >= left)
+ return syserror_set(-E2BIG, "Too many %cid mappings defined", u_or_g);
+
+ pos += fill;
+ }
+ if (!had_entry)
+ continue;
+
+ ret = write_id_mapping(map_type, pid, mapbuf, pos - mapbuf);
+ if (ret < 0)
+ return syserror("Failed to write mapping: %s", mapbuf);
+
+ memset(mapbuf, 0, sizeof(mapbuf));
+ }
+
+ return 0;
}
-int get_userns_fd(unsigned long nsid, unsigned long hostid, unsigned long range)
+int get_userns_fd_from_idmap(struct list *idmap)
{
int ret;
pid_t pid;
- char path[256];
+ char path_ns[STRLITERALLEN("/proc/") + INTTYPE_TO_STRLEN(pid_t) +
+ STRLITERALLEN("/ns/user") + 1];
- pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER);
+ pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER | CLONE_NEWNS);
if (pid < 0)
return -errno;
- ret = map_ids(pid, nsid, hostid, range);
+ ret = map_ids_from_idmap(idmap, pid);
if (ret < 0)
return ret;
- snprintf(path, sizeof(path), "/proc/%d/ns/user", pid);
- ret = open(path, O_RDONLY | O_CLOEXEC);
- kill(pid, SIGKILL);
- wait_for_pid(pid);
+ ret = snprintf(path_ns, sizeof(path_ns), "/proc/%d/ns/user", pid);
+ if (ret < 0 || (size_t)ret >= sizeof(path_ns))
+ ret = -EIO;
+ else
+ ret = open(path_ns, O_RDONLY | O_CLOEXEC | O_NOCTTY);
+
+ (void)kill(pid, SIGKILL);
+ (void)wait_for_pid(pid);
return ret;
}
-int wait_for_pid(pid_t pid)
+int get_userns_fd(unsigned long nsid, unsigned long hostid, unsigned long range)
{
- int status, ret;
-
-again:
- ret = waitpid(pid, &status, 0);
- if (ret == -1) {
- if (errno == EINTR)
- goto again;
-
- return -1;
- }
-
- if (!WIFEXITED(status))
- return -1;
-
- return WEXITSTATUS(status);
+ struct list head, uid_mapl, gid_mapl;
+ struct id_map uid_map = {
+ .map_type = ID_TYPE_UID,
+ .nsid = nsid,
+ .hostid = hostid,
+ .range = range,
+ };
+ struct id_map gid_map = {
+ .map_type = ID_TYPE_GID,
+ .nsid = nsid,
+ .hostid = hostid,
+ .range = range,
+ };
+
+ list_init(&head);
+ uid_mapl.elem = &uid_map;
+ gid_mapl.elem = &gid_map;
+ list_add_tail(&head, &uid_mapl);
+ list_add_tail(&head, &gid_mapl);
+
+ return get_userns_fd_from_idmap(&head);
}
diff --git a/src/idmapped-mounts/utils.h b/src/idmapped-mounts/utils.h
index efbf3bc3..4f976f9f 100644
--- a/src/idmapped-mounts/utils.h
+++ b/src/idmapped-mounts/utils.h
@@ -19,10 +19,81 @@
#include "missing.h"
+/* A few helpful macros. */
+#define STRLITERALLEN(x) (sizeof(""x"") - 1)
+
+#define INTTYPE_TO_STRLEN(type) \
+ (2 + (sizeof(type) <= 1 \
+ ? 3 \
+ : sizeof(type) <= 2 \
+ ? 5 \
+ : sizeof(type) <= 4 \
+ ? 10 \
+ : sizeof(type) <= 8 ? 20 : sizeof(int[-2 * (sizeof(type) > 8)])))
+
+#define syserror(format, ...) \
+ ({ \
+ fprintf(stderr, "%m - " format "\n", ##__VA_ARGS__); \
+ (-errno); \
+ })
+
+#define syserror_set(__ret__, format, ...) \
+ ({ \
+ typeof(__ret__) __internal_ret__ = (__ret__); \
+ errno = labs(__ret__); \
+ fprintf(stderr, "%m - " format "\n", ##__VA_ARGS__); \
+ __internal_ret__; \
+ })
+
+typedef enum idmap_type_t {
+ ID_TYPE_UID,
+ ID_TYPE_GID
+} idmap_type_t;
+
+struct id_map {
+ idmap_type_t map_type;
+ __u32 nsid;
+ __u32 hostid;
+ __u32 range;
+};
+
+struct list {
+ void *elem;
+ struct list *next;
+ struct list *prev;
+};
+
+#define list_for_each(__iterator, __list) \
+ for (__iterator = (__list)->next; __iterator != __list; __iterator = __iterator->next)
+
+static inline void list_init(struct list *list)
+{
+ list->elem = NULL;
+ list->next = list->prev = list;
+}
+
+static inline int list_empty(const struct list *list)
+{
+ return list == list->next;
+}
+
+static inline void __list_add(struct list *new, struct list *prev, struct list *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+static inline void list_add_tail(struct list *head, struct list *list)
+{
+ __list_add(list, head->prev, head);
+}
+
extern pid_t do_clone(int (*fn)(void *), void *arg, int flags);
-extern int get_userns_fd_cb(void *data);
extern int get_userns_fd(unsigned long nsid, unsigned long hostid,
unsigned long range);
+extern int get_userns_fd_from_idmap(struct list *idmap);
extern ssize_t read_nointr(int fd, void *buf, size_t count);
extern int wait_for_pid(pid_t pid);
extern ssize_t write_nointr(int fd, const void *buf, size_t count);
--
2.27.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 6/7] idmapped-mounts: add nested userns creation helpers
2021-05-07 15:00 [PATCH 0/7] idmapped mounts: extend testsuite and fixes Christian Brauner
` (4 preceding siblings ...)
2021-05-07 15:00 ` [PATCH 5/7] idmapped-mounts: refactor helpers Christian Brauner
@ 2021-05-07 15:00 ` Christian Brauner
2021-05-07 15:01 ` [PATCH 7/7] generic/638: add nested user namespace tests Christian Brauner
2021-05-23 15:08 ` [PATCH 0/7] idmapped mounts: extend testsuite and fixes Eryu Guan
7 siblings, 0 replies; 10+ messages in thread
From: Christian Brauner @ 2021-05-07 15:00 UTC (permalink / raw)
To: Eryu Guan, fstests; +Cc: Christoph Hellwig, Christian Brauner
From: Christian Brauner <christian.brauner@ubuntu.com>
Add a helper to create a nested userns hierarchy. This will be used in
follow-up tests.
Cc: fstests@vger.kernel.org
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
src/idmapped-mounts/idmapped-mounts.c | 14 ---
src/idmapped-mounts/mount-idmapped.c | 32 +-----
src/idmapped-mounts/utils.c | 160 +++++++++++++++++++++++++-
src/idmapped-mounts/utils.h | 25 ++++
4 files changed, 185 insertions(+), 46 deletions(-)
diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c
index 94b83c01..f5a48af7 100644
--- a/src/idmapped-mounts/idmapped-mounts.c
+++ b/src/idmapped-mounts/idmapped-mounts.c
@@ -390,20 +390,6 @@ static inline bool switch_fsids(uid_t fsuid, gid_t fsgid)
return true;
}
-static inline bool switch_ids(uid_t uid, gid_t gid)
-{
- if (setgroups(0, NULL))
- return log_errno(false, "failure: setgroups");
-
- if (setresgid(gid, gid, gid))
- return log_errno(false, "failure: setresgid");
-
- if (setresuid(uid, uid, uid))
- return log_errno(false, "failure: setresuid");
-
- return true;
-}
-
static inline bool switch_userns(int fd, uid_t uid, gid_t gid, bool drop_caps)
{
if (setns(fd, CLONE_NEWUSER))
diff --git a/src/idmapped-mounts/mount-idmapped.c b/src/idmapped-mounts/mount-idmapped.c
index ee3b4f05..46d110fc 100644
--- a/src/idmapped-mounts/mount-idmapped.c
+++ b/src/idmapped-mounts/mount-idmapped.c
@@ -33,36 +33,6 @@
static struct list active_map;
-static int add_map_entry(__u32 id_host,
- __u32 id_ns,
- __u32 range,
- idmap_type_t map_type)
-{
- struct list *new_list = NULL;
- struct id_map *newmap = NULL;
-
- newmap = malloc(sizeof(*newmap));
- if (!newmap)
- return -ENOMEM;
-
- new_list = malloc(sizeof(struct list));
- if (!new_list) {
- free(newmap);
- return -ENOMEM;
- }
-
- *newmap = (struct id_map){
- .hostid = id_host,
- .nsid = id_ns,
- .range = range,
- .map_type = map_type,
- };
-
- new_list->elem = newmap;
- list_add_tail(&active_map, new_list);
- return 0;
-}
-
static int parse_map(char *map)
{
char types[2] = {'u', 'g'};
@@ -91,7 +61,7 @@ static int parse_map(char *map)
else
map_type = ID_TYPE_GID;
- ret = add_map_entry(id_host, id_ns, range, map_type);
+ ret = add_map_entry(&active_map, id_host, id_ns, range, map_type);
if (ret < 0)
return ret;
}
diff --git a/src/idmapped-mounts/utils.c b/src/idmapped-mounts/utils.c
index e54f481d..6ffd6a23 100644
--- a/src/idmapped-mounts/utils.c
+++ b/src/idmapped-mounts/utils.c
@@ -3,11 +3,15 @@
#define _GNU_SOURCE
#endif
#include <fcntl.h>
+#include <grp.h>
#include <linux/limits.h>
+#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
-#include <sched.h>
+#include <sys/eventfd.h>
#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -137,6 +141,9 @@ static int map_ids_from_idmap(struct list *idmap, pid_t pid)
char mapbuf[4096] = {};
bool had_entry = false;
+ if (list_empty(idmap))
+ return 0;
+
for (idmap_type_t map_type = ID_TYPE_UID, u_or_g = 'u';
map_type <= ID_TYPE_GID; map_type++, u_or_g = 'g') {
char *pos = mapbuf;
@@ -225,3 +232,154 @@ int get_userns_fd(unsigned long nsid, unsigned long hostid, unsigned long range)
return get_userns_fd_from_idmap(&head);
}
+
+bool switch_ids(uid_t uid, gid_t gid)
+{
+ if (setgroups(0, NULL))
+ return syserror("failure: setgroups");
+
+ if (setresgid(gid, gid, gid))
+ return syserror("failure: setresgid");
+
+ if (setresuid(uid, uid, uid))
+ return syserror("failure: setresuid");
+
+ return true;
+}
+
+static int userns_fd_cb(void *data)
+{
+ struct userns_hierarchy *h = data;
+ char c;
+ int ret;
+
+ ret = read_nointr(h->fd_event, &c, 1);
+ if (ret < 0)
+ return syserror("failure: read from socketpair");
+
+ /* Only switch ids if someone actually wrote a mapping for us. */
+ if (c == '1') {
+ if (!switch_ids(0, 0))
+ return syserror("failure: switch ids to 0");
+
+ /* Ensure we can access proc files from processes we can ptrace. */
+ ret = prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+ if (ret < 0)
+ return syserror("failure: make dumpable");
+ }
+
+ ret = write_nointr(h->fd_event, "1", 1);
+ if (ret < 0)
+ return syserror("failure: write to socketpair");
+
+ ret = create_userns_hierarchy(++h);
+ if (ret < 0)
+ return syserror("failure: userns level %d", h->level);
+
+ return 0;
+}
+
+int create_userns_hierarchy(struct userns_hierarchy *h)
+{
+ int fret = -1;
+ char c;
+ int fd_socket[2];
+ int fd_userns = -EBADF, ret = -1;
+ ssize_t bytes;
+ pid_t pid;
+ char path[256];
+
+ if (h->level == MAX_USERNS_LEVEL)
+ return 0;
+
+ ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, fd_socket);
+ if (ret < 0)
+ return syserror("failure: create socketpair");
+
+ /* Note the CLONE_FILES | CLONE_VM when mucking with fds and memory. */
+ h->fd_event = fd_socket[1];
+ pid = do_clone(userns_fd_cb, h, CLONE_NEWUSER | CLONE_FILES | CLONE_VM);
+ if (pid < 0) {
+ syserror("failure: userns level %d", h->level);
+ goto out_close;
+ }
+
+ ret = map_ids_from_idmap(&h->id_map, pid);
+ if (ret < 0) {
+ kill(pid, SIGKILL);
+ syserror("failure: writing id mapping for userns level %d for %d", h->level, pid);
+ goto out_wait;
+ }
+
+ if (!list_empty(&h->id_map))
+ bytes = write_nointr(fd_socket[0], "1", 1); /* Inform the child we wrote a mapping. */
+ else
+ bytes = write_nointr(fd_socket[0], "0", 1); /* Inform the child we didn't write a mapping. */
+ if (bytes < 0) {
+ kill(pid, SIGKILL);
+ syserror("failure: write to socketpair");
+ goto out_wait;
+ }
+
+ /* Wait for child to set*id() and become dumpable. */
+ bytes = read_nointr(fd_socket[0], &c, 1);
+ if (bytes < 0) {
+ kill(pid, SIGKILL);
+ syserror("failure: read from socketpair");
+ goto out_wait;
+ }
+
+ snprintf(path, sizeof(path), "/proc/%d/ns/user", pid);
+ fd_userns = open(path, O_RDONLY | O_CLOEXEC);
+ if (fd_userns < 0) {
+ kill(pid, SIGKILL);
+ syserror("failure: open userns level %d for %d", h->level, pid);
+ goto out_wait;
+ }
+
+ fret = 0;
+
+out_wait:
+ if (!wait_for_pid(pid) && !fret) {
+ h->fd_userns = fd_userns;
+ fd_userns = -EBADF;
+ }
+
+out_close:
+ if (fd_userns >= 0)
+ close(fd_userns);
+ close(fd_socket[0]);
+ close(fd_socket[1]);
+ return fret;
+}
+
+int add_map_entry(struct list *head,
+ __u32 id_host,
+ __u32 id_ns,
+ __u32 range,
+ idmap_type_t map_type)
+{
+ struct list *new_list = NULL;
+ struct id_map *newmap = NULL;
+
+ newmap = malloc(sizeof(*newmap));
+ if (!newmap)
+ return -ENOMEM;
+
+ new_list = malloc(sizeof(struct list));
+ if (!new_list) {
+ free(newmap);
+ return -ENOMEM;
+ }
+
+ *newmap = (struct id_map){
+ .hostid = id_host,
+ .nsid = id_ns,
+ .range = range,
+ .map_type = map_type,
+ };
+
+ new_list->elem = newmap;
+ list_add_tail(head, new_list);
+ return 0;
+}
diff --git a/src/idmapped-mounts/utils.h b/src/idmapped-mounts/utils.h
index 4f976f9f..9694980e 100644
--- a/src/idmapped-mounts/utils.h
+++ b/src/idmapped-mounts/utils.h
@@ -10,6 +10,7 @@
#include <linux/types.h>
#include <sched.h>
#include <signal.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -19,6 +20,9 @@
#include "missing.h"
+/* Maximum number of nested user namespaces in the kernel. */
+#define MAX_USERNS_LEVEL 32
+
/* A few helpful macros. */
#define STRLITERALLEN(x) (sizeof(""x"") - 1)
@@ -63,6 +67,13 @@ struct list {
struct list *prev;
};
+struct userns_hierarchy {
+ int fd_userns;
+ int fd_event;
+ unsigned int level;
+ struct list id_map;
+};
+
#define list_for_each(__iterator, __list) \
for (__iterator = (__list)->next; __iterator != __list; __iterator = __iterator->next)
@@ -90,6 +101,16 @@ static inline void list_add_tail(struct list *head, struct list *list)
__list_add(list, head->prev, head);
}
+static inline void list_del(struct list *list)
+{
+ struct list *next, *prev;
+
+ next = list->next;
+ prev = list->prev;
+ next->prev = prev;
+ prev->next = next;
+}
+
extern pid_t do_clone(int (*fn)(void *), void *arg, int flags);
extern int get_userns_fd(unsigned long nsid, unsigned long hostid,
unsigned long range);
@@ -97,5 +118,9 @@ extern int get_userns_fd_from_idmap(struct list *idmap);
extern ssize_t read_nointr(int fd, void *buf, size_t count);
extern int wait_for_pid(pid_t pid);
extern ssize_t write_nointr(int fd, const void *buf, size_t count);
+extern bool switch_ids(uid_t uid, gid_t gid);
+extern int create_userns_hierarchy(struct userns_hierarchy *h);
+extern int add_map_entry(struct list *head, __u32 id_host, __u32 id_ns,
+ __u32 range, idmap_type_t map_type);
#endif /* __IDMAP_UTILS_H */
--
2.27.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 7/7] generic/638: add nested user namespace tests
2021-05-07 15:00 [PATCH 0/7] idmapped mounts: extend testsuite and fixes Christian Brauner
` (5 preceding siblings ...)
2021-05-07 15:00 ` [PATCH 6/7] idmapped-mounts: add nested userns creation helpers Christian Brauner
@ 2021-05-07 15:01 ` Christian Brauner
2021-05-23 15:08 ` [PATCH 0/7] idmapped mounts: extend testsuite and fixes Eryu Guan
7 siblings, 0 replies; 10+ messages in thread
From: Christian Brauner @ 2021-05-07 15:01 UTC (permalink / raw)
To: Eryu Guan, fstests; +Cc: Christoph Hellwig, Christian Brauner
From: Christian Brauner <christian.brauner@ubuntu.com>
Test ownership and ownership changes in a complex user namespace
hierarchy.
Cc: fstests@vger.kernel.org
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
.gitignore | 1 +
src/idmapped-mounts/Makefile | 8 +-
src/idmapped-mounts/idmapped-mounts.c | 715 ++++++++++++++++++++++++++
src/idmapped-mounts/utils.h | 4 +
tests/generic/638 | 42 ++
tests/generic/638.out | 2 +
tests/generic/group | 1 +
7 files changed, 771 insertions(+), 2 deletions(-)
create mode 100755 tests/generic/638
create mode 100644 tests/generic/638.out
diff --git a/.gitignore b/.gitignore
index da48e6f8..713e4886 100644
--- a/.gitignore
+++ b/.gitignore
@@ -181,6 +181,7 @@
/src/idmapped-mounts/idmapped-mounts
/src/idmapped-mounts/mount-idmapped
/src/idmapped-mounts/fscaps-in-ancestor-userns
+/src/idmapped-mounts/nested-userns
/src/log-writes/replay-log
/src/perf/*.pyc
diff --git a/src/idmapped-mounts/Makefile b/src/idmapped-mounts/Makefile
index 1bab6471..a205c830 100644
--- a/src/idmapped-mounts/Makefile
+++ b/src/idmapped-mounts/Makefile
@@ -4,7 +4,7 @@ TOPDIR = ../..
include $(TOPDIR)/include/builddefs
BINS = idmapped-mounts mount-idmapped
-LINKS = fscaps-in-ancestor-userns
+LINKS = fscaps-in-ancestor-userns nested-userns
TARGETS = $(BINS) $(LINKS)
CFILES_IDMAPPED_MOUNTS = idmapped-mounts.c utils.c
CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c
@@ -34,6 +34,9 @@ idmapped-mounts: $(CFILES_IDMAPPED_MOUNTS)
fscaps-in-ancestor-userns:
ln -sf idmapped-mounts fscaps-in-ancestor-userns
+nested-userns:
+ ln -sf idmapped-mounts nested-userns
+
mount-idmapped: $(CFILES_MOUNT_IDMAPPED)
@echo " [CC] $@"
$(Q)$(LTLINK) $(CFILES_MOUNT_IDMAPPED) -o $@ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
@@ -43,6 +46,7 @@ install:
$(INSTALL) -m 755 $(BINS) $(PKG_LIB_DIR)/src/idmapped-mounts
cd $(PKG_LIB_DIR)/src/idmapped-mounts && \
rm -f $(LINKS) && \
- $(LN_S) idmapped-mounts fscaps-in-ancestor-userns
+ $(LN_S) idmapped-mounts fscaps-in-ancestor-userns && \
+ $(LN_S) idmapped-mounts nested-userns
-include .dep
diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c
index f5a48af7..2e456018 100644
--- a/src/idmapped-mounts/idmapped-mounts.c
+++ b/src/idmapped-mounts/idmapped-mounts.c
@@ -8814,6 +8814,714 @@ out:
return fret;
}
+static int nested_userns(void)
+{
+ int fret = -1;
+ int ret;
+ pid_t pid;
+ struct list *it, *next;
+ struct userns_hierarchy hierarchy[] = {
+ { .level = 1, .fd_userns = -EBADF, },
+ { .level = 2, .fd_userns = -EBADF, },
+ { .level = 3, .fd_userns = -EBADF, },
+ { .level = 4, .fd_userns = -EBADF, },
+ /* Dummy entry that marks the end. */
+ { .level = MAX_USERNS_LEVEL, .fd_userns = -EBADF, },
+ };
+ struct mount_attr attr_level1 = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ .userns_fd = -EBADF,
+ };
+ struct mount_attr attr_level2 = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ .userns_fd = -EBADF,
+ };
+ struct mount_attr attr_level3 = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ .userns_fd = -EBADF,
+ };
+ struct mount_attr attr_level4 = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ .userns_fd = -EBADF,
+ };
+ int fd_dir1 = -EBADF,
+ fd_open_tree_level1 = -EBADF,
+ fd_open_tree_level2 = -EBADF,
+ fd_open_tree_level3 = -EBADF,
+ fd_open_tree_level4 = -EBADF;
+ const unsigned int id_file_range = 10000;
+
+ list_init(&hierarchy[0].id_map);
+ list_init(&hierarchy[1].id_map);
+ list_init(&hierarchy[2].id_map);
+ list_init(&hierarchy[3].id_map);
+
+ /*
+ * Give a large map to the outermost user namespace so we can create
+ * comfortable nested maps.
+ */
+ ret = add_map_entry(&hierarchy[0].id_map, 1000000, 0, 1000000000, ID_TYPE_UID);
+ if (ret) {
+ log_stderr("failure: adding uidmap for userns at level 1");
+ goto out;
+ }
+
+ ret = add_map_entry(&hierarchy[0].id_map, 1000000, 0, 1000000000, ID_TYPE_GID);
+ if (ret) {
+ log_stderr("failure: adding gidmap for userns at level 1");
+ goto out;
+ }
+
+ /* This is uid:0->2000000:100000000 in init userns. */
+ ret = add_map_entry(&hierarchy[1].id_map, 1000000, 0, 100000000, ID_TYPE_UID);
+ if (ret) {
+ log_stderr("failure: adding uidmap for userns at level 2");
+ goto out;
+ }
+
+ /* This is gid:0->2000000:100000000 in init userns. */
+ ret = add_map_entry(&hierarchy[1].id_map, 1000000, 0, 100000000, ID_TYPE_GID);
+ if (ret) {
+ log_stderr("failure: adding gidmap for userns at level 2");
+ goto out;
+ }
+
+ /* This is uid:0->3000000:999 in init userns. */
+ ret = add_map_entry(&hierarchy[2].id_map, 1000000, 0, 999, ID_TYPE_UID);
+ if (ret) {
+ log_stderr("failure: adding uidmap for userns at level 3");
+ goto out;
+ }
+
+ /* This is gid:0->3000000:999 in the init userns. */
+ ret = add_map_entry(&hierarchy[2].id_map, 1000000, 0, 999, ID_TYPE_GID);
+ if (ret) {
+ log_stderr("failure: adding gidmap for userns at level 3");
+ goto out;
+ }
+
+ /* id 999 will remain unmapped. */
+
+ /* This is uid:1000->2001000:1 in init userns. */
+ ret = add_map_entry(&hierarchy[2].id_map, 1000, 1000, 1, ID_TYPE_UID);
+ if (ret) {
+ log_stderr("failure: adding uidmap for userns at level 3");
+ goto out;
+ }
+
+ /* This is gid:1000->2001000:1 in init userns. */
+ ret = add_map_entry(&hierarchy[2].id_map, 1000, 1000, 1, ID_TYPE_GID);
+ if (ret) {
+ log_stderr("failure: adding gidmap for userns at level 3");
+ goto out;
+ }
+
+ /* This is uid:1001->3001001:10000 in init userns. */
+ ret = add_map_entry(&hierarchy[2].id_map, 1001001, 1001, 10000000, ID_TYPE_UID);
+ if (ret) {
+ log_stderr("failure: adding uidmap for userns at level 3");
+ goto out;
+ }
+
+ /* This is gid:1001->3001001:10000 in init userns. */
+ ret = add_map_entry(&hierarchy[2].id_map, 1001001, 1001, 10000000, ID_TYPE_GID);
+ if (ret) {
+ log_stderr("failure: adding gidmap for userns at level 3");
+ goto out;
+ }
+
+ /* Don't write a mapping in the 4th userns. */
+ list_empty(&hierarchy[4].id_map);
+
+ /* Create the actual userns hierarchy. */
+ ret = create_userns_hierarchy(hierarchy);
+ if (ret) {
+ log_stderr("failure: create userns hierarchy");
+ goto out;
+ }
+
+ attr_level1.userns_fd = hierarchy[0].fd_userns;
+ attr_level2.userns_fd = hierarchy[1].fd_userns;
+ attr_level3.userns_fd = hierarchy[2].fd_userns;
+ attr_level4.userns_fd = hierarchy[3].fd_userns;
+
+ /*
+ * Create one directory where we create files for each uid/gid within
+ * the first userns.
+ */
+ if (mkdirat(t_dir1_fd, DIR1, 0777)) {
+ log_stderr("failure: mkdirat");
+ goto out;
+ }
+
+ fd_dir1 = openat(t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC);
+ if (fd_dir1 < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ for (unsigned int id = 0; id <= id_file_range; id++) {
+ char file[256];
+
+ snprintf(file, sizeof(file), DIR1 "/" FILE1 "_%u", id);
+
+ if (mknodat(t_dir1_fd, file, S_IFREG | 0644, 0)) {
+ log_stderr("failure: create %s", file);
+ goto out;
+ }
+
+ if (fchownat(t_dir1_fd, file, id, id, AT_SYMLINK_NOFOLLOW)) {
+ log_stderr("failure: fchownat %s", file);
+ goto out;
+ }
+
+ if (!expected_uid_gid(t_dir1_fd, file, 0, id, id)) {
+ log_stderr("failure: check ownership %s", file);
+ goto out;
+ }
+ }
+
+ /* Create detached mounts for all the user namespaces. */
+ fd_open_tree_level1 = sys_open_tree(t_dir1_fd, DIR1,
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (fd_open_tree_level1 < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ fd_open_tree_level2 = sys_open_tree(t_dir1_fd, DIR1,
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (fd_open_tree_level2 < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ fd_open_tree_level3 = sys_open_tree(t_dir1_fd, DIR1,
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (fd_open_tree_level3 < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ fd_open_tree_level4 = sys_open_tree(t_dir1_fd, DIR1,
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (fd_open_tree_level4 < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ /* Turn detached mounts into detached idmapped mounts. */
+ if (sys_mount_setattr(fd_open_tree_level1, "", AT_EMPTY_PATH,
+ &attr_level1, sizeof(attr_level1))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ if (sys_mount_setattr(fd_open_tree_level2, "", AT_EMPTY_PATH,
+ &attr_level2, sizeof(attr_level2))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ if (sys_mount_setattr(fd_open_tree_level3, "", AT_EMPTY_PATH,
+ &attr_level3, sizeof(attr_level3))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ if (sys_mount_setattr(fd_open_tree_level4, "", AT_EMPTY_PATH,
+ &attr_level4, sizeof(attr_level4))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /* Verify that ownership looks correct for callers in the init userns. */
+ for (unsigned int id = 0; id <= id_file_range; id++) {
+ bool bret;
+ unsigned int id_level1, id_level2, id_level3;
+ char file[256];
+
+ snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+ id_level1 = id + 1000000;
+ if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1)) {
+ log_stderr("failure: check ownership %s", file);
+ goto out;
+ }
+
+ id_level2 = id + 2000000;
+ if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) {
+ log_stderr("failure: check ownership %s", file);
+ goto out;
+ }
+
+ if (id == 999) {
+ /* This id is unmapped. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+ } else if (id == 1000) {
+ id_level3 = id + 2000000; /* We punched a hole in the map at 1000. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ } else {
+ id_level3 = id + 3000000; /* Rest is business as usual. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ }
+ if (!bret) {
+ log_stderr("failure: check ownership %s", file);
+ goto out;
+ }
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) {
+ log_stderr("failure: check ownership %s", file);
+ goto out;
+ }
+ }
+
+ /* Verify that ownership looks correct for callers in the first userns. */
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(attr_level1.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ for (unsigned int id = 0; id <= id_file_range; id++) {
+ bool bret;
+ unsigned int id_level1, id_level2, id_level3;
+ char file[256];
+
+ snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+ id_level1 = id;
+ if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1))
+ die("failure: check ownership %s", file);
+
+ id_level2 = id + 1000000;
+ if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2))
+ die("failure: check ownership %s", file);
+
+ if (id == 999) {
+ /* This id is unmapped. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+ } else if (id == 1000) {
+ id_level3 = id + 1000000; /* We punched a hole in the map at 1000. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ } else {
+ id_level3 = id + 2000000; /* Rest is business as usual. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ }
+ if (!bret)
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* Verify that ownership looks correct for callers in the second userns. */
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(attr_level2.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ for (unsigned int id = 0; id <= id_file_range; id++) {
+ bool bret;
+ unsigned int id_level2, id_level3;
+ char file[256];
+
+ snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+ if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ id_level2 = id;
+ if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2))
+ die("failure: check ownership %s", file);
+
+ if (id == 999) {
+ /* This id is unmapped. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+ } else if (id == 1000) {
+ id_level3 = id; /* We punched a hole in the map at 1000. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ } else {
+ id_level3 = id + 1000000; /* Rest is business as usual. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ }
+ if (!bret)
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* Verify that ownership looks correct for callers in the third userns. */
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(attr_level3.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ for (unsigned int id = 0; id <= id_file_range; id++) {
+ bool bret;
+ unsigned int id_level2, id_level3;
+ char file[256];
+
+ snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+ if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (id == 1000) {
+ /*
+ * The idmapping of the third userns has a hole
+ * at uid/gid 1000. That means:
+ * - 1000->userns_0(2000000) // init userns
+ * - 1000->userns_1(2000000) // level 1
+ * - 1000->userns_2(1000000) // level 2
+ * - 1000->userns_3(1000) // level 3 (because level 3 has a hole)
+ */
+ id_level2 = id;
+ bret = expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2);
+ } else {
+ bret = expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid);
+ }
+ if (!bret)
+ die("failure: check ownership %s", file);
+
+
+ if (id == 999) {
+ /* This id is unmapped. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+ } else {
+ id_level3 = id; /* Rest is business as usual. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ }
+ if (!bret)
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* Verify that ownership looks correct for callers in the fourth userns. */
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (setns(attr_level4.userns_fd, CLONE_NEWUSER))
+ die("failure: switch_userns");
+
+ for (unsigned int id = 0; id <= id_file_range; id++) {
+ char file[256];
+
+ snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+ if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* Verify that chown works correctly for callers in the first userns. */
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(attr_level1.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ for (unsigned int id = 0; id <= id_file_range; id++) {
+ bool bret;
+ unsigned int id_level1, id_level2, id_level3, id_new;
+ char file[256];
+
+ snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+ id_new = id + 1;
+ if (fchownat(fd_open_tree_level1, file, id_new, id_new, AT_SYMLINK_NOFOLLOW))
+ die("failure: fchownat %s", file);
+
+ id_level1 = id_new;
+ if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1))
+ die("failure: check ownership %s", file);
+
+ id_level2 = id_new + 1000000;
+ if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2))
+ die("failure: check ownership %s", file);
+
+ if (id_new == 999) {
+ /* This id is unmapped. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+ } else if (id_new == 1000) {
+ id_level3 = id_new + 1000000; /* We punched a hole in the map at 1000. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ } else {
+ id_level3 = id_new + 2000000; /* Rest is business as usual. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ }
+ if (!bret)
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ /* Revert ownership. */
+ if (fchownat(fd_open_tree_level1, file, id, id, AT_SYMLINK_NOFOLLOW))
+ die("failure: fchownat %s", file);
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* Verify that chown works correctly for callers in the second userns. */
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(attr_level2.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ for (unsigned int id = 0; id <= id_file_range; id++) {
+ bool bret;
+ unsigned int id_level2, id_level3, id_new;
+ char file[256];
+
+ snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+ id_new = id + 1;
+ if (fchownat(fd_open_tree_level2, file, id_new, id_new, AT_SYMLINK_NOFOLLOW))
+ die("failure: fchownat %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ id_level2 = id_new;
+ if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2))
+ die("failure: check ownership %s", file);
+
+ if (id_new == 999) {
+ /* This id is unmapped. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid);
+ } else if (id_new == 1000) {
+ id_level3 = id_new; /* We punched a hole in the map at 1000. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ } else {
+ id_level3 = id_new + 1000000; /* Rest is business as usual. */
+ bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3);
+ }
+ if (!bret)
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ /* Revert ownership. */
+ if (fchownat(fd_open_tree_level2, file, id, id, AT_SYMLINK_NOFOLLOW))
+ die("failure: fchownat %s", file);
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* Verify that chown works correctly for callers in the third userns. */
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(attr_level3.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ for (unsigned int id = 0; id <= id_file_range; id++) {
+ unsigned int id_new;
+ char file[256];
+
+ snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+ id_new = id + 1;
+ if (id_new == 999 || id_new == 1000) {
+ /*
+ * We can't change ownership as we can't
+ * chown from or to an unmapped id.
+ */
+ if (!fchownat(fd_open_tree_level3, file, id_new, id_new, AT_SYMLINK_NOFOLLOW))
+ die("failure: fchownat %s", file);
+ } else {
+ if (fchownat(fd_open_tree_level3, file, id_new, id_new, AT_SYMLINK_NOFOLLOW))
+ die("failure: fchownat %s", file);
+ }
+
+ if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ /* There's no id 1000 anymore as we changed ownership for id 1000 to 1001 above. */
+ if (!expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (id_new == 999) {
+ /*
+ * We did not change ownership as we can't
+ * chown to an unmapped id.
+ */
+ if (!expected_uid_gid(fd_open_tree_level3, file, 0, id, id))
+ die("failure: check ownership %s", file);
+ } else if (id_new == 1000) {
+ /*
+ * We did not change ownership as we can't
+ * chown from an unmapped id.
+ */
+ if (!expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+ } else {
+ if (!expected_uid_gid(fd_open_tree_level3, file, 0, id_new, id_new))
+ die("failure: check ownership %s", file);
+ }
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ /* Revert ownership. */
+ if (id_new != 999 && id_new != 1000) {
+ if (fchownat(fd_open_tree_level3, file, id, id, AT_SYMLINK_NOFOLLOW))
+ die("failure: fchownat %s", file);
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* Verify that chown works correctly for callers in the fourth userns. */
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (setns(attr_level4.userns_fd, CLONE_NEWUSER))
+ die("failure: switch_userns");
+
+ for (unsigned int id = 0; id <= id_file_range; id++) {
+ char file[256];
+ unsigned long id_new;
+
+ snprintf(file, sizeof(file), FILE1 "_%u", id);
+
+ id_new = id + 1;
+ if (!fchownat(fd_open_tree_level4, file, id_new, id_new, AT_SYMLINK_NOFOLLOW))
+ die("failure: fchownat %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid))
+ die("failure: check ownership %s", file);
+
+ }
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ fret = 0;
+ log_debug("Ran test");
+
+out:
+ list_for_each_safe(it, &hierarchy[0].id_map, next) {
+ list_del(it);
+ free(it->elem);
+ free(it);
+ }
+
+ list_for_each_safe(it, &hierarchy[1].id_map, next) {
+ list_del(it);
+ free(it->elem);
+ free(it);
+ }
+
+ list_for_each_safe(it, &hierarchy[2].id_map, next) {
+ list_del(it);
+ free(it->elem);
+ free(it);
+ }
+
+ safe_close(hierarchy[0].fd_userns);
+ safe_close(hierarchy[1].fd_userns);
+ safe_close(hierarchy[2].fd_userns);
+ safe_close(fd_dir1);
+ safe_close(fd_open_tree_level1);
+ safe_close(fd_open_tree_level2);
+ safe_close(fd_open_tree_level3);
+ safe_close(fd_open_tree_level4);
+ return fret;
+}
+
static void usage(void)
{
fprintf(stderr, "Description:\n");
@@ -8894,6 +9602,10 @@ struct t_idmapped_mounts fscaps_in_ancestor_userns[] = {
{ fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns, "fscaps on idmapped mounts in user namespace writing fscap valid in ancestor userns", },
};
+struct t_idmapped_mounts t_nested_userns[] = {
+ { nested_userns, "test that nested user namespaces behave correctly when attached to idmapped mounts", },
+};
+
static bool run_test(struct t_idmapped_mounts suite[], size_t suite_size)
{
int i;
@@ -9027,6 +9739,9 @@ int main(int argc, char *argv[])
if (!run_test(fscaps_in_ancestor_userns,
ARRAY_SIZE(fscaps_in_ancestor_userns)))
goto out;
+ } else if (strcmp(invocation_name, "nested-userns") == 0) {
+ if (!run_test(t_nested_userns, ARRAY_SIZE(t_nested_userns)))
+ goto out;
} else {
die("idmapped mount test suite \"%s\" unknown", invocation_name);
}
diff --git a/src/idmapped-mounts/utils.h b/src/idmapped-mounts/utils.h
index 9694980e..afb3c228 100644
--- a/src/idmapped-mounts/utils.h
+++ b/src/idmapped-mounts/utils.h
@@ -77,6 +77,10 @@ struct userns_hierarchy {
#define list_for_each(__iterator, __list) \
for (__iterator = (__list)->next; __iterator != __list; __iterator = __iterator->next)
+#define list_for_each_safe(__iterator, __list, __next) \
+ for (__iterator = (__list)->next, __next = __iterator->next; \
+ __iterator != __list; __iterator = __next, __next = __next->next)
+
static inline void list_init(struct list *list)
{
list->elem = NULL;
diff --git a/tests/generic/638 b/tests/generic/638
new file mode 100755
index 00000000..7653c7bd
--- /dev/null
+++ b/tests/generic/638
@@ -0,0 +1,42 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2021 Christian Brauner. All Rights Reserved.
+#
+# FS QA Test 638
+#
+# Test that idmapped mounts behave correctly with complex user namespaces.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1 # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+ cd /
+ rm -f $tmp.*
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+# remove previous $seqres.full before test
+rm -f $seqres.full
+
+# real QA test starts here
+
+_supported_fs generic
+_require_idmapped_mounts
+_require_test
+
+echo "Silence is golden"
+
+$here/src/idmapped-mounts/nested-userns --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP"
+
+status=$?
+exit
diff --git a/tests/generic/638.out b/tests/generic/638.out
new file mode 100644
index 00000000..3113b1e3
--- /dev/null
+++ b/tests/generic/638.out
@@ -0,0 +1,2 @@
+QA output created by 638
+Silence is golden
diff --git a/tests/generic/group b/tests/generic/group
index 9dfefdf4..63bae9cc 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -640,3 +640,4 @@
635 auto quick atime bigtime shutdown
636 auto quick swap
637 auto quick idmapped
+638 auto quick idmapped
--
2.27.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 0/7] idmapped mounts: extend testsuite and fixes
2021-05-07 15:00 [PATCH 0/7] idmapped mounts: extend testsuite and fixes Christian Brauner
` (6 preceding siblings ...)
2021-05-07 15:01 ` [PATCH 7/7] generic/638: add nested user namespace tests Christian Brauner
@ 2021-05-23 15:08 ` Eryu Guan
7 siblings, 0 replies; 10+ messages in thread
From: Eryu Guan @ 2021-05-23 15:08 UTC (permalink / raw)
To: Christian Brauner; +Cc: fstests, Christoph Hellwig, Christian Brauner
On Fri, May 07, 2021 at 05:00:53PM +0200, Christian Brauner wrote:
> From: Christian Brauner <christian.brauner@ubuntu.com>
>
> Hey,
>
> This introduces two new idmapped mount tests.
> The first test extends the v3 fscaps tests for idmapped mounts.
> The second test verified that idmapped mounts behave correctly when
> nested user namespaces are used. In essence it creates a fairly complex
> nested user namespace hierarchy and then tests whether file ownership
> changes are correctly reflected in all idmapped mounts as seen from all
> those user namespaces.
> In addition this fixes a couple of minor things and shares more code
> between the mount-idmapped and idmapped-mounts binaries.
I applied the first 3 patches for this update. And I think patch 4 and 7
may need some rework. Patch 5 and 6 just didn't apply without patch 4.
Thanks,
Eryu
>
> Thanks!
> Christian
>
> Christian Brauner (7):
> idmapped-mounts: remove unused set_cloexec() helper
> idmapped-mounts: add missing newline to print_r()
> idmapped-mounts: split out run_test() function
> generic/637: add fscaps regression test
> idmapped-mounts: refactor helpers
> idmapped-mounts: add nested userns creation helpers
> generic/638: add nested user namespace tests
>
> .gitignore | 2 +
> src/idmapped-mounts/Makefile | 16 +-
> src/idmapped-mounts/idmapped-mounts.c | 931 ++++++++++++++++++++++++--
> src/idmapped-mounts/mount-idmapped.c | 228 +------
> src/idmapped-mounts/utils.c | 359 ++++++++--
> src/idmapped-mounts/utils.h | 102 ++-
> tests/generic/637 | 42 ++
> tests/generic/637.out | 2 +
> tests/generic/638 | 42 ++
> tests/generic/638.out | 2 +
> tests/generic/group | 2 +
> 11 files changed, 1397 insertions(+), 331 deletions(-)
> create mode 100755 tests/generic/637
> create mode 100644 tests/generic/637.out
> create mode 100755 tests/generic/638
> create mode 100644 tests/generic/638.out
>
>
> base-commit: 40818883aecd19581a71cc096d07eb9106c11b10
> --
> 2.27.0
^ permalink raw reply [flat|nested] 10+ messages in thread