1 // SPDX-License-Identifier: GPL-2.0
3 * Landlock tests - Ptrace
5 * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
6 * Copyright © 2019-2020 ANSSI
12 #include <linux/landlock.h>
14 #include <sys/prctl.h>
15 #include <sys/ptrace.h>
16 #include <sys/types.h>
22 /* Copied from security/yama/yama_lsm.c */
23 #define YAMA_SCOPE_DISABLED 0
24 #define YAMA_SCOPE_RELATIONAL 1
25 #define YAMA_SCOPE_CAPABILITY 2
26 #define YAMA_SCOPE_NO_ATTACH 3
28 static void create_domain(struct __test_metadata
*const _metadata
)
31 struct landlock_ruleset_attr ruleset_attr
= {
32 .handled_access_fs
= LANDLOCK_ACCESS_FS_MAKE_BLOCK
,
36 landlock_create_ruleset(&ruleset_attr
, sizeof(ruleset_attr
), 0);
37 EXPECT_LE(0, ruleset_fd
)
39 TH_LOG("Failed to create a ruleset: %s", strerror(errno
));
41 EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS
, 1, 0, 0, 0));
42 EXPECT_EQ(0, landlock_restrict_self(ruleset_fd
, 0));
43 EXPECT_EQ(0, close(ruleset_fd
));
46 static int test_ptrace_read(const pid_t pid
)
48 static const char path_template
[] = "/proc/%d/environ";
49 char procenv_path
[sizeof(path_template
) + 10];
50 int procenv_path_size
, fd
;
52 procenv_path_size
= snprintf(procenv_path
, sizeof(procenv_path
),
54 if (procenv_path_size
>= sizeof(procenv_path
))
57 fd
= open(procenv_path
, O_RDONLY
| O_CLOEXEC
);
61 * Mixing error codes from close(2) and open(2) should not lead to any
62 * (access type) confusion for this test.
69 static int get_yama_ptrace_scope(void)
73 const int fd
= open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY
);
78 if (read(fd
, buf
, 1) < 0) {
88 /* clang-format off */
89 FIXTURE(hierarchy
) {};
92 FIXTURE_VARIANT(hierarchy
)
94 const bool domain_both
;
95 const bool domain_parent
;
96 const bool domain_child
;
100 * Test multiple tracing combinations between a parent process P1 and a child
103 * Yama's scoped ptrace is presumed disabled. If enabled, this optional
104 * restriction is enforced in addition to any Landlock check, which means that
105 * all P2 requests to trace P1 would be denied.
111 * P1-. P1 -> P2 : allow
115 /* clang-format off */
116 FIXTURE_VARIANT_ADD(hierarchy
, allow_without_domain
) {
117 /* clang-format on */
118 .domain_both
= false,
119 .domain_parent
= false,
120 .domain_child
= false,
126 * P1--. P1 -> P2 : allow
132 /* clang-format off */
133 FIXTURE_VARIANT_ADD(hierarchy
, allow_with_one_domain
) {
134 /* clang-format on */
135 .domain_both
= false,
136 .domain_parent
= false,
137 .domain_child
= true,
143 * | P1 --. P1 -> P2 : deny
144 * '------' \ P2 -> P1 : allow
148 /* clang-format off */
149 FIXTURE_VARIANT_ADD(hierarchy
, deny_with_parent_domain
) {
150 /* clang-format on */
151 .domain_both
= false,
152 .domain_parent
= true,
153 .domain_child
= false,
157 * Parent + child domain (siblings)
159 * | P1 ---. P1 -> P2 : deny
160 * '------' \ P2 -> P1 : deny
165 /* clang-format off */
166 FIXTURE_VARIANT_ADD(hierarchy
, deny_with_sibling_domain
) {
167 /* clang-format on */
168 .domain_both
= false,
169 .domain_parent
= true,
170 .domain_child
= true,
174 * Same domain (inherited)
176 * | P1----. | P1 -> P2 : allow
177 * | \ | P2 -> P1 : allow
182 /* clang-format off */
183 FIXTURE_VARIANT_ADD(hierarchy
, allow_sibling_domain
) {
184 /* clang-format on */
186 .domain_parent
= false,
187 .domain_child
= false,
191 * Inherited + child domain
192 * .-----------------.
193 * | P1----. | P1 -> P2 : allow
194 * | \ | P2 -> P1 : deny
198 * '-----------------'
200 /* clang-format off */
201 FIXTURE_VARIANT_ADD(hierarchy
, allow_with_nested_domain
) {
202 /* clang-format on */
204 .domain_parent
= false,
205 .domain_child
= true,
209 * Inherited + parent domain
210 * .-----------------.
211 * |.------. | P1 -> P2 : deny
212 * || P1 ----. | P2 -> P1 : allow
216 * '-----------------'
218 /* clang-format off */
219 FIXTURE_VARIANT_ADD(hierarchy
, deny_with_nested_and_parent_domain
) {
220 /* clang-format on */
222 .domain_parent
= true,
223 .domain_child
= false,
227 * Inherited + parent and child domain (siblings)
228 * .-----------------.
229 * | .------. | P1 -> P2 : deny
230 * | | P1 . | P2 -> P1 : deny
236 * '-----------------'
238 /* clang-format off */
239 FIXTURE_VARIANT_ADD(hierarchy
, deny_with_forked_domain
) {
240 /* clang-format on */
242 .domain_parent
= true,
243 .domain_child
= true,
246 FIXTURE_SETUP(hierarchy
)
250 FIXTURE_TEARDOWN(hierarchy
)
254 /* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
255 TEST_F(hierarchy
, trace
)
258 int status
, err_proc_read
;
259 int pipe_child
[2], pipe_parent
[2];
260 int yama_ptrace_scope
;
263 bool can_read_child
, can_trace_child
, can_read_parent
, can_trace_parent
;
265 yama_ptrace_scope
= get_yama_ptrace_scope();
266 ASSERT_LE(0, yama_ptrace_scope
);
268 if (yama_ptrace_scope
> YAMA_SCOPE_DISABLED
)
269 TH_LOG("Incomplete tests due to Yama restrictions (scope %d)",
273 * can_read_child is true if a parent process can read its child
274 * process, which is only the case when the parent process is not
275 * isolated from the child with a dedicated Landlock domain.
277 can_read_child
= !variant
->domain_parent
;
280 * can_trace_child is true if a parent process can trace its child
281 * process. This depends on two conditions:
282 * - The parent process is not isolated from the child with a dedicated
284 * - Yama allows tracing children (up to YAMA_SCOPE_RELATIONAL).
286 can_trace_child
= can_read_child
&&
287 yama_ptrace_scope
<= YAMA_SCOPE_RELATIONAL
;
290 * can_read_parent is true if a child process can read its parent
291 * process, which is only the case when the child process is not
292 * isolated from the parent with a dedicated Landlock domain.
294 can_read_parent
= !variant
->domain_child
;
297 * can_trace_parent is true if a child process can trace its parent
298 * process. This depends on two conditions:
299 * - The child process is not isolated from the parent with a dedicated
301 * - Yama is disabled (YAMA_SCOPE_DISABLED).
303 can_trace_parent
= can_read_parent
&&
304 yama_ptrace_scope
<= YAMA_SCOPE_DISABLED
;
307 * Removes all effective and permitted capabilities to not interfere
308 * with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS.
310 drop_caps(_metadata
);
313 ASSERT_EQ(0, pipe2(pipe_child
, O_CLOEXEC
));
314 ASSERT_EQ(0, pipe2(pipe_parent
, O_CLOEXEC
));
315 if (variant
->domain_both
) {
316 create_domain(_metadata
);
317 if (!__test_passed(_metadata
))
318 /* Aborts before forking. */
327 ASSERT_EQ(0, close(pipe_parent
[1]));
328 ASSERT_EQ(0, close(pipe_child
[0]));
329 if (variant
->domain_child
)
330 create_domain(_metadata
);
332 /* Waits for the parent to be in a domain, if any. */
333 ASSERT_EQ(1, read(pipe_parent
[0], &buf_child
, 1));
335 /* Tests PTRACE_MODE_READ on the parent. */
336 err_proc_read
= test_ptrace_read(parent
);
337 if (can_read_parent
) {
338 EXPECT_EQ(0, err_proc_read
);
340 EXPECT_EQ(EACCES
, err_proc_read
);
343 /* Tests PTRACE_ATTACH on the parent. */
344 ret
= ptrace(PTRACE_ATTACH
, parent
, NULL
, 0);
345 if (can_trace_parent
) {
349 EXPECT_EQ(EPERM
, errno
);
352 ASSERT_EQ(parent
, waitpid(parent
, &status
, 0));
353 ASSERT_EQ(1, WIFSTOPPED(status
));
354 ASSERT_EQ(0, ptrace(PTRACE_DETACH
, parent
, NULL
, 0));
357 /* Tests child PTRACE_TRACEME. */
358 ret
= ptrace(PTRACE_TRACEME
);
359 if (can_trace_child
) {
363 EXPECT_EQ(EPERM
, errno
);
367 * Signals that the PTRACE_ATTACH test is done and the
368 * PTRACE_TRACEME test is ongoing.
370 ASSERT_EQ(1, write(pipe_child
[1], ".", 1));
372 if (can_trace_child
) {
373 ASSERT_EQ(0, raise(SIGSTOP
));
376 /* Waits for the parent PTRACE_ATTACH test. */
377 ASSERT_EQ(1, read(pipe_parent
[0], &buf_child
, 1));
378 _exit(_metadata
->exit_code
);
382 ASSERT_EQ(0, close(pipe_child
[1]));
383 ASSERT_EQ(0, close(pipe_parent
[0]));
384 if (variant
->domain_parent
)
385 create_domain(_metadata
);
387 /* Signals that the parent is in a domain, if any. */
388 ASSERT_EQ(1, write(pipe_parent
[1], ".", 1));
391 * Waits for the child to test PTRACE_ATTACH on the parent and start
392 * testing PTRACE_TRACEME.
394 ASSERT_EQ(1, read(pipe_child
[0], &buf_parent
, 1));
396 /* Tests child PTRACE_TRACEME. */
397 if (can_trace_child
) {
398 ASSERT_EQ(child
, waitpid(child
, &status
, 0));
399 ASSERT_EQ(1, WIFSTOPPED(status
));
400 ASSERT_EQ(0, ptrace(PTRACE_DETACH
, child
, NULL
, 0));
402 /* The child should not be traced by the parent. */
403 EXPECT_EQ(-1, ptrace(PTRACE_DETACH
, child
, NULL
, 0));
404 EXPECT_EQ(ESRCH
, errno
);
407 /* Tests PTRACE_MODE_READ on the child. */
408 err_proc_read
= test_ptrace_read(child
);
409 if (can_read_child
) {
410 EXPECT_EQ(0, err_proc_read
);
412 EXPECT_EQ(EACCES
, err_proc_read
);
415 /* Tests PTRACE_ATTACH on the child. */
416 ret
= ptrace(PTRACE_ATTACH
, child
, NULL
, 0);
417 if (can_trace_child
) {
421 EXPECT_EQ(EPERM
, errno
);
425 ASSERT_EQ(child
, waitpid(child
, &status
, 0));
426 ASSERT_EQ(1, WIFSTOPPED(status
));
427 ASSERT_EQ(0, ptrace(PTRACE_DETACH
, child
, NULL
, 0));
430 /* Signals that the parent PTRACE_ATTACH test is done. */
431 ASSERT_EQ(1, write(pipe_parent
[1], ".", 1));
432 ASSERT_EQ(child
, waitpid(child
, &status
, 0));
434 if (WIFSIGNALED(status
) || !WIFEXITED(status
) ||
435 WEXITSTATUS(status
) != EXIT_SUCCESS
)
436 _metadata
->exit_code
= KSFT_FAIL
;