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
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.