Skip to content

Commit 24e9b43

Browse files
f0sterSerge Hallyn
authored andcommitted
security: Add KUnit tests for kuid_root_in_ns and vfsuid_root_in_currentns
Add comprehensive KUnit tests for the namespace-related capability functions that Serge Hallyn refactored in commit 9891d2f ("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 <foster.ryan.r@gmail.com> Signed-off-by: Serge Hallyn <sergeh@kernel.org>
1 parent 9ace475 commit 24e9b43

4 files changed

Lines changed: 310 additions & 0 deletions

File tree

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5682,6 +5682,7 @@ F: include/trace/events/capability.h
56825682
F: include/uapi/linux/capability.h
56835683
F: kernel/capability.c
56845684
F: security/commoncap.c
5685+
F: security/commoncap_test.c
56855686

56865687
CAPELLA MICROSYSTEMS LIGHT SENSOR DRIVER
56875688
M: Kevin Tsai <ktsai@capellamicro.com>

security/Kconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,23 @@ config LSM
284284

285285
If unsure, leave this as the default.
286286

287+
config SECURITY_COMMONCAP_KUNIT_TEST
288+
bool "Build KUnit tests for commoncap" if !KUNIT_ALL_TESTS
289+
depends on KUNIT=y && USER_NS
290+
default KUNIT_ALL_TESTS
291+
help
292+
This builds the commoncap KUnit tests.
293+
294+
KUnit tests run during boot and output the results to the debug log
295+
in TAP format (https://testanything.org/). Only useful for kernel devs
296+
running KUnit test harness and are not for inclusion into a
297+
production build.
298+
299+
For more information on KUnit and unit tests in general please refer
300+
to the KUnit documentation in Documentation/dev-tools/kunit/.
301+
302+
If unsure, say N.
303+
287304
source "security/Kconfig.hardening"
288305

289306
endmenu

security/commoncap.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,3 +1521,7 @@ DEFINE_LSM(capability) = {
15211521
};
15221522

15231523
#endif /* CONFIG_SECURITY */
1524+
1525+
#ifdef CONFIG_SECURITY_COMMONCAP_KUNIT_TEST
1526+
#include "commoncap_test.c"
1527+
#endif

security/commoncap_test.c

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* KUnit tests for commoncap.c security functions
4+
*
5+
* Tests for security-critical functions in the capability subsystem,
6+
* particularly namespace-related capability checks.
7+
*/
8+
9+
#include <kunit/test.h>
10+
#include <linux/user_namespace.h>
11+
#include <linux/uidgid.h>
12+
#include <linux/cred.h>
13+
#include <linux/mnt_idmapping.h>
14+
#include <linux/module.h>
15+
#include <linux/slab.h>
16+
#include <linux/refcount.h>
17+
18+
#ifdef CONFIG_SECURITY_COMMONCAP_KUNIT_TEST
19+
20+
/* Functions are static in commoncap.c, but we can call them since we're
21+
* included in the same compilation unit when tests are enabled.
22+
*/
23+
24+
/**
25+
* test_vfsuid_root_in_currentns_init_ns - Test vfsuid_root_in_currentns with init ns
26+
*
27+
* Verifies that UID 0 in the init namespace correctly owns the current
28+
* namespace when running in init_user_ns.
29+
*
30+
* @test: KUnit test context
31+
*/
32+
static void test_vfsuid_root_in_currentns_init_ns(struct kunit *test)
33+
{
34+
vfsuid_t vfsuid;
35+
kuid_t kuid;
36+
37+
/* Create UID 0 in init namespace */
38+
kuid = KUIDT_INIT(0);
39+
vfsuid = VFSUIDT_INIT(kuid);
40+
41+
/* In init namespace, UID 0 should own current namespace */
42+
KUNIT_EXPECT_TRUE(test, vfsuid_root_in_currentns(vfsuid));
43+
}
44+
45+
/**
46+
* test_vfsuid_root_in_currentns_invalid - Test vfsuid_root_in_currentns with invalid vfsuid
47+
*
48+
* Verifies that an invalid vfsuid correctly returns false.
49+
*
50+
* @test: KUnit test context
51+
*/
52+
static void test_vfsuid_root_in_currentns_invalid(struct kunit *test)
53+
{
54+
vfsuid_t invalid_vfsuid;
55+
56+
/* Use the predefined invalid vfsuid */
57+
invalid_vfsuid = INVALID_VFSUID;
58+
59+
/* Invalid vfsuid should return false */
60+
KUNIT_EXPECT_FALSE(test, vfsuid_root_in_currentns(invalid_vfsuid));
61+
}
62+
63+
/**
64+
* test_vfsuid_root_in_currentns_nonzero - Test vfsuid_root_in_currentns with non-zero UID
65+
*
66+
* Verifies that a non-zero UID correctly returns false.
67+
*
68+
* @test: KUnit test context
69+
*/
70+
static void test_vfsuid_root_in_currentns_nonzero(struct kunit *test)
71+
{
72+
vfsuid_t vfsuid;
73+
kuid_t kuid;
74+
75+
/* Create a non-zero UID */
76+
kuid = KUIDT_INIT(1000);
77+
vfsuid = VFSUIDT_INIT(kuid);
78+
79+
/* Non-zero UID should return false */
80+
KUNIT_EXPECT_FALSE(test, vfsuid_root_in_currentns(vfsuid));
81+
}
82+
83+
/**
84+
* test_kuid_root_in_ns_init_ns_uid0 - Test kuid_root_in_ns with init namespace and UID 0
85+
*
86+
* Verifies that kuid_root_in_ns correctly identifies UID 0 in init namespace.
87+
* This tests the core namespace traversal logic. In init namespace, UID 0
88+
* maps to itself, so it should own the namespace.
89+
*
90+
* @test: KUnit test context
91+
*/
92+
static void test_kuid_root_in_ns_init_ns_uid0(struct kunit *test)
93+
{
94+
kuid_t kuid;
95+
struct user_namespace *init_ns;
96+
97+
kuid = KUIDT_INIT(0);
98+
init_ns = &init_user_ns;
99+
100+
/* UID 0 should own init namespace */
101+
KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(kuid, init_ns));
102+
}
103+
104+
/**
105+
* test_kuid_root_in_ns_init_ns_nonzero - Test kuid_root_in_ns with init namespace and non-zero UID
106+
*
107+
* Verifies that kuid_root_in_ns correctly rejects non-zero UIDs in init namespace.
108+
* Only UID 0 should own a namespace.
109+
*
110+
* @test: KUnit test context
111+
*/
112+
static void test_kuid_root_in_ns_init_ns_nonzero(struct kunit *test)
113+
{
114+
kuid_t kuid;
115+
struct user_namespace *init_ns;
116+
117+
kuid = KUIDT_INIT(1000);
118+
init_ns = &init_user_ns;
119+
120+
/* Non-zero UID should not own namespace */
121+
KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(kuid, init_ns));
122+
}
123+
124+
/**
125+
* create_test_user_ns_with_mapping - Create a mock user namespace with UID mapping
126+
*
127+
* Creates a minimal user namespace structure for testing where uid 0 in the
128+
* namespace maps to a specific kuid in the parent namespace.
129+
*
130+
* @test: KUnit test context
131+
* @parent_ns: Parent namespace (typically init_user_ns)
132+
* @mapped_kuid: The kuid that uid 0 in this namespace maps to in parent
133+
*
134+
* Returns: Pointer to allocated namespace, or NULL on failure
135+
*/
136+
static struct user_namespace *create_test_user_ns_with_mapping(struct kunit *test,
137+
struct user_namespace *parent_ns,
138+
kuid_t mapped_kuid)
139+
{
140+
struct user_namespace *ns;
141+
struct uid_gid_extent extent;
142+
143+
/* Allocate a test namespace - use kzalloc to zero all fields */
144+
ns = kunit_kzalloc(test, sizeof(*ns), GFP_KERNEL);
145+
if (!ns)
146+
return NULL;
147+
148+
/* Initialize basic namespace structure fields */
149+
ns->parent = parent_ns;
150+
ns->level = parent_ns ? parent_ns->level + 1 : 0;
151+
ns->owner = mapped_kuid;
152+
ns->group = KGIDT_INIT(0);
153+
154+
/* Initialize ns_common structure */
155+
refcount_set(&ns->ns.__ns_ref, 1);
156+
ns->ns.inum = 0; /* Mock inum */
157+
158+
/* Set up uid mapping: uid 0 in this namespace maps to mapped_kuid in parent
159+
* Format: first (uid in ns) : lower_first (kuid in parent) : count
160+
* So: uid 0 in ns -> kuid mapped_kuid in parent
161+
* This means from_kuid(ns, mapped_kuid) returns 0
162+
*/
163+
extent.first = 0; /* uid 0 in this namespace */
164+
extent.lower_first = __kuid_val(mapped_kuid); /* maps to this kuid in parent */
165+
extent.count = 1;
166+
167+
ns->uid_map.extent[0] = extent;
168+
ns->uid_map.nr_extents = 1;
169+
170+
/* Set up gid mapping: gid 0 maps to gid 0 in parent (simplified) */
171+
extent.first = 0;
172+
extent.lower_first = 0;
173+
extent.count = 1;
174+
175+
ns->gid_map.extent[0] = extent;
176+
ns->gid_map.nr_extents = 1;
177+
178+
return ns;
179+
}
180+
181+
/**
182+
* test_kuid_root_in_ns_with_mapping - Test kuid_root_in_ns with namespace where uid 0
183+
* maps to different kuid
184+
*
185+
* Creates a user namespace where uid 0 maps to kuid 1000 in the parent namespace.
186+
* Verifies that kuid_root_in_ns correctly identifies kuid 1000 as owning the namespace.
187+
*
188+
* Note: kuid_root_in_ns walks up the namespace hierarchy, so it checks the current
189+
* namespace first, then parent, then parent's parent, etc. So:
190+
* - kuid 1000 owns test_ns because from_kuid(test_ns, 1000) == 0
191+
* - kuid 0 also owns test_ns because from_kuid(init_user_ns, 0) == 0
192+
* (checked in parent)
193+
*
194+
* This tests the actual functionality as requested: creating namespaces with
195+
* different values for the namespace's uid 0.
196+
*
197+
* @test: KUnit test context
198+
*/
199+
static void test_kuid_root_in_ns_with_mapping(struct kunit *test)
200+
{
201+
struct user_namespace *test_ns;
202+
struct user_namespace *parent_ns;
203+
kuid_t mapped_kuid, other_kuid;
204+
205+
parent_ns = &init_user_ns;
206+
mapped_kuid = KUIDT_INIT(1000);
207+
other_kuid = KUIDT_INIT(2000);
208+
209+
test_ns = create_test_user_ns_with_mapping(test, parent_ns, mapped_kuid);
210+
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_ns);
211+
212+
/* kuid 1000 should own test_ns because it maps to uid 0 in test_ns */
213+
KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(mapped_kuid, test_ns));
214+
215+
/* kuid 0 should also own test_ns (checked via parent init_user_ns) */
216+
KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), test_ns));
217+
218+
/* Other kuids should not own test_ns */
219+
KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(other_kuid, test_ns));
220+
KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(500), test_ns));
221+
}
222+
223+
/**
224+
* test_kuid_root_in_ns_with_different_mappings - Test with multiple namespaces
225+
*
226+
* Creates multiple user namespaces with different UID mappings to verify
227+
* that kuid_root_in_ns correctly distinguishes between namespaces.
228+
*
229+
* Each namespace maps uid 0 to a different kuid, and we verify that each
230+
* kuid only owns its corresponding namespace (plus kuid 0 owns all via
231+
* init_user_ns parent).
232+
*
233+
* @test: KUnit test context
234+
*/
235+
static void test_kuid_root_in_ns_with_different_mappings(struct kunit *test)
236+
{
237+
struct user_namespace *ns1, *ns2, *ns3;
238+
239+
/* Create three independent namespaces, each mapping uid 0 to different kuids */
240+
ns1 = create_test_user_ns_with_mapping(test, &init_user_ns, KUIDT_INIT(1000));
241+
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns1);
242+
243+
ns2 = create_test_user_ns_with_mapping(test, &init_user_ns, KUIDT_INIT(2000));
244+
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns2);
245+
246+
ns3 = create_test_user_ns_with_mapping(test, &init_user_ns, KUIDT_INIT(3000));
247+
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns3);
248+
249+
/* Test ns1: kuid 1000 owns it, kuid 0 owns it (via parent), others do not */
250+
KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(1000), ns1));
251+
KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), ns1));
252+
KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(2000), ns1));
253+
KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(3000), ns1));
254+
255+
/* Test ns2: kuid 2000 owns it, kuid 0 owns it (via parent), others do not */
256+
KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(2000), ns2));
257+
KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), ns2));
258+
KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(1000), ns2));
259+
KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(3000), ns2));
260+
261+
/* Test ns3: kuid 3000 owns it, kuid 0 owns it (via parent), others do not */
262+
KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(3000), ns3));
263+
KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), ns3));
264+
KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(1000), ns3));
265+
KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(2000), ns3));
266+
}
267+
268+
static struct kunit_case commoncap_test_cases[] = {
269+
KUNIT_CASE(test_vfsuid_root_in_currentns_init_ns),
270+
KUNIT_CASE(test_vfsuid_root_in_currentns_invalid),
271+
KUNIT_CASE(test_vfsuid_root_in_currentns_nonzero),
272+
KUNIT_CASE(test_kuid_root_in_ns_init_ns_uid0),
273+
KUNIT_CASE(test_kuid_root_in_ns_init_ns_nonzero),
274+
KUNIT_CASE(test_kuid_root_in_ns_with_mapping),
275+
KUNIT_CASE(test_kuid_root_in_ns_with_different_mappings),
276+
{}
277+
};
278+
279+
static struct kunit_suite commoncap_test_suite = {
280+
.name = "commoncap",
281+
.test_cases = commoncap_test_cases,
282+
};
283+
284+
kunit_test_suite(commoncap_test_suite);
285+
286+
MODULE_LICENSE("GPL");
287+
288+
#endif /* CONFIG_SECURITY_COMMONCAP_KUNIT_TEST */

0 commit comments

Comments
 (0)