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