From patchwork Fri May 7 15:00:54 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12244799 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 C5899C43460 for ; Fri, 7 May 2021 15:01:18 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 8E2556145E for ; Fri, 7 May 2021 15:01:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237706AbhEGPCR (ORCPT ); Fri, 7 May 2021 11:02:17 -0400 Received: from mail.kernel.org ([198.145.29.99]:35496 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230486AbhEGPCR (ORCPT ); Fri, 7 May 2021 11:02:17 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id BF67861456; Fri, 7 May 2021 15:01:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1620399677; bh=H+6MzNUtrN83BCHR1jewpUl8a/cC//ACu97+3Za8q0I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Ry/+JaD3igzri0t6dc3ztbHzspurC+Y2kZdXGewgc87TLRSQ2WSBcwHwSjX1hy+ED l+gDfhvqUbVY6lU/yfchFmrZ8dkbpz3ya004fbzLgxvA9rQKAlpEWvfrcrbg5exECm 2Ey6hubixRd/BrWNVCfJjauOui9IiWf9CS9cFh/oOPk3t5eyqQDT/C7YKonC/y9u0x acujGQMGOGgnB2+DzLaNW5HYNrExcTyZ6W9KHwcFoo5H5Ua5iCgkDhZLgVaMH52wJh PwjN4LGytvQvCCU2jcWLMF7Y98QNUW8rcLbnBYOJnggFqtmtVVa5qNOn1F95hoFVAI +u8SPE3Sf5Wyw== From: Christian Brauner To: Eryu Guan , fstests@vger.kernel.org Cc: Christoph Hellwig , Christian Brauner Subject: [PATCH 1/7] idmapped-mounts: remove unused set_cloexec() helper Date: Fri, 7 May 2021 17:00:54 +0200 Message-Id: <20210507150100.968659-2-brauner@kernel.org> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210507150100.968659-1-brauner@kernel.org> References: <20210507150100.968659-1-brauner@kernel.org> MIME-Version: 1.0 X-Patch-Hashes: v=1; h=sha256; i=hiHOddqtCcQXlSPDY8SveY5GGlaMvcepRMRcWnPYkHE=; m=n9LDYBRRvluV4y4F+NbSLhCsfMMLo/OBv8QIm7HhcP0=; p=Rt+BVCU7X9Hm7sQjZEteG1pjdlfW32srgONjL3Lsg0A=; g=87f8cb1d65b23c7a95b4dfc8995c820b7ca1b6de X-Patch-Sig: m=pgp; i=christian.brauner@ubuntu.com; s=0x0x91C61BC06578DCA2; b=iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCYJVWGQAKCRCRxhvAZXjcolU+AQDG3ou 3l2Ri1HUa58kMSTCNJD7wDWLedjm3h/GqlaaGzwEA88/+2p+GV+Po+E57VCcO1VjMgRkqH1XWVfqE xRz9eA8= Precedence: bulk List-ID: X-Mailing-List: fstests@vger.kernel.org From: Christian Brauner This function has never been used so remove it. Cc: fstests@vger.kernel.org Signed-off-by: Christian Brauner --- 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)) From patchwork Fri May 7 15:00:55 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12244801 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 3FF6AC433B4 for ; Fri, 7 May 2021 15:01:20 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 137B16145D for ; Fri, 7 May 2021 15:01:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236714AbhEGPCT (ORCPT ); Fri, 7 May 2021 11:02:19 -0400 Received: from mail.kernel.org ([198.145.29.99]:35524 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230486AbhEGPCT (ORCPT ); Fri, 7 May 2021 11:02:19 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id CB0776145A; Fri, 7 May 2021 15:01:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1620399679; bh=+ZXapNqAJdsrwvPxOfZ6KztdX5TeVqdj/RqAVnkz1V0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=tof00WNf0FylO30Cd5fXfF2enNcFuM+JjYet2uHSlttDPjEsnbcLGWuJlCHVKhH4R /qaIN0IxqseXUlWC+haBrQQufIqzcyeQpa4l1vwJqvg7MjgBPctUh7VDvIHsp7+5HR EAj2c2kp/B7yv6mx4kOs+UfObn/0eQm2OpEEQA6XcD+95TyLSg1hf17SPFrZp3uEmu kx8OoYmKt4qzdZGuNuUmq0LRnMLKpu9vPuU2YzAmsYU7zp/J9qrx82eU9OURqil78G wUlpyoiXyk8tomA9OKOWG8dKz7yO6P/07E9K5ZMAM+MNu8lp+M8cPvuJr9sFAzhzvu dP6w4H3h28/3w== From: Christian Brauner To: Eryu Guan , fstests@vger.kernel.org Cc: Christoph Hellwig , Christian Brauner Subject: [PATCH 2/7] idmapped-mounts: add missing newline to print_r() Date: Fri, 7 May 2021 17:00:55 +0200 Message-Id: <20210507150100.968659-3-brauner@kernel.org> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210507150100.968659-1-brauner@kernel.org> References: <20210507150100.968659-1-brauner@kernel.org> MIME-Version: 1.0 X-Patch-Hashes: v=1; h=sha256; i=JIDOOp8bimcVseNEX/pKO9nFqJ2BhX8jlE+EuF+cbgI=; m=ZZPj429gxwBvBnqNpII5Wx4tuVMSKDV+oVGHXtQGJeY=; p=fRGcEXtgrISjJb3f2y3sdmIpl801y5jrpiZKRO3mD+o=; g=7ee073e9ad2f2546c157419ef52eb3825f2af27b X-Patch-Sig: m=pgp; i=christian.brauner@ubuntu.com; s=0x0x91C61BC06578DCA2; b=iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCYJVWGgAKCRCRxhvAZXjcomQ7AQC25JZ eP1qsvDaWFEWuUVk6nDbX1OsaNbJPjPK4dR7hPAD/VyAkm+rhs2j2FR+zoyZ/f9i4ZmqcXzedj1/I 6VrltQ0= Precedence: bulk List-ID: X-Mailing-List: fstests@vger.kernel.org From: Christian Brauner 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 Signed-off-by: Christian Brauner --- 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)"); From patchwork Fri May 7 15:00:56 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12244803 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 44062C433B4 for ; Fri, 7 May 2021 15:01:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1730F6145D for ; Fri, 7 May 2021 15:01:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237723AbhEGPCX (ORCPT ); Fri, 7 May 2021 11:02:23 -0400 Received: from mail.kernel.org ([198.145.29.99]:35556 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230486AbhEGPCU (ORCPT ); Fri, 7 May 2021 11:02:20 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 85FA1613F1; Fri, 7 May 2021 15:01:19 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1620399680; bh=TChMHz3f4VhRizYR7kPbAmL0/J5ymmJJ5F69tzDIux4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=iGxPFJkv0LaHrHzDYgxGaBPU997eQRsCw5CLi7rU+g518COZfCGjxOww23Eg5O0qn nX1dkmXCSnduvtTMClk43Tv2t5zYvby9EbajEkeX2buWVfJ7tjtDzdSvYyX3vVztKo VZ+E/6iMa3ivEjq9jR6y3avh4cPheuJ5VOPHmAMM6AizbvRlqYLpYM0YMRLKjVdOWa kvxY3AaPgHmuDK7sHp8KLBOFtqlf+qZGzDLtBSxXXImeDw/Vn8PkAEEsmAEzo6Gcx1 7cPfzk2AO5AXnJWJchZdW/MYOYpi20fWSOol52UTdtxwMDajPWLWdXuvcU5+AowC4a Iync2Rp2Mfa8g== From: Christian Brauner To: Eryu Guan , fstests@vger.kernel.org Cc: Christoph Hellwig , Christian Brauner Subject: [PATCH 3/7] idmapped-mounts: split out run_test() function Date: Fri, 7 May 2021 17:00:56 +0200 Message-Id: <20210507150100.968659-4-brauner@kernel.org> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210507150100.968659-1-brauner@kernel.org> References: <20210507150100.968659-1-brauner@kernel.org> MIME-Version: 1.0 X-Patch-Hashes: v=1; h=sha256; i=Q4DcEVc7A/XYHsNeiTZcC/3O1jIqMcceygN/o9mWeog=; m=sEmLOEJ9oZNN4XQf6DmR0kO1EiPJUU0dyNnxRMvKI0s=; p=XKsa0D6pDWE60PbXZvHQkCKz1XM6x1AySnfvntpfxjo=; g=060cb7b5eedd82ba01dcb9643b742e2e692ac5b8 X-Patch-Sig: m=pgp; i=christian.brauner@ubuntu.com; s=0x0x91C61BC06578DCA2; b=iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCYJVWGgAKCRCRxhvAZXjcogBNAP9PMK7 Jy0gK81CVg8uNenjnGk1ruA5ItreLsMNu/RvClAD/WOq0Edt6a4QiGfkbWDzsEW4cLURmDt/4cT6u sENALAE= Precedence: bulk List-ID: X-Mailing-List: fstests@vger.kernel.org From: Christian Brauner to make it easier to run subsets of tests. Cc: fstests@vger.kernel.org Signed-off-by: Christian Brauner --- 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; From patchwork Fri May 7 15:00:57 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12244805 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,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 828D3C433ED for ; Fri, 7 May 2021 15:01:28 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5155F6145E for ; Fri, 7 May 2021 15:01:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230486AbhEGPCX (ORCPT ); Fri, 7 May 2021 11:02:23 -0400 Received: from mail.kernel.org ([198.145.29.99]:35572 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237715AbhEGPCW (ORCPT ); Fri, 7 May 2021 11:02:22 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 4865061456; Fri, 7 May 2021 15:01:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1620399682; bh=7ZkR0jigI1iBn2qos9Xi+a1Qm1yXIGb73o9tcIq9GLY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ho+kq8sM4KF10nQauB4qIlJxvEc7k+PVuPUOuZg+HxELP5TOAypH615oKmGLglhUg exn9bDSFgQ12U0/jK/rkBPWgU77VBN438bwRP3+BMSaOSOgsRH/NyKV41wQF4yYBZG 0F4qXZQMFbP7OjDAeO7bbRgWAvgE9iVNzmnLw601lrwayDaMSsjWP7iN80qSSlHd0+ TEh4bEZyjxemTx+SaNpxShq1rrxsJvNFXAhGXgV63kIMFgQC6gh2yTULQWSP4rv1yj n8PEzzHBmoA8HJoTfTa8nanX/F+5+Qbp8ySTBieMsVX6T7TwRkztN01BioZQZD1NiG 0Mc98MbClbIJA== From: Christian Brauner To: Eryu Guan , fstests@vger.kernel.org Cc: Christoph Hellwig , Christian Brauner Subject: [PATCH 4/7] generic/637: add fscaps regression test Date: Fri, 7 May 2021 17:00:57 +0200 Message-Id: <20210507150100.968659-5-brauner@kernel.org> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210507150100.968659-1-brauner@kernel.org> References: <20210507150100.968659-1-brauner@kernel.org> MIME-Version: 1.0 X-Patch-Hashes: v=1; h=sha256; i=IBGcFj9YhQfSJLnu9l+bHcI0seZ3rT45nnTkRVEMla8=; m=EtZEQotKpb6JN8l4G45xBer7S6tIn3vMaU+740ytsuI=; p=JAaEbJqxsN96GF6s8N0LeOx2XFnUIIWev+PfM/w16w4=; g=b9abdf66341718d1675c24c7c42d671e6e688680 X-Patch-Sig: m=pgp; i=christian.brauner@ubuntu.com; s=0x0x91C61BC06578DCA2; b=iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCYJVWGgAKCRCRxhvAZXjcor7AAPkB3Oj qKbXXILxViWcerGw/TVTV0vv1bS80g1B0XBZn6AD+McN8vA+Sr1PXa4mg0Gy/IAQOtmAIjF3arS4k RwNHRwg= 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: fstests@vger.kernel.org Cc: Christoph Hellwig Signed-off-by: Christian Brauner --- .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 From patchwork Fri May 7 15:00:58 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12244807 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 86A94C433B4 for ; Fri, 7 May 2021 15:01:29 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 4F4FF6145E for ; Fri, 7 May 2021 15:01:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237730AbhEGPC2 (ORCPT ); Fri, 7 May 2021 11:02:28 -0400 Received: from mail.kernel.org ([198.145.29.99]:35588 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237711AbhEGPCY (ORCPT ); Fri, 7 May 2021 11:02:24 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 130646145A; Fri, 7 May 2021 15:01:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1620399684; bh=8zNN/ooM6gQDuXjjF5heDcGnawYvmgTpYyEiCKv/u2U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XES754bUcXD0MQVk/LYey2KCF6Gp/n30eXaVbCPmT5CTV0xfDVRbUyjQosGrIgFSc tweeSFpL96Ui+qRzXIvNeeJzTqpszbsGiIPS1mHV68FKF8o08d1zoC7cZDqvfU6mfJ BrRS1dl1vmyJSoxa/jr5SbCfyNA9ER1R0/TpgDqNaj0Fvj0pyKMl3Iae21S405yWen DNGB9S9BKUYT6l05Hip1oTdYj6tZQFh89s9PkpSDJ6iVYwuwnosX/7toy3NC/vpb2E LR1TnS2phV2XDR8bS6FzKSWsnYGTSQrpv4GI2pv6i69WUDv8sSGvZfbl20IFYWVwGs t2y5qGWa2rz+A== From: Christian Brauner To: Eryu Guan , fstests@vger.kernel.org Cc: Christoph Hellwig , Christian Brauner Subject: [PATCH 5/7] idmapped-mounts: refactor helpers Date: Fri, 7 May 2021 17:00:58 +0200 Message-Id: <20210507150100.968659-6-brauner@kernel.org> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210507150100.968659-1-brauner@kernel.org> References: <20210507150100.968659-1-brauner@kernel.org> MIME-Version: 1.0 X-Patch-Hashes: v=1; h=sha256; i=qx9oAe27XvP9wmdCMhY9mdk596Te0pbEEae30v7rnCc=; m=jbX11OXmTcYnZbKBU6aMvPVNTEVEawaxC8TP6Jg6XP8=; p=xGRWf+5zmzAtHVVWBIyd45Ek5Las0hhO6XL9+yPvivk=; g=af16b0b2f0ccde16689577c5f53a0c3581994552 X-Patch-Sig: m=pgp; i=christian.brauner@ubuntu.com; s=0x0x91C61BC06578DCA2; b=iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCYJVWGwAKCRCRxhvAZXjcoiWIAQDmn4g MQxRKqVuHNtfRpYfjPOo0u6Dg6UBwygCtZkdZ9gD/X6MjyF1JV3nKueQ+2BoQnps475mV52J7S8F7 wCNsIQc= 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 --- 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//{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 Fri May 7 15:00:59 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12244809 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 650D7C43461 for ; Fri, 7 May 2021 15:01:30 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 2CB6C613F1 for ; Fri, 7 May 2021 15:01:30 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237711AbhEGPC2 (ORCPT ); Fri, 7 May 2021 11:02:28 -0400 Received: from mail.kernel.org ([198.145.29.99]:35614 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237715AbhEGPC1 (ORCPT ); Fri, 7 May 2021 11:02:27 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id D99DC61400; Fri, 7 May 2021 15:01:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1620399686; bh=KyM09Gvjg+pYMSjLyU07vAFrJ5iRBa7aEYw6v2JIaEw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HBuj9uIq5QiRCY9P2Ug+PnvvFUSkStppFghwzqSkuzshf5X0xiBzLG4VUxac8NX7G yAAwBOf05U3x+vBl7EFzap93nf5jL/vx65q83l+hnLacZIY5tqEDulywDOaz1AaWwA eJypwG5nmFkqmbIFE23pocktmByatK27aJoEWmLn8OMRJX0mM5xnGj5lbXrunyhOew C3afczWQHXtp9OUFvNFb9U1DPtuTAaB6G2AiJxSKZRptop/M2gDEmS4EZhx10Mbg25 6s00HoFHIgTW96gJXAgF3xbu3kFmZ7djGV3LWXmPkOiTjloYNMOu5zDqZI/JOBCVEa AE9eD+hoA0/UA== From: Christian Brauner To: Eryu Guan , fstests@vger.kernel.org Cc: Christoph Hellwig , Christian Brauner Subject: [PATCH 6/7] idmapped-mounts: add nested userns creation helpers Date: Fri, 7 May 2021 17:00:59 +0200 Message-Id: <20210507150100.968659-7-brauner@kernel.org> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210507150100.968659-1-brauner@kernel.org> References: <20210507150100.968659-1-brauner@kernel.org> MIME-Version: 1.0 X-Patch-Hashes: v=1; h=sha256; i=G9mZDhfQorbFVYlNdXFEXJkcbShmhVhIcqeSawpi2DQ=; m=N0qhIWG3u7ujYqb2FV+c/S7aogLb17WAE4v21PoRXqc=; p=bhtRa9JRCEmRnDkVtsF1D71gmig1LgVPyMzUXY/PV7s=; g=fd7a99e2c49df3e8eff8ae6c0dfd2f9c470042ec X-Patch-Sig: m=pgp; i=christian.brauner@ubuntu.com; s=0x0x91C61BC06578DCA2; b=iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCYJVWGwAKCRCRxhvAZXjcojmQAQCkUtn w4kzaNf6WgQDFCDnQwQUcHEKhR6ttxCe033p9GQEApX7MKwAEpAADGTMaqgZVHoedwYj0flIaMBe2 BrhVOQ4= 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: fstests@vger.kernel.org Cc: Christoph Hellwig Signed-off-by: Christian Brauner --- 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 +#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 Fri May 7 15:01:00 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12244811 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 A0A6FC43460 for ; Fri, 7 May 2021 15:01:30 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 63BEA6145E for ; Fri, 7 May 2021 15:01:30 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237715AbhEGPC3 (ORCPT ); Fri, 7 May 2021 11:02:29 -0400 Received: from mail.kernel.org ([198.145.29.99]:35624 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237738AbhEGPC2 (ORCPT ); Fri, 7 May 2021 11:02:28 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id B36EA61456; Fri, 7 May 2021 15:01:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1620399688; bh=W4khVUG85d2sD293ua0w1eeDpKGeo23VSPEkBx6kb40=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=LNdOXNwMY8NbqC+rn5pcR3rXz71/RhsMrrhUegPXX+3NMluxis0j8qP7Qcaq72SSO 9UI9gRpvAA09JNmzkCKObF1Xyeri8xGlRHA0Zxh3nOXveOKmBLDNp2zivy0t9ZVnHb UdXSmzs+1xLW5uR/b2fLWPUElLzJymi8hkJmo1XKbW6Obuqoa6EobNKIbBTPeBOh4I tHJK3LRrDu7TlCUPbXOs6tMcJ+VuHHLyL70KW4WVZWIFyLX/M5RlUFQzwMUlNuhKea POUIWn4wCeuSrbmLs9tzlkWSXT5mUXwroZ/X7i9kZgM6VdL/IR4J2PuIxVnBGaytJD PrR8mmWDhW1YA== From: Christian Brauner To: Eryu Guan , fstests@vger.kernel.org Cc: Christoph Hellwig , Christian Brauner Subject: [PATCH 7/7] generic/638: add nested user namespace tests Date: Fri, 7 May 2021 17:01:00 +0200 Message-Id: <20210507150100.968659-8-brauner@kernel.org> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210507150100.968659-1-brauner@kernel.org> References: <20210507150100.968659-1-brauner@kernel.org> MIME-Version: 1.0 X-Patch-Hashes: v=1; h=sha256; i=GT+xC3cjW7m9spm1WVAylfXL4euZnI473h/hwwMkbbg=; m=KE5UNaVLEjY70jsc2vomYECSKyXztqtToX0Kcei4m3Y=; p=eE8hLodqnRaJ2fY3Vpxkd822eVIPh1EjFvRWWL9xzTI=; g=bdbe3e78aca52e8eed634a9072a82176db45cb1c X-Patch-Sig: m=pgp; i=christian.brauner@ubuntu.com; s=0x0x91C61BC06578DCA2; b=iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCYJVWGwAKCRCRxhvAZXjcojjtAQCKwcR 9XF0rXC/yGqqw5OT3mGOEA9nyOqMYW9QqML71zAD+IXONe+UBmISOmItF7NrEyFWut8/WXzWq1NfE Yb3ggwc= 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: fstests@vger.kernel.org Cc: Christoph Hellwig Signed-off-by: Christian Brauner --- .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