mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
selftests/landlock: Add network tracepoint tests
Add network-specific trace tests, co-located with the existing audit fixture so each subsystem's functional, audit, and trace tests live together. Parameterized fixtures verify the landlock_deny_access_net event: a bind or connect denied outside the ruleset emits exactly one event with the expected sport/dport, an allowed bind or connect emits none, and the unsandboxed baseline emits none. A separate fixture verifies the landlock_check_rule_net event on an allowed bind, anchoring its domain, access_request, port, and grants to exact values; only check_rule_fs had a dedicated field test before. Port fields are read in host endianness, matching the landlock_net_port_attr.port UAPI convention, so the decimal comparisons also catch byte-order regressions in the tracepoint plumbing. IPv6 trace tests are intentionally elided: IPv6 hook dispatch shares the current_check_access_socket() path with IPv4 (covered by the audit tests), and the trace fields do not depend on address family. Cc: Günther Noack <gnoack@google.com> Cc: Tingmao Wang <m@maowtm.org> Link: https://patch.msgid.link/20260811094338.288094-17-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
parent
30478db56c
commit
6e2df0117b
|
|
@ -10,11 +10,12 @@
|
|||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/landlock.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/landlock.h>
|
||||
#include <sched.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
|
|
@ -22,6 +23,9 @@
|
|||
|
||||
#include "audit.h"
|
||||
#include "common.h"
|
||||
#include "trace.h"
|
||||
|
||||
#define TRACE_TASK "net_test"
|
||||
|
||||
const short sock_port_start = (1 << 10);
|
||||
|
||||
|
|
@ -3285,4 +3289,588 @@ TEST_F(audit, sendmsg)
|
|||
EXPECT_EQ(0, close(sock_fd));
|
||||
}
|
||||
|
||||
/* Trace tests */
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE(trace_net) {
|
||||
/* clang-format on */
|
||||
int tracefs_ok;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(trace_net)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Isolate the network namespace so the bound port cannot collide. */
|
||||
setup_loopback(_metadata);
|
||||
|
||||
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_ACCESS_NET_ENABLE, true));
|
||||
ASSERT_EQ(0, tracefs_clear());
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(trace_net)
|
||||
{
|
||||
if (!self->tracefs_ok)
|
||||
return;
|
||||
|
||||
set_cap(_metadata, CAP_SYS_ADMIN);
|
||||
tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, false);
|
||||
tracefs_fixture_teardown();
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
/*
|
||||
* Baseline: verifies that without Landlock, the bind succeeds and no
|
||||
* deny_access_net trace event fires.
|
||||
*/
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT(trace_net)
|
||||
{
|
||||
/* clang-format on */
|
||||
bool sandbox;
|
||||
int bind_port_offset; /* 0 = allowed port, 1 = denied port */
|
||||
int expect_denied;
|
||||
};
|
||||
|
||||
/* Unsandboxed: no Landlock, bind should succeed with no events. */
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(trace_net, unsandboxed) {
|
||||
/* clang-format on */
|
||||
.sandbox = false,
|
||||
.bind_port_offset = 0,
|
||||
.expect_denied = 0,
|
||||
};
|
||||
|
||||
/* Denied: sandboxed, bind to port not in ruleset. */
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(trace_net, bind_denied) {
|
||||
/* clang-format on */
|
||||
.sandbox = true,
|
||||
.bind_port_offset = 1,
|
||||
.expect_denied = 1,
|
||||
};
|
||||
|
||||
/* Allowed: sandboxed, bind to port in ruleset. */
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT_ADD(trace_net, bind_allowed) {
|
||||
/* clang-format on */
|
||||
.sandbox = true,
|
||||
.bind_port_offset = 0,
|
||||
.expect_denied = 0,
|
||||
};
|
||||
|
||||
TEST_F(trace_net, deny_access_net_bind)
|
||||
{
|
||||
char *buf;
|
||||
int count, status;
|
||||
pid_t child;
|
||||
|
||||
if (!self->tracefs_ok)
|
||||
SKIP(return, "tracefs not available");
|
||||
|
||||
ASSERT_EQ(0, tracefs_clear_buf());
|
||||
|
||||
child = fork();
|
||||
ASSERT_LE(0, child);
|
||||
|
||||
if (child == 0) {
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
};
|
||||
int sock_fd;
|
||||
|
||||
if (variant->sandbox) {
|
||||
struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_net =
|
||||
LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
};
|
||||
struct landlock_net_port_attr port_attr = {
|
||||
.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
.port = sock_port_start,
|
||||
};
|
||||
int ruleset_fd;
|
||||
|
||||
ruleset_fd = landlock_create_ruleset(
|
||||
&ruleset_attr, sizeof(ruleset_attr), 0);
|
||||
if (ruleset_fd < 0)
|
||||
_exit(1);
|
||||
|
||||
if (landlock_add_rule(ruleset_fd,
|
||||
LANDLOCK_RULE_NET_PORT,
|
||||
&port_attr, 0)) {
|
||||
close(ruleset_fd);
|
||||
_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);
|
||||
}
|
||||
|
||||
sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (sock_fd < 0)
|
||||
_exit(1);
|
||||
|
||||
addr.sin_port =
|
||||
htons(sock_port_start + variant->bind_port_offset);
|
||||
if (variant->expect_denied) {
|
||||
/* Bind should be denied. */
|
||||
if (bind(sock_fd, (struct sockaddr *)&addr,
|
||||
sizeof(addr)) == 0) {
|
||||
close(sock_fd);
|
||||
_exit(2);
|
||||
}
|
||||
if (errno != EACCES) {
|
||||
close(sock_fd);
|
||||
_exit(3);
|
||||
}
|
||||
} else {
|
||||
/* Bind should succeed. */
|
||||
if (bind(sock_fd, (struct sockaddr *)&addr,
|
||||
sizeof(addr))) {
|
||||
close(sock_fd);
|
||||
_exit(2);
|
||||
}
|
||||
}
|
||||
close(sock_fd);
|
||||
_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_ACCESS_NET(TRACE_TASK));
|
||||
if (variant->expect_denied) {
|
||||
EXPECT_EQ(variant->expect_denied, count)
|
||||
{
|
||||
TH_LOG("Expected deny_access_net event, got %d\n%s",
|
||||
count, buf);
|
||||
}
|
||||
} else {
|
||||
EXPECT_EQ(0, count)
|
||||
{
|
||||
TH_LOG("Expected 0 deny_access_net events, "
|
||||
"got %d\n%s",
|
||||
count, buf);
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Anchors the denial fields shared by every deny_access_net event so a field
|
||||
* test proves more than sport/dport: the denying domain, the same-exec bit, the
|
||||
* audit-logging verdict, and the blocked access all stay populated.
|
||||
*/
|
||||
static void
|
||||
expect_net_deny_common_fields(struct __test_metadata *const _metadata,
|
||||
const char *const buf)
|
||||
{
|
||||
char field[64];
|
||||
|
||||
ASSERT_EQ(0,
|
||||
tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
|
||||
"domain", field, sizeof(field)));
|
||||
EXPECT_STRNE("0", field);
|
||||
|
||||
/* Same exec that restricted itself, no exec in between. */
|
||||
ASSERT_EQ(0,
|
||||
tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
|
||||
"same_exec", field, sizeof(field)));
|
||||
EXPECT_STREQ("1", field);
|
||||
|
||||
/* Default flags, same exec: audit would log this denial. */
|
||||
ASSERT_EQ(0,
|
||||
tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
|
||||
"logged", field, sizeof(field)));
|
||||
EXPECT_STREQ("1", field);
|
||||
|
||||
ASSERT_EQ(0,
|
||||
tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
|
||||
"blockers", field, sizeof(field)));
|
||||
EXPECT_STRNE("", field);
|
||||
}
|
||||
|
||||
/* Connect and field-check tests use a separate fixture without variants. */
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE(trace_net_connect) {
|
||||
/* clang-format on */
|
||||
int tracefs_ok;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(trace_net_connect)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Isolate the network namespace so the bound port cannot collide. */
|
||||
setup_loopback(_metadata);
|
||||
|
||||
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_ACCESS_NET_ENABLE, true));
|
||||
ASSERT_EQ(0, tracefs_clear());
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(trace_net_connect)
|
||||
{
|
||||
if (!self->tracefs_ok)
|
||||
return;
|
||||
|
||||
set_cap(_metadata, CAP_SYS_ADMIN);
|
||||
tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, false);
|
||||
tracefs_fixture_teardown();
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE_VARIANT(trace_net_connect) {
|
||||
/* clang-format on */
|
||||
/* handled_access_net, also the access allowed on the base port. */
|
||||
__u64 handled;
|
||||
/* Bind the allowed base port before the denied operation. */
|
||||
bool bind_base_first;
|
||||
/* Denied operation on the next port: connect (true) or bind (false). */
|
||||
bool deny_connect;
|
||||
};
|
||||
|
||||
/* clang-format off */
|
||||
|
||||
/* Denied connect(): sport=0, dport=<denied port>. */
|
||||
FIXTURE_VARIANT_ADD(trace_net_connect, connect_denied) {
|
||||
.handled = LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
.bind_base_first = false,
|
||||
.deny_connect = true,
|
||||
};
|
||||
|
||||
/* Denied bind(): sport=<denied port>, dport=0. */
|
||||
FIXTURE_VARIANT_ADD(trace_net_connect, bind_fields) {
|
||||
.handled = LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
.bind_base_first = false,
|
||||
.deny_connect = false,
|
||||
};
|
||||
|
||||
/* Denied connect() after an allowed bind(): the connect fields (sport=0). */
|
||||
FIXTURE_VARIANT_ADD(trace_net_connect, connect_after_bind) {
|
||||
.handled = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||
.bind_base_first = true,
|
||||
.deny_connect = true,
|
||||
};
|
||||
|
||||
/* clang-format on */
|
||||
|
||||
/*
|
||||
* A denied TCP bind(2) or connect(2) emits one deny_access_net event. The port
|
||||
* is reported in the field matching the denied operation, in host endianness
|
||||
* (the UAPI landlock_net_port_attr.port convention): a connect denial reports
|
||||
* sport=0 dport=<port>, a bind denial reports sport=<port> dport=0, so a
|
||||
* byte-order or field-swap bug is caught. A prior allowed bind
|
||||
* (connect_after_bind) does not change the connect denial's fields.
|
||||
*/
|
||||
TEST_F(trace_net_connect, deny_access_net)
|
||||
{
|
||||
pid_t child;
|
||||
int status;
|
||||
char *buf;
|
||||
char field[64], expected[16];
|
||||
|
||||
if (!self->tracefs_ok)
|
||||
SKIP(return, "tracefs not available");
|
||||
|
||||
child = fork();
|
||||
ASSERT_LE(0, child);
|
||||
|
||||
if (child == 0) {
|
||||
struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_net = variant->handled,
|
||||
};
|
||||
struct landlock_net_port_attr port_attr = {
|
||||
.allowed_access = variant->handled,
|
||||
.port = sock_port_start,
|
||||
};
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
};
|
||||
int ruleset_fd, sock_fd, optval = 1, ret;
|
||||
|
||||
ruleset_fd = landlock_create_ruleset(&ruleset_attr,
|
||||
sizeof(ruleset_attr), 0);
|
||||
if (ruleset_fd < 0)
|
||||
_exit(1);
|
||||
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
||||
&port_attr, 0)) {
|
||||
close(ruleset_fd);
|
||||
_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);
|
||||
|
||||
sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (sock_fd < 0)
|
||||
_exit(1);
|
||||
|
||||
/* Bind the allowed base port first (succeeds, no event). */
|
||||
if (variant->bind_base_first) {
|
||||
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
|
||||
sizeof(optval));
|
||||
addr.sin_port = htons(sock_port_start);
|
||||
if (bind(sock_fd, (struct sockaddr *)&addr,
|
||||
sizeof(addr))) {
|
||||
close(sock_fd);
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Denied operation on the next port. */
|
||||
addr.sin_port = htons(sock_port_start + 1);
|
||||
if (variant->deny_connect)
|
||||
ret = connect(sock_fd, (struct sockaddr *)&addr,
|
||||
sizeof(addr));
|
||||
else
|
||||
ret = bind(sock_fd, (struct sockaddr *)&addr,
|
||||
sizeof(addr));
|
||||
if (ret == 0) {
|
||||
close(sock_fd);
|
||||
_exit(2);
|
||||
}
|
||||
if (errno != EACCES) {
|
||||
close(sock_fd);
|
||||
_exit(3);
|
||||
}
|
||||
close(sock_fd);
|
||||
_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);
|
||||
|
||||
EXPECT_EQ(1, tracefs_count_matches(buf,
|
||||
REGEX_DENY_ACCESS_NET(TRACE_TASK)));
|
||||
|
||||
expect_net_deny_common_fields(_metadata, buf);
|
||||
|
||||
/*
|
||||
* The denied operation's port field carries the port; the other is 0.
|
||||
*/
|
||||
snprintf(expected, sizeof(expected), "%llu",
|
||||
(unsigned long long)(sock_port_start + 1));
|
||||
|
||||
ASSERT_EQ(0,
|
||||
tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
|
||||
"sport", field, sizeof(field)));
|
||||
EXPECT_STREQ(variant->deny_connect ? "0" : expected, field);
|
||||
|
||||
ASSERT_EQ(0,
|
||||
tracefs_extract_field(buf, REGEX_DENY_ACCESS_NET(TRACE_TASK),
|
||||
"dport", field, sizeof(field)));
|
||||
EXPECT_STREQ(variant->deny_connect ? expected : "0", field);
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* Field verification for the check_rule_net event on an allowed access. */
|
||||
|
||||
/* clang-format off */
|
||||
FIXTURE(trace_net_check_rule) {
|
||||
/* clang-format on */
|
||||
int tracefs_ok;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(trace_net_check_rule)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Isolate the network namespace so the bound port cannot collide. */
|
||||
setup_loopback(_metadata);
|
||||
|
||||
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_CHECK_RULE_NET_ENABLE, true));
|
||||
ASSERT_EQ(0, tracefs_clear());
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(trace_net_check_rule)
|
||||
{
|
||||
if (!self->tracefs_ok)
|
||||
return;
|
||||
|
||||
set_cap(_metadata, CAP_SYS_ADMIN);
|
||||
tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false);
|
||||
tracefs_fixture_teardown();
|
||||
clear_cap(_metadata, CAP_SYS_ADMIN);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that an allowed bind matching a net-port rule emits exactly one
|
||||
* landlock_check_rule_net event with the enforcing domain, the requested
|
||||
* access, the checked port (host endianness), and the per-layer grants. The
|
||||
* whole event is anchored to exact values so a revert of the check_rule_net
|
||||
* emit (or a byte-order or field-plumbing regression) fails the test.
|
||||
*/
|
||||
TEST_F(trace_net_check_rule, check_rule_net_fields)
|
||||
{
|
||||
pid_t child;
|
||||
int status;
|
||||
char *buf;
|
||||
char field[64], expected[16];
|
||||
|
||||
if (!self->tracefs_ok)
|
||||
SKIP(return, "tracefs not available");
|
||||
|
||||
child = fork();
|
||||
ASSERT_LE(0, child);
|
||||
|
||||
if (child == 0) {
|
||||
struct landlock_ruleset_attr ruleset_attr = {
|
||||
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
};
|
||||
struct landlock_net_port_attr port_attr = {
|
||||
.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||
.port = sock_port_start,
|
||||
};
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||
};
|
||||
int ruleset_fd, sock_fd;
|
||||
|
||||
ruleset_fd = landlock_create_ruleset(&ruleset_attr,
|
||||
sizeof(ruleset_attr), 0);
|
||||
if (ruleset_fd < 0)
|
||||
_exit(1);
|
||||
|
||||
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
|
||||
&port_attr, 0)) {
|
||||
close(ruleset_fd);
|
||||
_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);
|
||||
|
||||
/* Bind to the allowed port: succeeds and matches the rule. */
|
||||
sock_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (sock_fd < 0)
|
||||
_exit(1);
|
||||
|
||||
addr.sin_port = htons(sock_port_start);
|
||||
if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr))) {
|
||||
close(sock_fd);
|
||||
_exit(2);
|
||||
}
|
||||
close(sock_fd);
|
||||
_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);
|
||||
|
||||
/* A single-layer domain matching one port rule emits one event. */
|
||||
EXPECT_EQ(1,
|
||||
tracefs_count_matches(buf, REGEX_CHECK_RULE_NET(TRACE_TASK)))
|
||||
{
|
||||
TH_LOG("Expected 1 check_rule_net event\n%s", buf);
|
||||
}
|
||||
|
||||
ASSERT_EQ(0,
|
||||
tracefs_extract_field(buf, REGEX_CHECK_RULE_NET(TRACE_TASK),
|
||||
"domain", field, sizeof(field)));
|
||||
EXPECT_STRNE("0", field);
|
||||
|
||||
ASSERT_EQ(0, tracefs_extract_field(
|
||||
buf, REGEX_CHECK_RULE_NET(TRACE_TASK),
|
||||
"access_request", field, sizeof(field)));
|
||||
EXPECT_STREQ("bind_tcp", field);
|
||||
|
||||
/*
|
||||
* The port is reported in host endianness (UAPI convention), so on
|
||||
* little-endian htons(sock_port_start) would print a different value:
|
||||
* the exact match also catches byte-order regressions.
|
||||
*/
|
||||
ASSERT_EQ(0,
|
||||
tracefs_extract_field(buf, REGEX_CHECK_RULE_NET(TRACE_TASK),
|
||||
"port", field, sizeof(field)));
|
||||
snprintf(expected, sizeof(expected), "%llu",
|
||||
(unsigned long long)sock_port_start);
|
||||
EXPECT_STREQ(expected, field);
|
||||
|
||||
/* One layer that fully grants the request: grants={bind_tcp}. */
|
||||
ASSERT_EQ(0,
|
||||
tracefs_extract_field(buf, REGEX_CHECK_RULE_NET(TRACE_TASK),
|
||||
"grants", field, sizeof(field)));
|
||||
EXPECT_STREQ("{bind_tcp}", field);
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* IPv6 network trace tests are intentionally elided. IPv6 hook dispatch uses
|
||||
* the same current_check_access_socket() code path as IPv4, validated by the
|
||||
* audit tests in this file. The trace events use the same blockers/sport/dport
|
||||
* fields regardless of address family.
|
||||
*/
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user