Skip to content

Commit 50c058e

Browse files
gnoackl0kod
authored andcommitted
selftests/landlock: Add LANDLOCK_RESTRICT_SELF_TSYNC tests
Exercise various scenarios where Landlock domains are enforced across all of a processes' threads. Test coverage for security/landlock is 91.6% of 2130 lines according to LLVM 21. Cc: Andrew G. Morgan <morgan@kernel.org> Cc: John Johansen <john.johansen@canonical.com> Cc: Paul Moore <paul@paul-moore.com> Signed-off-by: Günther Noack <gnoack@google.com> Link: https://lore.kernel.org/r/20251127115136.3064948-3-gnoack@google.com [mic: Fix subject, use EXPECT_EQ(close()), make helpers static, add test coverage] Signed-off-by: Mickaël Salaün <mic@digikod.net>
1 parent 42fc7e6 commit 50c058e

2 files changed

Lines changed: 163 additions & 2 deletions

File tree

tools/testing/selftests/landlock/base_test.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ TEST(restrict_self_fd)
288288
EXPECT_EQ(EBADFD, errno);
289289
}
290290

291-
TEST(restrict_self_fd_flags)
291+
TEST(restrict_self_fd_logging_flags)
292292
{
293293
int fd;
294294

@@ -304,7 +304,7 @@ TEST(restrict_self_fd_flags)
304304
EXPECT_EQ(EBADFD, errno);
305305
}
306306

307-
TEST(restrict_self_flags)
307+
TEST(restrict_self_logging_flags)
308308
{
309309
const __u32 last_flag = LANDLOCK_RESTRICT_SELF_TSYNC;
310310

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Landlock tests - Enforcing the same restrictions across multiple threads
4+
*
5+
* Copyright © 2025 Günther Noack <gnoack3000@gmail.com>
6+
*/
7+
8+
#define _GNU_SOURCE
9+
#include <pthread.h>
10+
#include <sys/prctl.h>
11+
#include <linux/landlock.h>
12+
13+
#include "common.h"
14+
15+
/* create_ruleset - Create a simple ruleset FD common to all tests */
16+
static int create_ruleset(struct __test_metadata *const _metadata)
17+
{
18+
struct landlock_ruleset_attr ruleset_attr = {
19+
.handled_access_fs = (LANDLOCK_ACCESS_FS_WRITE_FILE |
20+
LANDLOCK_ACCESS_FS_TRUNCATE),
21+
};
22+
const int ruleset_fd =
23+
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
24+
25+
ASSERT_LE(0, ruleset_fd)
26+
{
27+
TH_LOG("landlock_create_ruleset: %s", strerror(errno));
28+
}
29+
return ruleset_fd;
30+
}
31+
32+
TEST(single_threaded_success)
33+
{
34+
const int ruleset_fd = create_ruleset(_metadata);
35+
36+
disable_caps(_metadata);
37+
38+
ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
39+
ASSERT_EQ(0, landlock_restrict_self(ruleset_fd,
40+
LANDLOCK_RESTRICT_SELF_TSYNC));
41+
42+
EXPECT_EQ(0, close(ruleset_fd));
43+
}
44+
45+
static void store_no_new_privs(void *data)
46+
{
47+
bool *nnp = data;
48+
49+
if (!nnp)
50+
return;
51+
*nnp = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
52+
}
53+
54+
static void *idle(void *data)
55+
{
56+
pthread_cleanup_push(store_no_new_privs, data);
57+
58+
while (true)
59+
sleep(1);
60+
61+
pthread_cleanup_pop(1);
62+
}
63+
64+
TEST(multi_threaded_success)
65+
{
66+
pthread_t t1, t2;
67+
bool no_new_privs1, no_new_privs2;
68+
const int ruleset_fd = create_ruleset(_metadata);
69+
70+
disable_caps(_metadata);
71+
72+
ASSERT_EQ(0, pthread_create(&t1, NULL, idle, &no_new_privs1));
73+
ASSERT_EQ(0, pthread_create(&t2, NULL, idle, &no_new_privs2));
74+
75+
ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
76+
77+
EXPECT_EQ(0, landlock_restrict_self(ruleset_fd,
78+
LANDLOCK_RESTRICT_SELF_TSYNC));
79+
80+
ASSERT_EQ(0, pthread_cancel(t1));
81+
ASSERT_EQ(0, pthread_cancel(t2));
82+
ASSERT_EQ(0, pthread_join(t1, NULL));
83+
ASSERT_EQ(0, pthread_join(t2, NULL));
84+
85+
/* The no_new_privs flag was implicitly enabled on all threads. */
86+
EXPECT_TRUE(no_new_privs1);
87+
EXPECT_TRUE(no_new_privs2);
88+
89+
EXPECT_EQ(0, close(ruleset_fd));
90+
}
91+
92+
TEST(multi_threaded_success_despite_diverging_domains)
93+
{
94+
pthread_t t1, t2;
95+
const int ruleset_fd = create_ruleset(_metadata);
96+
97+
disable_caps(_metadata);
98+
99+
ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
100+
101+
ASSERT_EQ(0, pthread_create(&t1, NULL, idle, NULL));
102+
ASSERT_EQ(0, pthread_create(&t2, NULL, idle, NULL));
103+
104+
/*
105+
* The main thread enforces a ruleset,
106+
* thereby bringing the threads' Landlock domains out of sync.
107+
*/
108+
EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
109+
110+
/* Still, TSYNC succeeds, bringing the threads in sync again. */
111+
EXPECT_EQ(0, landlock_restrict_self(ruleset_fd,
112+
LANDLOCK_RESTRICT_SELF_TSYNC));
113+
114+
ASSERT_EQ(0, pthread_cancel(t1));
115+
ASSERT_EQ(0, pthread_cancel(t2));
116+
ASSERT_EQ(0, pthread_join(t1, NULL));
117+
ASSERT_EQ(0, pthread_join(t2, NULL));
118+
EXPECT_EQ(0, close(ruleset_fd));
119+
}
120+
121+
struct thread_restrict_data {
122+
pthread_t t;
123+
int ruleset_fd;
124+
int result;
125+
};
126+
127+
static void *thread_restrict(void *data)
128+
{
129+
struct thread_restrict_data *d = data;
130+
131+
d->result = landlock_restrict_self(d->ruleset_fd,
132+
LANDLOCK_RESTRICT_SELF_TSYNC);
133+
return NULL;
134+
}
135+
136+
TEST(competing_enablement)
137+
{
138+
const int ruleset_fd = create_ruleset(_metadata);
139+
struct thread_restrict_data d[] = {
140+
{ .ruleset_fd = ruleset_fd },
141+
{ .ruleset_fd = ruleset_fd },
142+
};
143+
144+
disable_caps(_metadata);
145+
146+
ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
147+
ASSERT_EQ(0, pthread_create(&d[0].t, NULL, thread_restrict, &d[0]));
148+
ASSERT_EQ(0, pthread_create(&d[1].t, NULL, thread_restrict, &d[1]));
149+
150+
/* Wait for threads to finish. */
151+
ASSERT_EQ(0, pthread_join(d[0].t, NULL));
152+
ASSERT_EQ(0, pthread_join(d[1].t, NULL));
153+
154+
/* Expect that both succeeded. */
155+
EXPECT_EQ(0, d[0].result);
156+
EXPECT_EQ(0, d[1].result);
157+
158+
EXPECT_EQ(0, close(ruleset_fd));
159+
}
160+
161+
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)