Skip to content

Commit ea29236

Browse files
tahifahimil0kod
authored andcommitted
selftests/landlock: Test signal scoping
Provide tests for the signal scoping. If the signal is 0, no signal will be sent, but the permission of a process to send a signal will be checked. Likewise, this test consider one signal for each signal category: SIGTRAP, SIGURG, SIGHUP, and SIGTSTP. Signed-off-by: Tahera Fahimi <[email protected]> Link: https://lore.kernel.org/r/15dc202bb7f0a462ddeaa0c1cd630d2a7c6fa5c5.1725657728.git.fahimitahera@gmail.com [mic: Fix commit message, use dedicated variables per process, properly close FDs, extend send_sig_to_parent to make sure scoping works as expected] Signed-off-by: Mickaël Salaün <[email protected]>
1 parent 54a6e6b commit ea29236

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Landlock tests - Signal Scoping
4+
*
5+
* Copyright © 2024 Tahera Fahimi <[email protected]>
6+
*/
7+
8+
#define _GNU_SOURCE
9+
#include <errno.h>
10+
#include <fcntl.h>
11+
#include <linux/landlock.h>
12+
#include <signal.h>
13+
#include <sys/prctl.h>
14+
#include <sys/types.h>
15+
#include <sys/wait.h>
16+
#include <unistd.h>
17+
18+
#include "common.h"
19+
#include "scoped_common.h"
20+
21+
/* This variable is used for handling several signals. */
22+
static volatile sig_atomic_t is_signaled;
23+
24+
/* clang-format off */
25+
FIXTURE(scoping_signals) {};
26+
/* clang-format on */
27+
28+
FIXTURE_VARIANT(scoping_signals)
29+
{
30+
int sig;
31+
};
32+
33+
/* clang-format off */
34+
FIXTURE_VARIANT_ADD(scoping_signals, sigtrap) {
35+
/* clang-format on */
36+
.sig = SIGTRAP,
37+
};
38+
39+
/* clang-format off */
40+
FIXTURE_VARIANT_ADD(scoping_signals, sigurg) {
41+
/* clang-format on */
42+
.sig = SIGURG,
43+
};
44+
45+
/* clang-format off */
46+
FIXTURE_VARIANT_ADD(scoping_signals, sighup) {
47+
/* clang-format on */
48+
.sig = SIGHUP,
49+
};
50+
51+
/* clang-format off */
52+
FIXTURE_VARIANT_ADD(scoping_signals, sigtstp) {
53+
/* clang-format on */
54+
.sig = SIGTSTP,
55+
};
56+
57+
FIXTURE_SETUP(scoping_signals)
58+
{
59+
drop_caps(_metadata);
60+
61+
is_signaled = 0;
62+
}
63+
64+
FIXTURE_TEARDOWN(scoping_signals)
65+
{
66+
}
67+
68+
static void scope_signal_handler(int sig, siginfo_t *info, void *ucontext)
69+
{
70+
if (sig == SIGTRAP || sig == SIGURG || sig == SIGHUP || sig == SIGTSTP)
71+
is_signaled = 1;
72+
}
73+
74+
/*
75+
* In this test, a child process sends a signal to parent before and
76+
* after getting scoped.
77+
*/
78+
TEST_F(scoping_signals, send_sig_to_parent)
79+
{
80+
int pipe_parent[2];
81+
int status;
82+
pid_t child;
83+
pid_t parent = getpid();
84+
struct sigaction action = {
85+
.sa_sigaction = scope_signal_handler,
86+
.sa_flags = SA_SIGINFO,
87+
88+
};
89+
90+
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
91+
ASSERT_LE(0, sigaction(variant->sig, &action, NULL));
92+
93+
/* The process should not have already been signaled. */
94+
EXPECT_EQ(0, is_signaled);
95+
96+
child = fork();
97+
ASSERT_LE(0, child);
98+
if (child == 0) {
99+
char buf_child;
100+
int err;
101+
102+
EXPECT_EQ(0, close(pipe_parent[1]));
103+
104+
/*
105+
* The child process can send signal to parent when
106+
* domain is not scoped.
107+
*/
108+
err = kill(parent, variant->sig);
109+
ASSERT_EQ(0, err);
110+
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
111+
EXPECT_EQ(0, close(pipe_parent[0]));
112+
113+
create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
114+
115+
/*
116+
* The child process cannot send signal to the parent
117+
* anymore.
118+
*/
119+
err = kill(parent, variant->sig);
120+
ASSERT_EQ(-1, err);
121+
ASSERT_EQ(EPERM, errno);
122+
123+
/*
124+
* No matter of the domain, a process should be able to
125+
* send a signal to itself.
126+
*/
127+
ASSERT_EQ(0, is_signaled);
128+
ASSERT_EQ(0, raise(variant->sig));
129+
ASSERT_EQ(1, is_signaled);
130+
131+
_exit(_metadata->exit_code);
132+
return;
133+
}
134+
EXPECT_EQ(0, close(pipe_parent[0]));
135+
136+
/* Waits for a first signal to be received, without race condition. */
137+
while (!is_signaled && !usleep(1))
138+
;
139+
ASSERT_EQ(1, is_signaled);
140+
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
141+
EXPECT_EQ(0, close(pipe_parent[1]));
142+
is_signaled = 0;
143+
144+
ASSERT_EQ(child, waitpid(child, &status, 0));
145+
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
146+
WEXITSTATUS(status) != EXIT_SUCCESS)
147+
_metadata->exit_code = KSFT_FAIL;
148+
149+
EXPECT_EQ(0, is_signaled);
150+
}
151+
152+
/* clang-format off */
153+
FIXTURE(scoped_domains) {};
154+
/* clang-format on */
155+
156+
#include "scoped_base_variants.h"
157+
158+
FIXTURE_SETUP(scoped_domains)
159+
{
160+
drop_caps(_metadata);
161+
}
162+
163+
FIXTURE_TEARDOWN(scoped_domains)
164+
{
165+
}
166+
167+
/*
168+
* This test ensures that a scoped process cannot send signal out of
169+
* scoped domain.
170+
*/
171+
TEST_F(scoped_domains, check_access_signal)
172+
{
173+
pid_t child;
174+
pid_t parent = getpid();
175+
int status;
176+
bool can_signal_child, can_signal_parent;
177+
int pipe_parent[2], pipe_child[2];
178+
char buf_parent;
179+
int err;
180+
181+
can_signal_parent = !variant->domain_child;
182+
can_signal_child = !variant->domain_parent;
183+
184+
if (variant->domain_both)
185+
create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
186+
187+
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
188+
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
189+
190+
child = fork();
191+
ASSERT_LE(0, child);
192+
if (child == 0) {
193+
char buf_child;
194+
195+
EXPECT_EQ(0, close(pipe_child[0]));
196+
EXPECT_EQ(0, close(pipe_parent[1]));
197+
198+
if (variant->domain_child)
199+
create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
200+
201+
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
202+
EXPECT_EQ(0, close(pipe_child[1]));
203+
204+
/* Waits for the parent to send signals. */
205+
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
206+
EXPECT_EQ(0, close(pipe_parent[0]));
207+
208+
err = kill(parent, 0);
209+
if (can_signal_parent) {
210+
ASSERT_EQ(0, err);
211+
} else {
212+
ASSERT_EQ(-1, err);
213+
ASSERT_EQ(EPERM, errno);
214+
}
215+
/*
216+
* No matter of the domain, a process should be able to
217+
* send a signal to itself.
218+
*/
219+
ASSERT_EQ(0, raise(0));
220+
221+
_exit(_metadata->exit_code);
222+
return;
223+
}
224+
EXPECT_EQ(0, close(pipe_parent[0]));
225+
EXPECT_EQ(0, close(pipe_child[1]));
226+
227+
if (variant->domain_parent)
228+
create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
229+
230+
ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
231+
EXPECT_EQ(0, close(pipe_child[0]));
232+
233+
err = kill(child, 0);
234+
if (can_signal_child) {
235+
ASSERT_EQ(0, err);
236+
} else {
237+
ASSERT_EQ(-1, err);
238+
ASSERT_EQ(EPERM, errno);
239+
}
240+
ASSERT_EQ(0, raise(0));
241+
242+
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
243+
EXPECT_EQ(0, close(pipe_parent[1]));
244+
ASSERT_EQ(child, waitpid(child, &status, 0));
245+
246+
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
247+
WEXITSTATUS(status) != EXIT_SUCCESS)
248+
_metadata->exit_code = KSFT_FAIL;
249+
}
250+
251+
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)