brintos

brintos / linux-shallow public Read only

0
0
Text · 10.5 KiB · a19db4d Raw
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