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