From patchwork Thu Aug 12 16:01: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: 12433851 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 4355AC4338F for ; Thu, 12 Aug 2021 16:03:45 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 2103D6103A for ; Thu, 12 Aug 2021 16:03:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229689AbhHLQEJ (ORCPT ); Thu, 12 Aug 2021 12:04:09 -0400 Received: from mail.kernel.org ([198.145.29.99]:49772 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230029AbhHLQEH (ORCPT ); Thu, 12 Aug 2021 12:04:07 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 3F06D6103A; Thu, 12 Aug 2021 16:03:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1628784222; bh=BvJfgZvzyjAVTopP5uHTTAU7RtJlKBr4y4i/BIOw+io=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=huRFfP4Yixz9t5ql+Xi7QH9iti1AomuuJzTbq//PbTcTomDh9M9VQ5llbKObmB772 XAJyjlJiCU9JpouCORWiiq4PYSmVYZ9QQEkvo8MJ3in4Oq2qbHQMxYhmKydp8XnO+t nxf1jAwB3AY+NQdP8fGyaII7HYGoAVceR8Rj8NLs9cgJivOAprKJJ0jxyG1hMOiL3h 7wfkZBpxQo2t5kiFl5tpdBg60K4d9cnQIBRGrsizGe6KOVwg/OuIh+U/4lUrt9KmEi HeLYYYYMnf1Lv82XlZ4BrHk1oyqflqkAXlcmGpQ1YyvQt2MZfOEmQNQo9wsrEwe6ag DymncXgQe3/DA== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v3 1/8] idmapped-mounts: use die() helper Date: Thu, 12 Aug 2021 18:01:33 +0200 Message-Id: <20210812160140.990229-2-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210812160140.990229-1-brauner@kernel.org> References: <20210812160140.990229-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=1015; h=from:subject; bh=ZYzo4oE/HOpeigGQxHXByXH90wgqFFE8xdoODQXV3FM=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSKut5zOHl2Z7uq5ca21f2LdI9KszNODCg5GhzvLGOSyNMp +MC+o5SFQYyLQVZMkcWh3SRcbjlPxWajTA2YOaxMIEMYuDgFYCL1pxgZjioISBvasjf8art/K3q5yc PYhM1F/25ZSNld8nPJmxNvw/A/pIRP+0nv3kj3e6312u79KTrpf6QTTK47i79cvKE8YR4DAA== 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 the dedicated helper to report an error and exit with failure instead of hand-rolling it. Cc: Christoph Hellwig Cc: fstests@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ patch not present /* v3 */ - Christoph Hellwig : - Split into separate patch. --- src/idmapped-mounts/idmapped-mounts.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c index 2c212131..69dcc027 100644 --- a/src/idmapped-mounts/idmapped-mounts.c +++ b/src/idmapped-mounts/idmapped-mounts.c @@ -8804,10 +8804,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); } From patchwork Thu Aug 12 16:01:34 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12433853 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 8E2E1C4338F for ; Thu, 12 Aug 2021 16:03:51 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 6DDF86103E for ; Thu, 12 Aug 2021 16:03:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229519AbhHLQEP (ORCPT ); Thu, 12 Aug 2021 12:04:15 -0400 Received: from mail.kernel.org ([198.145.29.99]:49796 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229588AbhHLQEO (ORCPT ); Thu, 12 Aug 2021 12:04:14 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 4A9436103A; Thu, 12 Aug 2021 16:03:48 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1628784229; bh=DK0KjNjlK+n+8/glW+V5ILKmUMk8kqNXC041Y7JdIlY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=R9uaabNZeONxoXPL/eaRgqgNZh49PSWXD7zZr+dk8p35JQEjzKspsWcDm+QRhjf8c 9nT38y2ES9+s1jVwZOvnnIe2YPMBAeDDxN4bQUSZRew6iRqnvNy7eTeBeCoDFiCXYg ti9BW7UkWGnQXt3FSaz93ahd6C3FDOe+atKy+klVh7+o+l9DQY0o0ZNvj466f6Oew/ 97Pp/2GZAEyJaWs8kGGY/3DkzMGUvBLYhcB7AV8NLTVH/CHJFfNkwiK50desJmu/Qy dUDLan7Xlgz66elJw9jDyLAQ0YArbaCdGpHx6PVvKw9PWjA5Jpi6cbNTMj/kWs7Y+C 3orUHpz95AdkQ== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v3 2/8] idmapped-mounts: switch to getopt_long_only() Date: Thu, 12 Aug 2021 18:01:34 +0200 Message-Id: <20210812160140.990229-3-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210812160140.990229-1-brauner@kernel.org> References: <20210812160140.990229-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=1653; h=from:subject; bh=nRgHMrq/YOuXzb2I5/GHqf7G4cyNluPqHLUA6AH6zWs=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSKut43eFxUPtmJf6/Totu3wzpDRV/Ov7X3S8/ql5v5U0Wm duXt6yhlYRDjYpAVU2RxaDcJl1vOU7HZKFMDZg4rE8gQBi5OAZhIVjDDP63bnAFTOE/u2nH92FmW0H 35b18yrPu58Ph7d09p57hDN6oYGWavC52qv7H7jETr/r8eLC3djI82JV/O3CGu8zp7k3NQLzMA 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 We're not using the shortopts anywhere anyway so just rely on longopts. Cc: fstests@vger.kernel.org Suggested-by: Christoph Hellwig Signed-off-by: Christian Brauner --- /* v2 */ patch not present /* v3 */ - Christoph Hellwig : - Split into separate patch. --- src/idmapped-mounts/idmapped-mounts.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c index 69dcc027..e565246e 100644 --- a/src/idmapped-mounts/idmapped-mounts.c +++ b/src/idmapped-mounts/idmapped-mounts.c @@ -8717,8 +8717,11 @@ 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"); _exit(EXIT_SUCCESS); } @@ -8826,7 +8829,7 @@ int main(int argc, char *argv[]) int index = 0; bool supported = false; - while ((ret = getopt_long(argc, argv, "", longopts, &index)) != -1) { + while ((ret = getopt_long_only(argc, argv, "d:f:m:sh", longopts, &index)) != -1) { switch (ret) { case 'd': t_device = optarg; From patchwork Thu Aug 12 16:01:35 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12433855 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 953B8C4320A for ; Thu, 12 Aug 2021 16:03:57 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 7CB726103A for ; Thu, 12 Aug 2021 16:03:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230040AbhHLQEV (ORCPT ); Thu, 12 Aug 2021 12:04:21 -0400 Received: from mail.kernel.org ([198.145.29.99]:49830 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229894AbhHLQEV (ORCPT ); Thu, 12 Aug 2021 12:04:21 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 8B6BD604AC; Thu, 12 Aug 2021 16:03:54 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1628784235; bh=gNC/n+eskzlmrFALDhTHIbR0ofTes7QbvJCkpWOitMw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qkpHF8T/dHHKEbhu7Z5SnBSZ+Qn+eeAO6/d15TB69sfnyqsLTu1WIH6uKGpr54q8R ytj39l6s9pBtr1rOzNMxZrtGe1Gvlm4jZELfarTSnQxuplwDHHoQWq4qeO9vW6rnqx jzgMBwRToVMr9jo8myX+QgENWC3Ij8+Ix3lO3Mz7eN9ak1VMB9BeslSBz/T/L9xXul lfPW43cFRj/Y8Mt1s/RR0mlBks06BlFZ2cm5WFgyusOWnacjOH9oHkQkVHM76q8g/7 PHJpe4pbJCA0BUemytVhyTIY4nGsGptEhB5E0jm3RVOu48pTQfxBfBoJPWyLTYUtOQ QMCQgemBXCQjw== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v3 3/8] idmapped-mounts: introduce an explicit command line switch for testsuite Date: Thu, 12 Aug 2021 18:01:35 +0200 Message-Id: <20210812160140.990229-4-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210812160140.990229-1-brauner@kernel.org> References: <20210812160140.990229-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=2669; h=from:subject; bh=fpVXkPsVBKZwBBfhISoaIIsnX6MmldyBiCn0RRPshaE=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSKut7fOCVdvuVZU16S3f2PhzquVGaHflEt/pDpo9AfejJv 9e4XHaUsDGJcDLJiiiwO7Sbhcst5KjYbZWrAzGFlAhnCwMUpABM5GsnIsPx86by0xW9dZ6o2KvIGXv 9/x6a5LVxEWjdyomj9fc6pTIwMk1IMn3HPcz18K/Tx2d3L9ua8jyncmXIpcK930ddv+dW/2QE= 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 Introduce an explicit command line switch to runs the basic test suite. This prepares for the introduction of additional command line switches to run additional tests. Cc: Christoph Hellwig Cc: fstests@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ patch not present /* v3 */ - Christoph Hellwig : - Split into separate patch. --- src/idmapped-mounts/idmapped-mounts.c | 11 ++++++++--- tests/generic/633 | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/idmapped-mounts/idmapped-mounts.c b/src/idmapped-mounts/idmapped-mounts.c index e565246e..7723a222 100644 --- a/src/idmapped-mounts/idmapped-mounts.c +++ b/src/idmapped-mounts/idmapped-mounts.c @@ -8722,6 +8722,7 @@ static void usage(void) 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); } @@ -8732,7 +8733,8 @@ static const struct option longopts[] = { {"mountpoint", required_argument, 0, 'm'}, {"supported", no_argument, 0, 's'}, {"help", no_argument, 0, 'h'}, - {NULL, 0, 0, 0 }, + {"test-core", no_argument, 0, 'c'}, + {NULL, 0, 0, 0}, }; struct t_idmapped_mounts { @@ -8827,7 +8829,7 @@ 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_only(argc, argv, "d:f:m:sh", longopts, &index)) != -1) { switch (ret) { @@ -8843,6 +8845,9 @@ int main(int argc, char *argv[]) case 's': supported = true; break; + case 'c': + test_core = true; + break; case 'h': /* fallthrough */ default: @@ -8912,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 Thu Aug 12 16:01:36 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12433857 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 04096C4338F for ; Thu, 12 Aug 2021 16:04:03 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D8A1A6109F for ; Thu, 12 Aug 2021 16:04:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230116AbhHLQE1 (ORCPT ); Thu, 12 Aug 2021 12:04:27 -0400 Received: from mail.kernel.org ([198.145.29.99]:49906 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229909AbhHLQE1 (ORCPT ); Thu, 12 Aug 2021 12:04:27 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 7907E604AC; Thu, 12 Aug 2021 16:04:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1628784241; bh=+ccC6e5yIPt7Zs9zXwtK6d+abeqnyShnxQEC1gbFnMA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=rCOa/HhCvJWSuuZ36Ru6qjmxvho92aV2gK0zZQqGGTZUXOTZWaWRA0GV7lsIZEfFh /U1YIcy8N8hMNfn9PjRGQFWNcldHzakZy0JF34ajKkBKsg0EovVCUahnMRB0bdxoia Mx4Y9SCsLfDgCtbQaOG2nDiGgYZN3pFMykMuxPXNX3mbd8zmAZckiytmP5qsyldbfR 2WD8qv7v1qWfLVoDoefmxA+99odoXE4fzc+tpT2v1FAyIE6OIzbZkF+49XZCkAShaY M8WR45TSSxDrBh82WfqQfMHgIMXz+/3yp0jqmULyRbNt6zJkOGUdzi6CTh6tbjdUKi /zAKuhbULqHgw== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v3 4/8] generic/640: add fscaps regression test Date: Thu, 12 Aug 2021 18:01:36 +0200 Message-Id: <20210812160140.990229-5-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210812160140.990229-1-brauner@kernel.org> References: <20210812160140.990229-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=9641; h=from:subject; bh=xTcG9GFmyWCR1V01POSP7YelWGq2nJO/uPBscYSA/9k=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSKut7fsOlxSG7IWtMLi3csKkw+Euw8N6l88gYXS8F8k+xo hf73HaUsDGJcDLJiiiwO7Sbhcst5KjYbZWrAzGFlAhnCwMUpABP518jw3zU8ZWLvlqv610KtKt8tm+ 7SXvZ3q4tHZqzs8v9H70y4o8nI8DH7fswqzgn7pVpOejG0m6wwMGk/PVeYvepJtb3l27ZILgA= 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: fstests@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner --- /* 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. /* v3 */ unchanged --- src/idmapped-mounts/idmapped-mounts.c | 161 +++++++++++++++++++++++--- tests/generic/640 | 28 +++++ tests/generic/640.out | 2 + 3 files changed, 175 insertions(+), 16 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 7723a222..0b2d7a52 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, 'd'}, - {"fstype", required_argument, 0, 'f'}, - {"mountpoint", required_argument, 0, 'm'}, - {"supported", no_argument, 0, 's'}, - {"help", no_argument, 0, 'h'}, - {"test-core", no_argument, 0, 'c'}, - {NULL, 0, 0, 0}, + {"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'}, + {"test-core", no_argument, 0, 'c'}, + {"test-fscaps-regression", no_argument, 0, 'g'}, + {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,9 +8950,9 @@ 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_only(argc, argv, "d:f:m:sh", longopts, &index)) != -1) { + while ((ret = getopt_long_only(argc, argv, "d:f:m:g:shcg", longopts, &index)) != -1) { switch (ret) { case 'd': t_device = optarg; @@ -8848,6 +8969,9 @@ int main(int argc, char *argv[]) case 'c': test_core = true; break; + case 'g': + test_fscaps_regression = true; + break; case 'h': /* 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 Thu Aug 12 16:01:37 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12433859 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 725A1C4338F for ; Thu, 12 Aug 2021 16:04:06 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 584D66103E for ; Thu, 12 Aug 2021 16:04:06 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229894AbhHLQEb (ORCPT ); Thu, 12 Aug 2021 12:04:31 -0400 Received: from mail.kernel.org ([198.145.29.99]:49928 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229661AbhHLQEa (ORCPT ); Thu, 12 Aug 2021 12:04:30 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 44E74604AC; Thu, 12 Aug 2021 16:04:04 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1628784245; bh=ZFiFIaD37bgoKLwwagHcv3eJeDGhyn+fe0jXWuQPIT8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uJXl19smD26d+8XTONfPFv4XLllKYLYJBN2IElPy6hXA22mXrBG5H2NW23yKOaOu9 i2PMxhkbjO3t9MQoGuO7Ty6UgXS1StC46r8EkU9t2C5EljrdkQ1Z7mrhkMj9on5Gjv hXA0+jDSTPUH2VLLYAk5+0Qj+anCYFTZboCJAnj8QW7Y7uN8xZxEVpEhYIwSvrcU4j h4lDf6rcifekIev/cwmX609o/fgGrS/zPmdT7CAfIy2jk+99nBjURGY0vbwPA1uJfs tcNOaOkvLFtRXfYKx61el3Eo7jBA/+ETUOHVHK2dklZFDsGNZVO+ihPaf6ttrhGThD 7JcIeMmiGsbig== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v3 5/8] idmapped-mounts: refactor helpers Date: Thu, 12 Aug 2021 18:01:37 +0200 Message-Id: <20210812160140.990229-6-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210812160140.990229-1-brauner@kernel.org> References: <20210812160140.990229-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=15668; h=from:subject; bh=bc9NuDZOlwgA4csFL6VM2zmVOKqTu6EUFejrqsqUGVM=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSKut4//eppQeLNhjeJzfUZ1YcNFgZp+yu+uViyU/bd4b5d 9ksrOkpZGMS4GGTFFFkc2k3C5ZbzVGw2ytSAmcPKBDKEgYtTACayT5zhf0ZWgOKupWKXP+4wLPu0ef /SHann2RpYm9OtT+/5oOlyaQvDb5ZZRof9WkQ53tRHX9ZSuuyx4ghr5BT5cJXJityRh7gP8gEA 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 Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ 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 Thu Aug 12 16:01:38 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12433861 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 43297C4338F for ; Thu, 12 Aug 2021 16:04:10 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1D8436103A for ; Thu, 12 Aug 2021 16:04:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230244AbhHLQEe (ORCPT ); Thu, 12 Aug 2021 12:04:34 -0400 Received: from mail.kernel.org ([198.145.29.99]:49942 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229909AbhHLQEe (ORCPT ); Thu, 12 Aug 2021 12:04:34 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id EA9B76103E; Thu, 12 Aug 2021 16:04:07 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1628784249; bh=J0IMXAnlneUodBqqnGJiiKQvX2srL9iCKOitrTL3ZZo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=H8CpPY/CuZuoXyj0aBTUAy4Y0OSDtBz+52DpUoSxiJhArrKfquwzN70YOZ0jXWjJ+ BBcjI8V9DotFcJp9wUauAaG5LAfcck3N/JrTcp3E/IyWiwA0d7IlHEDa7RnAJZ0Fo/ MNn4RjU+p7HTfXrmD9PzCvMJnerfqO0EtTgWLc2PC1SUqRj4oF7JCSJYIo31VltAQc djdCNHog4g3Ehq1ftRJAPYZk5JKSNeBwedS5bAZ6gMI6CpbrUBervXjX6gS1AikY2o Hh187FP8yleBEgD4mevDtaYTgGcX51nu69INeMSW4K4uMY6DxWB1C+SHRMc+EkNxUo zD8o1V64OcP9Q== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v3 6/8] idmapped-mounts: add nested userns creation helpers Date: Thu, 12 Aug 2021 18:01:38 +0200 Message-Id: <20210812160140.990229-7-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210812160140.990229-1-brauner@kernel.org> References: <20210812160140.990229-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=9147; h=from:subject; bh=PAi6k0awXBf66no3UYbjMnH2zI7hQGieDclRWeU+UC8=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSKuj6o7foaKneVM3lX8cPne4+Wu2tqG5+4V68hfsH5WIXD xGNRHaUsDGJcDLJiiiwO7Sbhcst5KjYbZWrAzGFlAhnCwMUpABN5e5zhf6VO1PqndzIc21dMOCcy/0 31i01Mhyeukf3tqFE/7f7ew5kM/xSXLX+qcO1B7+OdnC0KpjIab6P6uHTWHSuy7rYwvtjPwgMA 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: fstests@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged --- 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 0b2d7a52..49f360ec 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 Thu Aug 12 16:01:39 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 12433863 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 82290C4338F for ; Thu, 12 Aug 2021 16:04:15 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 67723610A3 for ; Thu, 12 Aug 2021 16:04:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230270AbhHLQEj (ORCPT ); Thu, 12 Aug 2021 12:04:39 -0400 Received: from mail.kernel.org ([198.145.29.99]:49978 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229661AbhHLQEi (ORCPT ); Thu, 12 Aug 2021 12:04:38 -0400 Received: by mail.kernel.org (Postfix) with ESMTPSA id 112696104F; Thu, 12 Aug 2021 16:04:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1628784253; bh=NCLxLih8XgglMj/9AlrYNwkauNyxUQDKDNSQksHYyAg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=na4tzm2+yzMir7AXYZQq6Rgel6YDE3SM0/z7eWJQ/LqAAvAkEn8VLqLAI4OcLKFF3 QnI91Mg1FZi1888sOGXBBCXoGrJHIrWQLtxJesfRJ0sAbq6FCrTpPwoRtNnO0t/eYK a2SjcXdg2xONsvG6UIxuO2QN9q8RU5ICBoJkwqInFzGNr8/1bIH/pTL3CM/aNISAQb anTFtRCkW4IybJV34ioF+pASQ3puzDufUiXi/5AoD99r7Qrlu53tY4dC+Db85DRVGr V2Wu9VIlSfc9nr+nwXbKgCdRodZe0II2Eo+NFacK3LBW5c1J8T7vxkQccLC0YZU+jT MvHbvhOYTB3+Q== From: Christian Brauner To: fstests@vger.kernel.org, Eryu Guan , Christoph Hellwig Cc: Christian Brauner Subject: [PATCH v3 7/8] generic/641: add nested user namespace tests Date: Thu, 12 Aug 2021 18:01:39 +0200 Message-Id: <20210812160140.990229-8-brauner@kernel.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210812160140.990229-1-brauner@kernel.org> References: <20210812160140.990229-1-brauner@kernel.org> MIME-Version: 1.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=27526; h=from:subject; bh=AA3QY7bIIE4Y7TGVPO5ZE/0dS710N84WW7PX8VyrN6w=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMSSKuj7YW+oV9fh8kewWnltXOyp/nrnWcHbzWY5PN+7fPsXR PrN5dUcpC4MYF4OsmCKLQ7tJuNxynorNRpkaMHNYmUCGMHBxCsBEfj9jZJhp/GGj0zpBo0/19efNE+ ZLLvDdZbQ/3KxwW7i9lHVd+hNGhjZzcdNLYbvPFVcI7vbM8uxxrrK2Evx3Icfk4hc1oTWneAA= 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: fstests@vger.kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner --- /* 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. /* v3 */ unchanged --- src/idmapped-mounts/idmapped-mounts.c | 726 +++++++++++++++++++++++++- src/idmapped-mounts/utils.h | 4 + tests/generic/641 | 28 + tests/generic/641.out | 2 + 4 files changed, 758 insertions(+), 2 deletions(-) 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 49f360ec..1d569b89 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"); @@ -8825,6 +9533,7 @@ static void usage(void) 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"); + fprintf(stderr, "--test-nested-userns Run nested userns idmapped mount testsuite\n"); _exit(EXIT_SUCCESS); } @@ -8837,6 +9546,7 @@ static const struct option longopts[] = { {"help", no_argument, 0, 'h'}, {"test-core", no_argument, 0, 'c'}, {"test-fscaps-regression", no_argument, 0, 'g'}, + {"test-nested-userns", no_argument, 0, 'n'}, {NULL, 0, 0, 0}, }; @@ -8899,6 +9609,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,9 +9650,10 @@ 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_only(argc, argv, "d:f:m:g:shcg", longopts, &index)) != -1) { + while ((ret = getopt_long_only(argc, argv, "d:f:m:g:shcgn", longopts, &index)) != -1) { switch (ret) { case 'd': t_device = optarg; @@ -8958,6 +9673,9 @@ int main(int argc, char *argv[]) case 'g': test_fscaps_regression = true; break; + case 'n': + test_nested_userns = true; + break; case 'h': /* fallthrough */ default: @@ -9035,6 +9753,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