mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
selftests/landlock: Add scope and ptrace tracepoint tests
Add trace tests for the landlock_deny_ptrace, landlock_deny_scope_signal, and landlock_deny_scope_abstract_unix_socket tracepoints, each placed alongside the functional tests for its subsystem, mirroring the audit test layout. Each tracepoint is exercised by a fixture with three variants that pin both branches of the other-party domain field: denied against an unsandboxed other party (other-party domain ID 0), denied against a sandboxed other party (non-zero ID), and an allowed baseline that records no event. A second fixture per type exercises an alternate LSM hook that reaches the same tracepoint with the same other-party domain ID, since each denial type can be reached through more than one hook. The datagram abstract-unix variant does not assert peer_pid, which is 0 for a datagram peer (no SO_PEERCRED); sun_path is the reliable peer identifier. The ptrace fixtures install a plain domain-creating ruleset rather than a dedicated flag, since ptrace denial relies on domain ancestry, not on a specific scoped flag. The fixtures unshare the mount namespace and remount / as MS_PRIVATE before mounting tracefs so the helper instance is visible only to the test process. Cc: Günther Noack <gnoack@google.com> Cc: Tingmao Wang <m@maowtm.org> Link: https://patch.msgid.link/20260811094338.288094-18-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
parent
6e2df0117b
commit
aef2dd32dd
|
|
@ -11,7 +11,9 @@
|
|||
#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>
|
||||
|
|
@ -20,6 +22,7 @@
|
|||
|
||||
#include "audit.h"
|
||||
#include "common.h"
|
||||
#include "trace.h"
|
||||
|
||||
/* Copied from security/yama/yama_lsm.c */
|
||||
#define YAMA_SCOPE_DISABLED 0
|
||||
|
|
@ -430,4 +433,403 @@ TEST_F(audit, trace)
|
|||
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
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
|
|
@ -23,6 +24,9 @@
|
|||
#include "audit.h"
|
||||
#include "common.h"
|
||||
#include "scoped_common.h"
|
||||
#include "trace.h"
|
||||
|
||||
#define TRACE_TASK "scoped_abstract"
|
||||
|
||||
/* Number of pending connections queue to be hold. */
|
||||
const short backlog = 10;
|
||||
|
|
@ -1205,4 +1209,264 @@ TEST(self_connect)
|
|||
_metadata->exit_code = KSFT_FAIL;
|
||||
}
|
||||
|
||||
/* Trace tests */
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE(trace_unix) {
|
||||
/* clang-format on */
|
||||
int tracefs_ok;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(trace_unix)
|
||||
{
|
||||
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_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE,
|
||||
true));
|
||||
ASSERT_EQ(0, tracefs_clear());
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(trace_unix)
|
||||
{
|
||||
if (!self->tracefs_ok)
|
||||
return;
|
||||
|
||||
set_cap(_metadata, CAP_SYS_ADMIN);
|
||||
tracefs_enable_event(TRACEFS_DENY_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE,
|
||||
false);
|
||||
tracefs_fixture_teardown();
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT(trace_unix) {
|
||||
/* clang-format on */
|
||||
int sock_type; /* SOCK_STREAM (connect) or SOCK_DGRAM (sendto). */
|
||||
bool sandbox;
|
||||
bool sandbox_target; /* Peer owned by a domain: peer_domain != 0. */
|
||||
int expect_denied;
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
|
||||
/* Stream: sandboxed client connect() to an unsandboxed peer (peer_domain=0). */
|
||||
FIXTURE_VARIANT_ADD(trace_unix, stream_denied) {
|
||||
.sock_type = SOCK_STREAM, .sandbox = true,
|
||||
.sandbox_target = false, .expect_denied = 1,
|
||||
};
|
||||
|
||||
/* Stream: peer socket owned by a domain, so peer_domain != 0. */
|
||||
FIXTURE_VARIANT_ADD(trace_unix, stream_denied_scoped_peer) {
|
||||
.sock_type = SOCK_STREAM, .sandbox = true,
|
||||
.sandbox_target = true, .expect_denied = 1,
|
||||
};
|
||||
|
||||
/* Stream: unsandboxed client, connect() succeeds, no event. */
|
||||
FIXTURE_VARIANT_ADD(trace_unix, stream_allowed) {
|
||||
.sock_type = SOCK_STREAM, .sandbox = false,
|
||||
.sandbox_target = false, .expect_denied = 0,
|
||||
};
|
||||
|
||||
/* Datagram: sandboxed client sendto() an unsandboxed peer (peer_domain=0). */
|
||||
FIXTURE_VARIANT_ADD(trace_unix, dgram_denied) {
|
||||
.sock_type = SOCK_DGRAM, .sandbox = true,
|
||||
.sandbox_target = false, .expect_denied = 1,
|
||||
};
|
||||
|
||||
/* Datagram: peer socket owned by a domain, so peer_domain != 0. */
|
||||
FIXTURE_VARIANT_ADD(trace_unix, dgram_denied_scoped_peer) {
|
||||
.sock_type = SOCK_DGRAM, .sandbox = true,
|
||||
.sandbox_target = true, .expect_denied = 1,
|
||||
};
|
||||
|
||||
/* Datagram: unsandboxed client, sendto() succeeds, no event. */
|
||||
FIXTURE_VARIANT_ADD(trace_unix, dgram_allowed) {
|
||||
.sock_type = SOCK_DGRAM, .sandbox = false,
|
||||
.sandbox_target = false, .expect_denied = 0,
|
||||
};
|
||||
|
||||
/* clang-format on */
|
||||
|
||||
/*
|
||||
* A sandboxed thread reaching an abstract unix socket peer through connect(2)
|
||||
* (stream) or sendto(2) (datagram) is denied and emits
|
||||
* landlock_deny_scope_abstract_unix_socket. The abstract name is crafted with
|
||||
* a space and an embedded NUL followed by an "END" marker to check the
|
||||
* tracepoint escaping and its length handling (a raw space would break the
|
||||
* sun_path field regex; strlen() would truncate at the NUL and drop "END").
|
||||
* peer_pid is only meaningful for a stream peer (a datagram peer has no
|
||||
* SO_PEERCRED), so it is asserted only there.
|
||||
*/
|
||||
TEST_F(trace_unix, deny_scope_unix)
|
||||
{
|
||||
struct sockaddr_un addr = {
|
||||
.sun_family = AF_UNIX,
|
||||
};
|
||||
char *buf, field[128], expected_pid[16];
|
||||
int server_fd, count, status, name_len, addr_len;
|
||||
pid_t child;
|
||||
|
||||
if (!self->tracefs_ok)
|
||||
SKIP(return, "tracefs not available");
|
||||
|
||||
/*
|
||||
* For the non-zero peer_domain case, sandbox the parent before it
|
||||
* creates the server socket, so the socket carries the parent's domain
|
||||
* and peer_domain= is non-zero.
|
||||
*/
|
||||
if (variant->sandbox_target)
|
||||
create_scoped_domain(_metadata,
|
||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||||
|
||||
server_fd = socket(AF_UNIX, variant->sock_type | SOCK_CLOEXEC, 0);
|
||||
ASSERT_LE(0, server_fd);
|
||||
|
||||
addr.sun_path[0] = '\0';
|
||||
name_len = snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1,
|
||||
"landlock_trace_test_%d ", getpid());
|
||||
addr.sun_path[1 + name_len] = '\0';
|
||||
memcpy(addr.sun_path + 1 + name_len + 1, "END", 3);
|
||||
addr_len =
|
||||
offsetof(struct sockaddr_un, sun_path) + 1 + name_len + 1 + 3;
|
||||
|
||||
ASSERT_EQ(0, bind(server_fd, (struct sockaddr *)&addr, addr_len));
|
||||
if (variant->sock_type == SOCK_STREAM)
|
||||
ASSERT_EQ(0, listen(server_fd, 1));
|
||||
|
||||
child = fork();
|
||||
ASSERT_LE(0, child);
|
||||
|
||||
if (child == 0) {
|
||||
int client_fd, ret;
|
||||
|
||||
if (variant->sandbox) {
|
||||
struct landlock_ruleset_attr ruleset_attr = {
|
||||
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||
};
|
||||
int ruleset_fd;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
client_fd =
|
||||
socket(AF_UNIX, variant->sock_type | SOCK_CLOEXEC, 0);
|
||||
if (client_fd < 0)
|
||||
_exit(1);
|
||||
|
||||
if (variant->sock_type == SOCK_STREAM)
|
||||
ret = connect(client_fd, (struct sockaddr *)&addr,
|
||||
addr_len);
|
||||
else
|
||||
ret = sendto(client_fd, ".", 1, 0,
|
||||
(struct sockaddr *)&addr, addr_len);
|
||||
|
||||
if (variant->sandbox) {
|
||||
/* Reaching the peer should be denied. */
|
||||
if (ret != -1 || errno != EPERM) {
|
||||
close(client_fd);
|
||||
_exit(2);
|
||||
}
|
||||
} else {
|
||||
/* No sandbox: stream connect() == 0, sendto() == 1. */
|
||||
int ok = variant->sock_type == SOCK_STREAM ? 0 : 1;
|
||||
|
||||
if (ret != ok) {
|
||||
close(client_fd);
|
||||
_exit(2);
|
||||
}
|
||||
}
|
||||
close(client_fd);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
ASSERT_EQ(child, waitpid(child, &status, 0));
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
close(server_fd);
|
||||
|
||||
buf = tracefs_read_buf();
|
||||
ASSERT_NE(NULL, buf);
|
||||
|
||||
count = tracefs_count_matches(
|
||||
buf, REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(TRACE_TASK));
|
||||
if (!variant->expect_denied) {
|
||||
EXPECT_EQ(0, count)
|
||||
{
|
||||
TH_LOG("Expected 0 deny_scope events, got %d\n%s",
|
||||
count, buf);
|
||||
}
|
||||
free(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
EXPECT_EQ(variant->expect_denied, count)
|
||||
{
|
||||
TH_LOG("Expected deny_scope_abstract_unix_socket event, "
|
||||
"got %d\n%s",
|
||||
count, buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* sun_path is escaped: a raw space would break this field's [^ ]*$
|
||||
* regex, so a successful extract proves the space was escaped, and its
|
||||
* full length is honored: the "END" marker after the embedded NUL must
|
||||
* survive (strlen() would truncate it at the NUL).
|
||||
*/
|
||||
ASSERT_EQ(0, tracefs_extract_field(
|
||||
buf,
|
||||
REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(TRACE_TASK),
|
||||
"sun_path", field, sizeof(field)));
|
||||
EXPECT_NE(NULL, strstr(field, "END"))
|
||||
{
|
||||
TH_LOG("sun_path truncated or unescaped: %s", field);
|
||||
}
|
||||
|
||||
/* peer_pid is the parent's PID for a stream peer (0 for datagram). */
|
||||
if (variant->sock_type == SOCK_STREAM) {
|
||||
snprintf(expected_pid, sizeof(expected_pid), "%d", getpid());
|
||||
ASSERT_EQ(0, tracefs_extract_field(
|
||||
buf,
|
||||
REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(
|
||||
TRACE_TASK),
|
||||
"peer_pid", field, sizeof(field)));
|
||||
EXPECT_STREQ(expected_pid, field);
|
||||
}
|
||||
|
||||
/* peer_domain: 0 when the peer is unsandboxed, non-zero otherwise. */
|
||||
ASSERT_EQ(0, tracefs_extract_field(
|
||||
buf,
|
||||
REGEX_DENY_SCOPE_ABSTRACT_UNIX_SOCKET(TRACE_TASK),
|
||||
"peer_domain", field, sizeof(field)));
|
||||
EXPECT_EQ(variant->sandbox_target, strcmp("0", field) != 0)
|
||||
{
|
||||
TH_LOG("Unexpected peer_domain=%s", field);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
#include <fcntl.h>
|
||||
#include <linux/landlock.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
|
@ -18,6 +20,9 @@
|
|||
|
||||
#include "common.h"
|
||||
#include "scoped_common.h"
|
||||
#include "trace.h"
|
||||
|
||||
#define TRACE_TASK "scoped_signal_t"
|
||||
|
||||
/* This variable is used for handling several signals. */
|
||||
static volatile sig_atomic_t is_signaled;
|
||||
|
|
@ -762,4 +767,403 @@ TEST(sigio_to_pgid_self)
|
|||
EXPECT_EQ(0, close(trigger[1]));
|
||||
}
|
||||
|
||||
/* Trace tests */
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE(trace_signal) {
|
||||
/* clang-format on */
|
||||
int tracefs_ok;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(trace_signal)
|
||||
{
|
||||
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_SCOPE_SIGNAL_ENABLE, true));
|
||||
ASSERT_EQ(0, tracefs_clear());
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(trace_signal)
|
||||
{
|
||||
if (!self->tracefs_ok)
|
||||
return;
|
||||
|
||||
set_cap(_metadata, CAP_SYS_ADMIN);
|
||||
tracefs_enable_event(TRACEFS_DENY_SCOPE_SIGNAL_ENABLE, false);
|
||||
tracefs_fixture_teardown();
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT(trace_signal)
|
||||
{
|
||||
/* clang-format on */
|
||||
bool sandbox;
|
||||
bool sandbox_target;
|
||||
int expect_denied;
|
||||
};
|
||||
|
||||
/* Denied: sandboxed child signals unsandboxed parent (target_domain=0). */
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(trace_signal, denied) {
|
||||
/* clang-format on */
|
||||
.sandbox = true,
|
||||
.sandbox_target = false,
|
||||
.expect_denied = 1,
|
||||
};
|
||||
|
||||
/*
|
||||
* Denied: sandboxed child signals a sandboxed parent, so the target is in a
|
||||
* domain and target_domain= is non-zero.
|
||||
*/
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(trace_signal, denied_scoped_target) {
|
||||
/* clang-format on */
|
||||
.sandbox = true,
|
||||
.sandbox_target = true,
|
||||
.expect_denied = 1,
|
||||
};
|
||||
|
||||
/* Allowed: unsandboxed child signals unsandboxed parent. */
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(trace_signal, allowed) {
|
||||
/* clang-format on */
|
||||
.sandbox = false,
|
||||
.sandbox_target = false,
|
||||
.expect_denied = 0,
|
||||
};
|
||||
|
||||
TEST_F(trace_signal, deny_scope_signal)
|
||||
{
|
||||
char *buf, field[64], expected_pid[16];
|
||||
int count, status;
|
||||
pid_t child;
|
||||
|
||||
if (!self->tracefs_ok)
|
||||
SKIP(return, "tracefs not available");
|
||||
|
||||
/*
|
||||
* For the non-zero target_domain case, sandbox the parent (the signal
|
||||
* target) before forking. The child inherits that domain and adds its
|
||||
* own scoped layer, so the signal is still denied and target_domain=
|
||||
* names the parent's domain.
|
||||
*/
|
||||
if (variant->sandbox_target)
|
||||
create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (variant->sandbox) {
|
||||
/* Signal to unsandboxed parent should be denied. */
|
||||
if (kill(getppid(), 0) == 0)
|
||||
_exit(2);
|
||||
if (errno != EPERM)
|
||||
_exit(3);
|
||||
} else {
|
||||
/* No sandbox: kill should succeed. */
|
||||
if (kill(getppid(), 0) != 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_SCOPE_SIGNAL(TRACE_TASK));
|
||||
if (variant->expect_denied) {
|
||||
EXPECT_EQ(variant->expect_denied, count)
|
||||
{
|
||||
TH_LOG("Expected deny_scope_signal event, got %d\n%s",
|
||||
count, buf);
|
||||
}
|
||||
|
||||
/* Verify target_pid is the parent's PID. */
|
||||
snprintf(expected_pid, sizeof(expected_pid), "%d", getpid());
|
||||
ASSERT_EQ(0, tracefs_extract_field(
|
||||
buf, REGEX_DENY_SCOPE_SIGNAL(TRACE_TASK),
|
||||
"target_pid", field, sizeof(field)));
|
||||
EXPECT_STREQ(expected_pid, field);
|
||||
|
||||
/*
|
||||
* Verify target_domain: 0 when the target is unsandboxed,
|
||||
* non-zero when the target is in a domain.
|
||||
*/
|
||||
ASSERT_EQ(0, tracefs_extract_field(
|
||||
buf, REGEX_DENY_SCOPE_SIGNAL(TRACE_TASK),
|
||||
"target_domain", field, sizeof(field)));
|
||||
EXPECT_EQ(variant->sandbox_target, strcmp("0", field) != 0)
|
||||
{
|
||||
TH_LOG("Unexpected target_domain=%s", field);
|
||||
}
|
||||
} else {
|
||||
EXPECT_EQ(0, count)
|
||||
{
|
||||
TH_LOG("Expected 0 deny_scope_signal events, "
|
||||
"got %d\n%s",
|
||||
count, buf);
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Trace test for the asynchronous SIGIO/SIGURG delivery path
|
||||
* (hook_file_send_sigiotask), which reaches the same landlock_deny_scope_signal
|
||||
* tracepoint as a synchronous kill(2) but through fcntl(F_SETOWN).
|
||||
*/
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE(trace_fown) {
|
||||
/* clang-format on */
|
||||
int tracefs_ok;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(trace_fown)
|
||||
{
|
||||
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_SCOPE_SIGNAL_ENABLE, true));
|
||||
ASSERT_EQ(0, tracefs_clear());
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(trace_fown)
|
||||
{
|
||||
if (!self->tracefs_ok)
|
||||
return;
|
||||
|
||||
set_cap(_metadata, CAP_SYS_ADMIN);
|
||||
tracefs_enable_event(TRACEFS_DENY_SCOPE_SIGNAL_ENABLE, false);
|
||||
tracefs_fixture_teardown();
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT(trace_fown)
|
||||
{
|
||||
/* clang-format on */
|
||||
bool sandbox;
|
||||
bool sandbox_target;
|
||||
int expect_denied;
|
||||
};
|
||||
|
||||
/*
|
||||
* Denied: a sandboxed file owner's SIGURG is delivered to an unsandboxed target
|
||||
* process (target_domain=0).
|
||||
*/
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(trace_fown, denied) {
|
||||
/* clang-format on */
|
||||
.sandbox = true,
|
||||
.sandbox_target = false,
|
||||
.expect_denied = 1,
|
||||
};
|
||||
|
||||
/*
|
||||
* Denied: the SIGURG target sandboxes itself in its own domain, so the target
|
||||
* is in a domain and target_domain= is non-zero.
|
||||
*/
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(trace_fown, denied_scoped_target) {
|
||||
/* clang-format on */
|
||||
.sandbox = true,
|
||||
.sandbox_target = true,
|
||||
.expect_denied = 1,
|
||||
};
|
||||
|
||||
/* Allowed: an unsandboxed file owner delivers SIGURG. */
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(trace_fown, allowed) {
|
||||
/* clang-format on */
|
||||
.sandbox = false,
|
||||
.sandbox_target = false,
|
||||
.expect_denied = 0,
|
||||
};
|
||||
|
||||
TEST_F(trace_fown, deny_scope_fown)
|
||||
{
|
||||
int server_socket, recv_socket;
|
||||
struct service_fixture server_address;
|
||||
char buffer_parent, field[64], *buf;
|
||||
int status, count;
|
||||
int pipe_parent[2], pipe_child[2];
|
||||
pid_t child;
|
||||
|
||||
if (!self->tracefs_ok)
|
||||
SKIP(return, "tracefs not available");
|
||||
|
||||
memset(&server_address, 0, sizeof(server_address));
|
||||
set_unix_address(&server_address, 0);
|
||||
|
||||
ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
|
||||
ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
|
||||
|
||||
child = fork();
|
||||
ASSERT_LE(0, child);
|
||||
if (child == 0) {
|
||||
int client_socket;
|
||||
char buffer_child;
|
||||
|
||||
EXPECT_EQ(0, close(pipe_parent[1]));
|
||||
EXPECT_EQ(0, close(pipe_child[0]));
|
||||
|
||||
ASSERT_EQ(0, setup_signal_handler(SIGURG));
|
||||
client_socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, client_socket);
|
||||
|
||||
/*
|
||||
* The SIGURG target is this child; for the non-zero
|
||||
* target_domain case it sandboxes itself in its own domain,
|
||||
* unrelated to the file owner's domain.
|
||||
*/
|
||||
if (variant->sandbox_target)
|
||||
create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
|
||||
|
||||
/* Waits for the parent to listen. */
|
||||
ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1));
|
||||
ASSERT_EQ(0, connect(client_socket, &server_address.unix_addr,
|
||||
server_address.unix_addr_len));
|
||||
|
||||
/*
|
||||
* Waits for the parent to accept the connection, sandbox
|
||||
* itself, and call fcntl(F_SETOWN).
|
||||
*/
|
||||
ASSERT_EQ(1, read(pipe_parent[0], &buffer_child, 1));
|
||||
/* Triggers the asynchronous SIGURG to this file owner. */
|
||||
ASSERT_EQ(1, send(client_socket, ".", 1, MSG_OOB));
|
||||
EXPECT_EQ(0, close(client_socket));
|
||||
ASSERT_EQ(1, write(pipe_child[1], ".", 1));
|
||||
EXPECT_EQ(0, close(pipe_child[1]));
|
||||
|
||||
_exit(0);
|
||||
return;
|
||||
}
|
||||
EXPECT_EQ(0, close(pipe_parent[0]));
|
||||
EXPECT_EQ(0, close(pipe_child[1]));
|
||||
|
||||
server_socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
ASSERT_LE(0, server_socket);
|
||||
ASSERT_EQ(0, bind(server_socket, &server_address.unix_addr,
|
||||
server_address.unix_addr_len));
|
||||
ASSERT_EQ(0, listen(server_socket, backlog));
|
||||
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||||
|
||||
recv_socket = accept(server_socket, NULL, NULL);
|
||||
ASSERT_LE(0, recv_socket);
|
||||
|
||||
/*
|
||||
* The file owner is the denying subject; its domain is captured at
|
||||
* fcntl(F_SETOWN) time, so sandbox it before setting the owner.
|
||||
*/
|
||||
if (variant->sandbox)
|
||||
create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL);
|
||||
|
||||
/*
|
||||
* Sets the child to receive SIGURG for MSG_OOB. This uncommon use is a
|
||||
* valid attack scenario which also simplifies this test.
|
||||
*/
|
||||
ASSERT_EQ(0, fcntl(recv_socket, F_SETOWN, child));
|
||||
|
||||
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
|
||||
|
||||
/* Waits for the child to send MSG_OOB. */
|
||||
ASSERT_EQ(1, read(pipe_child[0], &buffer_parent, 1));
|
||||
EXPECT_EQ(0, close(pipe_child[0]));
|
||||
ASSERT_EQ(1, recv(recv_socket, &buffer_parent, 1, MSG_OOB));
|
||||
EXPECT_EQ(0, close(recv_socket));
|
||||
EXPECT_EQ(0, close(server_socket));
|
||||
|
||||
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_SCOPE_SIGNAL(TRACE_TASK));
|
||||
if (variant->expect_denied) {
|
||||
EXPECT_EQ(variant->expect_denied, count)
|
||||
{
|
||||
TH_LOG("Expected deny_scope_signal event, got %d\n%s",
|
||||
count, buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify target_domain: 0 when the target is unsandboxed,
|
||||
* non-zero when the target is in a domain.
|
||||
*/
|
||||
ASSERT_EQ(0, tracefs_extract_field(
|
||||
buf, REGEX_DENY_SCOPE_SIGNAL(TRACE_TASK),
|
||||
"target_domain", field, sizeof(field)));
|
||||
EXPECT_EQ(variant->sandbox_target, strcmp("0", field) != 0)
|
||||
{
|
||||
TH_LOG("Unexpected target_domain=%s", field);
|
||||
}
|
||||
} else {
|
||||
EXPECT_EQ(0, count)
|
||||
{
|
||||
TH_LOG("Expected 0 deny_scope_signal events, "
|
||||
"got %d\n%s",
|
||||
count, buf);
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
|
|
|||
|
|
@ -146,13 +146,14 @@
|
|||
"sport=[0-9]\\+ " \
|
||||
"dport=[0-9]\\+$"
|
||||
|
||||
#define REGEX_DENY_PTRACE(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_deny_ptrace: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"same_exec=[01] " \
|
||||
"logged=[01] " \
|
||||
"tracee_pid=[0-9]\\+ " \
|
||||
#define REGEX_DENY_PTRACE(task) \
|
||||
TRACE_PREFIX(task) \
|
||||
"landlock_deny_ptrace: " \
|
||||
"domain=[0-9a-f]\\+ " \
|
||||
"same_exec=[01] " \
|
||||
"logged=[01] " \
|
||||
"tracee_domain=[0-9a-f]\\+ " \
|
||||
"tracee_pid=[0-9]\\+ " \
|
||||
"tracee_comm=[^ ]*$"
|
||||
|
||||
#define REGEX_DENY_SCOPE_SIGNAL(task) \
|
||||
|
|
@ -161,6 +162,7 @@
|
|||
"domain=[0-9a-f]\\+ " \
|
||||
"same_exec=[01] " \
|
||||
"logged=[01] " \
|
||||
"target_domain=[0-9a-f]\\+ " \
|
||||
"target_pid=[0-9]\\+ " \
|
||||
"target_comm=[^ ]*$"
|
||||
|
||||
|
|
@ -170,6 +172,7 @@
|
|||
"domain=[0-9a-f]\\+ " \
|
||||
"same_exec=[01] " \
|
||||
"logged=[01] " \
|
||||
"peer_domain=[0-9a-f]\\+ " \
|
||||
"peer_pid=[0-9]\\+ " \
|
||||
"sun_path=[^ ]*$"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user