blob: 2644445a9d0205b2aa5a4ad8915a7fc3fae043e8 [file]
// SPDX-License-Identifier: GPL-2.0
/*
* Landlock tests - Ptrace
*
* Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2019-2020 ANSSI
* Copyright © 2024-2025 Microsoft Corporation
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/landlock.h>
#include <sched.h>
#include <signal.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "audit.h"
#include "common.h"
#include "trace.h"
/* Copied from security/yama/yama_lsm.c */
#define YAMA_SCOPE_DISABLED 0
#define YAMA_SCOPE_RELATIONAL 1
static void create_domain(struct __test_metadata *const _metadata)
{
int ruleset_fd;
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_BLOCK,
};
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
EXPECT_LE(0, ruleset_fd)
{
TH_LOG("Failed to create a ruleset: %s", strerror(errno));
}
EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
EXPECT_EQ(0, close(ruleset_fd));
}
static int test_ptrace_read(const pid_t pid)
{
static const char path_template[] = "/proc/%d/environ";
char procenv_path[sizeof(path_template) + 10];
int procenv_path_size, fd;
procenv_path_size = snprintf(procenv_path, sizeof(procenv_path),
path_template, pid);
if (procenv_path_size >= sizeof(procenv_path))
return E2BIG;
fd = open(procenv_path, O_RDONLY | O_CLOEXEC);
if (fd < 0)
return errno;
/*
* Mixing error codes from close(2) and open(2) should not lead to any
* (access type) confusion for this test.
*/
if (close(fd) != 0)
return errno;
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(scoped_domains) {};
/* clang-format on */
/*
* Test multiple tracing combinations between a parent process P1 and a child
* process P2.
*
* Yama's scoped ptrace is presumed disabled. If enabled, this optional
* restriction is enforced in addition to any Landlock check, which means that
* all P2 requests to trace P1 would be denied.
*/
#include "scoped_base_variants.h"
FIXTURE_SETUP(scoped_domains)
{
}
FIXTURE_TEARDOWN(scoped_domains)
{
}
/* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
TEST_F(scoped_domains, 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
* with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS.
*/
drop_caps(_metadata);
parent = getpid();
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
if (variant->domain_both) {
create_domain(_metadata);
if (!__test_passed(_metadata))
/* Aborts before forking. */
return;
}
child = fork();
ASSERT_LE(0, child);
if (child == 0) {
char buf_child;
ASSERT_EQ(0, close(pipe_parent[1]));
ASSERT_EQ(0, close(pipe_child[0]));
if (variant->domain_child)
create_domain(_metadata);
/* Waits for the parent to be in a domain, if any. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
/* 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 (can_trace_parent) {
EXPECT_EQ(0, ret);
} else {
EXPECT_EQ(-1, ret);
EXPECT_EQ(EPERM, errno);
}
if (ret == 0) {
ASSERT_EQ(parent, waitpid(parent, &status, 0));
ASSERT_EQ(1, WIFSTOPPED(status));
ASSERT_EQ(0, ptrace(PTRACE_DETACH, parent, NULL, 0));
}
/* Tests child PTRACE_TRACEME. */
ret = ptrace(PTRACE_TRACEME);
if (can_trace_child) {
EXPECT_EQ(0, ret);
} else {
EXPECT_EQ(-1, ret);
EXPECT_EQ(EPERM, errno);
}
/*
* Signals that the PTRACE_ATTACH test is done and the
* PTRACE_TRACEME test is ongoing.
*/
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
if (can_trace_child) {
ASSERT_EQ(0, raise(SIGSTOP));
}
/* Waits for the parent PTRACE_ATTACH test. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
_exit(_metadata->exit_code);
return;
}
ASSERT_EQ(0, close(pipe_child[1]));
ASSERT_EQ(0, close(pipe_parent[0]));
if (variant->domain_parent)
create_domain(_metadata);
/* Signals that the parent is in a domain, if any. */
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
/*
* Waits for the child to test PTRACE_ATTACH on the parent and start
* testing PTRACE_TRACEME.
*/
ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
/* Tests child PTRACE_TRACEME. */
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));
} else {
/* The child should not be traced by the parent. */
EXPECT_EQ(-1, ptrace(PTRACE_DETACH, child, NULL, 0));
EXPECT_EQ(ESRCH, errno);
}
/* 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 (can_trace_child) {
EXPECT_EQ(0, ret);
} else {
EXPECT_EQ(-1, ret);
EXPECT_EQ(EPERM, errno);
}
if (ret == 0) {
ASSERT_EQ(child, waitpid(child, &status, 0));
ASSERT_EQ(1, WIFSTOPPED(status));
ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
}
/* Signals that the parent PTRACE_ATTACH test is done. */
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
ASSERT_EQ(child, waitpid(child, &status, 0));
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
WEXITSTATUS(status) != EXIT_SUCCESS)
_metadata->exit_code = KSFT_FAIL;
}
static int matches_log_ptrace(struct __test_metadata *const _metadata,
int audit_fd, const pid_t opid)
{
static const char log_template[] = REGEX_LANDLOCK_PREFIX
" blockers=ptrace opid=%d ocomm=\"ptrace_test\"$";
char log_match[sizeof(log_template) + 10];
int log_match_len;
log_match_len =
snprintf(log_match, sizeof(log_match), log_template, opid);
if (log_match_len > sizeof(log_match))
return -E2BIG;
return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match,
NULL);
}
FIXTURE(audit)
{
struct audit_filter audit_filter;
int audit_fd;
};
FIXTURE_SETUP(audit)
{
disable_caps(_metadata);
set_cap(_metadata, CAP_AUDIT_CONTROL);
self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
EXPECT_LE(0, self->audit_fd);
clear_cap(_metadata, CAP_AUDIT_CONTROL);
}
FIXTURE_TEARDOWN_PARENT(audit)
{
EXPECT_EQ(0, audit_cleanup(-1, NULL));
}
/* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
TEST_F(audit, trace)
{
pid_t child;
int status;
int pipe_child[2], pipe_parent[2];
int yama_ptrace_scope;
char buf_parent;
struct audit_records records;
/* Makes sure there is no superfluous logged records. */
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
EXPECT_EQ(0, records.domain);
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);
/*
* Removes all effective and permitted capabilities to not interfere
* with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS.
*/
drop_caps(_metadata);
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
child = fork();
ASSERT_LE(0, child);
if (child == 0) {
char buf_child;
ASSERT_EQ(0, close(pipe_parent[1]));
ASSERT_EQ(0, close(pipe_child[0]));
/* Waits for the parent to be in a domain, if any. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
/* Tests child PTRACE_TRACEME. */
EXPECT_EQ(-1, ptrace(PTRACE_TRACEME));
EXPECT_EQ(EPERM, errno);
/* We should see the child process. */
EXPECT_EQ(0, matches_log_ptrace(_metadata, self->audit_fd,
getpid()));
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
/* Checks for a domain creation. */
EXPECT_EQ(1, records.domain);
/*
* Signals that the PTRACE_ATTACH test is done and the
* PTRACE_TRACEME test is ongoing.
*/
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
/* Waits for the parent PTRACE_ATTACH test. */
ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
_exit(_metadata->exit_code);
return;
}
ASSERT_EQ(0, close(pipe_child[1]));
ASSERT_EQ(0, close(pipe_parent[0]));
create_domain(_metadata);
/* Signals that the parent is in a domain. */
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
/*
* Waits for the child to test PTRACE_ATTACH on the parent and start
* testing PTRACE_TRACEME.
*/
ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
/* The child should not be traced by the parent. */
EXPECT_EQ(-1, ptrace(PTRACE_DETACH, child, NULL, 0));
EXPECT_EQ(ESRCH, errno);
/* Tests PTRACE_ATTACH on the child. */
EXPECT_EQ(-1, ptrace(PTRACE_ATTACH, child, NULL, 0));
EXPECT_EQ(EPERM, errno);
EXPECT_EQ(0, matches_log_ptrace(_metadata, self->audit_fd, child));
/* Signals that the parent PTRACE_ATTACH test is done. */
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
ASSERT_EQ(child, waitpid(child, &status, 0));
if (WIFSIGNALED(status) || !WIFEXITED(status) ||
WEXITSTATUS(status) != EXIT_SUCCESS)
_metadata->exit_code = KSFT_FAIL;
/* Makes sure there is no superfluous logged records. */
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
EXPECT_EQ(0, records.domain);
}
/* Trace tests */
/* clang-format off */
FIXTURE(trace_ptrace) {
/* clang-format on */
int tracefs_ok;
};
FIXTURE_SETUP(trace_ptrace)
{
int ret;
set_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(0, unshare(CLONE_NEWNS));
ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
ret = tracefs_fixture_setup();
if (ret) {
clear_cap(_metadata, CAP_SYS_ADMIN);
self->tracefs_ok = 0;
SKIP(return, "tracefs not available");
}
self->tracefs_ok = 1;
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, true));
ASSERT_EQ(0, tracefs_clear());
clear_cap(_metadata, CAP_SYS_ADMIN);
}
FIXTURE_TEARDOWN(trace_ptrace)
{
if (!self->tracefs_ok)
return;
set_cap(_metadata, CAP_SYS_ADMIN);
tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, false);
tracefs_fixture_teardown();
clear_cap(_metadata, CAP_SYS_ADMIN);
}
/* clang-format off */
FIXTURE_VARIANT(trace_ptrace)
{
/* clang-format on */
bool sandbox;
bool sandbox_target;
int expect_denied;
};
/* Denied: sandboxed child ptraces unsandboxed parent (tracee_domain=0). */
/* clang-format off */
FIXTURE_VARIANT_ADD(trace_ptrace, denied) {
/* clang-format on */
.sandbox = true,
.sandbox_target = false,
.expect_denied = 1,
};
/*
* Denied: sandboxed child ptraces a sandboxed parent, so the tracee is in a
* domain and tracee_domain= is non-zero.
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(trace_ptrace, denied_scoped_target) {
/* clang-format on */
.sandbox = true,
.sandbox_target = true,
.expect_denied = 1,
};
/* Allowed: unsandboxed child uses PTRACE_TRACEME. */
/* clang-format off */
FIXTURE_VARIANT_ADD(trace_ptrace, allowed) {
/* clang-format on */
.sandbox = false,
.sandbox_target = false,
.expect_denied = 0,
};
TEST_F(trace_ptrace, deny_ptrace)
{
char *buf, field[64], expected_pid[16];
int count, status;
pid_t child, parent;
if (!self->tracefs_ok)
SKIP(return, "tracefs not available");
parent = getpid();
/*
* Set a known comm so the denied variant can verify both the trace line
* task name and the tracee_comm= field.
*/
prctl(PR_SET_NAME, "ll_trace_test");
/*
* For the non-zero tracee_domain case, sandbox the parent (the tracee)
* before forking. The child inherits that domain and adds its own
* layer, so the child (tracer) is not an ancestor of the tracee and the
* ptrace is still denied, with tracee_domain= naming the parent's
* domain.
*/
if (variant->sandbox_target)
create_domain(_metadata);
child = fork();
ASSERT_LE(0, child);
if (child == 0) {
if (variant->sandbox) {
struct landlock_ruleset_attr ruleset_attr = {
.scoped = LANDLOCK_SCOPE_SIGNAL,
};
int ruleset_fd;
/*
* Any scope creates a domain. Ptrace denial checks
* domain ancestry, not specific flags.
*/
ruleset_fd = landlock_create_ruleset(
&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0)
_exit(1);
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
if (landlock_restrict_self(ruleset_fd, 0)) {
close(ruleset_fd);
_exit(1);
}
close(ruleset_fd);
/* PTRACE_ATTACH on unsandboxed parent: denied. */
if (ptrace(PTRACE_ATTACH, parent, NULL, NULL) == 0) {
ptrace(PTRACE_DETACH, parent, NULL, NULL);
_exit(2);
}
if (errno != EPERM)
_exit(3);
} else {
/* No sandbox: ptrace should succeed. */
if (ptrace(PTRACE_TRACEME) != 0)
_exit(1);
}
_exit(0);
}
ASSERT_EQ(child, waitpid(child, &status, 0));
ASSERT_TRUE(WIFEXITED(status));
EXPECT_EQ(0, WEXITSTATUS(status));
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
count = tracefs_count_matches(buf, REGEX_DENY_PTRACE("ll_trace_test"));
if (variant->expect_denied) {
EXPECT_EQ(variant->expect_denied, count)
{
TH_LOG("Expected deny_ptrace event, got %d\n%s", count,
buf);
}
/* Verify tracee_pid is the parent's TGID. */
snprintf(expected_pid, sizeof(expected_pid), "%d", parent);
ASSERT_EQ(0, tracefs_extract_field(
buf, REGEX_DENY_PTRACE("ll_trace_test"),
"tracee_pid", field, sizeof(field)));
EXPECT_STREQ(expected_pid, field);
/* Verify tracee_comm matches prctl(PR_SET_NAME). */
ASSERT_EQ(0, tracefs_extract_field(
buf, REGEX_DENY_PTRACE("ll_trace_test"),
"tracee_comm", field, sizeof(field)));
EXPECT_STREQ("ll_trace_test", field);
/*
* Verify tracee_domain: 0 when the tracee is unsandboxed,
* non-zero when the tracee is in a domain.
*/
ASSERT_EQ(0, tracefs_extract_field(
buf, REGEX_DENY_PTRACE("ll_trace_test"),
"tracee_domain", field, sizeof(field)));
EXPECT_EQ(variant->sandbox_target, strcmp("0", field) != 0)
{
TH_LOG("Unexpected tracee_domain=%s", field);
}
} else {
EXPECT_EQ(0, count)
{
TH_LOG("Expected 0 deny_ptrace events, got %d\n%s",
count, buf);
}
}
free(buf);
}
/* clang-format off */
FIXTURE(trace_ptrace_traceme) {
/* clang-format on */
int tracefs_ok;
};
FIXTURE_SETUP(trace_ptrace_traceme)
{
int ret;
set_cap(_metadata, CAP_SYS_ADMIN);
ASSERT_EQ(0, unshare(CLONE_NEWNS));
ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
ret = tracefs_fixture_setup();
if (ret) {
clear_cap(_metadata, CAP_SYS_ADMIN);
self->tracefs_ok = 0;
SKIP(return, "tracefs not available");
}
self->tracefs_ok = 1;
ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, true));
ASSERT_EQ(0, tracefs_clear());
clear_cap(_metadata, CAP_SYS_ADMIN);
}
FIXTURE_TEARDOWN(trace_ptrace_traceme)
{
if (!self->tracefs_ok)
return;
set_cap(_metadata, CAP_SYS_ADMIN);
tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, false);
tracefs_fixture_teardown();
clear_cap(_metadata, CAP_SYS_ADMIN);
}
/* clang-format off */
FIXTURE_VARIANT(trace_ptrace_traceme)
{
/* clang-format on */
bool sandbox_tracer;
bool sandbox_tracee;
int expect_denied;
};
/*
* Denied: a sandboxed tracer cannot trace the unsandboxed child that asked to
* be traced with PTRACE_TRACEME (tracee_domain=0).
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(trace_ptrace_traceme, denied) {
/* clang-format on */
.sandbox_tracer = true,
.sandbox_tracee = false,
.expect_denied = 1,
};
/*
* Denied: a sandboxed child in its own domain asks to be traced by a tracer in
* an unrelated domain, so the tracee is in a domain and tracee_domain= is
* non-zero.
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(trace_ptrace_traceme, denied_scoped_tracee) {
/* clang-format on */
.sandbox_tracer = true,
.sandbox_tracee = true,
.expect_denied = 1,
};
/* Allowed: unsandboxed child uses PTRACE_TRACEME with an unsandboxed tracer. */
/* clang-format off */
FIXTURE_VARIANT_ADD(trace_ptrace_traceme, allowed) {
/* clang-format on */
.sandbox_tracer = false,
.sandbox_tracee = false,
.expect_denied = 0,
};
TEST_F(trace_ptrace_traceme, deny_ptrace)
{
char *buf, field[64], expected_pid[16];
int count, status, sync_pipe[2];
pid_t child;
if (!self->tracefs_ok)
SKIP(return, "tracefs not available");
/*
* Set a known comm so the denied variant can verify both the trace line
* task name and the tracee_comm= field. The tracee is the current
* (child) task for PTRACE_TRACEME, so the child inherits this name.
*/
prctl(PR_SET_NAME, "ll_trace_test");
ASSERT_EQ(0, pipe2(sync_pipe, O_CLOEXEC));
child = fork();
ASSERT_LE(0, child);
if (child == 0) {
char c;
close(sync_pipe[1]);
/*
* The tracee is the current task; for the non-zero
* tracee_domain case it sandboxes itself in its own domain,
* unrelated to the tracer's domain, so PTRACE_TRACEME is still
* denied and tracee_domain= names the child's own domain.
*/
if (variant->sandbox_tracee)
create_domain(_metadata);
/* Waits for the tracer (parent) to enter its domain, if any. */
if (read(sync_pipe[0], &c, 1) != 1)
_exit(1);
close(sync_pipe[0]);
if (variant->expect_denied) {
if (ptrace(PTRACE_TRACEME) == 0)
_exit(2);
if (errno != EPERM)
_exit(3);
} else {
if (ptrace(PTRACE_TRACEME) != 0)
_exit(4);
/* Lets the tracer reap the trace-stop and detach. */
raise(SIGSTOP);
}
_exit(0);
}
close(sync_pipe[0]);
/*
* For a denial, the proposed tracer must be in a domain that is not an
* ancestor of the tracee's domain. Sandboxing the parent after the
* fork gives it a domain unrelated to the child.
*/
if (variant->sandbox_tracer)
create_domain(_metadata);
/* Signals the child that the tracer is in its domain, if any. */
ASSERT_EQ(1, write(sync_pipe[1], ".", 1));
close(sync_pipe[1]);
if (!variant->expect_denied) {
/* PTRACE_TRACEME succeeded: reap the SIGSTOP and detach. */
ASSERT_EQ(child, waitpid(child, &status, WUNTRACED));
ASSERT_TRUE(WIFSTOPPED(status));
ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
}
ASSERT_EQ(child, waitpid(child, &status, 0));
ASSERT_TRUE(WIFEXITED(status));
EXPECT_EQ(0, WEXITSTATUS(status));
buf = tracefs_read_buf();
ASSERT_NE(NULL, buf);
count = tracefs_count_matches(buf, REGEX_DENY_PTRACE("ll_trace_test"));
if (variant->expect_denied) {
EXPECT_EQ(variant->expect_denied, count)
{
TH_LOG("Expected deny_ptrace event, got %d\n%s", count,
buf);
}
/* Verify tracee_pid is the child's TGID (the traced task). */
snprintf(expected_pid, sizeof(expected_pid), "%d", child);
ASSERT_EQ(0, tracefs_extract_field(
buf, REGEX_DENY_PTRACE("ll_trace_test"),
"tracee_pid", field, sizeof(field)));
EXPECT_STREQ(expected_pid, field);
/*
* Verify tracee_domain: 0 when the tracee is unsandboxed,
* non-zero when the tracee is in a domain.
*/
ASSERT_EQ(0, tracefs_extract_field(
buf, REGEX_DENY_PTRACE("ll_trace_test"),
"tracee_domain", field, sizeof(field)));
EXPECT_EQ(variant->sandbox_tracee, strcmp("0", field) != 0)
{
TH_LOG("Unexpected tracee_domain=%s", field);
}
} else {
EXPECT_EQ(0, count)
{
TH_LOG("Expected 0 deny_ptrace events, got %d\n%s",
count, buf);
}
}
free(buf);
}
TEST_HARNESS_MAIN