From nobody Sat Feb 7 18:28:44 2026 Received: from mail-pf1-f178.google.com (mail-pf1-f178.google.com [209.85.210.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E247B19F137 for ; Tue, 30 Dec 2025 15:14:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767107696; cv=none; b=mJjoNc5QrFZ8F/zWf1ON+UaHwyMfjRAsfHUphTijFdLNoD3SV+hdGM1cTiVlCQF5mFa9YdmHlX+53Y4TrueU60zW3rqaqNrRQS+DcOiiPRvj6y3KjI/OAVDAbQSsG8ccgUG14LPYuxU7jCTlYSzMc69V7KrwugYCKrCq4q++nYg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767107696; c=relaxed/simple; bh=zfuRwUoxgDxlNkdCwNTuz7RKhd7EDzbBtPFEm4JXLUA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=goP+I5VxoL8plEVVXXdv6Vy7LED/g7+5kOJco0ZFTV1SwzzEuyoi0e1k3J1z2w/ijujCx5SQ/WrY7QRNiUYm63aZdn3w3/KTmO7z2KAzb6bY/C1p9TtwbkLOGyjWmIT0oazg69aDpgp6w4ve66HcmAHx276og6gVC5hC1lRMzUk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=YklL44Dc; arc=none smtp.client-ip=209.85.210.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="YklL44Dc" Received: by mail-pf1-f178.google.com with SMTP id d2e1a72fcca58-7e2762ad850so10489181b3a.3 for ; Tue, 30 Dec 2025 07:14:54 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1767107694; x=1767712494; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=HZqJmgB96yHwiSHK6vnRePdTZOZbZq1DduhT3s+wqOA=; b=YklL44Dcw6T+Es+mFh9iSYD5/sIKvPk8fbG97AnTBtiW71Y6X66J843KJlxac90ukp iwBlxwvadxaurptn+3zaQkgyjh32eyFPwcgKAyBVEmBlYtgzlphfgVfSVOJPnekGwxhQ bT9T0SLXtXge8WIWWLR59LR1J+XXRGFp/pvVVfOLCzc1HqChHJJwwhe/i3Eg1L5Zr59d ywxVKOXW5yFacTB3rKRfDFqR9uDq+n/88iAkyuWl+BsRJdAEgd9YqbdFUgdkCbJonFva uZlOnaXb7qCQLpAVTNgLpfbnGrWy831bgPL++A+OMz7AlQl8K8Rj35ahKu4jYBjKGQTK TP4w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1767107694; x=1767712494; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=HZqJmgB96yHwiSHK6vnRePdTZOZbZq1DduhT3s+wqOA=; b=OM9A3MhG3mHQI4iHMO6F5otht9mv+6MndjsIOg9MotxYB8RcIC0xmcsayx8NW5HCou fSKyg1EvonUtnX68P8XzCBtE5cn2qMfcaH5ibJmNlAciIQWGs84cmdf05gJAoxQEa720 T/WoVsvdnoU6PI1ckGa5mFA6IK2S6SO+V38H+Zu6IRziPxlrkrUsVvZeEsKCLgfFP93P D8CrLWTymIz620ohId4zeZHvjHWZiqjinTIPL+rSLioQsEDbtXRiI1Cu8KJ6lLfs9xDd M+R1Nx6mW1X0qggCjMyPpM2x3IXBtVtpRrUVv+5iYmeHzucY7JpYZBq6yxTKlVG1R5a2 DkmQ== X-Gm-Message-State: AOJu0YztuajnqcLwVSFo15Kq9jmSwNTS3GAUnAvGFa3EwCrcN+aBYKNx xLJI2CUczDSb0EcLZkrr2R/ebLfGxFXI0CA3EjmdKQsO2NcMBXxj2Apm8lQ/IEJw X-Gm-Gg: AY/fxX7X+SPAhFI9l7gXVBMzkjtRmT3IwF8ylOTuBIQ6Mj0o44Wx1OGXyXKLBVtDNrZ zS7yERia2naniAySLhM/Gylfxrv7R2nbsZeoe46YeQnTucvbIsnPsswQ5NOw80U6AbMscGgvomO YCJ0FR6W8Pyu/0yNjNcoArvr2auP9xmwX0PcPiMP0HOBPJ2mm0UYVSqakHl4i/qXrihihaFG8nq uPVoELnTaj0kl8N3DHb3C10unF66oRzcAX75yUyeOr/uctk00VMCxd1VqLkm+JMzR1M/v1SyyPx SEykca9yNRAsUnMi3QtMp4pXlvKQbNKxd8G4v+fzbiii+cp4sdhSW257FFAK2lVb/llIrEjql8o uSq3Ps/93aLwgP1ZTH8DbSn9xsjXGcl6alGTsARGLGhKirX19tiQZMBmq+9RIJPvICfA/7UZ8TJ M0CLHepHOE05BuKqOC4yPp6Ha89/xZyp10YZI6/MmE7NrgySkuNsM= X-Google-Smtp-Source: AGHT+IHhC0kBZacfjDk03Oy9QGMGRr+UrLFQ1oDBZ5B9heTPW7TysEZYlRfMGCHvCtIZsLAQQr014w== X-Received: by 2002:a05:6a00:e8c:b0:7b9:8142:96f0 with SMTP id d2e1a72fcca58-7ff64ad9ce2mr28717962b3a.23.1767107694027; Tue, 30 Dec 2025 07:14:54 -0800 (PST) Received: from kailas.hsd1.or.comcast.net ([2601:1c2:982:6040::e14d]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-7ff7e892926sm32753561b3a.66.2025.12.30.07.14.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 30 Dec 2025 07:14:53 -0800 (PST) From: Ryan Foster To: foster.ryan.r@gmail.com Cc: linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, paul@paul-moore.com, selinux@vger.kernel.org, serge@hallyn.com Subject: [PATCH v5] security: Add KUnit tests for kuid_root_in_ns and vfsuid_root_in_currentns Date: Tue, 30 Dec 2025 07:13:09 -0800 Message-ID: <20251230151450.196371-1-foster.ryan.r@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20251110143748.4144288-1-foster.ryan.r@gmail.com> References: <20251110143748.4144288-1-foster.ryan.r@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Hi all, Sorry for the spam, this aims to fix both issues. Attempted to reproduce CI config build locally. Thanks, Ryan Reviewed-by: Serge Hallyn --- Add comprehensive KUnit tests for the namespace-related capability functions that Serge Hallyn refactored in commit 9891d2f79a9f ("Clarify the rootid_owns_currentns"). The tests verify: - Basic functionality: UID 0 in init namespace, invalid vfsuid, non-zero UIDs - Actual namespace traversal: Creating user namespaces with different UID mappings where uid 0 maps to different kuids (e.g., 1000, 2000, 3000) - Hierarchy traversal: Testing multiple nested namespaces to verify correct namespace hierarchy traversal This addresses the feedback to "test the actual functionality" by creating real user namespaces with different values for the namespace's uid 0, rather than just basic input validation. The test file is included at the end of commoncap.c when CONFIG_SECURITY_COMMONCAP_KUNIT_TEST is enabled, following the standard kernel pattern (e.g., scsi_lib.c, ext4/mballoc.c). This allows tests to access static functions in the same compilation unit without modifying production code based on test configuration. The tests require CONFIG_USER_NS to be enabled since they rely on user namespace mapping functionality. The Kconfig dependency ensures the tests only build when this requirement is met. All 7 tests pass: - test_vfsuid_root_in_currentns_init_ns - test_vfsuid_root_in_currentns_invalid - test_vfsuid_root_in_currentns_nonzero - test_kuid_root_in_ns_init_ns_uid0 - test_kuid_root_in_ns_init_ns_nonzero - test_kuid_root_in_ns_with_mapping - test_kuid_root_in_ns_with_different_mappings Updated MAINTAINER capabilities to include commoncap test Signed-off-by: Ryan Foster --- MAINTAINERS | 1 + security/Kconfig | 17 +++ security/commoncap.c | 4 + security/commoncap_test.c | 290 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 312 insertions(+) create mode 100644 security/commoncap_test.c diff --git a/MAINTAINERS b/MAINTAINERS index c0030e126fc8..6f162c736dfb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5682,6 +5682,7 @@ F: include/trace/events/capability.h F: include/uapi/linux/capability.h F: kernel/capability.c F: security/commoncap.c +F: security/commoncap_test.c =20 CAPELLA MICROSYSTEMS LIGHT SENSOR DRIVER M: Kevin Tsai diff --git a/security/Kconfig b/security/Kconfig index 285f284dfcac..6a4393fce9a1 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -284,6 +284,23 @@ config LSM =20 If unsure, leave this as the default. =20 +config SECURITY_COMMONCAP_KUNIT_TEST + bool "Build KUnit tests for commoncap" if !KUNIT_ALL_TESTS + depends on KUNIT=3Dy && USER_NS + default KUNIT_ALL_TESTS + help + This builds the commoncap KUnit tests. + + KUnit tests run during boot and output the results to the debug log + in TAP format (https://testanything.org/). Only useful for kernel devs + running KUnit test harness and are not for inclusion into a + production build. + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + source "security/Kconfig.hardening" =20 endmenu diff --git a/security/commoncap.c b/security/commoncap.c index 8a23dfab7fac..3399535808fe 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -1521,3 +1521,7 @@ DEFINE_LSM(capability) =3D { }; =20 #endif /* CONFIG_SECURITY */ + +#ifdef CONFIG_SECURITY_COMMONCAP_KUNIT_TEST +#include "commoncap_test.c" +#endif diff --git a/security/commoncap_test.c b/security/commoncap_test.c new file mode 100644 index 000000000000..1088364a54e6 --- /dev/null +++ b/security/commoncap_test.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * KUnit tests for commoncap.c security functions + * + * Tests for security-critical functions in the capability subsystem, + * particularly namespace-related capability checks. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SECURITY_COMMONCAP_KUNIT_TEST + +/* Functions are static in commoncap.c, but we can call them since we're + * included in the same compilation unit when tests are enabled. + */ + +/** + * test_vfsuid_root_in_currentns_init_ns - Test vfsuid_root_in_currentns w= ith init ns + * + * Verifies that UID 0 in the init namespace correctly owns the current + * namespace when running in init_user_ns. + * + * @test: KUnit test context + */ +static void test_vfsuid_root_in_currentns_init_ns(struct kunit *test) +{ + vfsuid_t vfsuid; + kuid_t kuid; + + /* Create UID 0 in init namespace */ + kuid =3D KUIDT_INIT(0); + vfsuid =3D VFSUIDT_INIT(kuid); + + /* In init namespace, UID 0 should own current namespace */ + KUNIT_EXPECT_TRUE(test, vfsuid_root_in_currentns(vfsuid)); +} + +/** + * test_vfsuid_root_in_currentns_invalid - Test vfsuid_root_in_currentns w= ith invalid vfsuid + * + * Verifies that an invalid vfsuid correctly returns false. + * + * @test: KUnit test context + */ +static void test_vfsuid_root_in_currentns_invalid(struct kunit *test) +{ + vfsuid_t invalid_vfsuid; + + /* Use the predefined invalid vfsuid */ + invalid_vfsuid =3D INVALID_VFSUID; + + /* Invalid vfsuid should return false */ + KUNIT_EXPECT_FALSE(test, vfsuid_root_in_currentns(invalid_vfsuid)); +} + +/** + * test_vfsuid_root_in_currentns_nonzero - Test vfsuid_root_in_currentns w= ith non-zero UID + * + * Verifies that a non-zero UID correctly returns false. + * + * @test: KUnit test context + */ +static void test_vfsuid_root_in_currentns_nonzero(struct kunit *test) +{ + vfsuid_t vfsuid; + kuid_t kuid; + + /* Create a non-zero UID */ + kuid =3D KUIDT_INIT(1000); + vfsuid =3D VFSUIDT_INIT(kuid); + + /* Non-zero UID should return false */ + KUNIT_EXPECT_FALSE(test, vfsuid_root_in_currentns(vfsuid)); +} + +/** + * test_kuid_root_in_ns_init_ns_uid0 - Test kuid_root_in_ns with init name= space and UID 0 + * + * Verifies that kuid_root_in_ns correctly identifies UID 0 in init namesp= ace. + * This tests the core namespace traversal logic. In init namespace, UID 0 + * maps to itself, so it should own the namespace. + * + * @test: KUnit test context + */ +static void test_kuid_root_in_ns_init_ns_uid0(struct kunit *test) +{ + kuid_t kuid; + struct user_namespace *init_ns; + + kuid =3D KUIDT_INIT(0); + init_ns =3D &init_user_ns; + + /* UID 0 should own init namespace */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(kuid, init_ns)); +} + +/** + * test_kuid_root_in_ns_init_ns_nonzero - Test kuid_root_in_ns with init n= amespace and non-zero UID + * + * Verifies that kuid_root_in_ns correctly rejects non-zero UIDs in init n= amespace. + * Only UID 0 should own a namespace. + * + * @test: KUnit test context + */ +static void test_kuid_root_in_ns_init_ns_nonzero(struct kunit *test) +{ + kuid_t kuid; + struct user_namespace *init_ns; + + kuid =3D KUIDT_INIT(1000); + init_ns =3D &init_user_ns; + + /* Non-zero UID should not own namespace */ + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(kuid, init_ns)); +} + +/** + * create_test_user_ns_with_mapping - Create a mock user namespace with UI= D mapping + * + * Creates a minimal user namespace structure for testing where uid 0 in t= he + * namespace maps to a specific kuid in the parent namespace. + * + * @test: KUnit test context + * @parent_ns: Parent namespace (typically init_user_ns) + * @mapped_kuid: The kuid that uid 0 in this namespace maps to in parent + * + * Returns: Pointer to allocated namespace, or NULL on failure + */ +static struct user_namespace *create_test_user_ns_with_mapping(struct kuni= t *test, + struct user_namespace *parent_ns, + kuid_t mapped_kuid) +{ + struct user_namespace *ns; + struct uid_gid_extent extent; + + /* Allocate a test namespace - use kzalloc to zero all fields */ + ns =3D kunit_kzalloc(test, sizeof(*ns), GFP_KERNEL); + if (!ns) + return NULL; + + /* Initialize basic namespace structure fields */ + ns->parent =3D parent_ns; + ns->level =3D parent_ns ? parent_ns->level + 1 : 0; + ns->owner =3D mapped_kuid; + ns->group =3D KGIDT_INIT(0); + + /* Initialize ns_common structure */ + refcount_set(&ns->ns.__ns_ref, 1); + ns->ns.inum =3D 0; /* Mock inum */ + + /* Set up uid mapping: uid 0 in this namespace maps to mapped_kuid in par= ent + * Format: first (uid in ns) : lower_first (kuid in parent) : count + * So: uid 0 in ns -> kuid mapped_kuid in parent + * This means from_kuid(ns, mapped_kuid) returns 0 + */ + extent.first =3D 0; /* uid 0 in this namespa= ce */ + extent.lower_first =3D __kuid_val(mapped_kuid); /* maps to this kuid in = parent */ + extent.count =3D 1; + + ns->uid_map.extent[0] =3D extent; + ns->uid_map.nr_extents =3D 1; + + /* Set up gid mapping: gid 0 maps to gid 0 in parent (simplified) */ + extent.first =3D 0; + extent.lower_first =3D 0; + extent.count =3D 1; + + ns->gid_map.extent[0] =3D extent; + ns->gid_map.nr_extents =3D 1; + + return ns; +} + +/** + * test_kuid_root_in_ns_with_mapping - Test kuid_root_in_ns with namespace= where uid 0 + * maps to different kuid + * + * Creates a user namespace where uid 0 maps to kuid 1000 in the parent na= mespace. + * Verifies that kuid_root_in_ns correctly identifies kuid 1000 as owning = the namespace. + * + * Note: kuid_root_in_ns walks up the namespace hierarchy, so it checks th= e current + * namespace first, then parent, then parent's parent, etc. So: + * - kuid 1000 owns test_ns because from_kuid(test_ns, 1000) =3D=3D 0 + * - kuid 0 also owns test_ns because from_kuid(init_user_ns, 0) =3D=3D 0 + * (checked in parent) + * + * This tests the actual functionality as requested: creating namespaces w= ith + * different values for the namespace's uid 0. + * + * @test: KUnit test context + */ +static void test_kuid_root_in_ns_with_mapping(struct kunit *test) +{ + struct user_namespace *test_ns; + struct user_namespace *parent_ns; + kuid_t mapped_kuid, other_kuid; + + parent_ns =3D &init_user_ns; + mapped_kuid =3D KUIDT_INIT(1000); + other_kuid =3D KUIDT_INIT(2000); + + test_ns =3D create_test_user_ns_with_mapping(test, parent_ns, mapped_kuid= ); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_ns); + + /* kuid 1000 should own test_ns because it maps to uid 0 in test_ns */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(mapped_kuid, test_ns)); + + /* kuid 0 should also own test_ns (checked via parent init_user_ns) */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), test_ns)); + + /* Other kuids should not own test_ns */ + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(other_kuid, test_ns)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(500), test_ns)); +} + +/** + * test_kuid_root_in_ns_with_different_mappings - Test with multiple names= paces + * + * Creates multiple user namespaces with different UID mappings to verify + * that kuid_root_in_ns correctly handles different namespace hierarchies. + * + * Since kuid_root_in_ns walks up the hierarchy, kuids that map to 0 in in= it_user_ns + * will own all namespaces, while kuids that only map to 0 in specific nam= espaces + * will only own those namespaces and their children. + * + * @test: KUnit test context + */ +static void test_kuid_root_in_ns_with_different_mappings(struct kunit *tes= t) +{ + struct user_namespace *ns1, *ns2, *ns3; + + /* Create ns1 where uid 0 maps to kuid 1000 */ + ns1 =3D create_test_user_ns_with_mapping(test, &init_user_ns, KUIDT_INIT(= 1000)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns1); + + /* Create ns2 where uid 0 maps to kuid 2000 */ + ns2 =3D create_test_user_ns_with_mapping(test, &init_user_ns, KUIDT_INIT(= 2000)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns2); + + /* Create ns3 as a child of ns1 where uid 0 maps to kuid 3000 */ + ns3 =3D create_test_user_ns_with_mapping(test, ns1, KUIDT_INIT(3000)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns3); + + /* Test ns1: kuid 1000 owns it, kuid 0 owns it (via parent), kuid 2000 do= es not */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(1000), ns1)); + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), ns1)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(2000), ns1)); + + /* Test ns2: kuid 2000 owns it, kuid 0 owns it (via parent), kuid 1000 do= es not */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(2000), ns2)); + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), ns2)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(1000), ns2)); + + /* Test ns3: kuid 3000 owns it, kuid 1000 owns it (via parent ns1), + * kuid 0 owns it (via init_user_ns), kuid 2000 does not + */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(3000), ns3)); + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(1000), ns3)); + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), ns3)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(2000), ns3)); +} + +static struct kunit_case commoncap_test_cases[] =3D { + KUNIT_CASE(test_vfsuid_root_in_currentns_init_ns), + KUNIT_CASE(test_vfsuid_root_in_currentns_invalid), + KUNIT_CASE(test_vfsuid_root_in_currentns_nonzero), + KUNIT_CASE(test_kuid_root_in_ns_init_ns_uid0), + KUNIT_CASE(test_kuid_root_in_ns_init_ns_nonzero), + KUNIT_CASE(test_kuid_root_in_ns_with_mapping), + KUNIT_CASE(test_kuid_root_in_ns_with_different_mappings), + {} +}; + +static struct kunit_suite commoncap_test_suite =3D { + .name =3D "commoncap", + .test_cases =3D commoncap_test_cases, +}; + +kunit_test_suite(commoncap_test_suite); + +MODULE_LICENSE("GPL"); + +#endif /* CONFIG_SECURITY_COMMONCAP_KUNIT_TEST */ --=20 2.52.0