~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/tools/testing/selftests/landlock/ptrace_test.c

Version: ~ [ linux-6.11-rc3 ] ~ [ linux-6.10.4 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.45 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.104 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.164 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.223 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.281 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.319 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  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 
 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 
 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 
 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 */
 89 FIXTURE(hierarchy) {};
 90 /* clang-format on */
 91 
 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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 
246 FIXTURE_SETUP(hierarchy)
247 {
248 }
249 
250 FIXTURE_TEARDOWN(hierarchy)
251 {
252 }
253 
254 /* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
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 (!__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 
439 TEST_HARNESS_MAIN
440 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php