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 
create_domain(struct __test_metadata * const _metadata)22 static void create_domain(struct __test_metadata *const _metadata)
23 {
24 	int ruleset_fd;
25 	struct landlock_ruleset_attr ruleset_attr = {
26 		.handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_BLOCK,
27 	};
28 
29 	ruleset_fd = landlock_create_ruleset(&ruleset_attr,
30 			sizeof(ruleset_attr), 0);
31 	EXPECT_LE(0, ruleset_fd) {
32 		TH_LOG("Failed to create a ruleset: %s", strerror(errno));
33 	}
34 	EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
35 	EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
36 	EXPECT_EQ(0, close(ruleset_fd));
37 }
38 
test_ptrace_read(const pid_t pid)39 static int test_ptrace_read(const pid_t pid)
40 {
41 	static const char path_template[] = "/proc/%d/environ";
42 	char procenv_path[sizeof(path_template) + 10];
43 	int procenv_path_size, fd;
44 
45 	procenv_path_size = snprintf(procenv_path, sizeof(procenv_path),
46 			path_template, pid);
47 	if (procenv_path_size >= sizeof(procenv_path))
48 		return E2BIG;
49 
50 	fd = open(procenv_path, O_RDONLY | O_CLOEXEC);
51 	if (fd < 0)
52 		return errno;
53 	/*
54 	 * Mixing error codes from close(2) and open(2) should not lead to any
55 	 * (access type) confusion for this test.
56 	 */
57 	if (close(fd) != 0)
58 		return errno;
59 	return 0;
60 }
61 
FIXTURE(hierarchy)62 FIXTURE(hierarchy) { };
63 
FIXTURE_VARIANT(hierarchy)64 FIXTURE_VARIANT(hierarchy) {
65 	const bool domain_both;
66 	const bool domain_parent;
67 	const bool domain_child;
68 };
69 
70 /*
71  * Test multiple tracing combinations between a parent process P1 and a child
72  * process P2.
73  *
74  * Yama's scoped ptrace is presumed disabled.  If enabled, this optional
75  * restriction is enforced in addition to any Landlock check, which means that
76  * all P2 requests to trace P1 would be denied.
77  */
78 
79 /*
80  *        No domain
81  *
82  *   P1-.               P1 -> P2 : allow
83  *       \              P2 -> P1 : allow
84  *        'P2
85  */
FIXTURE_VARIANT_ADD(hierarchy,allow_without_domain)86 FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) {
87 	.domain_both = false,
88 	.domain_parent = false,
89 	.domain_child = false,
90 };
91 
92 /*
93  *        Child domain
94  *
95  *   P1--.              P1 -> P2 : allow
96  *        \             P2 -> P1 : deny
97  *        .'-----.
98  *        |  P2  |
99  *        '------'
100  */
FIXTURE_VARIANT_ADD(hierarchy,allow_with_one_domain)101 FIXTURE_VARIANT_ADD(hierarchy, allow_with_one_domain) {
102 	.domain_both = false,
103 	.domain_parent = false,
104 	.domain_child = true,
105 };
106 
107 /*
108  *        Parent domain
109  * .------.
110  * |  P1  --.           P1 -> P2 : deny
111  * '------'  \          P2 -> P1 : allow
112  *            '
113  *            P2
114  */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_parent_domain)115 FIXTURE_VARIANT_ADD(hierarchy, deny_with_parent_domain) {
116 	.domain_both = false,
117 	.domain_parent = true,
118 	.domain_child = false,
119 };
120 
121 /*
122  *        Parent + child domain (siblings)
123  * .------.
124  * |  P1  ---.          P1 -> P2 : deny
125  * '------'   \         P2 -> P1 : deny
126  *         .---'--.
127  *         |  P2  |
128  *         '------'
129  */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_sibling_domain)130 FIXTURE_VARIANT_ADD(hierarchy, deny_with_sibling_domain) {
131 	.domain_both = false,
132 	.domain_parent = true,
133 	.domain_child = true,
134 };
135 
136 /*
137  *         Same domain (inherited)
138  * .-------------.
139  * | P1----.     |      P1 -> P2 : allow
140  * |        \    |      P2 -> P1 : allow
141  * |         '   |
142  * |         P2  |
143  * '-------------'
144  */
FIXTURE_VARIANT_ADD(hierarchy,allow_sibling_domain)145 FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) {
146 	.domain_both = true,
147 	.domain_parent = false,
148 	.domain_child = false,
149 };
150 
151 /*
152  *         Inherited + child domain
153  * .-----------------.
154  * |  P1----.        |  P1 -> P2 : allow
155  * |         \       |  P2 -> P1 : deny
156  * |        .-'----. |
157  * |        |  P2  | |
158  * |        '------' |
159  * '-----------------'
160  */
FIXTURE_VARIANT_ADD(hierarchy,allow_with_nested_domain)161 FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) {
162 	.domain_both = true,
163 	.domain_parent = false,
164 	.domain_child = true,
165 };
166 
167 /*
168  *         Inherited + parent domain
169  * .-----------------.
170  * |.------.         |  P1 -> P2 : deny
171  * ||  P1  ----.     |  P2 -> P1 : allow
172  * |'------'    \    |
173  * |             '   |
174  * |             P2  |
175  * '-----------------'
176  */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_nested_and_parent_domain)177 FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) {
178 	.domain_both = true,
179 	.domain_parent = true,
180 	.domain_child = false,
181 };
182 
183 /*
184  *         Inherited + parent and child domain (siblings)
185  * .-----------------.
186  * | .------.        |  P1 -> P2 : deny
187  * | |  P1  .        |  P2 -> P1 : deny
188  * | '------'\       |
189  * |          \      |
190  * |        .--'---. |
191  * |        |  P2  | |
192  * |        '------' |
193  * '-----------------'
194  */
FIXTURE_VARIANT_ADD(hierarchy,deny_with_forked_domain)195 FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) {
196 	.domain_both = true,
197 	.domain_parent = true,
198 	.domain_child = true,
199 };
200 
FIXTURE_SETUP(hierarchy)201 FIXTURE_SETUP(hierarchy)
202 { }
203 
FIXTURE_TEARDOWN(hierarchy)204 FIXTURE_TEARDOWN(hierarchy)
205 { }
206 
207 /* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
TEST_F(hierarchy,trace)208 TEST_F(hierarchy, trace)
209 {
210 	pid_t child, parent;
211 	int status, err_proc_read;
212 	int pipe_child[2], pipe_parent[2];
213 	char buf_parent;
214 	long ret;
215 
216 	/*
217 	 * Removes all effective and permitted capabilities to not interfere
218 	 * with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS.
219 	 */
220 	drop_caps(_metadata);
221 
222 	parent = getpid();
223 	ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
224 	ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
225 	if (variant->domain_both) {
226 		create_domain(_metadata);
227 		if (!_metadata->passed)
228 			/* Aborts before forking. */
229 			return;
230 	}
231 
232 	child = fork();
233 	ASSERT_LE(0, child);
234 	if (child == 0) {
235 		char buf_child;
236 
237 		ASSERT_EQ(0, close(pipe_parent[1]));
238 		ASSERT_EQ(0, close(pipe_child[0]));
239 		if (variant->domain_child)
240 			create_domain(_metadata);
241 
242 		/* Waits for the parent to be in a domain, if any. */
243 		ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
244 
245 		/* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the parent. */
246 		err_proc_read = test_ptrace_read(parent);
247 		ret = ptrace(PTRACE_ATTACH, parent, NULL, 0);
248 		if (variant->domain_child) {
249 			EXPECT_EQ(-1, ret);
250 			EXPECT_EQ(EPERM, errno);
251 			EXPECT_EQ(EACCES, err_proc_read);
252 		} else {
253 			EXPECT_EQ(0, ret);
254 			EXPECT_EQ(0, err_proc_read);
255 		}
256 		if (ret == 0) {
257 			ASSERT_EQ(parent, waitpid(parent, &status, 0));
258 			ASSERT_EQ(1, WIFSTOPPED(status));
259 			ASSERT_EQ(0, ptrace(PTRACE_DETACH, parent, NULL, 0));
260 		}
261 
262 		/* Tests child PTRACE_TRACEME. */
263 		ret = ptrace(PTRACE_TRACEME);
264 		if (variant->domain_parent) {
265 			EXPECT_EQ(-1, ret);
266 			EXPECT_EQ(EPERM, errno);
267 		} else {
268 			EXPECT_EQ(0, ret);
269 		}
270 
271 		/*
272 		 * Signals that the PTRACE_ATTACH test is done and the
273 		 * PTRACE_TRACEME test is ongoing.
274 		 */
275 		ASSERT_EQ(1, write(pipe_child[1], ".", 1));
276 
277 		if (!variant->domain_parent) {
278 			ASSERT_EQ(0, raise(SIGSTOP));
279 		}
280 
281 		/* Waits for the parent PTRACE_ATTACH test. */
282 		ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
283 		_exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
284 		return;
285 	}
286 
287 	ASSERT_EQ(0, close(pipe_child[1]));
288 	ASSERT_EQ(0, close(pipe_parent[0]));
289 	if (variant->domain_parent)
290 		create_domain(_metadata);
291 
292 	/* Signals that the parent is in a domain, if any. */
293 	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
294 
295 	/*
296 	 * Waits for the child to test PTRACE_ATTACH on the parent and start
297 	 * testing PTRACE_TRACEME.
298 	 */
299 	ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
300 
301 	/* Tests child PTRACE_TRACEME. */
302 	if (!variant->domain_parent) {
303 		ASSERT_EQ(child, waitpid(child, &status, 0));
304 		ASSERT_EQ(1, WIFSTOPPED(status));
305 		ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
306 	} else {
307 		/* The child should not be traced by the parent. */
308 		EXPECT_EQ(-1, ptrace(PTRACE_DETACH, child, NULL, 0));
309 		EXPECT_EQ(ESRCH, errno);
310 	}
311 
312 	/* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the child. */
313 	err_proc_read = test_ptrace_read(child);
314 	ret = ptrace(PTRACE_ATTACH, child, NULL, 0);
315 	if (variant->domain_parent) {
316 		EXPECT_EQ(-1, ret);
317 		EXPECT_EQ(EPERM, errno);
318 		EXPECT_EQ(EACCES, err_proc_read);
319 	} else {
320 		EXPECT_EQ(0, ret);
321 		EXPECT_EQ(0, err_proc_read);
322 	}
323 	if (ret == 0) {
324 		ASSERT_EQ(child, waitpid(child, &status, 0));
325 		ASSERT_EQ(1, WIFSTOPPED(status));
326 		ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
327 	}
328 
329 	/* Signals that the parent PTRACE_ATTACH test is done. */
330 	ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
331 	ASSERT_EQ(child, waitpid(child, &status, 0));
332 	if (WIFSIGNALED(status) || !WIFEXITED(status) ||
333 			WEXITSTATUS(status) != EXIT_SUCCESS)
334 		_metadata->passed = 0;
335 }
336 
337 TEST_HARNESS_MAIN
338