Skip to content

Commit

Permalink
selftests/landlock: Test ptrace as much as possible with Yama
Browse files Browse the repository at this point in the history
Update ptrace tests according to all potential Yama security policies.
This is required to make such tests pass even if Yama is enabled.

Tests are not skipped but they now check both Landlock and Yama boundary
restrictions at run time to keep a maximum test coverage (i.e. positive
and negative testing).

Signed-off-by: Jeff Xu <jeffxu@google.com>
Link: https://lore.kernel.org/r/20230114020306.1407195-2-jeffxu@google.com
Cc: stable@vger.kernel.org
[mic: Add curly braces around EXPECT_EQ() to make it build, and improve
commit message]
Co-developed-by: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
  • Loading branch information
Jeff Xu authored and Mickaël Salaün committed Jan 27, 2023
1 parent 366617a commit 8677e55
Showing 1 changed file with 96 additions and 17 deletions.
113 changes: 96 additions & 17 deletions tools/testing/selftests/landlock/ptrace_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@

#include "common.h"

/* Copied from security/yama/yama_lsm.c */
#define YAMA_SCOPE_DISABLED 0
#define YAMA_SCOPE_RELATIONAL 1
#define YAMA_SCOPE_CAPABILITY 2
#define YAMA_SCOPE_NO_ATTACH 3

static void create_domain(struct __test_metadata *const _metadata)
{
int ruleset_fd;
Expand Down Expand Up @@ -60,6 +66,25 @@ static int test_ptrace_read(const pid_t pid)
return 0;
}

static int get_yama_ptrace_scope(void)
{
int ret;
char buf[2] = {};
const int fd = open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY);

if (fd < 0)
return 0;

if (read(fd, buf, 1) < 0) {
close(fd);
return -1;
}

ret = atoi(buf);
close(fd);
return ret;
}

/* clang-format off */
FIXTURE(hierarchy) {};
/* clang-format on */
Expand Down Expand Up @@ -232,8 +257,51 @@ TEST_F(hierarchy, trace)
pid_t child, parent;
int status, err_proc_read;
int pipe_child[2], pipe_parent[2];
int yama_ptrace_scope;
char buf_parent;
long ret;
bool can_read_child, can_trace_child, can_read_parent, can_trace_parent;

yama_ptrace_scope = get_yama_ptrace_scope();
ASSERT_LE(0, yama_ptrace_scope);

if (yama_ptrace_scope > YAMA_SCOPE_DISABLED)
TH_LOG("Incomplete tests due to Yama restrictions (scope %d)",
yama_ptrace_scope);

/*
* can_read_child is true if a parent process can read its child
* process, which is only the case when the parent process is not
* isolated from the child with a dedicated Landlock domain.
*/
can_read_child = !variant->domain_parent;

/*
* can_trace_child is true if a parent process can trace its child
* process. This depends on two conditions:
* - The parent process is not isolated from the child with a dedicated
* Landlock domain.
* - Yama allows tracing children (up to YAMA_SCOPE_RELATIONAL).
*/
can_trace_child = can_read_child &&
yama_ptrace_scope <= YAMA_SCOPE_RELATIONAL;

/*
* can_read_parent is true if a child process can read its parent
* process, which is only the case when the child process is not
* isolated from the parent with a dedicated Landlock domain.
*/
can_read_parent = !variant->domain_child;

/*
* can_trace_parent is true if a child process can trace its parent
* process. This depends on two conditions:
* - The child process is not isolated from the parent with a dedicated
* Landlock domain.
* - Yama is disabled (YAMA_SCOPE_DISABLED).
*/
can_trace_parent = can_read_parent &&
yama_ptrace_scope <= YAMA_SCOPE_DISABLED;

/*
* Removes all effective and permitted capabilities to not interfere
Expand Down Expand Up @@ -264,16 +332,21 @@ TEST_F(hierarchy, trace)
/* Waits for the parent to be in a domain, if any. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));

/* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the parent. */
/* Tests PTRACE_MODE_READ on the parent. */
err_proc_read = test_ptrace_read(parent);
if (can_read_parent) {
EXPECT_EQ(0, err_proc_read);
} else {
EXPECT_EQ(EACCES, err_proc_read);
}

/* Tests PTRACE_ATTACH on the parent. */
ret = ptrace(PTRACE_ATTACH, parent, NULL, 0);
if (variant->domain_child) {
if (can_trace_parent) {
EXPECT_EQ(0, ret);
} else {
EXPECT_EQ(-1, ret);
EXPECT_EQ(EPERM, errno);
EXPECT_EQ(EACCES, err_proc_read);
} else {
EXPECT_EQ(0, ret);
EXPECT_EQ(0, err_proc_read);
}
if (ret == 0) {
ASSERT_EQ(parent, waitpid(parent, &status, 0));
Expand All @@ -283,11 +356,11 @@ TEST_F(hierarchy, trace)

/* Tests child PTRACE_TRACEME. */
ret = ptrace(PTRACE_TRACEME);
if (variant->domain_parent) {
if (can_trace_child) {
EXPECT_EQ(0, ret);
} else {
EXPECT_EQ(-1, ret);
EXPECT_EQ(EPERM, errno);
} else {
EXPECT_EQ(0, ret);
}

/*
Expand All @@ -296,7 +369,7 @@ TEST_F(hierarchy, trace)
*/
ASSERT_EQ(1, write(pipe_child[1], ".", 1));

if (!variant->domain_parent) {
if (can_trace_child) {
ASSERT_EQ(0, raise(SIGSTOP));
}

Expand All @@ -321,7 +394,7 @@ TEST_F(hierarchy, trace)
ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));

/* Tests child PTRACE_TRACEME. */
if (!variant->domain_parent) {
if (can_trace_child) {
ASSERT_EQ(child, waitpid(child, &status, 0));
ASSERT_EQ(1, WIFSTOPPED(status));
ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
Expand All @@ -331,17 +404,23 @@ TEST_F(hierarchy, trace)
EXPECT_EQ(ESRCH, errno);
}

/* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the child. */
/* Tests PTRACE_MODE_READ on the child. */
err_proc_read = test_ptrace_read(child);
if (can_read_child) {
EXPECT_EQ(0, err_proc_read);
} else {
EXPECT_EQ(EACCES, err_proc_read);
}

/* Tests PTRACE_ATTACH on the child. */
ret = ptrace(PTRACE_ATTACH, child, NULL, 0);
if (variant->domain_parent) {
if (can_trace_child) {
EXPECT_EQ(0, ret);
} else {
EXPECT_EQ(-1, ret);
EXPECT_EQ(EPERM, errno);
EXPECT_EQ(EACCES, err_proc_read);
} else {
EXPECT_EQ(0, ret);
EXPECT_EQ(0, err_proc_read);
}

if (ret == 0) {
ASSERT_EQ(child, waitpid(child, &status, 0));
ASSERT_EQ(1, WIFSTOPPED(status));
Expand Down

0 comments on commit 8677e55

Please sign in to comment.