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 (!_metadata->passed)
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->passed ? EXIT_SUCCESS : EXIT_FAILURE);
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));
433 if (WIFSIGNALED(status) || !WIFEXITED(status) ||
434 WEXITSTATUS(status) != EXIT_SUCCESS)
435 _metadata->passed = 0;