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 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 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 62 FIXTURE(hierarchy) { }; 63 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 195 FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) { 196 .domain_both = true, 197 .domain_parent = true, 198 .domain_child = true, 199 }; 200 201 FIXTURE_SETUP(hierarchy) 202 { } 203 204 FIXTURE_TEARDOWN(hierarchy) 205 { } 206 207 /* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */ 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