cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

ptrace_test.c (8708B)


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