xref: /linux/tools/testing/selftests/landlock/ptrace_test.c (revision de5817bbfb569f22406970f81360ac3f694ba6e8)
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