1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Landlock tests - Ptrace
4 *
5 * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
6 * Copyright © 2019-2020 ANSSI
7 */
8
9 #define _GNU_SOURCE
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <linux/landlock.h>
13 #include <signal.h>
14 #include <sys/prctl.h>
15 #include <sys/ptrace.h>
16 #include <sys/types.h>
17 #include <sys/wait.h>
18 #include <unistd.h>
19
20 #include "common.h"
21
22 /* Copied from security/yama/yama_lsm.c */
23 #define YAMA_SCOPE_DISABLED 0
24 #define YAMA_SCOPE_RELATIONAL 1
25
create_domain(struct __test_metadata * const _metadata)26 static void create_domain(struct __test_metadata *const _metadata)
27 {
28 int ruleset_fd;
29 struct landlock_ruleset_attr ruleset_attr = {
30 .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_BLOCK,
31 };
32
33 ruleset_fd =
34 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
35 EXPECT_LE(0, ruleset_fd)
36 {
37 TH_LOG("Failed to create a ruleset: %s", strerror(errno));
38 }
39 EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
40 EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
41 EXPECT_EQ(0, close(ruleset_fd));
42 }
43
test_ptrace_read(const pid_t pid)44 static int test_ptrace_read(const pid_t pid)
45 {
46 static const char path_template[] = "/proc/%d/environ";
47 char procenv_path[sizeof(path_template) + 10];
48 int procenv_path_size, fd;
49
50 procenv_path_size = snprintf(procenv_path, sizeof(procenv_path),
51 path_template, pid);
52 if (procenv_path_size >= sizeof(procenv_path))
53 return E2BIG;
54
55 fd = open(procenv_path, O_RDONLY | O_CLOEXEC);
56 if (fd < 0)
57 return errno;
58 /*
59 * Mixing error codes from close(2) and open(2) should not lead to any
60 * (access type) confusion for this test.
61 */
62 if (close(fd) != 0)
63 return errno;
64 return 0;
65 }
66
get_yama_ptrace_scope(void)67 static int get_yama_ptrace_scope(void)
68 {
69 int ret;
70 char buf[2] = {};
71 const int fd = open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY);
72
73 if (fd < 0)
74 return 0;
75
76 if (read(fd, buf, 1) < 0) {
77 close(fd);
78 return -1;
79 }
80
81 ret = atoi(buf);
82 close(fd);
83 return ret;
84 }
85
86 /* clang-format off */
FIXTURE(hierarchy)87 FIXTURE(hierarchy) {};
88 /* clang-format on */
89
FIXTURE_VARIANT(hierarchy)90 FIXTURE_VARIANT(hierarchy)
91 {
92 const bool domain_both;
93 const bool domain_parent;
94 const bool domain_child;
95 };
96
97 /*
98 * Test multiple tracing combinations between a parent process P1 and a child
99 * process P2.
100 *
101 * Yama's scoped ptrace is presumed disabled. If enabled, this optional
102 * restriction is enforced in addition to any Landlock check, which means that
103 * all P2 requests to trace P1 would be denied.
104 */
105
106 /*
107 * No domain
108 *
109 * P1-. P1 -> P2 : allow
110 * \ P2 -> P1 : allow
111 * 'P2
112 */
113 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,allow_without_domain)114 FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) {
115 /* clang-format on */
116 .domain_both = false,
117 .domain_parent = false,
118 .domain_child = false,
119 };
120
121 /*
122 * Child domain
123 *
124 * P1--. P1 -> P2 : allow
125 * \ P2 -> P1 : deny
126 * .'-----.
127 * | P2 |
128 * '------'
129 */
130 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,allow_with_one_domain)131 FIXTURE_VARIANT_ADD(hierarchy, allow_with_one_domain) {
132 /* clang-format on */
133 .domain_both = false,
134 .domain_parent = false,
135 .domain_child = true,
136 };
137
138 /*
139 * Parent domain
140 * .------.
141 * | P1 --. P1 -> P2 : deny
142 * '------' \ P2 -> P1 : allow
143 * '
144 * P2
145 */
146 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_parent_domain)147 FIXTURE_VARIANT_ADD(hierarchy, deny_with_parent_domain) {
148 /* clang-format on */
149 .domain_both = false,
150 .domain_parent = true,
151 .domain_child = false,
152 };
153
154 /*
155 * Parent + child domain (siblings)
156 * .------.
157 * | P1 ---. P1 -> P2 : deny
158 * '------' \ P2 -> P1 : deny
159 * .---'--.
160 * | P2 |
161 * '------'
162 */
163 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_sibling_domain)164 FIXTURE_VARIANT_ADD(hierarchy, deny_with_sibling_domain) {
165 /* clang-format on */
166 .domain_both = false,
167 .domain_parent = true,
168 .domain_child = true,
169 };
170
171 /*
172 * Same domain (inherited)
173 * .-------------.
174 * | P1----. | P1 -> P2 : allow
175 * | \ | P2 -> P1 : allow
176 * | ' |
177 * | P2 |
178 * '-------------'
179 */
180 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,allow_sibling_domain)181 FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) {
182 /* clang-format on */
183 .domain_both = true,
184 .domain_parent = false,
185 .domain_child = false,
186 };
187
188 /*
189 * Inherited + child domain
190 * .-----------------.
191 * | P1----. | P1 -> P2 : allow
192 * | \ | P2 -> P1 : deny
193 * | .-'----. |
194 * | | P2 | |
195 * | '------' |
196 * '-----------------'
197 */
198 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,allow_with_nested_domain)199 FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) {
200 /* clang-format on */
201 .domain_both = true,
202 .domain_parent = false,
203 .domain_child = true,
204 };
205
206 /*
207 * Inherited + parent domain
208 * .-----------------.
209 * |.------. | P1 -> P2 : deny
210 * || P1 ----. | P2 -> P1 : allow
211 * |'------' \ |
212 * | ' |
213 * | P2 |
214 * '-----------------'
215 */
216 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_nested_and_parent_domain)217 FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) {
218 /* clang-format on */
219 .domain_both = true,
220 .domain_parent = true,
221 .domain_child = false,
222 };
223
224 /*
225 * Inherited + parent and child domain (siblings)
226 * .-----------------.
227 * | .------. | P1 -> P2 : deny
228 * | | P1 . | P2 -> P1 : deny
229 * | '------'\ |
230 * | \ |
231 * | .--'---. |
232 * | | P2 | |
233 * | '------' |
234 * '-----------------'
235 */
236 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_forked_domain)237 FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) {
238 /* clang-format on */
239 .domain_both = true,
240 .domain_parent = true,
241 .domain_child = true,
242 };
243
FIXTURE_SETUP(hierarchy)244 FIXTURE_SETUP(hierarchy)
245 {
246 }
247
FIXTURE_TEARDOWN(hierarchy)248 FIXTURE_TEARDOWN(hierarchy)
249 {
250 }
251
252 /* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
TEST_F(hierarchy,trace)253 TEST_F(hierarchy, trace)
254 {
255 pid_t child, parent;
256 int status, err_proc_read;
257 int pipe_child[2], pipe_parent[2];
258 int yama_ptrace_scope;
259 char buf_parent;
260 long ret;
261 bool can_read_child, can_trace_child, can_read_parent, can_trace_parent;
262
263 yama_ptrace_scope = get_yama_ptrace_scope();
264 ASSERT_LE(0, yama_ptrace_scope);
265
266 if (yama_ptrace_scope > YAMA_SCOPE_DISABLED)
267 TH_LOG("Incomplete tests due to Yama restrictions (scope %d)",
268 yama_ptrace_scope);
269
270 /*
271 * can_read_child is true if a parent process can read its child
272 * process, which is only the case when the parent process is not
273 * isolated from the child with a dedicated Landlock domain.
274 */
275 can_read_child = !variant->domain_parent;
276
277 /*
278 * can_trace_child is true if a parent process can trace its child
279 * process. This depends on two conditions:
280 * - The parent process is not isolated from the child with a dedicated
281 * Landlock domain.
282 * - Yama allows tracing children (up to YAMA_SCOPE_RELATIONAL).
283 */
284 can_trace_child = can_read_child &&
285 yama_ptrace_scope <= YAMA_SCOPE_RELATIONAL;
286
287 /*
288 * can_read_parent is true if a child process can read its parent
289 * process, which is only the case when the child process is not
290 * isolated from the parent with a dedicated Landlock domain.
291 */
292 can_read_parent = !variant->domain_child;
293
294 /*
295 * can_trace_parent is true if a child process can trace its parent
296 * process. This depends on two conditions:
297 * - The child process is not isolated from the parent with a dedicated
298 * Landlock domain.
299 * - Yama is disabled (YAMA_SCOPE_DISABLED).
300 */
301 can_trace_parent = can_read_parent &&
302 yama_ptrace_scope <= YAMA_SCOPE_DISABLED;
303
304 /*
305 * Removes all effective and permitted capabilities to not interfere
306 * with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS.
307 */
308 drop_caps(_metadata);
309
310 parent = getpid();
311 ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
312 ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
313 if (variant->domain_both) {
314 create_domain(_metadata);
315 if (!__test_passed(_metadata))
316 /* Aborts before forking. */
317 return;
318 }
319
320 child = fork();
321 ASSERT_LE(0, child);
322 if (child == 0) {
323 char buf_child;
324
325 ASSERT_EQ(0, close(pipe_parent[1]));
326 ASSERT_EQ(0, close(pipe_child[0]));
327 if (variant->domain_child)
328 create_domain(_metadata);
329
330 /* Waits for the parent to be in a domain, if any. */
331 ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
332
333 /* Tests PTRACE_MODE_READ on the parent. */
334 err_proc_read = test_ptrace_read(parent);
335 if (can_read_parent) {
336 EXPECT_EQ(0, err_proc_read);
337 } else {
338 EXPECT_EQ(EACCES, err_proc_read);
339 }
340
341 /* Tests PTRACE_ATTACH on the parent. */
342 ret = ptrace(PTRACE_ATTACH, parent, NULL, 0);
343 if (can_trace_parent) {
344 EXPECT_EQ(0, ret);
345 } else {
346 EXPECT_EQ(-1, ret);
347 EXPECT_EQ(EPERM, errno);
348 }
349 if (ret == 0) {
350 ASSERT_EQ(parent, waitpid(parent, &status, 0));
351 ASSERT_EQ(1, WIFSTOPPED(status));
352 ASSERT_EQ(0, ptrace(PTRACE_DETACH, parent, NULL, 0));
353 }
354
355 /* Tests child PTRACE_TRACEME. */
356 ret = ptrace(PTRACE_TRACEME);
357 if (can_trace_child) {
358 EXPECT_EQ(0, ret);
359 } else {
360 EXPECT_EQ(-1, ret);
361 EXPECT_EQ(EPERM, errno);
362 }
363
364 /*
365 * Signals that the PTRACE_ATTACH test is done and the
366 * PTRACE_TRACEME test is ongoing.
367 */
368 ASSERT_EQ(1, write(pipe_child[1], ".", 1));
369
370 if (can_trace_child) {
371 ASSERT_EQ(0, raise(SIGSTOP));
372 }
373
374 /* Waits for the parent PTRACE_ATTACH test. */
375 ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
376 _exit(_metadata->exit_code);
377 return;
378 }
379
380 ASSERT_EQ(0, close(pipe_child[1]));
381 ASSERT_EQ(0, close(pipe_parent[0]));
382 if (variant->domain_parent)
383 create_domain(_metadata);
384
385 /* Signals that the parent is in a domain, if any. */
386 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
387
388 /*
389 * Waits for the child to test PTRACE_ATTACH on the parent and start
390 * testing PTRACE_TRACEME.
391 */
392 ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
393
394 /* Tests child PTRACE_TRACEME. */
395 if (can_trace_child) {
396 ASSERT_EQ(child, waitpid(child, &status, 0));
397 ASSERT_EQ(1, WIFSTOPPED(status));
398 ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
399 } else {
400 /* The child should not be traced by the parent. */
401 EXPECT_EQ(-1, ptrace(PTRACE_DETACH, child, NULL, 0));
402 EXPECT_EQ(ESRCH, errno);
403 }
404
405 /* Tests PTRACE_MODE_READ on the child. */
406 err_proc_read = test_ptrace_read(child);
407 if (can_read_child) {
408 EXPECT_EQ(0, err_proc_read);
409 } else {
410 EXPECT_EQ(EACCES, err_proc_read);
411 }
412
413 /* Tests PTRACE_ATTACH on the child. */
414 ret = ptrace(PTRACE_ATTACH, child, NULL, 0);
415 if (can_trace_child) {
416 EXPECT_EQ(0, ret);
417 } else {
418 EXPECT_EQ(-1, ret);
419 EXPECT_EQ(EPERM, errno);
420 }
421
422 if (ret == 0) {
423 ASSERT_EQ(child, waitpid(child, &status, 0));
424 ASSERT_EQ(1, WIFSTOPPED(status));
425 ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
426 }
427
428 /* Signals that the parent PTRACE_ATTACH test is done. */
429 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
430 ASSERT_EQ(child, waitpid(child, &status, 0));
431
432 if (WIFSIGNALED(status) || !WIFEXITED(status) ||
433 WEXITSTATUS(status) != EXIT_SUCCESS)
434 _metadata->exit_code = KSFT_FAIL;
435 }
436
437 TEST_HARNESS_MAIN
438