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 #define YAMA_SCOPE_CAPABILITY 2
26 #define YAMA_SCOPE_NO_ATTACH 3
27
create_domain(struct __test_metadata * const _metadata)28 static void create_domain(struct __test_metadata *const _metadata)
29 {
30 int ruleset_fd;
31 struct landlock_ruleset_attr ruleset_attr = {
32 .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_BLOCK,
33 };
34
35 ruleset_fd =
36 landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
37 EXPECT_LE(0, ruleset_fd)
38 {
39 TH_LOG("Failed to create a ruleset: %s", strerror(errno));
40 }
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));
44 }
45
test_ptrace_read(const pid_t pid)46 static int test_ptrace_read(const pid_t pid)
47 {
48 static const char path_template[] = "/proc/%d/environ";
49 char procenv_path[sizeof(path_template) + 10];
50 int procenv_path_size, fd;
51
52 procenv_path_size = snprintf(procenv_path, sizeof(procenv_path),
53 path_template, pid);
54 if (procenv_path_size >= sizeof(procenv_path))
55 return E2BIG;
56
57 fd = open(procenv_path, O_RDONLY | O_CLOEXEC);
58 if (fd < 0)
59 return errno;
60 /*
61 * Mixing error codes from close(2) and open(2) should not lead to any
62 * (access type) confusion for this test.
63 */
64 if (close(fd) != 0)
65 return errno;
66 return 0;
67 }
68
get_yama_ptrace_scope(void)69 static int get_yama_ptrace_scope(void)
70 {
71 int ret;
72 char buf[2] = {};
73 const int fd = open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY);
74
75 if (fd < 0)
76 return 0;
77
78 if (read(fd, buf, 1) < 0) {
79 close(fd);
80 return -1;
81 }
82
83 ret = atoi(buf);
84 close(fd);
85 return ret;
86 }
87
88 /* clang-format off */
FIXTURE(hierarchy)89 FIXTURE(hierarchy) {};
90 /* clang-format on */
91
FIXTURE_VARIANT(hierarchy)92 FIXTURE_VARIANT(hierarchy)
93 {
94 const bool domain_both;
95 const bool domain_parent;
96 const bool domain_child;
97 };
98
99 /*
100 * Test multiple tracing combinations between a parent process P1 and a child
101 * process P2.
102 *
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.
106 */
107
108 /*
109 * No domain
110 *
111 * P1-. P1 -> P2 : allow
112 * \ P2 -> P1 : allow
113 * 'P2
114 */
115 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,allow_without_domain)116 FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) {
117 /* clang-format on */
118 .domain_both = false,
119 .domain_parent = false,
120 .domain_child = false,
121 };
122
123 /*
124 * Child domain
125 *
126 * P1--. P1 -> P2 : allow
127 * \ P2 -> P1 : deny
128 * .'-----.
129 * | P2 |
130 * '------'
131 */
132 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,allow_with_one_domain)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,
138 };
139
140 /*
141 * Parent domain
142 * .------.
143 * | P1 --. P1 -> P2 : deny
144 * '------' \ P2 -> P1 : allow
145 * '
146 * P2
147 */
148 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_parent_domain)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,
154 };
155
156 /*
157 * Parent + child domain (siblings)
158 * .------.
159 * | P1 ---. P1 -> P2 : deny
160 * '------' \ P2 -> P1 : deny
161 * .---'--.
162 * | P2 |
163 * '------'
164 */
165 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_sibling_domain)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,
171 };
172
173 /*
174 * Same domain (inherited)
175 * .-------------.
176 * | P1----. | P1 -> P2 : allow
177 * | \ | P2 -> P1 : allow
178 * | ' |
179 * | P2 |
180 * '-------------'
181 */
182 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,allow_sibling_domain)183 FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) {
184 /* clang-format on */
185 .domain_both = true,
186 .domain_parent = false,
187 .domain_child = false,
188 };
189
190 /*
191 * Inherited + child domain
192 * .-----------------.
193 * | P1----. | P1 -> P2 : allow
194 * | \ | P2 -> P1 : deny
195 * | .-'----. |
196 * | | P2 | |
197 * | '------' |
198 * '-----------------'
199 */
200 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,allow_with_nested_domain)201 FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) {
202 /* clang-format on */
203 .domain_both = true,
204 .domain_parent = false,
205 .domain_child = true,
206 };
207
208 /*
209 * Inherited + parent domain
210 * .-----------------.
211 * |.------. | P1 -> P2 : deny
212 * || P1 ----. | P2 -> P1 : allow
213 * |'------' \ |
214 * | ' |
215 * | P2 |
216 * '-----------------'
217 */
218 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_nested_and_parent_domain)219 FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) {
220 /* clang-format on */
221 .domain_both = true,
222 .domain_parent = true,
223 .domain_child = false,
224 };
225
226 /*
227 * Inherited + parent and child domain (siblings)
228 * .-----------------.
229 * | .------. | P1 -> P2 : deny
230 * | | P1 . | P2 -> P1 : deny
231 * | '------'\ |
232 * | \ |
233 * | .--'---. |
234 * | | P2 | |
235 * | '------' |
236 * '-----------------'
237 */
238 /* clang-format off */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_forked_domain)239 FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) {
240 /* clang-format on */
241 .domain_both = true,
242 .domain_parent = true,
243 .domain_child = true,
244 };
245
FIXTURE_SETUP(hierarchy)246 FIXTURE_SETUP(hierarchy)
247 {
248 }
249
FIXTURE_TEARDOWN(hierarchy)250 FIXTURE_TEARDOWN(hierarchy)
251 {
252 }
253
254 /* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
TEST_F(hierarchy,trace)255 TEST_F(hierarchy, trace)
256 {
257 pid_t child, parent;
258 int status, err_proc_read;
259 int pipe_child[2], pipe_parent[2];
260 int yama_ptrace_scope;
261 char buf_parent;
262 long ret;
263 bool can_read_child, can_trace_child, can_read_parent, can_trace_parent;
264
265 yama_ptrace_scope = get_yama_ptrace_scope();
266 ASSERT_LE(0, yama_ptrace_scope);
267
268 if (yama_ptrace_scope > YAMA_SCOPE_DISABLED)
269 TH_LOG("Incomplete tests due to Yama restrictions (scope %d)",
270 yama_ptrace_scope);
271
272 /*
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.
276 */
277 can_read_child = !variant->domain_parent;
278
279 /*
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
283 * Landlock domain.
284 * - Yama allows tracing children (up to YAMA_SCOPE_RELATIONAL).
285 */
286 can_trace_child = can_read_child &&
287 yama_ptrace_scope <= YAMA_SCOPE_RELATIONAL;
288
289 /*
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.
293 */
294 can_read_parent = !variant->domain_child;
295
296 /*
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
300 * Landlock domain.
301 * - Yama is disabled (YAMA_SCOPE_DISABLED).
302 */
303 can_trace_parent = can_read_parent &&
304 yama_ptrace_scope <= YAMA_SCOPE_DISABLED;
305
306 /*
307 * Removes all effective and permitted capabilities to not interfere
308 * with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS.
309 */
310 drop_caps(_metadata);
311
312 parent = getpid();
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. */
319 return;
320 }
321
322 child = fork();
323 ASSERT_LE(0, child);
324 if (child == 0) {
325 char buf_child;
326
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);
331
332 /* Waits for the parent to be in a domain, if any. */
333 ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
334
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);
339 } else {
340 EXPECT_EQ(EACCES, err_proc_read);
341 }
342
343 /* Tests PTRACE_ATTACH on the parent. */
344 ret = ptrace(PTRACE_ATTACH, parent, NULL, 0);
345 if (can_trace_parent) {
346 EXPECT_EQ(0, ret);
347 } else {
348 EXPECT_EQ(-1, ret);
349 EXPECT_EQ(EPERM, errno);
350 }
351 if (ret == 0) {
352 ASSERT_EQ(parent, waitpid(parent, &status, 0));
353 ASSERT_EQ(1, WIFSTOPPED(status));
354 ASSERT_EQ(0, ptrace(PTRACE_DETACH, parent, NULL, 0));
355 }
356
357 /* Tests child PTRACE_TRACEME. */
358 ret = ptrace(PTRACE_TRACEME);
359 if (can_trace_child) {
360 EXPECT_EQ(0, ret);
361 } else {
362 EXPECT_EQ(-1, ret);
363 EXPECT_EQ(EPERM, errno);
364 }
365
366 /*
367 * Signals that the PTRACE_ATTACH test is done and the
368 * PTRACE_TRACEME test is ongoing.
369 */
370 ASSERT_EQ(1, write(pipe_child[1], ".", 1));
371
372 if (can_trace_child) {
373 ASSERT_EQ(0, raise(SIGSTOP));
374 }
375
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);
379 return;
380 }
381
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);
386
387 /* Signals that the parent is in a domain, if any. */
388 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
389
390 /*
391 * Waits for the child to test PTRACE_ATTACH on the parent and start
392 * testing PTRACE_TRACEME.
393 */
394 ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
395
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));
401 } else {
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);
405 }
406
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);
411 } else {
412 EXPECT_EQ(EACCES, err_proc_read);
413 }
414
415 /* Tests PTRACE_ATTACH on the child. */
416 ret = ptrace(PTRACE_ATTACH, child, NULL, 0);
417 if (can_trace_child) {
418 EXPECT_EQ(0, ret);
419 } else {
420 EXPECT_EQ(-1, ret);
421 EXPECT_EQ(EPERM, errno);
422 }
423
424 if (ret == 0) {
425 ASSERT_EQ(child, waitpid(child, &status, 0));
426 ASSERT_EQ(1, WIFSTOPPED(status));
427 ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
428 }
429
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;
436 }
437
438 TEST_HARNESS_MAIN
439