440 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Landlock tests - Ptrace4 *5 * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>6 * Copyright © 2019-2020 ANSSI7 */8 9#define _GNU_SOURCE10#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 024#define YAMA_SCOPE_RELATIONAL 125#define YAMA_SCOPE_CAPABILITY 226#define YAMA_SCOPE_NO_ATTACH 327 28static 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 46static 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 any62 * (access type) confusion for this test.63 */64 if (close(fd) != 0)65 return errno;66 return 0;67}68 69static 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 */89FIXTURE(hierarchy) {};90/* clang-format on */91 92FIXTURE_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 child101 * process P2.102 *103 * Yama's scoped ptrace is presumed disabled. If enabled, this optional104 * restriction is enforced in addition to any Landlock check, which means that105 * all P2 requests to trace P1 would be denied.106 */107 108/*109 * No domain110 *111 * P1-. P1 -> P2 : allow112 * \ P2 -> P1 : allow113 * 'P2114 */115/* clang-format off */116FIXTURE_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 domain125 *126 * P1--. P1 -> P2 : allow127 * \ P2 -> P1 : deny128 * .'-----.129 * | P2 |130 * '------'131 */132/* clang-format off */133FIXTURE_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 domain142 * .------.143 * | P1 --. P1 -> P2 : deny144 * '------' \ P2 -> P1 : allow145 * '146 * P2147 */148/* clang-format off */149FIXTURE_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 : deny160 * '------' \ P2 -> P1 : deny161 * .---'--.162 * | P2 |163 * '------'164 */165/* clang-format off */166FIXTURE_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 : allow177 * | \ | P2 -> P1 : allow178 * | ' |179 * | P2 |180 * '-------------'181 */182/* clang-format off */183FIXTURE_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 domain192 * .-----------------.193 * | P1----. | P1 -> P2 : allow194 * | \ | P2 -> P1 : deny195 * | .-'----. |196 * | | P2 | |197 * | '------' |198 * '-----------------'199 */200/* clang-format off */201FIXTURE_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 domain210 * .-----------------.211 * |.------. | P1 -> P2 : deny212 * || P1 ----. | P2 -> P1 : allow213 * |'------' \ |214 * | ' |215 * | P2 |216 * '-----------------'217 */218/* clang-format off */219FIXTURE_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 : deny230 * | | P1 . | P2 -> P1 : deny231 * | '------'\ |232 * | \ |233 * | .--'---. |234 * | | P2 | |235 * | '------' |236 * '-----------------'237 */238/* clang-format off */239FIXTURE_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 246FIXTURE_SETUP(hierarchy)247{248}249 250FIXTURE_TEARDOWN(hierarchy)251{252}253 254/* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */255TEST_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 child274 * process, which is only the case when the parent process is not275 * 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 child281 * process. This depends on two conditions:282 * - The parent process is not isolated from the child with a dedicated283 * 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 parent291 * process, which is only the case when the child process is not292 * 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 parent298 * process. This depends on two conditions:299 * - The child process is not isolated from the parent with a dedicated300 * 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 interfere308 * 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 the368 * 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 start392 * 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 439TEST_HARNESS_MAIN440