selftests/landlock: Test ptrace as much as possible with Yama

commit 8677e555f1 upstream.

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>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Jeff Xu 2023-01-14 02:03:06 +00:00 committed by Greg Kroah-Hartman
parent 6c51c07028
commit 21477427ec
1 changed files with 96 additions and 17 deletions

View File

@ -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;
@ -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 */
@ -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
@ -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));
@ -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);
}
/*
@ -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));
}
@ -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));
@ -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));