From patchwork Sat Jul 31 16:58:29 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12412511 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-19.7 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3B338C432BE for ; Sat, 31 Jul 2021 16:58:52 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 16E7061052 for ; Sat, 31 Jul 2021 16:58:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229635AbhGaQ65 (ORCPT ); Sat, 31 Jul 2021 12:58:57 -0400 Received: from mail.kernel.org ([198.145.29.99]:41690 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229475AbhGaQ65 (ORCPT ); Sat, 31 Jul 2021 12:58:57 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 126F360EFD; Sat, 31 Jul 2021 16:58:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1627750731; bh=11hbWxGYflk2PPCgEEi/9TZro13z1gNUk2VPcQdvFKs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XvG3HyjTPWOv6AjCa+VjZqaISCo2SgcmRl9DqUkg4LpoPxCfThTBjz/N9c+22i04i Eobt9cx7L0kYgXLfUI5InCS2Hc1Dv0RY3jBU/Mc8ueBivpDVjRa0AlyFU0k4JtdXJB rnuedkrxMQgme2qf8sIoMPAqpcs75EfAwoeZx3zJKPjfO33Wfl02z3d9Mku4C5pN4P bIdLDgBHiHB8Bv+ZYvjs9cPQjO8PM3KlsZ5Tp4AqhucPvAL1CVx9fxqyTM3ySPFq+l ECMeSZFWLbALn5umeUe8HA0QieE52u1jfr/kvid2DB9ud8G8JzC7SvjKsxuIkLt6N1 T/RXAXFcMw8mA== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v2 1/6] idmapped-mounts: prepare for additional tests Date: Sat, 31 Jul 2021 18:58:29 +0200 Message-Id: <20210731165834.479633-2-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210731165834.479633-1-brauner@kernel.org> References: <20210731165834.479633-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=3938; h=from:subject; bh=95q/08UlIxfdzt2b6kHD1rGy5hkfzY++CK3mdLJmXDY=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSyNkpcOLRwsX3b1mm2rF8Oy1ipWR+wYimyO5l20VmBh2nO xCctHaUsDGJcDLJiiiwO7Sbhcst5KjYbZWrAzGFlAhnCwMUpABOJP83I8OjTVQ0lvV1dCmLfVv1gy9 BQ5L5mdqpLY8mloH+vPhWIxTMy/Nl1pMjhX45K0vu64LYNFndDwmrFf3NlPL4QkCXAJ7ybGwA= X-Developer-Key: i=christian.brauner@ubuntu.com; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Precedence: bulk List-ID: X-Mailing-List: fstests@vger.kernel.org From: Christian Brauner - Use sequential option numbering for option parsing since we're only using longopts anyway and to easier correlate test parsing in the switch with the options specified in longopts. - Introduce an explicit command line switch to runs the basic test suite. This prepares for the introduction of additional command line switches to run tests. - Update help output. - Use die() when logging test failure. Cc: fstests@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ new patch --- src/idmapped-mounts/idmapped-mounts.c | 44 +++++++++++++++------------ tests/generic/633 | 3 +- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c index 2c212131..c8635362 100644 --- a/src/idmapped-mounts/idmapped-mounts.c +++ b/src/idmapped-mounts/idmapped-mounts.c @@ -8717,19 +8717,24 @@ static void usage(void) fprintf(stderr, " Run idmapped mount tests\n\n"); fprintf(stderr, "Arguments:\n"); - fprintf(stderr, "-d --device Device used in the tests\n"); - fprintf(stderr, "-m --mountpoint Mountpoint of device\n"); + fprintf(stderr, "--device Device used in the tests\n"); + fprintf(stderr, "--fstype Filesystem type used in the tests\n"); + fprintf(stderr, "--help Print help\n"); + fprintf(stderr, "--mountpoint Mountpoint of device\n"); + fprintf(stderr, "--supported Test whether idmapped mounts are supported on this filesystem\n"); + fprintf(stderr, "--test-core Run core idmapped mount testsuite\n"); _exit(EXIT_SUCCESS); } static const struct option longopts[] = { - {"device", required_argument, 0, 'd'}, - {"fstype", required_argument, 0, 'f'}, - {"mountpoint", required_argument, 0, 'm'}, - {"supported", no_argument, 0, 's'}, - {"help", no_argument, 0, 'h'}, - {NULL, 0, 0, 0 }, + {"device", required_argument, 0, 1}, + {"fstype", required_argument, 0, 2}, + {"mountpoint", required_argument, 0, 3}, + {"supported", no_argument, 0, 4}, + {"help", no_argument, 0, 5}, + {"test-core", no_argument, 0, 6}, + {NULL, 0, 0, 0}, }; struct t_idmapped_mounts { @@ -8804,10 +8809,8 @@ static bool run_test(struct t_idmapped_mounts suite[], size_t suite_size) if (pid == 0) { ret = t->test(); - if (ret) { - fprintf(stderr, "failure: %s\n", t->description); - exit(EXIT_FAILURE); - } + if (ret) + die("failure: %s", t->description); exit(EXIT_SUCCESS); } @@ -8826,23 +8829,26 @@ int main(int argc, char *argv[]) { int fret, ret; int index = 0; - bool supported = false; + bool supported = false, test_core = false; while ((ret = getopt_long(argc, argv, "", longopts, &index)) != -1) { switch (ret) { - case 'd': + case 1: t_device = optarg; break; - case 'f': + case 2: t_fstype = optarg; break; - case 'm': + case 3: t_mountpoint = optarg; break; - case 's': + case 4: supported = true; break; - case 'h': + case 6: + test_core = true; + break; + case 5: /* fallthrough */ default: usage(); @@ -8911,7 +8917,7 @@ int main(int argc, char *argv[]) fret = EXIT_FAILURE; - if (!run_test(basic_suite, ARRAY_SIZE(basic_suite))) + if (test_core && !run_test(basic_suite, ARRAY_SIZE(basic_suite))) goto out; fret = EXIT_SUCCESS; diff --git a/tests/generic/633 b/tests/generic/633 index 6be8a69e..67501177 100755 --- a/tests/generic/633 +++ b/tests/generic/633 @@ -20,7 +20,8 @@ _require_test echo "Silence is golden" -$here/src/idmapped-mounts/idmapped-mounts --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP" +$here/src/idmapped-mounts/idmapped-mounts --test-core --device "$TEST_DEV" \ + --mount "$TEST_DIR" --fstype "$FSTYP" status=$? exit From patchwork Sat Jul 31 16:58:30 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12412513 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-19.7 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2A34FC4338F for ; Sat, 31 Jul 2021 16:58:54 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 094AF61050 for ; Sat, 31 Jul 2021 16:58:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229647AbhGaQ67 (ORCPT ); Sat, 31 Jul 2021 12:58:59 -0400 Received: from mail.kernel.org ([198.145.29.99]:41704 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229475AbhGaQ67 (ORCPT ); Sat, 31 Jul 2021 12:58:59 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id C58BB60F46; Sat, 31 Jul 2021 16:58:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1627750733; bh=WBKmgpCL1OLO4t1+JIoma/oR8W4nvU4KKpHxGUpDHso=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=W4nNGHHup5EN7Ki/a+QV4O5ipF6vE4Sa1Vh6FuCsWRMOqIRl/6bEv7poGUwrpKI5w jPedK8z+5O6/pm72pDAdeYdjk8cpMCA0erdVDxp9J9DVYMPuyf3+WiGSNGnl9EPTNc FtQ3rssBlOcAGVeaQHVIKHXGL3lf4gl1vAyxvUKjRtb0Tu4EUPUgNPTWKM25virlHH J7G4A5E+WUa+GnbRk01AAT7FmoP2xNcAk/aJI9JYl4fPT//DbYsPdIETZc3v2PC3Tj TpZScD9gJ2T94qq8OzT/5KR9e0UfL9X1YyskhrJo+nuwHC6L6lXuaqhJPUqQntCLGX OswNSFtlRXqXA== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v2 2/6] generic/640: add fscaps regression test Date: Sat, 31 Jul 2021 18:58:30 +0200 Message-Id: <20210731165834.479633-3-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210731165834.479633-1-brauner@kernel.org> References: <20210731165834.479633-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=9432; h=from:subject; bh=hkOZ8gVvY4huNYSzZl5myzlQ5fSeu24GJaF5uNRpED0=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSyNkqYPdaV3RqZ5JTV7HFwuWe9x78tDAvmMbL+PlfuefJG 8iyGjlIWBjEuBlkxRRaHdpNwueU8FZuNMjVg5rAygQxh4OIUgIn8E2X4xfTN9Ohm+edqzKnXrr7pDj H93ND29oxayO/vMlI3GLVUZjD807sYGHz+tfqU6mzvPSI6kh4vJmWx358W1GS7QVww8t9uLgA= X-Developer-Key: i=christian.brauner@ubuntu.com; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Precedence: bulk List-ID: X-Mailing-List: fstests@vger.kernel.org From: Christian Brauner 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: Christoph Hellwig Cc: fstests@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ - Eryu Guan : - Don't create symlinks for each test. Instead, use the same binary and introduce new options to specify which tests to run. --- src/idmapped-mounts/idmapped-mounts.c | 159 +++++++++++++++++++++++--- tests/generic/640 | 28 +++++ tests/generic/640.out | 2 + 3 files changed, 174 insertions(+), 15 deletions(-) create mode 100755 tests/generic/640 create mode 100644 tests/generic/640.out diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c index c8635362..66411970 100644 --- a/src/idmapped-mounts/idmapped-mounts.c +++ b/src/idmapped-mounts/idmapped-mounts.c @@ -3199,6 +3199,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; @@ -8717,24 +8832,26 @@ static void usage(void) fprintf(stderr, " Run idmapped mount tests\n\n"); fprintf(stderr, "Arguments:\n"); - fprintf(stderr, "--device Device used in the tests\n"); - fprintf(stderr, "--fstype Filesystem type used in the tests\n"); - fprintf(stderr, "--help Print help\n"); - fprintf(stderr, "--mountpoint Mountpoint of device\n"); - fprintf(stderr, "--supported Test whether idmapped mounts are supported on this filesystem\n"); - fprintf(stderr, "--test-core Run core idmapped mount testsuite\n"); + fprintf(stderr, "--device Device used in the tests\n"); + fprintf(stderr, "--fstype Filesystem type used in the tests\n"); + fprintf(stderr, "--help Print help\n"); + fprintf(stderr, "--mountpoint Mountpoint of device\n"); + fprintf(stderr, "--supported Test whether idmapped mounts are supported on this filesystem\n"); + fprintf(stderr, "--test-core Run core idmapped mount testsuite\n"); + fprintf(stderr, "--test-fscaps-regression Run fscap regression tests\n"); _exit(EXIT_SUCCESS); } static const struct option longopts[] = { - {"device", required_argument, 0, 1}, - {"fstype", required_argument, 0, 2}, - {"mountpoint", required_argument, 0, 3}, - {"supported", no_argument, 0, 4}, - {"help", no_argument, 0, 5}, - {"test-core", no_argument, 0, 6}, - {NULL, 0, 0, 0}, + {"device", required_argument, 0, 1}, + {"fstype", required_argument, 0, 2}, + {"mountpoint", required_argument, 0, 3}, + {"supported", no_argument, 0, 4}, + {"help", no_argument, 0, 5}, + {"test-core", no_argument, 0, 6}, + {"test-fscaps-regression", no_argument, 0, 7}, + {NULL, 0, 0, 0}, }; struct t_idmapped_mounts { @@ -8748,7 +8865,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", }, @@ -8792,6 +8909,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; @@ -8829,7 +8950,7 @@ int main(int argc, char *argv[]) { int fret, ret; int index = 0; - bool supported = false, test_core = false; + bool supported = false, test_core = false, test_fscaps_regression = false; while ((ret = getopt_long(argc, argv, "", longopts, &index)) != -1) { switch (ret) { @@ -8848,6 +8969,9 @@ int main(int argc, char *argv[]) case 6: test_core = true; break; + case 7: + test_fscaps_regression = true; + break; case 5: /* fallthrough */ default: @@ -8920,6 +9044,11 @@ int main(int argc, char *argv[]) if (test_core && !run_test(basic_suite, ARRAY_SIZE(basic_suite))) goto out; + if (test_fscaps_regression && + !run_test(fscaps_in_ancestor_userns, + ARRAY_SIZE(fscaps_in_ancestor_userns))) + goto out; + fret = EXIT_SUCCESS; out: diff --git a/tests/generic/640 b/tests/generic/640 new file mode 100755 index 00000000..a3795b2d --- /dev/null +++ b/tests/generic/640 @@ -0,0 +1,28 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2021 Christian Brauner. All Rights Reserved. +# +# FS QA Test 640 +# +# Test that fscaps on idmapped mounts behave correctly. +# +. ./common/preamble +_begin_fstest auto quick cap idmapped mount + +# get standard environment, filters and checks +. ./common/rc +. ./common/filter + +# real QA test starts here + +_supported_fs generic +_require_idmapped_mounts +_require_test + +echo "Silence is golden" + +$here/src/idmapped-mounts/idmapped-mounts --test-fscaps-regression \ + --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP" + +status=$? +exit diff --git a/tests/generic/640.out b/tests/generic/640.out new file mode 100644 index 00000000..a336a0f5 --- /dev/null +++ b/tests/generic/640.out @@ -0,0 +1,2 @@ +QA output created by 640 +Silence is golden From patchwork Sat Jul 31 16:58:31 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12412515 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-19.7 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT, WEIRD_QUOTING autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E2ED0C432BE for ; Sat, 31 Jul 2021 16:58:55 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C650461050 for ; Sat, 31 Jul 2021 16:58:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229707AbhGaQ7B (ORCPT ); Sat, 31 Jul 2021 12:59:01 -0400 Received: from mail.kernel.org ([198.145.29.99]:41726 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229475AbhGaQ7B (ORCPT ); Sat, 31 Jul 2021 12:59:01 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 6BF2561042; Sat, 31 Jul 2021 16:58:53 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1627750734; bh=vwagtGH0TFqbGPDhVJPmBQnOljzdUgn94PStRI5ueTA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XD9gYhJSapdrGQkd9+9ELbFyg7HHNuvbP8PNF8fmoVlQeHStyDy5ONzdmQhSqz5PG tOIO08wi0G6P7ldNR1eHJQ5UjR14aDBMC7PNfYq+zLWItyXQmgloMrH0Tkk58ZqBcm 7ruVLPIg7ELQUUc5+tjWWHq9b6K+7O4nkowOuHPyVr9dT7s7my8DZRO8/kVpzF0G4Y 49MBLK1htMVkRoUae/s1dCPhEmQzQP3j4qw+Ao3XLDgKzzZouTCPs27jmPayhPoESM vcQef4LiqMz1dPn/zBjtSjyj3CTzKtqMXxfg0u5kY39z1Fgakat8QAqqbbt5mkrRaP pY+fFFjsXOZpg== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v2 3/6] idmapped-mounts: refactor helpers Date: Sat, 31 Jul 2021 18:58:31 +0200 Message-Id: <20210731165834.479633-4-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210731165834.479633-1-brauner@kernel.org> References: <20210731165834.479633-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=15636; h=from:subject; bh=Zti6VBbeqCKOlUS/nBJTxK50J2PugYZt7BhrArrg+Fo=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSyNkoumPHqkGCh+WyFb8VHfF19GRt/L9hr9+xd98ngMyyx bzZLdZSyMIhxMciKKbI4tJuEyy3nqdhslKkBM4eVCWQIAxenAEzE9gEjw2zeo1c3Ne+f7dzOlhgVWu cTV1PuFa5j9UT/wJ/gXXNmHmRkeHBy2fs3pw1XOJkwJZ7n6rp/JOH46Q3Pv5vLNzL+9u5czgIA X-Developer-Key: i=christian.brauner@ubuntu.com; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Precedence: bulk List-ID: X-Mailing-List: fstests@vger.kernel.org From: Christian Brauner 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 Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ unchanged --- src/idmapped-mounts/mount-idmapped.c | 197 ------------------------- src/idmapped-mounts/utils.c | 209 +++++++++++++++++++-------- src/idmapped-mounts/utils.h | 73 +++++++++- 3 files changed, 223 insertions(+), 256 deletions(-) diff --git a/src/idmapped-mounts/mount-idmapped.c b/src/idmapped-mounts/mount-idmapped.c index 219104e7..b1209057 100644 --- a/src/idmapped-mounts/mount-idmapped.c +++ b/src/idmapped-mounts/mount-idmapped.c @@ -27,77 +27,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, @@ -166,132 +95,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; - idmap_type_t map_type, u_or_g; - - for (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//{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//{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); From patchwork Sat Jul 31 16:58:32 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12412517 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-19.7 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT, WEIRD_QUOTING autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 57CCCC4338F for ; Sat, 31 Jul 2021 16:58:57 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 36CC960EFD for ; Sat, 31 Jul 2021 16:58:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229970AbhGaQ7D (ORCPT ); Sat, 31 Jul 2021 12:59:03 -0400 Received: from mail.kernel.org ([198.145.29.99]:41740 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229475AbhGaQ7C (ORCPT ); Sat, 31 Jul 2021 12:59:02 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 2DC9460F3A; Sat, 31 Jul 2021 16:58:54 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1627750736; bh=iONb6AaHsjlk9zPESvQGPymE7iQ2G6gY3VWfDYC1hVM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OgbncRicyjGRUF6qvGXyYTDVW/jw/k5q/Ximhpfg9IJm4eT7/QX+20XoBAV/egGYg L8JPN3JwCIUQdiqhOi9i60zexol41XqjOkVF0YR3LL9lSdTryXwQsmXLPl+qBUOnlW BbyQE1Xs2gQcc4cPBae1APN0vUKN1KvDKObo3rISjZdmLJ/geBxl0YL44jPKOWN5dB ZU7Tr8vlWVzloElMekG8wtXY56QdpZ8GAR+CZLDzs2iyCq0naqYYCw/abQBxew4D2u di0ZnZ5EZGDck1OCBIAnM4o7egsffyrNzcLYj79W9UNbCfXC7WmohnxZusfINm2pCj bnXke8/m7BUqQ== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v2 4/6] idmapped-mounts: add nested userns creation helpers Date: Sat, 31 Jul 2021 18:58:32 +0200 Message-Id: <20210731165834.479633-5-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210731165834.479633-1-brauner@kernel.org> References: <20210731165834.479633-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=9089; h=from:subject; bh=Bch0vwMgMBv/uz+h7f/gNkW1XyhCI8Le248qkWZRdzA=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSyNkr63l/REvfT9CmH4T8B94X2F72WHj8UEPjXP+vtz4J/ ASKOHaUsDGJcDLJiiiwO7Sbhcst5KjYbZWrAzGFlAhnCwMUpABOZx8bIsET9fgRTw+QsqbiwXS/n1h y0uXeSx6dxt8ob9fz/Irk++YwMPzrMV2Xtb7XQX1pTf81odaR3V7Tyut6cWrtT3FdTX+7mBgA= X-Developer-Key: i=christian.brauner@ubuntu.com; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Precedence: bulk List-ID: X-Mailing-List: fstests@vger.kernel.org From: Christian Brauner Add a helper to create a nested userns hierarchy. This will be used in follow-up tests. Cc: Christoph Hellwig Cc: fstests@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- 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 66411970..c60af942 100644 --- a/src/idmapped-mounts/idmapped-mounts.c +++ b/src/idmapped-mounts/idmapped-mounts.c @@ -388,20 +388,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 b1209057..d8490bed 100644 --- a/src/idmapped-mounts/mount-idmapped.c +++ b/src/idmapped-mounts/mount-idmapped.c @@ -29,36 +29,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'}; @@ -87,7 +57,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 +#include #include +#include #include #include -#include +#include #include +#include +#include #include #include #include @@ -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 #include #include +#include #include #include #include @@ -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 */ From patchwork Sat Jul 31 16:58:33 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12412519 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-19.7 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0B130C432BE for ; Sat, 31 Jul 2021 16:58:59 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E23DE61050 for ; Sat, 31 Jul 2021 16:58:58 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229694AbhGaQ7E (ORCPT ); Sat, 31 Jul 2021 12:59:04 -0400 Received: from mail.kernel.org ([198.145.29.99]:41754 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230284AbhGaQ7E (ORCPT ); Sat, 31 Jul 2021 12:59:04 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id C9CCF60F46; Sat, 31 Jul 2021 16:58:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1627750738; bh=htgDStzsxT4PJM+65hsMSU7iGhUWYm2k1J0CnrVpPb8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=DytGnnYNkUSi+1xAx0mfEkUxrSCFM2dk6sAqRp3GAuSeojKKD2xGVLozLsw0dcV4T rWHJF720AM1pVEiJa57jiwYFvwUzoDAhUubsKxdgtoVoNwuJvCfDedl5+z4tBA2m6h 7Can0wqutPxQM6DEdO9CRPO/+WKncqMsX60iJOeMiN6vKv5/INH7D9xJOYu9v7hOoA ayDmP9AJGgCwiHBxpIjOARdMrHcY+9WYnCgrpSBNS9HpWpCqXO4InffjiYNVVYHSv7 tSiOLEIWDigPvupVjAQGwigVzt5MDTdvy7nnLygJTkmFnLxbNtqlX8siwvVoTG95DU MS9abL3kZ2mfQ== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v2 5/6] generic/641: add nested user namespace tests Date: Sat, 31 Jul 2021 18:58:33 +0200 Message-Id: <20210731165834.479633-6-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210731165834.479633-1-brauner@kernel.org> References: <20210731165834.479633-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=26867; h=from:subject; bh=u019CSWSErOzEY4wtEaSqzU78YK3htNLWtZkqybgGPk=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSyNkruvPPei1PZZrHIjfQpdieO32iQkpugvfShd96fgtkf 31mv7ChlYRDjYpAVU2RxaDcJl1vOU7HZKFMDZg4rE8gQBi5OAZjIHxeG/1UB7FN/Sn9a8j19s6rFS3 WfGql3n2y5nPODGg5HfnhduZjhr8AnhjPWT5admdK2W3Ban2VSYv/KptBpdc9KC1/P3uh6hgMA X-Developer-Key: i=christian.brauner@ubuntu.com; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 Precedence: bulk List-ID: X-Mailing-List: fstests@vger.kernel.org From: Christian Brauner Test ownership and ownership changes in a complex user namespace hierarchy. Cc: Christoph Hellwig Cc: fstests@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ - Eryu Guan : - Don't create symlinks for each test. Instead, use the same binary and introduce new options to specify which tests to run. --- src/idmapped-mounts/idmapped-mounts.c | 723 +++++++++++++++++++++++++- src/idmapped-mounts/utils.h | 4 + tests/generic/641 | 28 + tests/generic/641.out | 2 + 4 files changed, 756 insertions(+), 1 deletion(-) create mode 100755 tests/generic/641 create mode 100644 tests/generic/641.out diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c index c60af942..f155e0b4 100644 --- a/src/idmapped-mounts/idmapped-mounts.c +++ b/src/idmapped-mounts/idmapped-mounts.c @@ -8812,6 +8812,714 @@ out: return fret; } +static int nested_userns(void) +{ + int fret = -1; + int ret; + pid_t pid; + struct list *it, *next; + struct userns_hierarchy hierarchy[] = { + { .level = 1, .fd_userns = -EBADF, }, + { .level = 2, .fd_userns = -EBADF, }, + { .level = 3, .fd_userns = -EBADF, }, + { .level = 4, .fd_userns = -EBADF, }, + /* Dummy entry that marks the end. */ + { .level = MAX_USERNS_LEVEL, .fd_userns = -EBADF, }, + }; + struct mount_attr attr_level1 = { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = -EBADF, + }; + struct mount_attr attr_level2 = { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = -EBADF, + }; + struct mount_attr attr_level3 = { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = -EBADF, + }; + struct mount_attr attr_level4 = { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = -EBADF, + }; + int fd_dir1 = -EBADF, + fd_open_tree_level1 = -EBADF, + fd_open_tree_level2 = -EBADF, + fd_open_tree_level3 = -EBADF, + fd_open_tree_level4 = -EBADF; + const unsigned int id_file_range = 10000; + + list_init(&hierarchy[0].id_map); + list_init(&hierarchy[1].id_map); + list_init(&hierarchy[2].id_map); + list_init(&hierarchy[3].id_map); + + /* + * Give a large map to the outermost user namespace so we can create + * comfortable nested maps. + */ + ret = add_map_entry(&hierarchy[0].id_map, 1000000, 0, 1000000000, ID_TYPE_UID); + if (ret) { + log_stderr("failure: adding uidmap for userns at level 1"); + goto out; + } + + ret = add_map_entry(&hierarchy[0].id_map, 1000000, 0, 1000000000, ID_TYPE_GID); + if (ret) { + log_stderr("failure: adding gidmap for userns at level 1"); + goto out; + } + + /* This is uid:0->2000000:100000000 in init userns. */ + ret = add_map_entry(&hierarchy[1].id_map, 1000000, 0, 100000000, ID_TYPE_UID); + if (ret) { + log_stderr("failure: adding uidmap for userns at level 2"); + goto out; + } + + /* This is gid:0->2000000:100000000 in init userns. */ + ret = add_map_entry(&hierarchy[1].id_map, 1000000, 0, 100000000, ID_TYPE_GID); + if (ret) { + log_stderr("failure: adding gidmap for userns at level 2"); + goto out; + } + + /* This is uid:0->3000000:999 in init userns. */ + ret = add_map_entry(&hierarchy[2].id_map, 1000000, 0, 999, ID_TYPE_UID); + if (ret) { + log_stderr("failure: adding uidmap for userns at level 3"); + goto out; + } + + /* This is gid:0->3000000:999 in the init userns. */ + ret = add_map_entry(&hierarchy[2].id_map, 1000000, 0, 999, ID_TYPE_GID); + if (ret) { + log_stderr("failure: adding gidmap for userns at level 3"); + goto out; + } + + /* id 999 will remain unmapped. */ + + /* This is uid:1000->2001000:1 in init userns. */ + ret = add_map_entry(&hierarchy[2].id_map, 1000, 1000, 1, ID_TYPE_UID); + if (ret) { + log_stderr("failure: adding uidmap for userns at level 3"); + goto out; + } + + /* This is gid:1000->2001000:1 in init userns. */ + ret = add_map_entry(&hierarchy[2].id_map, 1000, 1000, 1, ID_TYPE_GID); + if (ret) { + log_stderr("failure: adding gidmap for userns at level 3"); + goto out; + } + + /* This is uid:1001->3001001:10000 in init userns. */ + ret = add_map_entry(&hierarchy[2].id_map, 1001001, 1001, 10000000, ID_TYPE_UID); + if (ret) { + log_stderr("failure: adding uidmap for userns at level 3"); + goto out; + } + + /* This is gid:1001->3001001:10000 in init userns. */ + ret = add_map_entry(&hierarchy[2].id_map, 1001001, 1001, 10000000, ID_TYPE_GID); + if (ret) { + log_stderr("failure: adding gidmap for userns at level 3"); + goto out; + } + + /* Don't write a mapping in the 4th userns. */ + list_empty(&hierarchy[4].id_map); + + /* Create the actual userns hierarchy. */ + ret = create_userns_hierarchy(hierarchy); + if (ret) { + log_stderr("failure: create userns hierarchy"); + goto out; + } + + attr_level1.userns_fd = hierarchy[0].fd_userns; + attr_level2.userns_fd = hierarchy[1].fd_userns; + attr_level3.userns_fd = hierarchy[2].fd_userns; + attr_level4.userns_fd = hierarchy[3].fd_userns; + + /* + * Create one directory where we create files for each uid/gid within + * the first userns. + */ + if (mkdirat(t_dir1_fd, DIR1, 0777)) { + log_stderr("failure: mkdirat"); + goto out; + } + + fd_dir1 = openat(t_dir1_fd, DIR1, O_DIRECTORY | O_CLOEXEC); + if (fd_dir1 < 0) { + log_stderr("failure: openat"); + goto out; + } + + for (unsigned int id = 0; id <= id_file_range; id++) { + char file[256]; + + snprintf(file, sizeof(file), DIR1 "/" FILE1 "_%u", id); + + if (mknodat(t_dir1_fd, file, S_IFREG | 0644, 0)) { + log_stderr("failure: create %s", file); + goto out; + } + + if (fchownat(t_dir1_fd, file, id, id, AT_SYMLINK_NOFOLLOW)) { + log_stderr("failure: fchownat %s", file); + goto out; + } + + if (!expected_uid_gid(t_dir1_fd, file, 0, id, id)) { + log_stderr("failure: check ownership %s", file); + goto out; + } + } + + /* Create detached mounts for all the user namespaces. */ + fd_open_tree_level1 = sys_open_tree(t_dir1_fd, DIR1, + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (fd_open_tree_level1 < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + fd_open_tree_level2 = sys_open_tree(t_dir1_fd, DIR1, + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (fd_open_tree_level2 < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + fd_open_tree_level3 = sys_open_tree(t_dir1_fd, DIR1, + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (fd_open_tree_level3 < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + fd_open_tree_level4 = sys_open_tree(t_dir1_fd, DIR1, + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + if (fd_open_tree_level4 < 0) { + log_stderr("failure: sys_open_tree"); + goto out; + } + + /* Turn detached mounts into detached idmapped mounts. */ + if (sys_mount_setattr(fd_open_tree_level1, "", AT_EMPTY_PATH, + &attr_level1, sizeof(attr_level1))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + if (sys_mount_setattr(fd_open_tree_level2, "", AT_EMPTY_PATH, + &attr_level2, sizeof(attr_level2))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + if (sys_mount_setattr(fd_open_tree_level3, "", AT_EMPTY_PATH, + &attr_level3, sizeof(attr_level3))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + if (sys_mount_setattr(fd_open_tree_level4, "", AT_EMPTY_PATH, + &attr_level4, sizeof(attr_level4))) { + log_stderr("failure: sys_mount_setattr"); + goto out; + } + + /* Verify that ownership looks correct for callers in the init userns. */ + for (unsigned int id = 0; id <= id_file_range; id++) { + bool bret; + unsigned int id_level1, id_level2, id_level3; + char file[256]; + + snprintf(file, sizeof(file), FILE1 "_%u", id); + + id_level1 = id + 1000000; + if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1)) { + log_stderr("failure: check ownership %s", file); + goto out; + } + + id_level2 = id + 2000000; + if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) { + log_stderr("failure: check ownership %s", file); + goto out; + } + + if (id == 999) { + /* This id is unmapped. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid); + } else if (id == 1000) { + id_level3 = id + 2000000; /* We punched a hole in the map at 1000. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } else { + id_level3 = id + 3000000; /* Rest is business as usual. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } + if (!bret) { + log_stderr("failure: check ownership %s", file); + goto out; + } + + if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) { + log_stderr("failure: check ownership %s", file); + goto out; + } + } + + /* Verify that ownership looks correct for callers in the first userns. */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr_level1.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + for (unsigned int id = 0; id <= id_file_range; id++) { + bool bret; + unsigned int id_level1, id_level2, id_level3; + char file[256]; + + snprintf(file, sizeof(file), FILE1 "_%u", id); + + id_level1 = id; + if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1)) + die("failure: check ownership %s", file); + + id_level2 = id + 1000000; + if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) + die("failure: check ownership %s", file); + + if (id == 999) { + /* This id is unmapped. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid); + } else if (id == 1000) { + id_level3 = id + 1000000; /* We punched a hole in the map at 1000. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } else { + id_level3 = id + 2000000; /* Rest is business as usual. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } + if (!bret) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* Verify that ownership looks correct for callers in the second userns. */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr_level2.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + for (unsigned int id = 0; id <= id_file_range; id++) { + bool bret; + unsigned int id_level2, id_level3; + char file[256]; + + snprintf(file, sizeof(file), FILE1 "_%u", id); + + if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + id_level2 = id; + if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) + die("failure: check ownership %s", file); + + if (id == 999) { + /* This id is unmapped. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid); + } else if (id == 1000) { + id_level3 = id; /* We punched a hole in the map at 1000. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } else { + id_level3 = id + 1000000; /* Rest is business as usual. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } + if (!bret) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* Verify that ownership looks correct for callers in the third userns. */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr_level3.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + for (unsigned int id = 0; id <= id_file_range; id++) { + bool bret; + unsigned int id_level2, id_level3; + char file[256]; + + snprintf(file, sizeof(file), FILE1 "_%u", id); + + if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + if (id == 1000) { + /* + * The idmapping of the third userns has a hole + * at uid/gid 1000. That means: + * - 1000->userns_0(2000000) // init userns + * - 1000->userns_1(2000000) // level 1 + * - 1000->userns_2(1000000) // level 2 + * - 1000->userns_3(1000) // level 3 (because level 3 has a hole) + */ + id_level2 = id; + bret = expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2); + } else { + bret = expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid); + } + if (!bret) + die("failure: check ownership %s", file); + + + if (id == 999) { + /* This id is unmapped. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid); + } else { + id_level3 = id; /* Rest is business as usual. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } + if (!bret) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* Verify that ownership looks correct for callers in the fourth userns. */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (setns(attr_level4.userns_fd, CLONE_NEWUSER)) + die("failure: switch_userns"); + + for (unsigned int id = 0; id <= id_file_range; id++) { + char file[256]; + + snprintf(file, sizeof(file), FILE1 "_%u", id); + + if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* Verify that chown works correctly for callers in the first userns. */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr_level1.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + for (unsigned int id = 0; id <= id_file_range; id++) { + bool bret; + unsigned int id_level1, id_level2, id_level3, id_new; + char file[256]; + + snprintf(file, sizeof(file), FILE1 "_%u", id); + + id_new = id + 1; + if (fchownat(fd_open_tree_level1, file, id_new, id_new, AT_SYMLINK_NOFOLLOW)) + die("failure: fchownat %s", file); + + id_level1 = id_new; + if (!expected_uid_gid(fd_open_tree_level1, file, 0, id_level1, id_level1)) + die("failure: check ownership %s", file); + + id_level2 = id_new + 1000000; + if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) + die("failure: check ownership %s", file); + + if (id_new == 999) { + /* This id is unmapped. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid); + } else if (id_new == 1000) { + id_level3 = id_new + 1000000; /* We punched a hole in the map at 1000. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } else { + id_level3 = id_new + 2000000; /* Rest is business as usual. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } + if (!bret) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + /* Revert ownership. */ + if (fchownat(fd_open_tree_level1, file, id, id, AT_SYMLINK_NOFOLLOW)) + die("failure: fchownat %s", file); + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* Verify that chown works correctly for callers in the second userns. */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr_level2.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + for (unsigned int id = 0; id <= id_file_range; id++) { + bool bret; + unsigned int id_level2, id_level3, id_new; + char file[256]; + + snprintf(file, sizeof(file), FILE1 "_%u", id); + + id_new = id + 1; + if (fchownat(fd_open_tree_level2, file, id_new, id_new, AT_SYMLINK_NOFOLLOW)) + die("failure: fchownat %s", file); + + if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + id_level2 = id_new; + if (!expected_uid_gid(fd_open_tree_level2, file, 0, id_level2, id_level2)) + die("failure: check ownership %s", file); + + if (id_new == 999) { + /* This id is unmapped. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid); + } else if (id_new == 1000) { + id_level3 = id_new; /* We punched a hole in the map at 1000. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } else { + id_level3 = id_new + 1000000; /* Rest is business as usual. */ + bret = expected_uid_gid(fd_open_tree_level3, file, 0, id_level3, id_level3); + } + if (!bret) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + /* Revert ownership. */ + if (fchownat(fd_open_tree_level2, file, id, id, AT_SYMLINK_NOFOLLOW)) + die("failure: fchownat %s", file); + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* Verify that chown works correctly for callers in the third userns. */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (!switch_userns(attr_level3.userns_fd, 0, 0, false)) + die("failure: switch_userns"); + + for (unsigned int id = 0; id <= id_file_range; id++) { + unsigned int id_new; + char file[256]; + + snprintf(file, sizeof(file), FILE1 "_%u", id); + + id_new = id + 1; + if (id_new == 999 || id_new == 1000) { + /* + * We can't change ownership as we can't + * chown from or to an unmapped id. + */ + if (!fchownat(fd_open_tree_level3, file, id_new, id_new, AT_SYMLINK_NOFOLLOW)) + die("failure: fchownat %s", file); + } else { + if (fchownat(fd_open_tree_level3, file, id_new, id_new, AT_SYMLINK_NOFOLLOW)) + die("failure: fchownat %s", file); + } + + if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + /* There's no id 1000 anymore as we changed ownership for id 1000 to 1001 above. */ + if (!expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + if (id_new == 999) { + /* + * We did not change ownership as we can't + * chown to an unmapped id. + */ + if (!expected_uid_gid(fd_open_tree_level3, file, 0, id, id)) + die("failure: check ownership %s", file); + } else if (id_new == 1000) { + /* + * We did not change ownership as we can't + * chown from an unmapped id. + */ + if (!expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + } else { + if (!expected_uid_gid(fd_open_tree_level3, file, 0, id_new, id_new)) + die("failure: check ownership %s", file); + } + + if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + /* Revert ownership. */ + if (id_new != 999 && id_new != 1000) { + if (fchownat(fd_open_tree_level3, file, id, id, AT_SYMLINK_NOFOLLOW)) + die("failure: fchownat %s", file); + } + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + /* Verify that chown works correctly for callers in the fourth userns. */ + pid = fork(); + if (pid < 0) { + log_stderr("failure: fork"); + goto out; + } + if (pid == 0) { + if (setns(attr_level4.userns_fd, CLONE_NEWUSER)) + die("failure: switch_userns"); + + for (unsigned int id = 0; id <= id_file_range; id++) { + char file[256]; + unsigned long id_new; + + snprintf(file, sizeof(file), FILE1 "_%u", id); + + id_new = id + 1; + if (!fchownat(fd_open_tree_level4, file, id_new, id_new, AT_SYMLINK_NOFOLLOW)) + die("failure: fchownat %s", file); + + if (!expected_uid_gid(fd_open_tree_level1, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level2, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level3, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + if (!expected_uid_gid(fd_open_tree_level4, file, 0, t_overflowuid, t_overflowgid)) + die("failure: check ownership %s", file); + + } + + exit(EXIT_SUCCESS); + } + if (wait_for_pid(pid)) + goto out; + + fret = 0; + log_debug("Ran test"); + +out: + list_for_each_safe(it, &hierarchy[0].id_map, next) { + list_del(it); + free(it->elem); + free(it); + } + + list_for_each_safe(it, &hierarchy[1].id_map, next) { + list_del(it); + free(it->elem); + free(it); + } + + list_for_each_safe(it, &hierarchy[2].id_map, next) { + list_del(it); + free(it->elem); + free(it); + } + + safe_close(hierarchy[0].fd_userns); + safe_close(hierarchy[1].fd_userns); + safe_close(hierarchy[2].fd_userns); + safe_close(fd_dir1); + safe_close(fd_open_tree_level1); + safe_close(fd_open_tree_level2); + safe_close(fd_open_tree_level3); + safe_close(fd_open_tree_level4); + return fret; +} + static void usage(void) { fprintf(stderr, "Description:\n"); @@ -8837,6 +9545,7 @@ static const struct option longopts[] = { {"help", no_argument, 0, 5}, {"test-core", no_argument, 0, 6}, {"test-fscaps-regression", no_argument, 0, 7}, + {"test-nested-userns", no_argument, 0, 8}, {NULL, 0, 0, 0}, }; @@ -8899,6 +9608,10 @@ struct t_idmapped_mounts fscaps_in_ancestor_userns[] = { { fscaps_idmapped_mounts_in_userns_valid_in_ancestor_userns, "fscaps on idmapped mounts in user namespace writing fscap valid in ancestor userns", }, }; +struct t_idmapped_mounts t_nested_userns[] = { + { nested_userns, "test that nested user namespaces behave correctly when attached to idmapped mounts", }, +}; + static bool run_test(struct t_idmapped_mounts suite[], size_t suite_size) { int i; @@ -8936,7 +9649,8 @@ int main(int argc, char *argv[]) { int fret, ret; int index = 0; - bool supported = false, test_core = false, test_fscaps_regression = false; + bool supported = false, test_core = false, + test_fscaps_regression = false, test_nested_userns = false; while ((ret = getopt_long(argc, argv, "", longopts, &index)) != -1) { switch (ret) { @@ -8958,6 +9672,9 @@ int main(int argc, char *argv[]) case 7: test_fscaps_regression = true; break; + case 8: + test_nested_userns = true; + break; case 5: /* fallthrough */ default: @@ -9035,6 +9752,10 @@ int main(int argc, char *argv[]) ARRAY_SIZE(fscaps_in_ancestor_userns))) goto out; + if (test_nested_userns && + !run_test(t_nested_userns, ARRAY_SIZE(t_nested_userns))) + goto out; + fret = EXIT_SUCCESS; out: 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/641 b/tests/generic/641 new file mode 100755 index 00000000..a3a41f02 --- /dev/null +++ b/tests/generic/641 @@ -0,0 +1,28 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2021 Christian Brauner. All Rights Reserved. +# +# FS QA Test 641 +# +# Test that idmapped mounts behave correctly with complex user namespaces. +# +. ./common/preamble +_begin_fstest auto quick idmapped mount + +# get standard environment, filters and checks +. ./common/rc +. ./common/filter + +# real QA test starts here + +_supported_fs generic +_require_idmapped_mounts +_require_test + +echo "Silence is golden" + +$here/src/idmapped-mounts/idmapped-mounts --test-nested-userns \ + --device "$TEST_DEV" --mount "$TEST_DIR" --fstype "$FSTYP" + +status=$? +exit diff --git a/tests/generic/641.out b/tests/generic/641.out new file mode 100644 index 00000000..216333fd --- /dev/null +++ b/tests/generic/641.out @@ -0,0 +1,2 @@ +QA output created by 641 +Silence is golden