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
28static 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
46static 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
69static 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 */
89FIXTURE(hierarchy) {};
90/* clang-format on */
91
92FIXTURE_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 */
116FIXTURE_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 */
133FIXTURE_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 */
149FIXTURE_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 */
166FIXTURE_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 */
183FIXTURE_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 */
201FIXTURE_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 */
219FIXTURE_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 */
239FIXTURE_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
246FIXTURE_SETUP(hierarchy)
247{
248}
249
250FIXTURE_TEARDOWN(hierarchy)
251{
252}
253
254/* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
255TEST_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 (!__test_passed(_metadata))
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->exit_code);
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
434	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
435	    WEXITSTATUS(status) != EXIT_SUCCESS)
436		_metadata->exit_code = KSFT_FAIL;
437}
438
439TEST_HARNESS_MAIN
440