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