Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Günther Noack and committed by
Mickaël Salaün
50c058e3 42fc7e65

+163 -2
+2 -2
tools/testing/selftests/landlock/base_test.c
··· 288 288 EXPECT_EQ(EBADFD, errno); 289 289 } 290 290 291 - TEST(restrict_self_fd_flags) 291 + TEST(restrict_self_fd_logging_flags) 292 292 { 293 293 int fd; 294 294 ··· 304 304 EXPECT_EQ(EBADFD, errno); 305 305 } 306 306 307 - TEST(restrict_self_flags) 307 + TEST(restrict_self_logging_flags) 308 308 { 309 309 const __u32 last_flag = LANDLOCK_RESTRICT_SELF_TSYNC; 310 310
+161
tools/testing/selftests/landlock/tsync_test.c
··· 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