Skip to content

Commit a54142d

Browse files
committed
selftests/landlock: Test tsync interruption and cancellation paths
Add tsync_interrupt test to exercise the signal interruption path in landlock_restrict_sibling_threads(). When a signal interrupts wait_for_completion_interruptible() while the calling thread waits for sibling threads to finish credential preparation, the kernel: 1. Sets ERESTARTNOINTR to request a transparent syscall restart. 2. Calls cancel_tsync_works() to opportunistically dequeue task works that have not started running yet. 3. Breaks out of the preparation loop, then unblocks remaining task works via complete_all() and waits for them to finish. 4. Returns the error, causing abort_creds() in the syscall handler. Specifically, cancel_tsync_works() in its entirety, the ERESTARTNOINTR error branch in landlock_restrict_sibling_threads(), and the abort_creds() error branch in the landlock_restrict_self() syscall handler are timing-dependent and not exercised by the existing tsync tests, making code coverage measurements non-deterministic. The test spawns a signaler thread that rapidly sends SIGUSR1 to the calling thread while it performs landlock_restrict_self() with LANDLOCK_RESTRICT_SELF_TSYNC. Since ERESTARTNOINTR causes a transparent restart, userspace always sees the syscall succeed. This is a best-effort coverage test: the interruption path is exercised when the signal lands during the preparation wait, which depends on thread scheduling. The test creates enough idle sibling threads (200) to ensure multiple serialized waves of credential preparation even on machines with many cores (e.g., 64), widening the window for the signaler. Deterministic coverage would require wrapping the wait call with ALLOW_ERROR_INJECTION() and using CONFIG_FAIL_FUNCTION. Test coverage for security/landlock was 90.2% of 2105 lines according to LLVM 21, and it is now 91.1% of 2105 lines with this new test. Cc: Günther Noack <[email protected]> Cc: Justin Suess <[email protected]> Cc: Tingmao Wang <[email protected]> Cc: Yihan Ding <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Mickaël Salaün <[email protected]>
1 parent 697f514 commit a54142d

1 file changed

Lines changed: 90 additions & 1 deletion

File tree

tools/testing/selftests/landlock/tsync_test.c

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
*/
77

88
#define _GNU_SOURCE
9+
#include <linux/landlock.h>
910
#include <pthread.h>
11+
#include <signal.h>
1012
#include <sys/prctl.h>
11-
#include <linux/landlock.h>
1213

1314
#include "common.h"
1415

@@ -158,4 +159,92 @@ TEST(competing_enablement)
158159
EXPECT_EQ(0, close(ruleset_fd));
159160
}
160161

162+
static void signal_nop_handler(int sig)
163+
{
164+
}
165+
166+
struct signaler_data {
167+
pthread_t target;
168+
volatile bool stop;
169+
};
170+
171+
static void *signaler_thread(void *data)
172+
{
173+
struct signaler_data *sd = data;
174+
175+
while (!sd->stop)
176+
pthread_kill(sd->target, SIGUSR1);
177+
178+
return NULL;
179+
}
180+
181+
/*
182+
* Number of idle sibling threads. This must be large enough that even on
183+
* machines with many cores, the sibling threads cannot all complete their
184+
* credential preparation in a single parallel wave, otherwise the signaler
185+
* thread has no window to interrupt wait_for_completion_interruptible().
186+
* 200 threads on a 64-core machine yields ~3 serialized waves, giving the
187+
* tight signal loop enough time to land an interruption.
188+
*/
189+
#define NUM_IDLE_THREADS 200
190+
191+
/*
192+
* Exercises the tsync interruption and cancellation paths in tsync.c.
193+
*
194+
* When a signal interrupts the calling thread while it waits for sibling
195+
* threads to finish their credential preparation
196+
* (wait_for_completion_interruptible in landlock_restrict_sibling_threads),
197+
* the kernel sets ERESTARTNOINTR, cancels queued task works that have not
198+
* started yet (cancel_tsync_works), then waits for the remaining works to
199+
* finish. On the error return, syscalls.c aborts the prepared credentials.
200+
* The kernel automatically restarts the syscall, so userspace sees success.
201+
*/
202+
TEST(tsync_interrupt)
203+
{
204+
size_t i;
205+
pthread_t threads[NUM_IDLE_THREADS];
206+
pthread_t signaler;
207+
struct signaler_data sd;
208+
struct sigaction sa = {};
209+
const int ruleset_fd = create_ruleset(_metadata);
210+
211+
disable_caps(_metadata);
212+
213+
/* Install a no-op SIGUSR1 handler so the signal does not kill us. */
214+
sa.sa_handler = signal_nop_handler;
215+
sigemptyset(&sa.sa_mask);
216+
ASSERT_EQ(0, sigaction(SIGUSR1, &sa, NULL));
217+
218+
ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
219+
220+
for (i = 0; i < NUM_IDLE_THREADS; i++)
221+
ASSERT_EQ(0, pthread_create(&threads[i], NULL, idle, NULL));
222+
223+
/*
224+
* Start a signaler thread that continuously sends SIGUSR1 to the
225+
* calling thread. This maximizes the chance of interrupting
226+
* wait_for_completion_interruptible() in the kernel's tsync path.
227+
*/
228+
sd.target = pthread_self();
229+
sd.stop = false;
230+
ASSERT_EQ(0, pthread_create(&signaler, NULL, signaler_thread, &sd));
231+
232+
/*
233+
* The syscall may be interrupted and transparently restarted by the
234+
* kernel (ERESTARTNOINTR). From userspace, it should always succeed.
235+
*/
236+
EXPECT_EQ(0, landlock_restrict_self(ruleset_fd,
237+
LANDLOCK_RESTRICT_SELF_TSYNC));
238+
239+
sd.stop = true;
240+
ASSERT_EQ(0, pthread_join(signaler, NULL));
241+
242+
for (i = 0; i < NUM_IDLE_THREADS; i++) {
243+
ASSERT_EQ(0, pthread_cancel(threads[i]));
244+
ASSERT_EQ(0, pthread_join(threads[i], NULL));
245+
}
246+
247+
EXPECT_EQ(0, close(ruleset_fd));
248+
}
249+
161250
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)