[PATCH v9 7/9] selftests/landlock: add tests for quiet flag with net rules
Tingmao Wang
m at maowtm.org
Wed May 27 01:01:17 UTC 2026
Tests that:
- Quiet flag works on network rules
- Quiet flag applied to unrelated ports has no effect
- Denied access not in quiet_access_net is still logged
This is not as thorough as the fs tests, but given the shared logic it
should be sufficient. There is also no "optional" access for network
rules.
Signed-off-by: Tingmao Wang <m at maowtm.org>
Assisted-by: GitHub Copilot:claude-opus-4.7 copilot-review
---
Changes in v9:
- Rebased on top of UDP support series
Changes in v3:
- New patch
tools/testing/selftests/landlock/net_test.c | 138 ++++++++++++++++++--
1 file changed, 128 insertions(+), 10 deletions(-)
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 2c72fda3c606..879126b0da4e 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -2738,12 +2738,22 @@ TEST_F(port_specific, bind_connect_1023)
EXPECT_EQ(0, close(bind_fd));
}
+/**
+ * matches_auditlog - Check audit log for a network access denial
+ *
+ * @audit_fd: Audit file descriptor.
+ * @blockers: A regex-escaped blocker string, e.g., "net\.bind_tcp".
+ * @dir_addr: Either "saddr" or "daddr", ignored if addr is NULL.
+ * @addr: A regex-escaped IP address string, or NULL.
+ * @dir_port: Either "src" or "dst", ignored if addr is NULL.
+ * @port: A port number, ignored if addr is NULL.
+ */
static int matches_auditlog(const int audit_fd, const char *const blockers,
const char *const dir_addr, const char *const addr,
- const char *const dir_port)
+ const char *const dir_port, const __u16 port)
{
static const char log_with_addrport_tmpl[] = REGEX_LANDLOCK_PREFIX
- " blockers=%s %s=%s %s=1024$";
+ " blockers=%s %s=%s %s=%u$";
static const char log_without_addrport_tmpl[] = REGEX_LANDLOCK_PREFIX
" blockers=%s";
/*
@@ -2751,8 +2761,9 @@ static int matches_auditlog(const int audit_fd, const char *const blockers,
* Max strlen(dir_addr): 5
* Max strlen(addr): 12
* Max strlen(dir_port): 4
+ * Max strlen(%u port): 5
*/
- char log_match[sizeof(log_with_addrport_tmpl) + 37];
+ char log_match[sizeof(log_with_addrport_tmpl) + 42];
int log_match_len;
if (addr == NULL)
@@ -2761,7 +2772,7 @@ static int matches_auditlog(const int audit_fd, const char *const blockers,
else
log_match_len = snprintf(log_match, sizeof(log_match),
log_with_addrport_tmpl, blockers,
- dir_addr, addr, dir_port);
+ dir_addr, addr, dir_port, port);
if (log_match_len > sizeof(log_match))
return -E2BIG;
@@ -2773,6 +2784,8 @@ FIXTURE(audit)
{
struct service_fixture srv0;
struct service_fixture srv1;
+ /* srv2 has a rule with no access but quiet bit set. */
+ struct service_fixture srv2;
struct service_fixture unspec_srv0;
struct audit_filter audit_filter;
int audit_fd;
@@ -2832,6 +2845,7 @@ FIXTURE_SETUP(audit)
ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
ASSERT_EQ(0, set_service(&self->srv1, variant->prot, 1));
+ ASSERT_EQ(0, set_service(&self->srv2, variant->prot, 2));
ASSERT_EQ(0, set_service(&self->unspec_srv0, prot_unspec, 0));
setup_loopback(_metadata);
@@ -2862,6 +2876,11 @@ TEST_F(audit, bind)
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_net = access_rights,
+ .quiet_access_net = access_rights,
+ };
+ const struct landlock_net_port_attr quiet_rule = {
+ .allowed_access = 0,
+ .port = self->srv2.port,
};
struct audit_records records;
int ruleset_fd, sock_fd;
@@ -2869,6 +2888,8 @@ TEST_F(audit, bind)
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd);
+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &quiet_rule, LANDLOCK_ADD_RULE_QUIET));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
@@ -2876,13 +2897,24 @@ TEST_F(audit, bind)
ASSERT_LE(0, sock_fd);
EXPECT_EQ(-EACCES, bind_variant(sock_fd, &self->srv0));
EXPECT_EQ(0, matches_auditlog(self->audit_fd, audit_evt, "saddr",
- variant->addr, "src"));
+ variant->addr, "src", self->srv0.port));
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
EXPECT_EQ(1, records.domain);
EXPECT_EQ(0, close(sock_fd));
+
+ /* Bind to srv2 (with quiet rule): no new audit logs. */
+ sock_fd = socket_variant(&self->srv2);
+ ASSERT_LE(0, sock_fd);
+ EXPECT_EQ(-EACCES, bind_variant(sock_fd, &self->srv2));
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+ EXPECT_EQ(0, records.domain);
+
+ EXPECT_EQ(0, close(sock_fd));
}
TEST_F(audit, connect)
@@ -2899,11 +2931,16 @@ TEST_F(audit, connect)
const int access_rights = bind_right | conn_right;
const struct landlock_ruleset_attr ruleset_attr = {
.handled_access_net = access_rights,
+ .quiet_access_net = access_rights,
};
const struct landlock_net_port_attr rule_connect_p1 = {
.allowed_access = conn_right,
.port = self->srv1.port,
};
+ const struct landlock_net_port_attr quiet_rule = {
+ .allowed_access = 0,
+ .port = self->srv2.port,
+ };
struct audit_records records;
int ruleset_fd, sock_fd;
@@ -2912,6 +2949,8 @@ TEST_F(audit, connect)
ASSERT_LE(0, ruleset_fd);
ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
&rule_connect_p1, 0));
+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &quiet_rule, LANDLOCK_ADD_RULE_QUIET));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
@@ -2919,7 +2958,7 @@ TEST_F(audit, connect)
ASSERT_LE(0, sock_fd);
EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv0));
EXPECT_EQ(0, matches_auditlog(self->audit_fd, audit_evt, "daddr",
- variant->addr, "dest"));
+ variant->addr, "dest", self->srv0.port));
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
@@ -2930,12 +2969,90 @@ TEST_F(audit, connect)
EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv1));
EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.bind_udp",
- NULL, NULL, NULL));
+ NULL, NULL, NULL, 0));
EXPECT_EQ(0, records.access);
EXPECT_EQ(1, records.domain);
}
EXPECT_EQ(0, close(sock_fd));
+
+ /* Connect to srv2 (with quiet rule): no new audit logs. */
+ sock_fd = socket_variant(&self->srv2);
+ ASSERT_LE(0, sock_fd);
+ EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv2));
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+ EXPECT_EQ(0, records.domain);
+
+ EXPECT_EQ(0, close(sock_fd));
+}
+
+/* Quieting bind access has no effect on connect. */
+TEST_F(audit, connect_quiet_bind)
+{
+ const char *audit_evt = (variant->prot.type == SOCK_STREAM ?
+ "net\\.connect_tcp" :
+ "net\\.connect_send_udp");
+ const int bind_right = (variant->prot.type == SOCK_STREAM ?
+ LANDLOCK_ACCESS_NET_BIND_TCP :
+ LANDLOCK_ACCESS_NET_BIND_UDP);
+ const int conn_right = (variant->prot.type == SOCK_STREAM ?
+ LANDLOCK_ACCESS_NET_CONNECT_TCP :
+ LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
+ const int access_rights = bind_right | conn_right;
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = access_rights,
+ .quiet_access_net = bind_right,
+ };
+ const struct landlock_ruleset_attr ruleset_attr_2 = {
+ .handled_access_net = access_rights,
+ .quiet_access_net = conn_right,
+ };
+ const struct landlock_net_port_attr quiet_rule = {
+ .allowed_access = 0,
+ .port = self->srv2.port,
+ };
+ struct audit_records records;
+ int ruleset_fd, sock_fd;
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &quiet_rule, LANDLOCK_ADD_RULE_QUIET));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ sock_fd = socket_variant(&self->srv2);
+ ASSERT_LE(0, sock_fd);
+ EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv2));
+ EXPECT_EQ(0, matches_auditlog(self->audit_fd, audit_evt, "daddr",
+ variant->addr, "dest", self->srv2.port));
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+
+ EXPECT_EQ(0, close(sock_fd));
+
+ /* New layer that also denies connect but has the correct quiet bit. */
+ ruleset_fd = landlock_create_ruleset(&ruleset_attr_2,
+ sizeof(ruleset_attr_2), 0);
+ ASSERT_LE(0, ruleset_fd);
+ ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &quiet_rule, LANDLOCK_ADD_RULE_QUIET));
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ sock_fd = socket_variant(&self->srv2);
+ ASSERT_LE(0, sock_fd);
+ EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv2));
+
+ /* Quieted - no logs expected. */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
+
+ EXPECT_EQ(0, close(sock_fd));
}
TEST_F(audit, sendmsg)
@@ -2967,7 +3084,8 @@ TEST_F(audit, sendmsg)
ASSERT_LE(0, sock_fd);
EXPECT_EQ(-EACCES, sendto_variant(sock_fd, &self->srv0, "A", 1, 0));
EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.connect_send_udp",
- "daddr", variant->addr, "dest"));
+ "daddr", variant->addr, "dest",
+ self->srv0.port));
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
@@ -2976,14 +3094,14 @@ TEST_F(audit, sendmsg)
/* Check that autobind generates a denied bind event. */
EXPECT_EQ(-EACCES, sendto_variant(sock_fd, &self->srv1, "A", 1, 0));
EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.bind_udp", NULL,
- NULL, NULL));
+ NULL, NULL, 0));
EXPECT_EQ(0, records.access);
EXPECT_EQ(1, records.domain);
EXPECT_EQ(-EACCES,
sendto_variant(sock_fd, &self->unspec_srv0, "B", 1, 0));
EXPECT_EQ(0, matches_auditlog(self->audit_fd, "net\\.connect_send_udp",
- "daddr", NULL, "dest"));
+ "daddr", NULL, "dest", 0));
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
EXPECT_EQ(0, records.access);
--
2.54.0
More information about the Linux-security-module-archive
mailing list