* [PATCH v10 8/9] selftests/landlock: Add tests for quiet flag with scope
From: Tingmao Wang @ 2026-06-01 0:00 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1780272022.git.m@maowtm.org>
Enhance scoped_audit.connect_to_child and audit_flags.signal to test
interaction with various quiet flag settings.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
Changes in v4:
- New patch
tools/testing/selftests/landlock/audit_test.c | 25 ++++--
.../landlock/scoped_abstract_unix_test.c | 77 ++++++++++++++++---
2 files changed, 87 insertions(+), 15 deletions(-)
diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
index 7044781357c0..de4c89cdc0be 100644
--- a/tools/testing/selftests/landlock/audit_test.c
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -794,30 +794,42 @@ FIXTURE(audit_flags)
FIXTURE_VARIANT(audit_flags)
{
const int restrict_flags;
+ const __u64 quiet_scoped;
};
/* clang-format off */
FIXTURE_VARIANT_ADD(audit_flags, default) {
/* clang-format on */
.restrict_flags = 0,
+ .quiet_scoped = 0,
};
/* clang-format off */
FIXTURE_VARIANT_ADD(audit_flags, same_exec_off) {
/* clang-format on */
.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF,
+ .quiet_scoped = 0,
};
/* clang-format off */
FIXTURE_VARIANT_ADD(audit_flags, subdomains_off) {
/* clang-format on */
.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
+ .quiet_scoped = 0,
};
/* clang-format off */
FIXTURE_VARIANT_ADD(audit_flags, cross_exec_on) {
/* clang-format on */
.restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON,
+ .quiet_scoped = 0,
+};
+
+/* clang-format off */
+FIXTURE_VARIANT_ADD(audit_flags, signal_quieted) {
+ /* clang-format on */
+ .restrict_flags = 0,
+ .quiet_scoped = LANDLOCK_SCOPE_SIGNAL,
};
FIXTURE_SETUP(audit_flags)
@@ -861,12 +873,16 @@ TEST_F(audit_flags, signal)
pid_t child;
struct audit_records records;
__u64 deallocated_dom = 2;
+ bool expect_audit = !(variant->restrict_flags &
+ LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) &&
+ !(variant->quiet_scoped & LANDLOCK_SCOPE_SIGNAL);
child = fork();
ASSERT_LE(0, child);
if (child == 0) {
const struct landlock_ruleset_attr ruleset_attr = {
.scoped = LANDLOCK_SCOPE_SIGNAL,
+ .quiet_scoped = variant->quiet_scoped,
};
int ruleset_fd;
@@ -883,8 +899,7 @@ TEST_F(audit_flags, signal)
EXPECT_EQ(-1, kill(getppid(), 0));
EXPECT_EQ(EPERM, errno);
- if (variant->restrict_flags &
- LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) {
+ if (!expect_audit) {
EXPECT_EQ(-EAGAIN, matches_log_signal(
_metadata, self->audit_fd,
getppid(), self->domain_id));
@@ -911,8 +926,7 @@ TEST_F(audit_flags, signal)
/* Makes sure there is no superfluous logged records. */
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
- if (variant->restrict_flags &
- LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) {
+ if (!expect_audit) {
EXPECT_EQ(0, records.access);
} else {
EXPECT_EQ(1, records.access);
@@ -936,8 +950,7 @@ TEST_F(audit_flags, signal)
WEXITSTATUS(status) != EXIT_SUCCESS)
_metadata->exit_code = KSFT_FAIL;
- if (variant->restrict_flags &
- LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF) {
+ if (!expect_audit) {
/*
* No deallocation record: denials=0 never matches a real
* record.
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
index 72f97648d4a7..d16555f7b0d3 100644
--- a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
+++ b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
@@ -293,6 +293,45 @@ FIXTURE_TEARDOWN_PARENT(scoped_audit)
EXPECT_EQ(0, audit_cleanup(-1, NULL));
}
+FIXTURE_VARIANT(scoped_audit)
+{
+ const __u64 scoped;
+ const __u64 quiet_scoped;
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, no_quiet)
+{
+ // clang-format on
+ .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+ .quiet_scoped = 0,
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, quiet_abstract_socket)
+{
+ // clang-format on
+ .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+ .quiet_scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET,
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, quiet_abstract_socket_2)
+{
+ // clang-format on
+ .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL,
+ .quiet_scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
+ LANDLOCK_SCOPE_SIGNAL,
+};
+
+// clang-format off
+FIXTURE_VARIANT_ADD(scoped_audit, quiet_unrelated)
+{
+ // clang-format on
+ .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL,
+ .quiet_scoped = LANDLOCK_SCOPE_SIGNAL,
+};
+
/* python -c 'print(b"\0selftests-landlock-abstract-unix-".hex().upper())' */
#define ABSTRACT_SOCKET_PATH_PREFIX \
"0073656C6674657374732D6C616E646C6F636B2D61627374726163742D756E69782D"
@@ -308,6 +347,13 @@ TEST_F(scoped_audit, connect_to_child)
char buf;
int dgram_client;
struct audit_records records;
+ int ruleset_fd;
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .scoped = variant->scoped,
+ .quiet_scoped = variant->quiet_scoped,
+ };
+ bool should_audit =
+ !(variant->quiet_scoped & LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
/* Makes sure there is no superfluous logged records. */
EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
@@ -345,7 +391,14 @@ TEST_F(scoped_audit, connect_to_child)
EXPECT_EQ(0, close(pipe_child[1]));
EXPECT_EQ(0, close(pipe_parent[0]));
- create_scoped_domain(_metadata, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd)
+ {
+ TH_LOG("Failed to create a ruleset: %s", strerror(errno));
+ }
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
/* Signals that the parent is in a domain, if any. */
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
@@ -360,14 +413,20 @@ TEST_F(scoped_audit, connect_to_child)
EXPECT_EQ(-1, err_dgram);
EXPECT_EQ(EPERM, errno);
- EXPECT_EQ(
- 0,
- audit_match_record(
- self->audit_fd, AUDIT_LANDLOCK_ACCESS,
- REGEX_LANDLOCK_PREFIX
- " blockers=scope\\.abstract_unix_socket path=" ABSTRACT_SOCKET_PATH_PREFIX
- "[0-9A-F]\\+$",
- NULL));
+ if (should_audit) {
+ EXPECT_EQ(
+ 0,
+ audit_match_record(
+ self->audit_fd, AUDIT_LANDLOCK_ACCESS,
+ REGEX_LANDLOCK_PREFIX
+ " blockers=scope\\.abstract_unix_socket path=" ABSTRACT_SOCKET_PATH_PREFIX
+ "[0-9A-F]\\+$",
+ NULL));
+ }
+
+ /* No other logs */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ EXPECT_EQ(0, records.access);
ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
EXPECT_EQ(0, close(dgram_client));
--
2.54.0
^ permalink raw reply related
* [PATCH v10 7/9] selftests/landlock: add tests for quiet flag with net rules
From: Tingmao Wang @ 2026-06-01 0:00 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1780272022.git.m@maowtm.org>
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@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
^ permalink raw reply related
* [PATCH v10 6/9] selftests/landlock: add tests for quiet flag with fs rules
From: Tingmao Wang @ 2026-06-01 0:00 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1780272022.git.m@maowtm.org>
Test various interactions of the quiet flag with filesystem rules:
- Non-optional access (tested with open and rename).
- Optional access (tested with truncate and ioctl).
- Behaviour around mounts matches with normal Landlock rules.
- Behaviour around disconnected directories matches with normal Landlock
rules (test expected behaviour of 9a868cdbe66a ("landlock: Fix handling of
disconnected directories") applied to the collected quiet flag).
- Multiple layers works as expected.
Assisted-by: GitHub Copilot:claude-opus-4.6 copilot-review
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
Changes in v10:
- Fix grammar in some comments
- if brackets
Changes in v8:
- Rebase, resolve conflict, then clang-format
- Remove previously added comment about domain allocation record leakage -
this is now documented properly by 239fd9a6f948 ("selftests/landlock:
Drain stale audit records on init")
- Fix missing EXPECT_EQ on audit_count_records() return value
Changes in v6:
- Change quiet bool argument of add_path_beneath into a __u32 flags
(suggested by Justin Suess)
- Rename quiet_behind_mountpoint_ignored_disconnected to
quiet_behind_mountpoint_disconnected and fix test due to disconnected
directory handling changes
Changes in v5:
- Add quiet_two_layers_different_handled_{1,2,3} variants.
Changes in v3:
- New patch
tools/testing/selftests/landlock/fs_test.c | 2447 +++++++++++++++++++-
1 file changed, 2438 insertions(+), 9 deletions(-)
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 10d9355ade5f..5f5d75fabe07 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -720,7 +720,7 @@ TEST_F_FORK(layout1, rule_with_unhandled_access)
static void add_path_beneath(struct __test_metadata *const _metadata,
const int ruleset_fd, const __u64 allowed_access,
- const char *const path)
+ const char *const path, __u32 flags)
{
struct landlock_path_beneath_attr path_beneath = {
.allowed_access = allowed_access,
@@ -733,7 +733,7 @@ static void add_path_beneath(struct __test_metadata *const _metadata,
strerror(errno));
}
ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
- &path_beneath, 0))
+ &path_beneath, flags))
{
TH_LOG("Failed to update the ruleset with \"%s\": %s", path,
strerror(errno));
@@ -780,7 +780,7 @@ static int create_ruleset(struct __test_metadata *const _metadata,
continue;
add_path_beneath(_metadata, ruleset_fd, rules[i].access,
- rules[i].path);
+ rules[i].path, 0);
}
return ruleset_fd;
}
@@ -1310,7 +1310,7 @@ TEST_F_FORK(layout1, inherit_subset)
* ANDed with the previous ones.
*/
add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
- dir_s1d2);
+ dir_s1d2, 0);
/*
* According to ruleset_fd, dir_s1d2 should now have the
* LANDLOCK_ACCESS_FS_READ_FILE and LANDLOCK_ACCESS_FS_WRITE_FILE
@@ -1342,7 +1342,7 @@ TEST_F_FORK(layout1, inherit_subset)
* Try to get more privileges by adding new access rights to the parent
* directory: dir_s1d1.
*/
- add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1);
+ add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1, 0);
enforce_ruleset(_metadata, ruleset_fd);
/* Same tests and results as above. */
@@ -1365,7 +1365,7 @@ TEST_F_FORK(layout1, inherit_subset)
* that there was no rule tied to it before.
*/
add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
- dir_s1d3);
+ dir_s1d3, 0);
enforce_ruleset(_metadata, ruleset_fd);
ASSERT_EQ(0, close(ruleset_fd));
@@ -1417,7 +1417,7 @@ TEST_F_FORK(layout1, inherit_superset)
add_path_beneath(_metadata, ruleset_fd,
LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_READ_DIR,
- dir_s1d2);
+ dir_s1d2, 0);
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
@@ -3970,7 +3970,7 @@ static int ioctl_error(struct __test_metadata *const _metadata, int fd,
unsigned int cmd)
{
char buf[128]; /* sufficiently large */
- int res, stdinbak_fd;
+ int res, stdinbak_fd, err;
/*
* Depending on the IOCTL command, parts of the zeroed-out buffer might
@@ -3985,13 +3985,14 @@ static int ioctl_error(struct __test_metadata *const _metadata, int fd,
/* Invokes the IOCTL with a zeroed-out buffer. */
bzero(&buf, sizeof(buf));
res = ioctl(fd, cmd, &buf);
+ err = errno;
/* Restores the old FD 0 and closes the backup FD. */
ASSERT_EQ(0, dup2(stdinbak_fd, 0));
ASSERT_EQ(0, close(stdinbak_fd));
if (res < 0)
- return errno;
+ return err;
return 0;
}
@@ -4789,6 +4790,7 @@ FIXTURE(layout1_bind) {};
static const char bind_dir_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3";
static const char bind_file1_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f1";
+static const char bind_file2_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f2";
/* Move targets for disconnected path tests. */
static const char dir_s4d1[] = TMP_DIR "/s4d1";
@@ -7764,4 +7766,2431 @@ TEST_F(audit_layout1, mount)
EXPECT_EQ(1, records.domain);
}
+static bool debug_quiet_tests;
+
+FIXTURE(audit_quiet_layout1)
+{
+ struct audit_filter audit_filter;
+ int audit_fd;
+};
+
+FIXTURE_SETUP(audit_quiet_layout1)
+{
+ prepare_layout(_metadata);
+ create_layout1(_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);
+
+ if (getenv("DEBUG_QUIET_TESTS"))
+ debug_quiet_tests = true;
+}
+
+FIXTURE_TEARDOWN_PARENT(audit_quiet_layout1)
+{
+ remove_layout1(_metadata);
+ cleanup_layout(_metadata);
+
+ set_cap(_metadata, CAP_AUDIT_CONTROL);
+ EXPECT_EQ(0, audit_cleanup(-1, NULL));
+ clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+struct a_rule {
+ const char *path;
+ __u64 access;
+ bool quiet;
+};
+
+struct a_layer {
+ __u64 handled_access_fs;
+ __u64 quiet_access_fs;
+ struct a_rule rules[6];
+ __u64 restrict_flags;
+};
+
+struct a_target {
+ /* File/dir to try open. */
+ const char *target;
+ /* Open mode (one of O_RDONLY, O_WRONLY, or O_RDWR). */
+ int open_mode;
+ /* Should open succeed? */
+ bool expect_open_success;
+ /* If open fails, whether to expect an audit log for read. */
+ bool audit_read_blocked;
+ /* If open fails, whether to expect an audit log for write. */
+ bool audit_write_blocked;
+ /* If ftruncate() is expected to be allowed. */
+ bool expect_truncate_success;
+ /* If ftruncate fails, whether to expect an audit log. */
+ bool audit_truncate;
+ /*
+ * If ioctl() is expected to be allowed (ioctl not attempted if
+ * neither this nor expect_ioctl_denied is set).
+ */
+ bool expect_ioctl_allowed;
+ /* If ioctl() is expected to be denied. */
+ bool expect_ioctl_denied;
+ /* If ioctl fails, whether to expect an audit log. */
+ bool audit_ioctl;
+};
+
+#define AUDIT_QUIET_MAX_TARGETS 10
+
+FIXTURE_VARIANT(audit_quiet_layout1)
+{
+ struct a_layer layers[3];
+ struct a_target targets[AUDIT_QUIET_MAX_TARGETS];
+};
+
+#define FS_R LANDLOCK_ACCESS_FS_READ_FILE
+#define FS_W LANDLOCK_ACCESS_FS_WRITE_FILE
+#define FS_TRUNC LANDLOCK_ACCESS_FS_TRUNCATE
+#define FS_IOCTL LANDLOCK_ACCESS_FS_IOCTL_DEV
+
+static int sprint_access_bits(char *buf, size_t buflen, __u64 access)
+{
+ size_t offset = 0;
+
+ if (buflen < strlen("rwti make_reg remove_file refer") + 1)
+ abort();
+
+ buf[0] = '\0';
+ if (access & FS_R)
+ offset += snprintf(buf + offset, buflen - offset, "r");
+ if (access & FS_W)
+ offset += snprintf(buf + offset, buflen - offset, "w");
+ if (access & FS_TRUNC)
+ offset += snprintf(buf + offset, buflen - offset, "t");
+ if (access & FS_IOCTL)
+ offset += snprintf(buf + offset, buflen - offset, "i");
+ if (access & LANDLOCK_ACCESS_FS_MAKE_REG)
+ offset += snprintf(buf + offset, buflen - offset, ",make_reg");
+ if (access & LANDLOCK_ACCESS_FS_REMOVE_FILE)
+ offset +=
+ snprintf(buf + offset, buflen - offset, ",remove_file");
+ if (access & LANDLOCK_ACCESS_FS_REFER)
+ offset += snprintf(buf + offset, buflen - offset, ",refer");
+
+ if (buf[0] == ',') {
+ offset--;
+ memmove(buf, buf + 1, offset);
+ buf[offset] = '\0';
+ }
+
+ return offset;
+}
+
+static int apply_a_layer(struct __test_metadata *const _metadata,
+ const struct a_layer *l)
+{
+ struct landlock_ruleset_attr rs_attr = {
+ .handled_access_fs = l->handled_access_fs,
+ .quiet_access_fs = l->quiet_access_fs,
+ };
+ int rs_fd;
+ int i;
+ const struct a_rule *r;
+ char handled_access_s[33], quiet_access_s[33], rule_access_s[33];
+
+ if (!l->handled_access_fs)
+ return 0;
+
+ rs_fd = landlock_create_ruleset(&rs_attr, sizeof(rs_attr), 0);
+ ASSERT_LE(0, rs_fd);
+
+ for (i = 0; i < ARRAY_SIZE(l->rules); i++) {
+ r = &l->rules[i];
+ if (!r->path)
+ continue;
+
+ add_path_beneath(_metadata, rs_fd, r->access, r->path,
+ r->quiet ? LANDLOCK_ADD_RULE_QUIET : 0);
+ }
+
+ ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+ ASSERT_EQ(0, landlock_restrict_self(rs_fd, l->restrict_flags))
+ {
+ TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
+ }
+ ASSERT_EQ(0, close(rs_fd));
+
+ if (debug_quiet_tests) {
+ sprint_access_bits(handled_access_s, sizeof(handled_access_s),
+ l->handled_access_fs);
+ sprint_access_bits(quiet_access_s, sizeof(quiet_access_s),
+ l->quiet_access_fs);
+ TH_LOG("applied layer: handled=%s quiet=%s restrict_flags=0x%llx",
+ handled_access_s, quiet_access_s,
+ (unsigned long long)l->restrict_flags);
+ for (i = 0; i < ARRAY_SIZE(l->rules); i++) {
+ r = &l->rules[i];
+ if (!r->path)
+ continue;
+
+ sprint_access_bits(rule_access_s, sizeof(rule_access_s),
+ r->access);
+ TH_LOG(" rule[%d]: path=%s access=%s quiet=%d", i,
+ r->path, rule_access_s, r->quiet);
+ }
+ }
+ return 0;
+}
+
+void audit_quiet_layout1_test_body(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(audit_quiet_layout1) * self,
+ const struct a_target *targets)
+{
+ struct audit_records records = {};
+ int i;
+ const struct a_target *target;
+ int fd = -1;
+ int open_mode;
+ int ret;
+ bool expect_audit;
+ const char *blocker;
+
+ for (i = 0; i < AUDIT_QUIET_MAX_TARGETS; i++) {
+ target = &targets[i];
+ if (!target->target)
+ continue;
+
+ open_mode = target->open_mode & (O_RDONLY | O_WRONLY | O_RDWR);
+
+ EXPECT_TRUE(open_mode == O_RDONLY || open_mode == O_WRONLY ||
+ open_mode == O_RDWR);
+
+ if (target->expect_open_success) {
+ EXPECT_FALSE(target->audit_read_blocked);
+ EXPECT_FALSE(target->audit_write_blocked);
+ }
+ if (target->expect_truncate_success)
+ EXPECT_TRUE(target->expect_open_success &&
+ !target->audit_truncate);
+
+ if (debug_quiet_tests)
+ TH_LOG("Try open \"%s\" with %s%s", target->target,
+ open_mode != O_WRONLY ? "r" : "",
+ open_mode != O_RDONLY ? "w" : "");
+
+ fd = openat(AT_FDCWD, target->target, open_mode | O_CLOEXEC);
+ if (target->expect_open_success) {
+ ASSERT_LE(0, fd)
+ {
+ TH_LOG("Failed to open \"%s\": %s",
+ target->target, strerror(errno));
+ };
+ } else {
+ ASSERT_EQ(-1, fd);
+ ASSERT_EQ(EACCES, errno);
+ }
+
+ expect_audit = true;
+
+ if (target->audit_read_blocked && target->audit_write_blocked)
+ blocker = "fs\\.write_file,fs\\.read_file";
+ else if (target->audit_read_blocked)
+ blocker = "fs\\.read_file";
+ else if (target->audit_write_blocked)
+ blocker = "fs\\.write_file";
+ else
+ expect_audit = false;
+
+ if (expect_audit)
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+ blocker, target->target));
+
+ /* Check that we see no (other) logs. */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+
+ if (target->expect_open_success && fd >= 0) {
+ if (debug_quiet_tests)
+ TH_LOG("Try ftruncate \"%s\"", target->target);
+
+ ret = ftruncate(fd, 0);
+ if (target->expect_truncate_success) {
+ ASSERT_EQ(0, ret);
+ } else {
+ ASSERT_EQ(-1, ret);
+ if (open_mode != O_RDONLY)
+ ASSERT_EQ(EACCES, errno);
+ }
+
+ if (target->audit_truncate)
+ ASSERT_EQ(0, matches_log_fs(_metadata,
+ self->audit_fd,
+ "fs\\.truncate",
+ target->target));
+
+ if (target->expect_ioctl_allowed ||
+ target->expect_ioctl_denied) {
+ if (debug_quiet_tests)
+ TH_LOG("Try ioctl FIONREAD on \"%s\"",
+ target->target);
+
+ ret = ioctl_error(_metadata, fd, FIONREAD);
+ if (target->expect_ioctl_allowed)
+ ASSERT_NE(EACCES, ret);
+ else
+ ASSERT_EQ(EACCES, ret);
+ }
+
+ if (target->audit_ioctl)
+ ASSERT_EQ(0, matches_log_fs_extra(
+ _metadata, self->audit_fd,
+ "fs\\.ioctl_dev",
+ target->target,
+ " ioctlcmd=0x541b\\+"));
+
+ /* Check that we see no other logs. */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd,
+ &records));
+ ASSERT_EQ(0, records.access);
+ ASSERT_EQ(0, close(fd));
+ }
+ }
+}
+
+TEST_F(audit_quiet_layout1, base)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(variant->layers); i++)
+ ASSERT_EQ(0, apply_a_layer(_metadata, &variant->layers[i]));
+
+ audit_quiet_layout1_test_body(_metadata, self, variant->targets);
+}
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_simple) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC,
+ .quiet_access_fs = FS_R,
+ .rules = {
+ { .path = dir_s1d1, .access = 0, .quiet = true },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ },
+ /* Not covered by quiet */
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ /* Access not quieted */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_WRONLY,
+ .audit_write_blocked = true,
+ },
+ /*
+ * Quiet flag only takes effect if all blocked access bits are
+ * quieted, otherwise audit log emitted as normal (with all blockers)
+ */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ .audit_read_blocked = true,
+ .audit_write_blocked = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_allow_read) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC,
+ .quiet_access_fs = FS_W,
+ .rules = {
+ { .path = dir_s1d1, .access = FS_R, .quiet = true },
+ /* Quiet flags inherit down and are not overridden */
+ { .path = file1_s1d1, .access = FS_R, .quiet = false },
+ { .path = file1_s2d3, .access = 0, .quiet = true },
+ },
+ },
+ },
+ .targets = {
+ /* Read ok */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ },
+ /* Write quieted */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_WRONLY,
+ },
+ /* Read allowed, write quieted so no audit */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ },
+ /* Not covered by quiet */
+ {
+ .target = file1_s2d2,
+ .open_mode = O_WRONLY,
+ .audit_write_blocked = true,
+ },
+ {
+ .target = file1_s2d2,
+ .open_mode = O_RDWR,
+ .audit_read_blocked = true,
+ .audit_write_blocked = true,
+ },
+ /* Single file quiet */
+ {
+ .target = file1_s2d3,
+ .open_mode = O_WRONLY,
+ },
+ /* Wrong file */
+ {
+ .target = file2_s2d3,
+ .open_mode = O_WRONLY,
+ .audit_write_blocked = true,
+ },
+ /* Access not quieted */
+ {
+ .target = file1_s2d3,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ /* Some access not quieted */
+ {
+ .target = file1_s2d3,
+ .open_mode = O_RDWR,
+ .audit_read_blocked = true,
+ .audit_write_blocked = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_allow_write) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC,
+ .quiet_access_fs = FS_R,
+ .rules = {
+ { .path = dir_s1d1, .access = FS_W, .quiet = true },
+ },
+ },
+ },
+ .targets = {
+ /* Read quieted */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ },
+ /* Truncate not quieted */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ .audit_truncate = true,
+ },
+ /* Not covered by quiet */
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ /* Write allowed, read quieted so no audit */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, allow_write_quiet_trunc) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC,
+ .quiet_access_fs = FS_TRUNC,
+ .rules = {
+ { .path = dir_s1d1, .access = FS_W, .quiet = true },
+ { .path = dir_s2d1, .access = FS_W, .quiet = false },
+ },
+ },
+ },
+ .targets = {
+ /* Read not allowed and not quieted */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ /* Truncate quieted */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ },
+ /* Not covered by quiet (truncate) */
+ {
+ .target = file1_s2d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ .audit_truncate = true,
+ },
+ /* Not covered by quiet (read/write) */
+ {
+ .target = file1_s3d1,
+ .open_mode = O_RDWR,
+ .audit_read_blocked = true,
+ .audit_write_blocked = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, allow_rw_quiet_trunc) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC,
+ .quiet_access_fs = FS_TRUNC,
+ .rules = {
+ { .path = dir_s1d1, .access = FS_R | FS_W, .quiet = true },
+ { .path = dir_s2d1, .access = FS_R | FS_W, .quiet = false },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ .audit_truncate = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_all) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .rules = {
+ { .path = dir_s1d1, .access = 0, .quiet = true },
+ { .path = file1_s2d1, .access = FS_R | FS_W, .quiet = true },
+ { .path = file1_s2d3, .access = 0, .quiet = true },
+ { .path = dir_s3d1, .access = FS_W, .quiet = false },
+ { .path = "/dev/zero", .access = FS_R, .quiet = false },
+ { .path = "/dev/null", .access = FS_R, .quiet = true },
+ },
+ },
+ },
+ .targets = {
+ /* No logs */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ },
+ {
+ .target = file1_s1d1,
+ .open_mode = O_WRONLY,
+ },
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ },
+ /* Truncate quieted - no log */
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ },
+ /* Truncate not covered by quiet */
+ {
+ .target = file1_s3d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ .audit_truncate = true,
+ },
+ /* Not covered by quiet */
+ {
+ .target = file1_s3d1,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ /* Single file quiet */
+ {
+ .target = file1_s2d3,
+ .open_mode = O_RDWR,
+ },
+ /* Wrong file */
+ {
+ .target = file2_s2d3,
+ .open_mode = O_RDWR,
+ .audit_read_blocked = true,
+ .audit_write_blocked = true,
+ },
+ /* Ioctl quieted */
+ {
+ .target = "/dev/null",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ },
+ /* Ioctl not quieted */
+ {
+ .target = "/dev/zero",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ .audit_ioctl = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_across_mountpoint) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC,
+ .quiet_access_fs = FS_R,
+ .rules = {
+ { .path = dir_s3d1, .access = 0, .quiet = true },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s3d3,
+ .open_mode = O_RDONLY,
+ },
+ /* Not covered by quiet */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ .audit_read_blocked = true,
+ .audit_write_blocked = true,
+ },
+ /* Access not quieted */
+ {
+ .target = file1_s3d3,
+ .open_mode = O_WRONLY,
+ .audit_write_blocked = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, allow_all_quiet) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = FS_R | FS_W | FS_TRUNC,
+ .quiet = true
+ },
+ {
+ .path = "/dev/null",
+ .access = FS_R | FS_W | FS_IOCTL,
+ .quiet = true
+ },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ .expect_truncate_success = true,
+ },
+ {
+ .target = "/dev/null",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_allowed = true,
+ },
+ },
+};
+
+/*
+ * With LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF, it doesn't matter what
+ * the quiet flags below the layer say.
+ */
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, subdomains_off) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R,
+ .restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF,
+ .rules = {
+ { .path = "/", .access = FS_R, .quiet = false },
+ }
+ },
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R,
+ .rules = {
+ { .path = dir_s1d1, .access = 0, .quiet = true },
+ { .path = file1_s2d2, .access = FS_R | FS_W, .quiet = true },
+ { .path = file1_s2d3, .access = FS_R | FS_W, .quiet = false },
+ { .path = "/dev/null", .access = FS_R | FS_W, .quiet = true },
+ { .path = "/dev/zero", .access = FS_R | FS_W, .quiet = false },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDWR,
+ },
+ {
+ .target = file1_s2d2,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ /* No audit_truncate */
+ },
+ {
+ .target = file1_s2d3,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ /* No audit_truncate */
+ },
+ {
+ .target = "/dev/null",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ /* No audit_ioctl */
+ },
+ {
+ .target = "/dev/zero",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ /* No audit_ioctl */
+ },
+ },
+};
+
+/*
+ * With LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF, it doesn't matter what
+ * the quiet flags on the layer say.
+ */
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, same_exec_off) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R,
+ .restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF,
+ .rules = {
+ { .path = dir_s1d1, .access = 0, .quiet = true },
+ { .path = file1_s2d2, .access = FS_R | FS_W, .quiet = true },
+ { .path = file1_s2d3, .access = FS_R | FS_W, .quiet = false },
+ { .path = "/dev/null", .access = FS_R | FS_W, .quiet = true },
+ { .path = "/dev/zero", .access = FS_R | FS_W, .quiet = false },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDWR,
+ },
+ {
+ .target = file1_s2d2,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ /* No audit_truncate */
+ },
+ {
+ .target = file1_s2d3,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ /* No audit_truncate */
+ },
+ {
+ .target = "/dev/null",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ /* No audit_ioctl */
+ },
+ {
+ .target = "/dev/zero",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ /* No audit_ioctl */
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_1) {
+ /* Here, rules that deny access are always quiet. */
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = FS_W,
+ .quiet = true,
+ },
+ {
+ .path = dir_s2d1,
+ .access = FS_R | FS_W | FS_TRUNC,
+ .quiet = false,
+ },
+ {
+ .path = "/dev/null",
+ .access = FS_R,
+ .quiet = true,
+ },
+ {
+ .path = "/dev/zero",
+ .access = FS_R | FS_W | FS_IOCTL,
+ .quiet = false,
+ },
+ },
+ },
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = FS_R | FS_W | FS_TRUNC,
+ .quiet = false,
+ },
+ {
+ .path = dir_s2d1,
+ .access = FS_W,
+ .quiet = true,
+ },
+ {
+ .path = "/dev/null",
+ .access = FS_R | FS_W | FS_IOCTL,
+ .quiet = false,
+ },
+ {
+ .path = "/dev/zero",
+ .access = FS_R,
+ .quiet = true,
+ },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ },
+ {
+ .target = file1_s1d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDONLY,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ },
+ {
+ .target = "/dev/null",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ },
+ {
+ .target = "/dev/zero",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_2) {
+ /* Here, rules that deny access are never quiet. */
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = FS_W,
+ .quiet = false
+ },
+ {
+ .path = dir_s2d1,
+ .access = FS_R | FS_W | FS_TRUNC,
+ .quiet = true
+ },
+ {
+ .path = "/dev/null",
+ .access = FS_R,
+ .quiet = false
+ },
+ {
+ .path = "/dev/zero",
+ .access = FS_R | FS_W | FS_IOCTL,
+ .quiet = true
+ },
+ },
+ },
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = FS_R | FS_W | FS_TRUNC,
+ .quiet = true
+ },
+ {
+ .path = dir_s2d1,
+ .access = FS_W,
+ .quiet = false
+ },
+ {
+ .path = "/dev/null",
+ .access = FS_R | FS_W | FS_IOCTL,
+ .quiet = true
+ },
+ {
+ .path = "/dev/zero",
+ .access = FS_R,
+ .quiet = false
+ },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ {
+ .target = file1_s1d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ .audit_truncate = true,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ .audit_truncate = true,
+ },
+ {
+ .target = "/dev/null",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ .audit_ioctl = true,
+ },
+ {
+ .target = "/dev/zero",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ .audit_ioctl = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_3) {
+ /* This time only the second layer quiets things. */
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = FS_W,
+ .quiet = false,
+ },
+ {
+ .path = dir_s2d1,
+ .access = FS_R | FS_W | FS_TRUNC,
+ .quiet = false,
+ },
+ {
+ .path = "/dev/null",
+ .access = FS_R,
+ .quiet = false,
+ },
+ {
+ .path = "/dev/zero",
+ .access = FS_R | FS_W | FS_IOCTL,
+ .quiet = false,
+ },
+ },
+ },
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = FS_R | FS_W | FS_TRUNC,
+ .quiet = false,
+ },
+ {
+ .path = dir_s2d1,
+ .access = FS_W,
+ .quiet = true,
+ },
+ {
+ .path = "/dev/null",
+ .access = FS_R | FS_W | FS_IOCTL,
+ .quiet = false,
+ },
+ {
+ .path = "/dev/zero",
+ .access = FS_R,
+ .quiet = true,
+ },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ {
+ .target = file1_s1d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ .audit_truncate = true,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDONLY,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ },
+ {
+ .target = "/dev/null",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ .audit_ioctl = true,
+ },
+ {
+ .target = "/dev/zero",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_different_quiet_access) {
+ /* Here, rules that deny access are always quiet. */
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = FS_W,
+ .quiet = true,
+ },
+ {
+ .path = dir_s2d1,
+ .access = FS_R | FS_W | FS_TRUNC,
+ .quiet = false,
+ },
+ {
+ .path = "/dev/null",
+ .access = FS_R,
+ .quiet = true,
+ },
+ {
+ .path = "/dev/zero",
+ .access = FS_R | FS_W | FS_IOCTL,
+ .quiet = false,
+ },
+ },
+ },
+ {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_IOCTL,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = FS_R | FS_W | FS_TRUNC,
+ .quiet = false,
+ },
+ {
+ .path = dir_s2d1,
+ .access = FS_W,
+ .quiet = true,
+ },
+ {
+ .path = "/dev/null",
+ .access = FS_R | FS_W | FS_IOCTL,
+ .quiet = false,
+ },
+ {
+ .path = "/dev/zero",
+ .access = FS_R,
+ .quiet = true,
+ },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ },
+ {
+ .target = file1_s1d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ {
+ .target = file1_s2d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ .audit_truncate = true,
+ },
+ {
+ .target = "/dev/null",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ },
+ {
+ .target = "/dev/zero",
+ .open_mode = O_RDONLY,
+ .expect_open_success = true,
+ .expect_ioctl_denied = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_different_handled_1) {
+ /* Quiet from layer 1 */
+ .layers = {
+ {
+ .handled_access_fs = FS_R,
+ .quiet_access_fs = FS_R,
+ .rules = {
+ {
+ .path = file1_s1d1,
+ .access = FS_R,
+ .quiet = true,
+ },
+ {
+ .path = file2_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = file1_s1d2,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = file2_s1d2,
+ .access = FS_R,
+ .quiet = true,
+ },
+ },
+ },
+ {
+ .handled_access_fs = FS_W,
+ .quiet_access_fs = FS_W,
+ .rules = {
+ {
+ .path = file1_s1d1,
+ .access = FS_W,
+ .quiet = false,
+ },
+ /* Nothing for file2_s1d1 */
+ {
+ .path = file1_s1d2,
+ .access = FS_W,
+ .quiet = false,
+ },
+ /* Nothing for file2_s1d2 */
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ .expect_truncate_success = true,
+ },
+ /* Missing both, youngest layer denies write, not quiet */
+ {
+ .target = file2_s1d1,
+ .open_mode = O_RDWR,
+ .audit_write_blocked = true,
+ },
+ /* Missing read, denied and quieted by layer 1 */
+ {
+ .target = file1_s1d2,
+ .open_mode = O_RDWR,
+ },
+ /* Missing write, denied and not quieted by layer 2 */
+ {
+ .target = file2_s1d2,
+ .open_mode = O_RDWR,
+ .audit_write_blocked = true,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_different_handled_2) {
+ /* Quiet from layer 2 */
+ .layers = {
+ {
+ .handled_access_fs = FS_R,
+ .quiet_access_fs = FS_R,
+ .rules = {
+ {
+ .path = file1_s1d1,
+ .access = FS_R,
+ .quiet = false,
+ },
+ /* Nothing for file2_s1d1 and file1_s1d2 */
+ {
+ .path = file2_s1d2,
+ .access = FS_R,
+ .quiet = false,
+ },
+ },
+ },
+ {
+ .handled_access_fs = FS_W,
+ .quiet_access_fs = FS_W,
+ .rules = {
+ {
+ .path = file1_s1d1,
+ .access = FS_W,
+ .quiet = true,
+ },
+ {
+ .path = file2_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = file1_s1d2,
+ .access = FS_W,
+ .quiet = true,
+ },
+ {
+ .path = file2_s1d2,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ .expect_truncate_success = true,
+ },
+ /* Missing both, youngest layer denies write, quiet */
+ {
+ .target = file2_s1d1,
+ .open_mode = O_RDWR,
+ },
+ /* Missing read, denied and not quieted by layer 1 */
+ {
+ .target = file1_s1d2,
+ .open_mode = O_RDWR,
+ .audit_read_blocked = true,
+ },
+ /* Missing write, denied and quieted by layer 2 */
+ {
+ .target = file2_s1d2,
+ .open_mode = O_RDWR,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, quiet_two_layers_different_handled_3) {
+ /* Quiet from both layers */
+ .layers = {
+ {
+ .handled_access_fs = FS_R,
+ .quiet_access_fs = FS_R,
+ .rules = {
+ {
+ .path = file1_s1d1,
+ .access = FS_R,
+ .quiet = true,
+ },
+ {
+ .path = file2_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = file1_s1d2,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = file2_s1d2,
+ .access = FS_R,
+ .quiet = true,
+ },
+ },
+ },
+ {
+ .handled_access_fs = FS_W,
+ .quiet_access_fs = FS_W,
+ .rules = {
+ {
+ .path = file1_s1d1,
+ .access = FS_W,
+ .quiet = true,
+ },
+ {
+ .path = file2_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = file1_s1d2,
+ .access = FS_W,
+ .quiet = true,
+ },
+ {
+ .path = file2_s1d2,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ },
+ },
+ .targets = {
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ .expect_open_success = true,
+ .expect_truncate_success = true,
+ },
+ {
+ .target = file2_s1d1,
+ .open_mode = O_RDWR,
+ },
+ {
+ .target = file1_s1d2,
+ .open_mode = O_RDWR,
+ },
+ {
+ .target = file2_s1d2,
+ .open_mode = O_RDWR,
+ },
+ },
+};
+
+FIXTURE_VARIANT_ADD(audit_quiet_layout1, without_quiet_then_with_quiet) {
+ .layers = {
+ {
+ .handled_access_fs = FS_R | FS_W,
+ .quiet_access_fs = FS_R,
+ .rules = {
+ { .path = dir_s1d1, .access = FS_W, .quiet = false },
+ { .path = dir_s1d1, .access = 0, .quiet = true },
+ },
+ },
+ },
+ .targets = {
+ /* Read denied and quieted */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDONLY,
+ },
+ /* Write ok */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_WRONLY,
+ .expect_open_success = true,
+ .expect_truncate_success = true,
+ },
+ /* Write ok, read denied and quieted */
+ {
+ .target = file1_s1d1,
+ .open_mode = O_RDWR,
+ },
+ /* Not covered by quiet */
+ {
+ .target = file1_s2d1,
+ .open_mode = O_RDONLY,
+ .audit_read_blocked = true,
+ },
+ },
+};
+
+/*
+ * The following TEST_F extend the above test cases to test more layers,
+ * with the inserted layers having varying configurations.
+ */
+
+/* Extra allow all layers, quiet or not, does not change any behaviour. */
+TEST_F(audit_quiet_layout1, allow_all_layer)
+{
+ struct a_layer allow_all_layer = {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = 0,
+ .rules = {
+ {
+ .path = "/",
+ .access = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet = false,
+ },
+ },
+ };
+ int i;
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &allow_all_layer));
+ for (i = 0; i < ARRAY_SIZE(variant->layers); i++)
+ ASSERT_EQ(0, apply_a_layer(_metadata, &variant->layers[i]));
+
+ audit_quiet_layout1_test_body(_metadata, self, variant->targets);
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &allow_all_layer));
+
+ audit_quiet_layout1_test_body(_metadata, self, variant->targets);
+
+ /*
+ * SELF_LOG flags or quiet bits from inner allowing layers should not
+ * affect behaviour.
+ */
+ allow_all_layer.quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL;
+ allow_all_layer.rules[0].quiet = true;
+ /*
+ * Note: this only works because we're not checking counts of domain
+ * alloc/dealloc logs
+ */
+ allow_all_layer.restrict_flags =
+ LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF |
+ LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF;
+ ASSERT_EQ(0, apply_a_layer(_metadata, &allow_all_layer));
+
+ audit_quiet_layout1_test_body(_metadata, self, variant->targets);
+}
+
+/*
+ * Add useless outer layers until we reach the layer limit. Should not
+ * change anything.
+ */
+TEST_F(audit_quiet_layout1, many_outer_layers)
+{
+ struct a_layer useless_layer = {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC,
+ .rules = {
+ { .path = "/", .access = FS_R | FS_W | FS_TRUNC, .quiet = true },
+ },
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(variant->layers); i++) {
+ if (variant->layers[i].handled_access_fs == 0)
+ break;
+ }
+
+ for (; i < LANDLOCK_MAX_NUM_LAYERS; i++)
+ ASSERT_EQ(0, apply_a_layer(_metadata, &useless_layer));
+
+ for (i = 0; i < ARRAY_SIZE(variant->layers); i++)
+ ASSERT_EQ(0, apply_a_layer(_metadata, &variant->layers[i]));
+
+ audit_quiet_layout1_test_body(_metadata, self, variant->targets);
+}
+
+/*
+ * An inner layer that denies and quiets everything should result in no
+ * logs.
+ */
+TEST_F(audit_quiet_layout1, deny_all_quiet_layer)
+{
+ struct a_layer deny_all_layer = {
+ .handled_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .quiet_access_fs = FS_R | FS_W | FS_TRUNC | FS_IOCTL,
+ .rules = {
+ { .path = "/", .access = 0, .quiet = true },
+ },
+ };
+ int i;
+ FIXTURE_VARIANT(audit_quiet_layout1) variant_2 = {};
+
+ /* Any open should fail with no logs. */
+ for (i = 0; i < ARRAY_SIZE(variant->targets); i++) {
+ const struct a_target *target = &variant->targets[i];
+
+ variant_2.targets[i] = (struct a_target){
+ .target = target->target,
+ .open_mode = target->open_mode,
+ /* We denied everything, open should always fail. */
+ .expect_open_success = false,
+ };
+ }
+
+ for (i = 0; i < ARRAY_SIZE(variant->layers); i++)
+ ASSERT_EQ(0, apply_a_layer(_metadata, &variant->layers[i]));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &deny_all_layer));
+
+ audit_quiet_layout1_test_body(_metadata, self, variant_2.targets);
+}
+
+/*
+ * An inner layer that denies everything without quiet should produce logs
+ * for all access.
+ */
+TEST_F(audit_quiet_layout1, deny_all_layer)
+{
+ struct a_layer deny_all_layer = {
+ .handled_access_fs = FS_R | FS_W,
+ .quiet_access_fs = FS_R | FS_W,
+ };
+ int i;
+ FIXTURE_VARIANT(audit_quiet_layout1) variant_2 = {};
+ bool test_has_subdomains_off = false;
+
+ for (i = 0; i < ARRAY_SIZE(variant->layers); i++) {
+ if (variant->layers[i].restrict_flags &
+ LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF) {
+ test_has_subdomains_off = true;
+ break;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(variant->targets); i++) {
+ const struct a_target *target = &variant->targets[i];
+
+ variant_2.targets[i] = (struct a_target){
+ .target = target->target,
+ .open_mode = target->open_mode,
+
+ /* We denied everything, open should always fail. */
+ .expect_open_success = false,
+ /* Audit should always happen as long as open request contains read. */
+ .audit_read_blocked = !test_has_subdomains_off &&
+ target->open_mode != O_WRONLY,
+ /* Audit should always happen as long as open request contains write. */
+ .audit_write_blocked = !test_has_subdomains_off &&
+ target->open_mode != O_RDONLY,
+ };
+ }
+
+ for (i = 0; i < ARRAY_SIZE(variant->layers); i++)
+ ASSERT_EQ(0, apply_a_layer(_metadata, &variant->layers[i]));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &deny_all_layer));
+
+ audit_quiet_layout1_test_body(_metadata, self, variant_2.targets);
+}
+
+/* Uses layout1_bind hierarchy */
+FIXTURE(audit_quiet_rename)
+{
+ struct audit_filter audit_filter;
+ int audit_fd;
+};
+
+FIXTURE_SETUP(audit_quiet_rename)
+{
+ prepare_layout(_metadata);
+ create_layout1(_metadata);
+
+ set_cap(_metadata, CAP_SYS_ADMIN);
+ ASSERT_EQ(0, mount(dir_s1d2, dir_s2d2, NULL, MS_BIND, NULL));
+ clear_cap(_metadata, CAP_SYS_ADMIN);
+
+ 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);
+
+ if (getenv("DEBUG_QUIET_TESTS"))
+ debug_quiet_tests = true;
+}
+
+FIXTURE_TEARDOWN_PARENT(audit_quiet_rename)
+{
+ remove_layout1(_metadata);
+ cleanup_layout(_metadata);
+
+ /* umount(dir_s2d2)) is handled by namespace lifetime. */
+
+ remove_path(file1_s4d1);
+ remove_path(file2_s4d1);
+
+ set_cap(_metadata, CAP_AUDIT_CONTROL);
+ EXPECT_EQ(0, audit_cleanup(-1, NULL));
+ clear_cap(_metadata, CAP_AUDIT_CONTROL);
+}
+
+static void simple_quiet_rename(struct __test_metadata *const _metadata,
+ FIXTURE_DATA(audit_quiet_rename) *const self,
+ __u64 handled_access, __u64 quiet_access,
+ bool source_allow, bool dest_allow,
+ bool source_quiet, bool dest_quiet,
+ const char *source_blockers,
+ const char *dest_blockers)
+{
+ /* We will move file1_s1d1 to file1_s2d1 */
+ struct a_layer layer = {
+ .handled_access_fs = handled_access,
+ .quiet_access_fs = quiet_access,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = source_allow ? handled_access : 0,
+ .quiet = source_quiet,
+ },
+ {
+ .path = dir_s2d1,
+ .access = dest_allow ? handled_access : 0,
+ .quiet = dest_quiet,
+ },
+ },
+ };
+ struct audit_records records = {};
+ int ret, err;
+
+ /* Skip landlock_add_rule for useless rules. */
+ if (!source_allow && !source_quiet)
+ layer.rules[0].path = NULL;
+ if (!dest_allow && !dest_quiet)
+ layer.rules[1].path = NULL;
+
+ EXPECT_EQ(0, unlink(file1_s2d1));
+ EXPECT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ if (debug_quiet_tests)
+ TH_LOG("Try renameat \"%s\" to \"%s\"", file1_s1d1, file1_s2d1);
+ ret = renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1);
+ err = errno;
+ if (ret != 0 && debug_quiet_tests) {
+ TH_LOG("renameat error: %s", err == EXDEV ? "EXDEV" :
+ err == EACCES ? "EACCES" :
+ strerror(err));
+ }
+ if (source_allow && dest_allow) {
+ ASSERT_EQ(0, ret);
+ } else {
+ ASSERT_EQ(-1, ret);
+ if (handled_access & (LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE)) {
+ ASSERT_EQ(EACCES, err);
+ } else {
+ ASSERT_EQ(EXDEV, err);
+ }
+
+ if (source_blockers)
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+ source_blockers, dir_s1d1));
+ if (dest_blockers)
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+ dest_blockers, dir_s2d1));
+ }
+ /*
+ * No other logs. records.domain not checked per reasoning in
+ * audit_quiet_layout1_test_body.
+ */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, rename_ok)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+
+ simple_quiet_rename(_metadata, self, access, access, true, true, false,
+ false, NULL, NULL);
+}
+
+TEST_F(audit_quiet_rename, no_quiet)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+
+ simple_quiet_rename(_metadata, self, access, access, false, false,
+ false, false, "fs\\.remove_file,fs\\.refer",
+ "fs\\.make_reg,fs\\.refer");
+}
+
+TEST_F(audit_quiet_rename, quiet)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+
+ simple_quiet_rename(_metadata, self, access, access, false, false, true,
+ true, NULL, NULL);
+}
+
+TEST_F(audit_quiet_rename, source_no_quiet_dest_quiet)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+
+ simple_quiet_rename(_metadata, self, access, access, false, false,
+ false, true, "fs\\.remove_file,fs\\.refer", NULL);
+}
+
+TEST_F(audit_quiet_rename, source_quiet_dest_no_quiet)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+
+ simple_quiet_rename(_metadata, self, access, access, false, false, true,
+ false, NULL, "fs\\.make_reg,fs\\.refer");
+}
+
+TEST_F(audit_quiet_rename, only_quiet_refer)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+
+ simple_quiet_rename(_metadata, self, access, LANDLOCK_ACCESS_FS_REFER,
+ false, false, true, true,
+ "fs\\.remove_file,fs\\.refer",
+ "fs\\.make_reg,fs\\.refer");
+}
+
+TEST_F(audit_quiet_rename, source_allow_dest_quiet)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+
+ simple_quiet_rename(_metadata, self, access, access, true, false, false,
+ true, NULL, NULL);
+}
+
+TEST_F(audit_quiet_rename, source_quiet_dest_allow)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+
+ simple_quiet_rename(_metadata, self, access, access, false, true, true,
+ false, NULL, NULL);
+}
+
+TEST_F(audit_quiet_rename, handle_all_deny_quiet_refer)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer = {
+ .handled_access_fs = access,
+ .quiet_access_fs = LANDLOCK_ACCESS_FS_REFER,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ .quiet = true,
+ },
+ {
+ .path = dir_s2d1,
+ .access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ .quiet = true,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ EXPECT_EQ(0, unlink(file1_s2d1));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+ ASSERT_EQ(EXDEV, errno);
+
+ /* No logs */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, handle_all_deny_not_quiet_refer)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer = {
+ .handled_access_fs = access,
+ .quiet_access_fs = 0,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ .quiet = false,
+ },
+ {
+ .path = dir_s2d1,
+ .access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ .quiet = false,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ EXPECT_EQ(0, unlink(file1_s2d1));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+ ASSERT_EQ(EXDEV, errno);
+
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
+ dir_s1d1));
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
+ dir_s2d1));
+
+ /* No other logs */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, handle_all_deny_refer_quiet_source_not_quiet_dest)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer = {
+ .handled_access_fs = access,
+ .quiet_access_fs = LANDLOCK_ACCESS_FS_REFER,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ .quiet = true,
+ },
+ {
+ .path = dir_s2d1,
+ .access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE,
+ .quiet = false,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ EXPECT_EQ(0, unlink(file1_s2d1));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+ ASSERT_EQ(EXDEV, errno);
+
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd, "fs\\.refer",
+ dir_s2d1));
+
+ /* No other logs */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_same_dir)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file2_s1d1));
+ ASSERT_EQ(EACCES, errno);
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_flag_on_file_ignored)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = file1_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = file1_s2d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+ ASSERT_EQ(EACCES, errno);
+
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+ "fs\\.remove_file,fs\\.refer", dir_s1d1));
+ /* We didn't unlink destination file */
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+ "fs\\.remove_file,fs\\.make_reg,fs\\.refer",
+ dir_s2d1));
+
+ /* No other logs */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_flag_on_file_ignored_same_dir)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = file1_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = file2_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file2_s1d1));
+ ASSERT_EQ(EACCES, errno);
+
+ ASSERT_EQ(0,
+ matches_log_fs(_metadata, self->audit_fd,
+ "fs\\.remove_file,fs\\.make_reg", dir_s1d1));
+
+ /* No other logs */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, two_layers_different_quiet1)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer1 = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = access,
+ .quiet = false,
+ },
+ {
+ .path = dir_s2d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct a_layer layer2 = {
+ .handled_access_fs = access,
+ .quiet_access_fs = LANDLOCK_ACCESS_FS_REFER,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = dir_s2d1,
+ .access = access,
+ .quiet = false,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ EXPECT_EQ(0, unlink(file1_s2d1));
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer1));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer2));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+ ASSERT_EQ(EACCES, errno);
+
+ /*
+ * The youngest denial will be layer 2. Refer is quieted but we are
+ * also missing remove_file on source.
+ */
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+ "fs\\.remove_file,fs\\.refer", dir_s1d1));
+ /* No other logs */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, two_layers_different_quiet2)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer1 = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = access,
+ .quiet = false,
+ },
+ {
+ .path = dir_s2d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct a_layer layer2 = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_REFER,
+ .quiet_access_fs = LANDLOCK_ACCESS_FS_REFER,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = dir_s2d1,
+ .access = LANDLOCK_ACCESS_FS_REFER,
+ .quiet = false,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ EXPECT_EQ(0, unlink(file1_s2d1));
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer1));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer2));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+ ASSERT_EQ(EACCES, errno);
+
+ /*
+ * The youngest denial will be layer 2, but refer is quieted (and that
+ * layer does not handle any other accesses).
+ */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, two_layers_different_quiet3)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer1 = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = access,
+ .quiet = false,
+ },
+ {
+ .path = dir_s2d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct a_layer layer2 = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = dir_s2d1,
+ .access = access,
+ .quiet = false,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ EXPECT_EQ(0, unlink(file1_s2d1));
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer1));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer2));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+ ASSERT_EQ(EACCES, errno);
+
+ /*
+ * The youngest denial will be layer 2, in which everything is
+ * quieted.
+ */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename,
+ first_layer_quiet_deny_all_second_layer_not_quiet_deny_all)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer1 = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = dir_s2d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct a_layer layer2 = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {},
+ };
+ struct audit_records records = {};
+
+ EXPECT_EQ(0, unlink(file1_s2d1));
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer1));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer2));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+ ASSERT_EQ(EACCES, errno);
+
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+ "fs\\.remove_file,fs\\.refer", dir_s1d1));
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+ "fs\\.make_reg,fs\\.refer", dir_s2d1));
+ /* No other logs. */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename,
+ first_layer_quiet_deny_all_second_layer_dest_not_quiet)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer1 = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ {
+ .path = dir_s2d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct a_layer layer2 = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ EXPECT_EQ(0, unlink(file1_s2d1));
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer1));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer2));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1));
+ ASSERT_EQ(EACCES, errno);
+
+ /*
+ * Source is quieted but destination is not.
+ */
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+ "fs\\.make_reg,fs\\.refer", dir_s2d1));
+ /* No other logs. */
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, rename_xchg)
+{
+ struct a_layer layer = {
+ .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER,
+ .quiet_access_fs = LANDLOCK_ACCESS_FS_MAKE_REG,
+ .rules = { {
+ .path = dir_s1d1,
+ .access = LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER,
+ .quiet = true,
+ },
+ {
+ .path = dir_s2d1,
+ .access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER,
+ .quiet = false,
+ } },
+ };
+ struct audit_records records = {};
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d1,
+ RENAME_EXCHANGE));
+ ASSERT_EQ(EACCES, errno);
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_on_parent_mount)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s2d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ EXPECT_EQ(0, unlink(file2_s1d3));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, bind_file1_s1d3, AT_FDCWD,
+ bind_file2_s1d3));
+ ASSERT_EQ(EACCES, errno);
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_behind_mountpoint_ignored)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s1d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct audit_records records = {};
+
+ EXPECT_EQ(0, unlink(file2_s1d3));
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1, renameat(AT_FDCWD, bind_file1_s1d3, AT_FDCWD,
+ bind_file2_s1d3));
+ ASSERT_EQ(EACCES, errno);
+ ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
+ "fs\\.remove_file,fs\\.make_reg",
+ bind_dir_s1d3));
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_on_parent_mount_disconnected)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s2d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct audit_records records = {};
+ int bind_s1d3_fd;
+
+ EXPECT_EQ(0, unlink(file2_s1d3));
+
+ bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_DIRECTORY);
+ ASSERT_GE(bind_s1d3_fd, 0);
+
+ /* Make s1d3 disconnected. */
+ create_directory(_metadata, dir_s4d1);
+ ASSERT_EQ(0, renameat(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s4d2));
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1,
+ renameat(bind_s1d3_fd, file1_name, bind_s1d3_fd, file2_name));
+ ASSERT_EQ(EACCES, errno);
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
+TEST_F(audit_quiet_rename, quiet_behind_mountpoint_disconnected)
+{
+ __u64 access = LANDLOCK_ACCESS_FS_MAKE_REG |
+ LANDLOCK_ACCESS_FS_REMOVE_FILE |
+ LANDLOCK_ACCESS_FS_REFER;
+ struct a_layer layer = {
+ .handled_access_fs = access,
+ .quiet_access_fs = access,
+ .rules = {
+ {
+ .path = dir_s4d1,
+ .access = 0,
+ .quiet = true,
+ },
+ },
+ };
+ struct audit_records records = {};
+ int bind_s1d3_fd;
+
+ EXPECT_EQ(0, unlink(file2_s1d3));
+
+ bind_s1d3_fd = open(bind_dir_s1d3, O_PATH | O_DIRECTORY);
+ ASSERT_GE(bind_s1d3_fd, 0);
+
+ /* Make s1d3 disconnected. */
+ create_directory(_metadata, dir_s4d1);
+ ASSERT_EQ(0, renameat(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s4d2));
+
+ ASSERT_EQ(0, apply_a_layer(_metadata, &layer));
+
+ ASSERT_EQ(-1,
+ renameat(bind_s1d3_fd, file1_name, bind_s1d3_fd, file2_name));
+ ASSERT_EQ(EACCES, errno);
+
+ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+ ASSERT_EQ(0, records.access);
+}
+
TEST_HARNESS_MAIN
--
2.54.0
^ permalink raw reply related
* [PATCH v10 5/9] selftests/landlock: Replace hard-coded 16 with a constant
From: Tingmao Wang @ 2026-06-01 0:00 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1780272022.git.m@maowtm.org>
The next commit will reuse this number. Make it a shared constant to
future-proof changes.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
Changes in v3:
- New patch
tools/testing/selftests/landlock/audit_test.c | 2 +-
tools/testing/selftests/landlock/common.h | 2 ++
tools/testing/selftests/landlock/fs_test.c | 2 +-
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
index 132c18006d07..7044781357c0 100644
--- a/tools/testing/selftests/landlock/audit_test.c
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -79,7 +79,7 @@ TEST_F(audit, layers)
.scoped = LANDLOCK_SCOPE_SIGNAL,
};
int status, ruleset_fd, i;
- __u64(*domain_stack)[16];
+ __u64(*domain_stack)[LANDLOCK_MAX_NUM_LAYERS];
__u64 prev_dom = 3;
pid_t child;
diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
index 90551650299c..7206d5105d66 100644
--- a/tools/testing/selftests/landlock/common.h
+++ b/tools/testing/selftests/landlock/common.h
@@ -25,6 +25,8 @@
/* TEST_F_FORK() should not be used for new tests. */
#define TEST_F_FORK(fixture_name, test_name) TEST_F(fixture_name, test_name)
+#define LANDLOCK_MAX_NUM_LAYERS 16
+
static const char bin_sandbox_and_launch[] = "./sandbox-and-launch";
static const char bin_wait_pipe[] = "./wait-pipe";
static const char bin_wait_pipe_sandbox[] = "./wait-pipe-sandbox";
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index cdb47fc1fc0a..10d9355ade5f 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -1441,7 +1441,7 @@ TEST_F_FORK(layout0, max_layers)
};
const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
- for (i = 0; i < 16; i++)
+ for (i = 0; i < LANDLOCK_MAX_NUM_LAYERS; i++)
enforce_ruleset(_metadata, ruleset_fd);
for (i = 0; i < 2; i++) {
--
2.54.0
^ permalink raw reply related
* [PATCH v10 4/9] samples/landlock: Add quiet flag support to sandboxer
From: Tingmao Wang @ 2026-06-01 0:00 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1780272022.git.m@maowtm.org>
Adds ability to set which access bits to quiet via LL_*_QUIET_ACCESS (FS,
NET or SCOPED), and attach quiet flags to individual objects via
LL_*_QUIET for FS and NET.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
Changes in v10:
- Remove stray __attribute__((fallthrough)); (Thanks Justin for
spotting)
Changes in v9:
- Add udp connect / bind quiet flag support
Changes in v8:
- Rebase on top of mic/next
- populate_ruleset_net() already does not require the env var to be
present, so remove redundant comment and check above
populate_ruleset_net(ENV_NET_QUIET_NAME, ...).
Changes in v6:
- Make populate_ruleset_{fs,net} take a flags argument instead of a bool
quiet (suggested by Justin Suess)
- Fix if braces style
Changes in v3:
- Minor change to the above commit message.
Changes in v2:
- Added new environment variables to control which quiet access bits to
set on the rule, and populate quiet_access_* from it.
- Added support for quieting net rules and scoped access. Renamed patch
title.
- Increment ABI version
samples/landlock/sandboxer.c | 133 ++++++++++++++++++++++++++++++++---
1 file changed, 122 insertions(+), 11 deletions(-)
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index 94e399e6b146..73a81ecd3696 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -58,9 +58,14 @@ static inline int landlock_restrict_self(const int ruleset_fd,
#define ENV_FS_RO_NAME "LL_FS_RO"
#define ENV_FS_RW_NAME "LL_FS_RW"
+#define ENV_FS_QUIET_NAME "LL_FS_QUIET"
+#define ENV_FS_QUIET_ACCESS_NAME "LL_FS_QUIET_ACCESS"
#define ENV_TCP_BIND_NAME "LL_TCP_BIND"
#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"
+#define ENV_NET_QUIET_NAME "LL_NET_QUIET"
+#define ENV_NET_QUIET_ACCESS_NAME "LL_NET_QUIET_ACCESS"
#define ENV_SCOPED_NAME "LL_SCOPED"
+#define ENV_SCOPED_QUIET_ACCESS_NAME "LL_SCOPED_QUIET_ACCESS"
#define ENV_FORCE_LOG_NAME "LL_FORCE_LOG"
#define ENV_UDP_BIND_NAME "LL_UDP_BIND"
#define ENV_UDP_CONNECT_SEND_NAME "LL_UDP_CONNECT_SEND"
@@ -119,7 +124,7 @@ static int parse_path(char *env_path, const char ***const path_list)
/* clang-format on */
static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
- const __u64 allowed_access)
+ const __u64 allowed_access, __u32 flags)
{
int num_paths, i, ret = 1;
char *env_path_name;
@@ -169,7 +174,7 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
if (!S_ISDIR(statbuf.st_mode))
path_beneath.allowed_access &= ACCESS_FILE;
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
- &path_beneath, 0)) {
+ &path_beneath, flags)) {
fprintf(stderr,
"Failed to update the ruleset with \"%s\": %s\n",
path_list[i], strerror(errno));
@@ -187,7 +192,7 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
}
static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
- const __u64 allowed_access)
+ const __u64 allowed_access, __u32 flags)
{
int ret = 1;
char *env_port_name, *env_port_name_next, *strport;
@@ -215,7 +220,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
}
net_port.port = port;
if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &net_port, 0)) {
+ &net_port, flags)) {
fprintf(stderr,
"Failed to update the ruleset with port \"%llu\": %s\n",
net_port.port, strerror(errno));
@@ -303,6 +308,58 @@ static bool check_ruleset_scope(const char *const env_var,
/* clang-format on */
+static int add_quiet_access(__u64 *const quiet_access,
+ const __u64 handled_access,
+ const char *const env_var, const bool default_all)
+{
+ char *env_quiet_access, *env_quiet_access_next, *str_access;
+
+ if (default_all)
+ *quiet_access = handled_access;
+ else
+ *quiet_access = 0;
+
+ env_quiet_access = getenv(env_var);
+ if (!env_quiet_access)
+ return 0;
+
+ env_quiet_access = strdup(env_quiet_access);
+ env_quiet_access_next = env_quiet_access;
+ unsetenv(env_var);
+ *quiet_access = 0;
+
+ while ((str_access = strsep(&env_quiet_access_next, ENV_DELIMITER))) {
+ if (strcmp(str_access, "") == 0)
+ continue;
+ else if (strcmp(str_access, "r") == 0)
+ *quiet_access |= ACCESS_FS_ROUGHLY_READ;
+ else if (strcmp(str_access, "w") == 0)
+ *quiet_access |= ACCESS_FS_ROUGHLY_WRITE;
+ else if (strcmp(str_access, "b") == 0)
+ *quiet_access |= LANDLOCK_ACCESS_NET_BIND_TCP;
+ else if (strcmp(str_access, "c") == 0)
+ *quiet_access |= LANDLOCK_ACCESS_NET_CONNECT_TCP;
+ else if (strcmp(str_access, "ub") == 0)
+ *quiet_access |= LANDLOCK_ACCESS_NET_BIND_UDP;
+ else if (strcmp(str_access, "uc") == 0)
+ *quiet_access |= LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP;
+ else if (strcmp(str_access, "a") == 0)
+ *quiet_access |= LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;
+ else if (strcmp(str_access, "s") == 0)
+ *quiet_access |= LANDLOCK_SCOPE_SIGNAL;
+ else {
+ fprintf(stderr, "Unknown quiet access \"%s\"\n",
+ str_access);
+ free(env_quiet_access);
+ return -1;
+ }
+ }
+
+ free(env_quiet_access);
+ *quiet_access &= handled_access;
+ return 0;
+}
+
#define LANDLOCK_ABI_LAST 10
#define XSTR(s) #s
@@ -336,6 +393,22 @@ static const char help[] =
"\n"
"A sandboxer should not log denied access requests to avoid spamming logs, "
"but to test audit we can set " ENV_FORCE_LOG_NAME "=1\n"
+ ENV_FS_QUIET_NAME " and " ENV_NET_QUIET_NAME ", both optional, can then be used "
+ "to make access to some denied paths or network ports not trigger audit logging.\n"
+ ENV_FS_QUIET_ACCESS_NAME " and " ENV_NET_QUIET_ACCESS_NAME " can be used to specify "
+ "which accesses should be quieted (defaults to all):\n"
+ "* " ENV_FS_QUIET_ACCESS_NAME ": file system accesses to quiet\n"
+ " - \"r\" to quiet all file/dir read accesses\n"
+ " - \"w\" to quiet all file/dir write accesses\n"
+ "* " ENV_NET_QUIET_ACCESS_NAME ": network accesses to quiet\n"
+ " - \"b\" to quiet tcp bind denials\n"
+ " - \"c\" to quiet tcp connect denials\n"
+ " - \"ub\" to quiet udp bind denials\n"
+ " - \"uc\" to quiet udp connect / send denials\n"
+ "In addition, " ENV_SCOPED_QUIET_ACCESS_NAME " can be set to quiet all denials for "
+ "scoped actions (defaults to none).\n"
+ " - \"a\" to quiet abstract unix socket denials\n"
+ " - \"s\" to quiet signal denials\n"
"\n"
"Example:\n"
ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
@@ -368,7 +441,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP,
.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL,
+ .quiet_access_fs = 0,
+ .quiet_access_net = 0,
+ .quiet_scoped = 0,
};
+
+ bool quiet_supported = true;
int supported_restrict_flags = LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON;
int set_restrict_flags = 0;
@@ -459,6 +537,8 @@ int main(const int argc, char *const argv[], char *const *const envp)
ruleset_attr.handled_access_net &=
~(LANDLOCK_ACCESS_NET_BIND_UDP |
LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP);
+ /* Don't add quiet flags for ABI < 10 later on. */
+ quiet_supported = false;
/* Must be printed for any ABI < LANDLOCK_ABI_LAST. */
fprintf(stderr,
@@ -525,6 +605,25 @@ int main(const int argc, char *const argv[], char *const *const envp)
unsetenv(ENV_FORCE_LOG_NAME);
}
+ /*
+ * Add quiet for fs/net handled access bits. Doing this alone has no
+ * effect unless we later add quiet rules per FS_QUIET/NET_QUIET.
+ */
+ if (quiet_supported) {
+ if (add_quiet_access(&ruleset_attr.quiet_access_fs,
+ ruleset_attr.handled_access_fs,
+ ENV_FS_QUIET_ACCESS_NAME, true))
+ return 1;
+ if (add_quiet_access(&ruleset_attr.quiet_access_net,
+ ruleset_attr.handled_access_net,
+ ENV_NET_QUIET_ACCESS_NAME, true))
+ return 1;
+ if (add_quiet_access(&ruleset_attr.quiet_scoped,
+ ruleset_attr.scoped,
+ ENV_SCOPED_QUIET_ACCESS_NAME, false))
+ return 1;
+ }
+
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) {
@@ -532,30 +631,42 @@ int main(const int argc, char *const argv[], char *const *const envp)
return 1;
}
- if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
+ if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro, 0))
goto err_close_ruleset;
- }
- if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
+ if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw, 0))
goto err_close_ruleset;
+
+ /* Don't require this env to be present. */
+ if (quiet_supported && getenv(ENV_FS_QUIET_NAME)) {
+ if (populate_ruleset_fs(ENV_FS_QUIET_NAME, ruleset_fd, 0,
+ LANDLOCK_ADD_RULE_QUIET))
+ goto err_close_ruleset;
}
if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd,
- LANDLOCK_ACCESS_NET_BIND_TCP)) {
+ LANDLOCK_ACCESS_NET_BIND_TCP, 0)) {
goto err_close_ruleset;
}
if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd,
- LANDLOCK_ACCESS_NET_CONNECT_TCP)) {
+ LANDLOCK_ACCESS_NET_CONNECT_TCP, 0)) {
goto err_close_ruleset;
}
if (populate_ruleset_net(ENV_UDP_BIND_NAME, ruleset_fd,
- LANDLOCK_ACCESS_NET_BIND_UDP)) {
+ LANDLOCK_ACCESS_NET_BIND_UDP, 0)) {
goto err_close_ruleset;
}
if (populate_ruleset_net(ENV_UDP_CONNECT_SEND_NAME, ruleset_fd,
- LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)) {
+ LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP, 0)) {
goto err_close_ruleset;
}
+ if (quiet_supported) {
+ if (populate_ruleset_net(ENV_NET_QUIET_NAME, ruleset_fd, 0,
+ LANDLOCK_ADD_RULE_QUIET)) {
+ goto err_close_ruleset;
+ }
+ }
+
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("Failed to restrict privileges");
goto err_close_ruleset;
--
2.54.0
^ permalink raw reply related
* [PATCH v10 3/9] landlock: Suppress logging when quiet flag is present
From: Tingmao Wang @ 2026-06-01 0:00 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1780272022.git.m@maowtm.org>
The quietness behaviour is as documented in the previous patch.
For optional accesses, since the existing deny_masks can only store 2x4bit
of layer index, with no way to represent "no layer", we need to either
expand it or have another field to correctly handle quieting of those.
This commit uses the latter approach - we add another field to store which
optional access (of the 2) are covered by quiet rules in their respective
layers as stored in deny_masks.
We can avoid making struct landlock_file_security larger by converting the
existing fown_layer to a 4bit field. This commit does that, and adds test
to ensure that it is large enough for LANDLOCK_MAX_NUM_LAYERS-1.
Assisted-by: GitHub Copilot:claude-opus-4.7 copilot-review
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
Changes in v10:
- clang-format header file changes too
- Fix grammar in some comments
Changes in v9:
- Fix conflict
- Applied struct layer_masks changes to this as well.
- Replace 4 with HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1) in
landlock_get_quiet_optional_accesses()
- Replace 4 with HWEIGHT in (existing) get_layer_from_deny_masks as
well.
- Use optional_access_t typedef for all quiet_optional_accesses values
instead of u8
Changes in v8:
- Rebase on top of mic/next
- Populate request.rule_flags in hook_unix_find()
Changes in v7:
- Following change in commit 1, now we need to copy rule_flags into
landlock_request before calling landlock_log_denial for relevant fs
denials
- Remove left over param comment
Changes in v5:
- Update code style and comment in get_layer_from_deny_masks() and
landlock_log_denial()
- Now that rule_flags is moved into landlock_request, this version removes
the extra parameter for landlock_log_denial and gets rid of
no_rule_flags, simplifying some code.
- Fix build failure without CONFIG_AUDIT (reported by Justin Suess)
Changes in v3:
- Renamed patch title from "Check for quiet flag in landlock_log_denial"
to this given the growth.
- Moved quiet bit check after domain_exec check
- Rename, style and comment fixes suggested by Mickaël.
- Squashed patch 6/6 from v2 "Implement quiet for optional accesses" into
this one. Changes to that below:
- Refactor the quiet flag setting in get_layer_from_deny_masks() to be
more clear.
- Add KUnit tests
- Fix comments, add WARN_ON_ONCE, use __const_hweight64() as suggested by
review
- Move build_check_file_security to fs.c
- Use a typedef for quiet_optional_accesses, add static_assert, and
improve docs on landlock_get_quiet_optional_accesses.
Changes in v2:
- Supports the new quiet access masks.
- Support quieting scope requests (but not ptrace and attempted mounting
for now)
security/landlock/access.h | 5 +
security/landlock/audit.c | 261 ++++++++++++++++++++++++++++++++++---
security/landlock/audit.h | 1 +
security/landlock/domain.c | 33 +++++
security/landlock/domain.h | 4 +
security/landlock/fs.c | 29 +++++
security/landlock/fs.h | 17 ++-
security/landlock/net.c | 15 +--
8 files changed, 335 insertions(+), 30 deletions(-)
diff --git a/security/landlock/access.h b/security/landlock/access.h
index 42d8b5134358..8cc4ee3427e5 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -141,4 +141,9 @@ static inline bool access_mask_subset(access_mask_t subset,
return (subset | superset) == superset;
}
+/* A bitmask that is large enough to hold set of optional accesses. */
+typedef u8 optional_access_t;
+static_assert(BITS_PER_TYPE(optional_access_t) >=
+ HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL));
+
#endif /* _SECURITY_LANDLOCK_ACCESS_H */
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 8c56f7f6467a..2d8197cc8fe3 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -249,7 +249,9 @@ static void test_get_denied_layer(struct kunit *const test)
static size_t
get_layer_from_deny_masks(access_mask_t *const access_request,
const access_mask_t all_existing_optional_access,
- const deny_masks_t deny_masks)
+ const deny_masks_t deny_masks,
+ optional_access_t quiet_optional_accesses,
+ bool *quiet)
{
const unsigned long access_opt = all_existing_optional_access;
const unsigned long access_req = *access_request;
@@ -257,6 +259,7 @@ get_layer_from_deny_masks(access_mask_t *const access_request,
size_t youngest_layer = 0;
size_t access_index = 0;
unsigned long access_bit;
+ bool should_quiet = false;
/* This will require change with new object types. */
WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
@@ -265,20 +268,33 @@ get_layer_from_deny_masks(access_mask_t *const access_request,
BITS_PER_TYPE(access_mask_t)) {
if (access_req & BIT(access_bit)) {
const size_t layer =
- (deny_masks >> (access_index * 4)) &
+ (deny_masks >>
+ (access_index *
+ HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
(LANDLOCK_MAX_NUM_LAYERS - 1);
+ const bool layer_has_quiet =
+ !!(quiet_optional_accesses & BIT(access_index));
if (layer > youngest_layer) {
youngest_layer = layer;
missing = BIT(access_bit);
+ should_quiet = layer_has_quiet;
} else if (layer == youngest_layer) {
missing |= BIT(access_bit);
+ /*
+ * Whether the layer has rules with quiet flag covering
+ * the file accessed does not depend on the access, and so
+ * the following WARN_ON_ONCE() should not fail.
+ */
+ WARN_ON_ONCE(should_quiet && !layer_has_quiet);
+ should_quiet = layer_has_quiet;
}
}
access_index++;
}
*access_request = missing;
+ *quiet = should_quiet;
return youngest_layer;
}
@@ -288,42 +304,188 @@ static void test_get_layer_from_deny_masks(struct kunit *const test)
{
deny_masks_t deny_mask;
access_mask_t access;
+ optional_access_t quiet_optional_accesses;
+ bool quiet;
/* truncate:0 ioctl_dev:2 */
deny_mask = 0x20;
+ quiet_optional_accesses = 0;
access = LANDLOCK_ACCESS_FS_TRUNCATE;
KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(&access,
- _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask));
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* layer denying truncate: quiet, ioctl: not quiet */
+ quiet_optional_accesses = 0b01;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* Reverse order - truncate:2 ioctl_dev:0 */
+ deny_mask = 0x02;
+ quiet_optional_accesses = 0;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* layer denying truncate: quiet, ioctl: not quiet */
+ quiet_optional_accesses = 0b01;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(&access,
- _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask));
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ /* layer denying truncate: not quiet, ioctl: quiet */
+ quiet_optional_accesses = 0b10;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
/* truncate:15 ioctl_dev:15 */
deny_mask = 0xff;
+ quiet_optional_accesses = 0;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access,
+ LANDLOCK_ACCESS_FS_TRUNCATE |
+ LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* Both quiet (same layer so quietness must be the same) */
+ quiet_optional_accesses = 0b11;
access = LANDLOCK_ACCESS_FS_TRUNCATE;
KUNIT_EXPECT_EQ(test, 15,
- get_layer_from_deny_masks(&access,
- _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask));
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
KUNIT_EXPECT_EQ(test, 15,
- get_layer_from_deny_masks(&access,
- _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask));
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
KUNIT_EXPECT_EQ(test, access,
LANDLOCK_ACCESS_FS_TRUNCATE |
LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, true);
}
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
@@ -354,6 +516,22 @@ static bool is_valid_request(const struct landlock_request *const request)
return true;
}
+static access_mask_t
+pick_access_mask_for_request_type(const enum landlock_request_type type,
+ const struct access_masks access_masks)
+{
+ switch (type) {
+ case LANDLOCK_REQUEST_FS_ACCESS:
+ return access_masks.fs;
+ case LANDLOCK_REQUEST_NET_ACCESS:
+ return access_masks.net;
+ default:
+ WARN_ONCE(1, "Invalid request type %d passed to %s", type,
+ __func__);
+ return 0;
+ }
+}
+
/**
* landlock_log_denial - Create audit records related to a denial
*
@@ -367,6 +545,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
struct landlock_hierarchy *youngest_denied;
size_t youngest_layer;
access_mask_t missing;
+ bool object_quiet_flag = false, quiet_applicable_to_access = false;
if (WARN_ON_ONCE(!subject || !subject->domain ||
!subject->domain->hierarchy || !request))
@@ -382,10 +561,15 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
youngest_layer = get_denied_layer(subject->domain,
&missing,
request->layer_masks);
+ object_quiet_flag =
+ request->layer_masks->layers[youngest_layer]
+ .quiet;
} else {
youngest_layer = get_layer_from_deny_masks(
&missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
- request->deny_masks);
+ request->deny_masks,
+ request->quiet_optional_accesses,
+ &object_quiet_flag);
}
youngest_denied =
get_hierarchy(subject->domain, youngest_layer);
@@ -420,6 +604,53 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
return;
}
+ /*
+ * Checks if the object is marked quiet by the layer that denied the
+ * request. If it's a different layer that marked it as quiet, but
+ * that layer is not the one that denied the request, we should still
+ * audit log the denial.
+ */
+ if (object_quiet_flag) {
+ /*
+ * We now check if the denied requests are all covered by the
+ * layer's quiet access bits.
+ */
+ const access_mask_t quiet_mask =
+ pick_access_mask_for_request_type(
+ request->type, youngest_denied->quiet_masks);
+
+ quiet_applicable_to_access = (quiet_mask & missing) == missing;
+ } else {
+ /*
+ * Either the object is not quiet, or this is a scope request. We
+ * check request->type to distinguish between the two cases.
+ */
+ const access_mask_t quiet_mask =
+ youngest_denied->quiet_masks.scope;
+
+ switch (request->type) {
+ case LANDLOCK_REQUEST_SCOPE_SIGNAL:
+ quiet_applicable_to_access =
+ !!(quiet_mask & LANDLOCK_SCOPE_SIGNAL);
+ break;
+ case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
+ quiet_applicable_to_access =
+ !!(quiet_mask &
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ break;
+ /*
+ * Leave LANDLOCK_REQUEST_PTRACE and
+ * LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY unhandled for now - they are
+ * never quiet.
+ */
+ default:
+ break;
+ }
+ }
+
+ if (quiet_applicable_to_access)
+ return;
+
/* Uses consistent allocation flags wrt common_lsm_audit(). */
ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
AUDIT_LANDLOCK_ACCESS);
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index b85d752273ac..620f8a24291d 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -48,6 +48,7 @@ struct landlock_request {
/* Required fields for requests with deny masks. */
const access_mask_t all_existing_optional_access;
deny_masks_t deny_masks;
+ optional_access_t quiet_optional_accesses;
};
#ifdef CONFIG_AUDIT
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index d1a4d8b33ee1..60c356dacc83 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -157,6 +157,39 @@ get_layer_deny_mask(const access_mask_t all_existing_optional_access,
<< ((access_weight - 1) * HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1));
}
+/**
+ * landlock_get_quiet_optional_accesses - Get optional accesses which are
+ * "covered" by quiet rule flags.
+ *
+ * Returns a bitmask of which optional accesses are denied by layers for
+ * which the quiet flag was collected during the path walk.
+ */
+optional_access_t landlock_get_quiet_optional_accesses(
+ const access_mask_t all_existing_optional_access,
+ const deny_masks_t deny_masks, const struct layer_masks *const masks)
+{
+ const unsigned long access_opt = all_existing_optional_access;
+ size_t access_index = 0;
+ unsigned long access_bit;
+ optional_access_t quiet_optional_accesses = 0;
+
+ /* This will require change with new object types. */
+ WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
+
+ for_each_set_bit(access_bit, &access_opt,
+ BITS_PER_TYPE(access_mask_t)) {
+ const u8 layer =
+ (deny_masks >> (access_index *
+ HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
+ (LANDLOCK_MAX_NUM_LAYERS - 1);
+
+ if (masks->layers[layer].quiet)
+ quiet_optional_accesses |= BIT(access_index);
+ access_index++;
+ }
+ return quiet_optional_accesses;
+}
+
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
static void test_get_layer_deny_mask(struct kunit *const test)
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 9f560f3c3bd1..2a1660e3dea7 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -126,6 +126,10 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
const access_mask_t optional_access,
const struct layer_masks *const masks);
+optional_access_t landlock_get_quiet_optional_accesses(
+ const access_mask_t all_existing_optional_access,
+ const deny_masks_t deny_masks, const struct layer_masks *const masks);
+
int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy);
static inline void
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index cc0852f77311..a8cb3179f815 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1727,8 +1727,31 @@ get_required_file_open_access(const struct file *const file)
return access;
}
+static void build_check_file_security(void)
+{
+#ifdef CONFIG_AUDIT
+ const struct landlock_file_security file_sec = {
+ .quiet_optional_accesses = ~0,
+ .fown_layer = ~0,
+ };
+
+ /*
+ * Make sure quiet_optional_accesses has enough bits to cover all
+ * optional accesses. The use of __const_hweight64() rather than
+ * HWEIGHT() is due to GCC erroring about non-constants in
+ * BUILD_BUG_ON call when using the latter, and the use of the 64bit
+ * version is for future-proofing.
+ */
+ BUILD_BUG_ON(__const_hweight64((u64)file_sec.quiet_optional_accesses) <
+ __const_hweight64(_LANDLOCK_ACCESS_FS_OPTIONAL));
+ /* Makes sure all layers can be identified. */
+ BUILD_BUG_ON(file_sec.fown_layer < LANDLOCK_MAX_NUM_LAYERS - 1);
+#endif /* CONFIG_AUDIT */
+}
+
static int hook_file_alloc_security(struct file *const file)
{
+ build_check_file_security();
/*
* Grants all access rights, even if most of them are not checked later
* on. It is more consistent.
@@ -1805,6 +1828,10 @@ static int hook_file_open(struct file *const file)
#ifdef CONFIG_AUDIT
landlock_file(file)->deny_masks = landlock_get_deny_masks(
_LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks);
+ landlock_file(file)->quiet_optional_accesses =
+ landlock_get_quiet_optional_accesses(
+ _LANDLOCK_ACCESS_FS_OPTIONAL,
+ landlock_file(file)->deny_masks, &layer_masks);
#endif /* CONFIG_AUDIT */
if (access_mask_subset(open_access_request, allowed_access))
@@ -1841,6 +1868,7 @@ static int hook_file_truncate(struct file *const file)
.access = LANDLOCK_ACCESS_FS_TRUNCATE,
#ifdef CONFIG_AUDIT
.deny_masks = landlock_file(file)->deny_masks,
+ .quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
#endif /* CONFIG_AUDIT */
});
return -EACCES;
@@ -1880,6 +1908,7 @@ static int hook_file_ioctl_common(const struct file *const file,
.access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
#ifdef CONFIG_AUDIT
.deny_masks = landlock_file(file)->deny_masks,
+ .quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
#endif /* CONFIG_AUDIT */
});
return -EACCES;
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index cb7e654933ac..d0fca7da2466 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -63,11 +63,20 @@ struct landlock_file_security {
* _LANDLOCK_ACCESS_FS_OPTIONAL).
*/
deny_masks_t deny_masks;
+ /**
+ * @quiet_optional_accesses: Stores which optional accesses are
+ * covered by quiet rules within the layer referred to in deny_masks,
+ * one access per bit. Does not take into account whether the quiet
+ * access bits are actually set in the layer's corresponding
+ * landlock_hierarchy.
+ */
+ optional_access_t quiet_optional_accesses
+ : HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL);
/**
* @fown_layer: Layer level of @fown_subject->domain with
* LANDLOCK_SCOPE_SIGNAL.
*/
- u8 fown_layer;
+ u8 fown_layer : 4;
#endif /* CONFIG_AUDIT */
/**
@@ -82,12 +91,6 @@ struct landlock_file_security {
#ifdef CONFIG_AUDIT
-/* Makes sure all layers can be identified. */
-/* clang-format off */
-static_assert((typeof_member(struct landlock_file_security, fown_layer))~0 >=
- LANDLOCK_MAX_NUM_LAYERS);
-/* clang-format off */
-
#endif /* CONFIG_AUDIT */
/**
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 71868289748a..60894cff973e 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -241,14 +241,13 @@ static int current_check_access_socket(struct socket *const sock,
audit_net.family = address->sa_family;
audit_net.sk = sock->sk;
- landlock_log_denial(subject,
- &(struct landlock_request){
- .type = LANDLOCK_REQUEST_NET_ACCESS,
- .audit.type = LSM_AUDIT_DATA_NET,
- .audit.u.net = &audit_net,
- .access = access_request,
- .layer_masks = &layer_masks,
- });
+ landlock_log_denial(
+ subject,
+ &(struct landlock_request){ .type = LANDLOCK_REQUEST_NET_ACCESS,
+ .audit.type = LSM_AUDIT_DATA_NET,
+ .audit.u.net = &audit_net,
+ .access = access_request,
+ .layer_masks = &layer_masks });
return -EACCES;
}
--
2.54.0
^ permalink raw reply related
* [PATCH v10 2/9] landlock: Add API support and docs for the quiet flags
From: Tingmao Wang @ 2026-06-01 0:00 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1780272022.git.m@maowtm.org>
Adds the UAPI for the quiet flags feature (but not the implementation
yet).
Even though currently LANDLOCK_ADD_RULE_QUIET only affects audit
logging, in the future this can also be used as part of a supervisor
mechanism, where it will also suppress denial notifications on a
per-object basis. Thus the name is deliberately generic, as opposed to
e.g. LANDLOCK_ADD_RULE_LOG_QUIET.
According to pahole, even after adding the struct access_masks quiet_masks
in struct landlock_hierarchy, the u32 log_* bitfield still only has a size
of 2 bytes, so there's minimal wasted space.
Signed-off-by: Tingmao Wang <m@maowtm.org>
---
Changes in v9:
- Move a mistakenly included hunk into patch 1
- Doc change for sys_landlock_create_ruleset to add missing
"quiet_scoped | scoped == scoped" requirement.
- Doc changes for struct landlock_ruleset_attr, and re-wrap added bits
wider to stay consistent with the existing block.
- Other style changes from suggestions
- Added mention of this flag in the audit section of
Documentation/admin-guide/LSM/landlock.rst
- Added a block for this new flag to the "Previous limitations" section
in Documentation/userspace-api/landlock.rst
Changes in v8:
- The new Landlock ABI version is now v10 as a result of rebase.
- Allocate a rule_flags in hook_unix_find() and pass to
is_access_to_paths_allowed().
Changes in v6:
- Fix typo in doc
Changes in v5:
- Doc fixes.
- Fix build failure without CONFIG_AUDIT / CONFIG_INET (reported by Justin
Suess)
Changes in v4:
- Minor update to this commit message.
- Fix minor formatting
Changes in v3:
- Updated docs from Mickaël's suggestions.
Changes in v2:
- Per suggestion, added support for quieting only certain access bits,
controlled by extra quiet_access_* fields in the ruleset_attr.
- Added docs for the extra fields and made updates to doc changes in v1.
In particular, call out that the effect of LANDLOCK_ADD_RULE_QUIET is
independent from the access bits passed in rule_attr
- landlock_add_rule will return -EINVAL when LANDLOCK_ADD_RULE_QUIET is
used but the ruleset does not have any quiet access bits set for the
given rule type.
- ABI version bump to v8
- Syntactic and comment changes per suggestion.
Documentation/admin-guide/LSM/landlock.rst | 9 ++-
Documentation/userspace-api/landlock.rst | 11 +++
include/uapi/linux/landlock.h | 61 +++++++++++++++++
security/landlock/domain.h | 5 ++
security/landlock/fs.c | 4 +-
security/landlock/fs.h | 2 +-
security/landlock/net.c | 5 +-
security/landlock/net.h | 5 +-
security/landlock/ruleset.c | 12 +++-
security/landlock/ruleset.h | 12 +++-
security/landlock/syscalls.c | 71 +++++++++++++++-----
tools/testing/selftests/landlock/base_test.c | 2 +-
12 files changed, 168 insertions(+), 31 deletions(-)
diff --git a/Documentation/admin-guide/LSM/landlock.rst b/Documentation/admin-guide/LSM/landlock.rst
index 9923874e2156..ccc32dad1d1c 100644
--- a/Documentation/admin-guide/LSM/landlock.rst
+++ b/Documentation/admin-guide/LSM/landlock.rst
@@ -19,8 +19,10 @@ Audit
Denied access requests are logged by default for a sandboxed program if `audit`
is enabled. This default behavior can be changed with the
sys_landlock_restrict_self() flags (cf.
-Documentation/userspace-api/landlock.rst). Landlock logs can also be masked
-thanks to audit rules. Landlock can generate 2 audit record types.
+Documentation/userspace-api/landlock.rst), or suppressed on a per-object
+basis by using ``LANDLOCK_ADD_RULE_QUIET`` (ABI 10+). Landlock logs can
+also be masked thanks to audit rules. Landlock can generate 2 audit
+record types.
Record types
------------
@@ -172,7 +174,8 @@ If you get spammed with audit logs related to Landlock, this is either an
attack attempt or a bug in the security policy. We can put in place some
filters to limit noise with two complementary ways:
-- with sys_landlock_restrict_self()'s flags if we can fix the sandboxed
+- with sys_landlock_restrict_self()'s flags, or
+ ``LANDLOCK_ADD_RULE_QUIET`` (ABI 10+) if we can fix the sandboxed
programs,
- or with audit rules (see :manpage:`auditctl(8)`).
diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst
index 45861fa75685..138d504cb498 100644
--- a/Documentation/userspace-api/landlock.rst
+++ b/Documentation/userspace-api/landlock.rst
@@ -722,6 +722,17 @@ Starting with the Landlock ABI version 9, it is possible to restrict
connections to pathname UNIX domain sockets (:manpage:`unix(7)`) using
the new ``LANDLOCK_ACCESS_FS_RESOLVE_UNIX`` right.
+Quiet rule flag (ABI < 10)
+-----------------------------------------
+
+Starting with the Landlock ABI version 10, it is possible to selectively
+suppress audit logs for specific denied accesses on a per-object basis with
+the ``LANDLOCK_ADD_RULE_QUIET`` flag of sys_landlock_add_rule(), in
+combination with the ``quiet_access_fs`` and ``quiet_access_net`` fields of
+struct landlock_ruleset_attr. It is also now possible to suppress audit logs
+for scope accesses via the ``quiet_scoped`` field of struct
+landlock_ruleset_attr.
+
.. _kernel_support:
Kernel support
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index b147223efc97..90a0752b61bf 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -32,6 +32,19 @@
* *handle* a wide range or all access rights that they know about at build time
* (and that they have tested with a kernel that supported them all).
*
+ * @quiet_access_fs and @quiet_access_net are bitmasks of actions for which a
+ * denial by this layer will not trigger an audit log if the corresponding
+ * object (or its children, for filesystem rules) is marked with the "quiet" bit
+ * via %LANDLOCK_ADD_RULE_QUIET, even if logging would normally take place per
+ * landlock_restrict_self() flags. @quiet_scoped is similar, except that it
+ * does not require marking any objects as quiet - if the ruleset is created
+ * with any bits set in @quiet_scoped, then denial of such scoped resources will
+ * not trigger any log. These 3 fields are available since Landlock ABI version
+ * 10.
+ *
+ * @quiet_access_fs, @quiet_access_net and @quiet_scoped must be a subset of
+ * @handled_access_fs, @handled_access_net and @scoped respectively.
+ *
* This structure can grow in future Landlock versions.
*/
struct landlock_ruleset_attr {
@@ -51,6 +64,21 @@ struct landlock_ruleset_attr {
* resources (e.g. IPCs).
*/
__u64 scoped;
+ /**
+ * @quiet_access_fs: Bitmask of filesystem actions which should not be
+ * logged if per-object quiet flag is set.
+ */
+ __u64 quiet_access_fs;
+ /**
+ * @quiet_access_net: Bitmask of network actions which should not be
+ * logged if per-object quiet flag is set.
+ */
+ __u64 quiet_access_net;
+ /**
+ * @quiet_scoped: Bitmask of scoped actions which should not be
+ * logged.
+ */
+ __u64 quiet_scoped;
};
/**
@@ -69,6 +97,39 @@ struct landlock_ruleset_attr {
#define LANDLOCK_CREATE_RULESET_ERRATA (1U << 1)
/* clang-format on */
+/**
+ * DOC: landlock_add_rule_flags
+ *
+ * **Flags**
+ *
+ * %LANDLOCK_ADD_RULE_QUIET
+ * Together with the quiet_* fields in struct landlock_ruleset_attr,
+ * this flag controls whether Landlock will log audit messages when
+ * access to the objects covered by this rule is denied by this layer.
+ *
+ * If audit logging is enabled, when Landlock denies an access, it will
+ * suppress the audit log if all of the following are true:
+ *
+ * - this layer is the innermost layer that denied the access;
+ * - all accesses denied by this layer are part of the quiet_* fields
+ * in the related struct landlock_ruleset_attr;
+ * - the object (or one of its parents, for filesystem rules) is
+ * marked as "quiet" via %LANDLOCK_ADD_RULE_QUIET.
+ *
+ * Because logging is only suppressed by a layer if the layer denies
+ * access, a sandboxed program cannot use this flag to "hide" access
+ * denials, without denying itself the access in the first place.
+ *
+ * The effect of this flag does not depend on the value of
+ * allowed_access in the passed in rule_attr. When this flag is
+ * present, the caller is also allowed to pass in an empty
+ * allowed_access.
+ */
+
+/* clang-format off */
+#define LANDLOCK_ADD_RULE_QUIET (1U << 0)
+/* clang-format on */
+
/**
* DOC: landlock_restrict_self_flags
*
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index af100a8cd939..9f560f3c3bd1 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -111,6 +111,11 @@ struct landlock_hierarchy {
* %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON. Set to false by default.
*/
log_new_exec : 1;
+ /**
+ * @quiet_masks: Bitmasks of access that should be quieted (i.e. not
+ * logged) if the related object is marked as quiet.
+ */
+ struct access_masks quiet_masks;
#endif /* CONFIG_AUDIT */
};
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index f7c1bc64de20..cc0852f77311 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -325,7 +325,7 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
*/
int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
const struct path *const path,
- access_mask_t access_rights)
+ access_mask_t access_rights, const int flags)
{
int err;
struct landlock_id id = {
@@ -346,7 +346,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
if (IS_ERR(id.key.object))
return PTR_ERR(id.key.object);
mutex_lock(&ruleset->lock);
- err = landlock_insert_rule(ruleset, id, access_rights);
+ err = landlock_insert_rule(ruleset, id, access_rights, flags);
mutex_unlock(&ruleset->lock);
/*
* No need to check for an error because landlock_insert_rule()
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index bf9948941f2f..cb7e654933ac 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -126,6 +126,6 @@ __init void landlock_add_fs_hooks(void);
int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
const struct path *const path,
- access_mask_t access_hierarchy);
+ access_mask_t access_hierarchy, const int flags);
#endif /* _SECURITY_LANDLOCK_FS_H */
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 981a362c24db..71868289748a 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -20,7 +20,8 @@
#include "ruleset.h"
int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
- const u16 port, access_mask_t access_rights)
+ const u16 port, access_mask_t access_rights,
+ const int flags)
{
int err;
const struct landlock_id id = {
@@ -35,7 +36,7 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
~landlock_get_net_access_mask(ruleset, 0);
mutex_lock(&ruleset->lock);
- err = landlock_insert_rule(ruleset, id, access_rights);
+ err = landlock_insert_rule(ruleset, id, access_rights, flags);
mutex_unlock(&ruleset->lock);
return err;
diff --git a/security/landlock/net.h b/security/landlock/net.h
index 09960c237a13..72c47f4d6803 100644
--- a/security/landlock/net.h
+++ b/security/landlock/net.h
@@ -16,7 +16,8 @@
__init void landlock_add_net_hooks(void);
int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
- const u16 port, access_mask_t access_rights);
+ const u16 port, access_mask_t access_rights,
+ const int flags);
#else /* IS_ENABLED(CONFIG_INET) */
static inline void landlock_add_net_hooks(void)
{
@@ -24,7 +25,7 @@ static inline void landlock_add_net_hooks(void)
static inline int
landlock_append_net_rule(struct landlock_ruleset *const ruleset, const u16 port,
- access_mask_t access_rights)
+ access_mask_t access_rights, const int flags)
{
return -EAFNOSUPPORT;
}
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 91948e406e69..f01c3e14e55d 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -21,6 +21,7 @@
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
+#include <uapi/linux/landlock.h>
#include "access.h"
#include "domain.h"
@@ -255,6 +256,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
if (WARN_ON_ONCE(this->layers[0].level != 0))
return -EINVAL;
this->layers[0].access |= (*layers)[0].access;
+ this->layers[0].flags.quiet |= (*layers)[0].flags.quiet;
return 0;
}
@@ -305,12 +307,15 @@ static void build_check_layer(void)
/* @ruleset must be locked by the caller. */
int landlock_insert_rule(struct landlock_ruleset *const ruleset,
const struct landlock_id id,
- const access_mask_t access)
+ const access_mask_t access, const int flags)
{
struct landlock_layer layers[] = { {
.access = access,
/* When @level is zero, insert_rule() extends @ruleset. */
.level = 0,
+ .flags = {
+ .quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET),
+ },
} };
build_check_layer();
@@ -351,6 +356,7 @@ static int merge_tree(struct landlock_ruleset *const dst,
return -EINVAL;
layers[0].access = walker_rule->layers[0].access;
+ layers[0].flags = walker_rule->layers[0].flags;
err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
if (err)
@@ -581,6 +587,10 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
if (err)
return ERR_PTR(err);
+#ifdef CONFIG_AUDIT
+ new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
+#endif /* CONFIG_AUDIT */
+
return no_free_ptr(new_dom);
}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index f80ca487d125..d54bdb925e96 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -156,8 +156,8 @@ struct landlock_ruleset {
* @work_free: Enables to free a ruleset within a lockless
* section. This is only used by
* landlock_put_ruleset_deferred() when @usage reaches zero.
- * The fields @lock, @usage, @num_rules, @num_layers and
- * @access_masks are then unused.
+ * The fields @lock, @usage, @num_rules, @num_layers, @quiet_masks
+ * and @access_masks are then unused.
*/
struct work_struct work_free;
struct {
@@ -183,6 +183,12 @@ struct landlock_ruleset {
* non-merged ruleset (i.e. not a domain).
*/
u32 num_layers;
+ /**
+ * @quiet_masks: Stores the quiet flags for an unmerged
+ * ruleset. For a merged domain, this is stored in each
+ * layer's struct landlock_hierarchy instead.
+ */
+ struct access_masks quiet_masks;
/**
* @access_masks: Contains the subset of filesystem and
* network actions that are restricted by a ruleset.
@@ -213,7 +219,7 @@ DEFINE_FREE(landlock_put_ruleset, struct landlock_ruleset *,
int landlock_insert_rule(struct landlock_ruleset *const ruleset,
const struct landlock_id id,
- const access_mask_t access);
+ const access_mask_t access, const int flags);
struct landlock_ruleset *
landlock_merge_ruleset(struct landlock_ruleset *const parent,
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index d45469d5d464..08b6045d6926 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -105,8 +105,11 @@ static void build_check_abi(void)
ruleset_size = sizeof(ruleset_attr.handled_access_fs);
ruleset_size += sizeof(ruleset_attr.handled_access_net);
ruleset_size += sizeof(ruleset_attr.scoped);
+ ruleset_size += sizeof(ruleset_attr.quiet_access_fs);
+ ruleset_size += sizeof(ruleset_attr.quiet_access_net);
+ ruleset_size += sizeof(ruleset_attr.quiet_scoped);
BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
- BUILD_BUG_ON(sizeof(ruleset_attr) != 24);
+ BUILD_BUG_ON(sizeof(ruleset_attr) != 48);
path_beneath_size = sizeof(path_beneath_attr.allowed_access);
path_beneath_size += sizeof(path_beneath_attr.parent_fd);
@@ -193,6 +196,9 @@ const int landlock_abi_version = 10;
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
* - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small
* @size;
+ * - %EINVAL: quiet_access_fs, quiet_access_net, or quiet_scoped is not a
+ * subset of the corresponding handled_access_fs, handled_access_net, or
+ * scoped;
* - %E2BIG: @attr or @size inconsistencies;
* - %EFAULT: @attr or @size inconsistencies;
* - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
@@ -249,6 +255,21 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE)
return -EINVAL;
+ /*
+ * Check that quiet masks are subsets of the respective handled masks.
+ * Because of the checks above this is sufficient to also ensure that
+ * the quiet masks are valid access masks.
+ */
+ if ((ruleset_attr.quiet_access_fs | ruleset_attr.handled_access_fs) !=
+ ruleset_attr.handled_access_fs)
+ return -EINVAL;
+ if ((ruleset_attr.quiet_access_net | ruleset_attr.handled_access_net) !=
+ ruleset_attr.handled_access_net)
+ return -EINVAL;
+ if ((ruleset_attr.quiet_scoped | ruleset_attr.scoped) !=
+ ruleset_attr.scoped)
+ return -EINVAL;
+
/* Checks arguments and transforms to kernel struct. */
ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
ruleset_attr.handled_access_net,
@@ -256,6 +277,10 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
+ ruleset->quiet_masks.fs = ruleset_attr.quiet_access_fs;
+ ruleset->quiet_masks.net = ruleset_attr.quiet_access_net;
+ ruleset->quiet_masks.scope = ruleset_attr.quiet_scoped;
+
/* Creates anonymous FD referring to the ruleset. */
ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops,
ruleset, O_RDWR | O_CLOEXEC);
@@ -320,7 +345,7 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
}
static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
- const void __user *const rule_attr)
+ const void __user *const rule_attr, int flags)
{
struct landlock_path_beneath_attr path_beneath_attr;
struct path path;
@@ -335,9 +360,10 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
/*
* Informs about useless rule: empty allowed_access (i.e. deny rules)
- * are ignored in path walks.
+ * are ignored in path walks. However, the rule is not useless if it
+ * is there to hold a quiet flag.
*/
- if (!path_beneath_attr.allowed_access)
+ if (!flags && !path_beneath_attr.allowed_access)
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
@@ -345,6 +371,10 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
if ((path_beneath_attr.allowed_access | mask) != mask)
return -EINVAL;
+ /* Checks for useless quiet flag. */
+ if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.fs)
+ return -EINVAL;
+
/* Gets and checks the new rule. */
err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
if (err)
@@ -352,13 +382,13 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
/* Imports the new rule. */
err = landlock_append_fs_rule(ruleset, &path,
- path_beneath_attr.allowed_access);
+ path_beneath_attr.allowed_access, flags);
path_put(&path);
return err;
}
static int add_rule_net_port(struct landlock_ruleset *ruleset,
- const void __user *const rule_attr)
+ const void __user *const rule_attr, int flags)
{
struct landlock_net_port_attr net_port_attr;
int res;
@@ -371,9 +401,10 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
/*
* Informs about useless rule: empty allowed_access (i.e. deny rules)
- * are ignored by network actions.
+ * are ignored by network actions. However, the rule is not useless
+ * if it is there to hold a quiet flag.
*/
- if (!net_port_attr.allowed_access)
+ if (!flags && !net_port_attr.allowed_access)
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
@@ -381,13 +412,17 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
if ((net_port_attr.allowed_access | mask) != mask)
return -EINVAL;
+ /* Checks for useless quiet flag. */
+ if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.net)
+ return -EINVAL;
+
/* Denies inserting a rule with port greater than 65535. */
if (net_port_attr.port > U16_MAX)
return -EINVAL;
/* Imports the new rule. */
return landlock_append_net_rule(ruleset, net_port_attr.port,
- net_port_attr.allowed_access);
+ net_port_attr.allowed_access, flags);
}
/**
@@ -398,7 +433,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
* @rule_type: Identify the structure type pointed to by @rule_attr:
* %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
* @rule_attr: Pointer to a rule (matching the @rule_type).
- * @flags: Must be 0.
+ * @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET.
*
* This system call enables to define a new rule and add it to an existing
* ruleset.
@@ -408,20 +443,25 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
* - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not
* supported by the running kernel;
- * - %EINVAL: @flags is not 0;
+ * - %EINVAL: @flags is not valid;
* - %EINVAL: The rule accesses are inconsistent (i.e.
* &landlock_path_beneath_attr.allowed_access or
* &landlock_net_port_attr.allowed_access is not a subset of the ruleset
* handled accesses)
* - %EINVAL: &landlock_net_port_attr.port is greater than 65535;
+ * - %EINVAL: LANDLOCK_ADD_RULE_QUIET is passed but the ruleset has no
+ * quiet access bits set for the corresponding rule type.
* - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access is
- * 0);
+ * 0) and no flags;
* - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
* member of @rule_attr is not a file descriptor as expected;
* - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
* @rule_attr is not the expected file descriptor type;
* - %EPERM: @ruleset_fd has no write access to the underlying ruleset;
* - %EFAULT: @rule_attr was not a valid address.
+ *
+ * .. kernel-doc:: include/uapi/linux/landlock.h
+ * :identifiers: landlock_add_rule_flags
*/
SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
const enum landlock_rule_type, rule_type,
@@ -432,8 +472,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
if (!is_initialized())
return -EOPNOTSUPP;
- /* No flag for now. */
- if (flags)
+ if (flags && flags != LANDLOCK_ADD_RULE_QUIET)
return -EINVAL;
/* Gets and checks the ruleset. */
@@ -443,9 +482,9 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
switch (rule_type) {
case LANDLOCK_RULE_PATH_BENEATH:
- return add_rule_path_beneath(ruleset, rule_attr);
+ return add_rule_path_beneath(ruleset, rule_attr, flags);
case LANDLOCK_RULE_NET_PORT:
- return add_rule_net_port(ruleset, rule_attr);
+ return add_rule_net_port(ruleset, rule_attr, flags);
default:
return -EINVAL;
}
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 6c8113c2ded1..84e91fcaa1b2 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -201,7 +201,7 @@ TEST(add_rule_checks_ordering)
ASSERT_LE(0, ruleset_fd);
/* Checks invalid flags. */
- ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1));
+ ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 100));
ASSERT_EQ(EINVAL, errno);
/* Checks invalid ruleset FD. */
--
2.54.0
^ permalink raw reply related
* [PATCH v10 1/9] landlock: Add a place for flags to layer rules
From: Tingmao Wang @ 2026-06-01 0:00 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
Abhinav Saxena, linux-security-module
In-Reply-To: <cover.1780272022.git.m@maowtm.org>
To avoid unnecessarily increasing the size of struct landlock_layer, we
make the layer level a u8 and use the space to store the flags struct.
struct layer_access_masks is renamed to struct layer_masks, and a new
field is added to track whether a quiet flag rule is seen for each
layer. Through use of bitfields, this does not increase the size of the
struct.
Cc: Justin Suess <utilityemal77@gmail.com>
Assisted-by: GitHub Copilot:claude-opus-4.7 copilot-review
Signed-off-by: Tingmao Wang <m@maowtm.org>
Co-developed-by: Justin Suess <utilityemal77@gmail.com>
Signed-off-by: Justin Suess <utilityemal77@gmail.com>
Tested-by: Justin Suess <utilityemal77@gmail.com>
---
Changes in v10:
- Doc for struct layer_mask members
- clang-format header file changes
- Add Tested-by for Justin Suess
Changes in v9:
- Move a hunk from patch 2 to here
- Fix comment and format
- Renamed struct layer_access_masks to struct layer_masks, and moved the
content of struct collected_rule_flags into this struct, getting rid
of the extra struct collected_rule_flags and function parameters.
This is following a discussion in [3]. The flag is now initialized in
landlock_init_layer_masks as false.
- Thus also removed now unnecessary layer_mask_t
Changes in v8:
- Rebase on top of mic/next
- Add Co-developed-by: Justin Suess for handling this rebase initially
- layer_mask_t was removed in [1] but we still need it for the
collected_rule_flags. Rather than using raw u16, I've chosen to
re-define it back in ruleset.h (it was in access.h).
Changes in v7:
- Take rule_flags separately from landlock_request in
is_access_to_paths_allowed to avoid writing to the landlock_request
variable if CONFIG_AUDIT is disabled (to enable compiler elision).
- Due to the above change, we don't need rule_flags in landlock_request in
this commit anymore (will be added later).
Changes in v6:
- Rebased to include the revised disconnected directory handling changes
(without the "reverting" behaviour)
Changes in v5:
- Move rule_flags into landlock_request. This lets us get rid of the
extra parameters to is_access_to_paths_allowed (and later on,
landlock_log_denial), and thus less code changes.
Changes in v3:
- Comment changes, move local variables, simplify if branch
Changes in v2:
- Comment changes
- Rebased to include disconnected directory handling changes on mic/next
and add backing up of collected_rule_flags.
[1]: https://lore.kernel.org/all/20260125195853.109967-1-gnoack3000@gmail.com/
[2]: https://lore.kernel.org/all/20251221194301.247484-1-utilityemal77@gmail.com/
[3]: https://lore.kernel.org/all/20260524.eFiz4hahrami@digikod.net/
security/landlock/access.h | 39 +++++++---
security/landlock/audit.c | 20 ++---
security/landlock/audit.h | 2 +-
security/landlock/domain.c | 19 ++---
security/landlock/domain.h | 2 +-
security/landlock/fs.c | 147 +++++++++++++++++++-----------------
security/landlock/limits.h | 3 +
security/landlock/net.c | 2 +-
security/landlock/ruleset.c | 33 +++++---
security/landlock/ruleset.h | 17 ++++-
10 files changed, 173 insertions(+), 111 deletions(-)
diff --git a/security/landlock/access.h b/security/landlock/access.h
index c19d5bc13944..42d8b5134358 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -62,18 +62,39 @@ static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
sizeof(typeof_member(union access_masks_all, all)));
/**
- * struct layer_access_masks - A boolean matrix of layers and access rights
- *
- * This has a bit for each combination of layer numbers and access rights.
- * During access checks, it is used to represent the access rights for each
- * layer which still need to be fulfilled. When all bits are 0, the access
- * request is considered to be fulfilled.
+ * struct layer_mask - The unfulfilled access rights and rule flags for
+ * a layer.
+ */
+struct layer_mask {
+ /**
+ * @access: During access checks, this is used to represent the access
+ * rights for each layer which still need to be fulfilled. When all
+ * bits are 0, the access request is allowed by this layer.
+ */
+ access_mask_t access : LANDLOCK_NUM_ACCESS_MAX;
+#ifdef CONFIG_AUDIT
+ /**
+ * @quiet: Whether we have encountered a rule with the quiet flag for
+ * this layer. Used to control audit logging.
+ */
+ bool quiet : 1;
+#endif /* CONFIG_AUDIT */
+};
+
+/*
+ * Make sure that we don't increase the size of struct layer_mask when
+ * storing rule flags.
+ */
+static_assert(sizeof(struct layer_mask) == sizeof(access_mask_t));
+
+/**
+ * struct layer_masks - An array of struct layer_mask, one per layer.
*/
-struct layer_access_masks {
+struct layer_masks {
/**
- * @access: The unfulfilled access rights for each layer.
+ * @layers: The unfulfilled access rights for each layer.
*/
- access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
+ struct layer_mask layers[LANDLOCK_MAX_NUM_LAYERS];
};
/*
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 851647197a01..8c56f7f6467a 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -187,11 +187,11 @@ static void test_get_hierarchy(struct kunit *const test)
/* Get the youngest layer that denied the access_request. */
static size_t get_denied_layer(const struct landlock_ruleset *const domain,
access_mask_t *const access_request,
- const struct layer_access_masks *masks)
+ const struct layer_masks *masks)
{
- for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) {
- if (masks->access[i] & *access_request) {
- *access_request &= masks->access[i];
+ for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
+ if (masks->layers[i].access & *access_request) {
+ *access_request &= masks->layers[i].access;
return i;
}
}
@@ -208,12 +208,12 @@ static void test_get_denied_layer(struct kunit *const test)
const struct landlock_ruleset dom = {
.num_layers = 5,
};
- const struct layer_access_masks masks = {
- .access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
- LANDLOCK_ACCESS_FS_READ_DIR,
- .access[1] = LANDLOCK_ACCESS_FS_READ_FILE |
- LANDLOCK_ACCESS_FS_READ_DIR,
- .access[2] = LANDLOCK_ACCESS_FS_REMOVE_DIR,
+ const struct layer_masks masks = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
+ LANDLOCK_ACCESS_FS_READ_DIR,
+ .layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE |
+ LANDLOCK_ACCESS_FS_READ_DIR,
+ .layers[2].access = LANDLOCK_ACCESS_FS_REMOVE_DIR,
};
access_mask_t access;
diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 56778331b58c..b85d752273ac 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -43,7 +43,7 @@ struct landlock_request {
access_mask_t access;
/* Required fields for requests with layer masks. */
- const struct layer_access_masks *layer_masks;
+ const struct layer_masks *layer_masks;
/* Required fields for requests with deny masks. */
const access_mask_t all_existing_optional_access;
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 5dd06f7c2312..d1a4d8b33ee1 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -184,7 +184,7 @@ static void test_get_layer_deny_mask(struct kunit *const test)
deny_masks_t
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
const access_mask_t optional_access,
- const struct layer_access_masks *const masks)
+ const struct layer_masks *const masks)
{
const unsigned long access_opt = optional_access;
unsigned long access_bit;
@@ -201,8 +201,9 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
if (WARN_ON_ONCE(!access_opt))
return 0;
- for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) {
- const access_mask_t denied = masks->access[i] & optional_access;
+ for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
+ const access_mask_t denied = masks->layers[i].access &
+ optional_access;
const unsigned long newly_denied = denied & ~all_denied;
if (!newly_denied)
@@ -222,12 +223,12 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
static void test_landlock_get_deny_masks(struct kunit *const test)
{
- const struct layer_access_masks layers1 = {
- .access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
- LANDLOCK_ACCESS_FS_IOCTL_DEV,
- .access[1] = LANDLOCK_ACCESS_FS_TRUNCATE,
- .access[2] = LANDLOCK_ACCESS_FS_IOCTL_DEV,
- .access[9] = LANDLOCK_ACCESS_FS_EXECUTE,
+ const struct layer_masks layers1 = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
+ LANDLOCK_ACCESS_FS_IOCTL_DEV,
+ .layers[1].access = LANDLOCK_ACCESS_FS_TRUNCATE,
+ .layers[2].access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
+ .layers[9].access = LANDLOCK_ACCESS_FS_EXECUTE,
};
KUNIT_EXPECT_EQ(test, 0x1,
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 35cac8f6daee..af100a8cd939 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -119,7 +119,7 @@ struct landlock_hierarchy {
deny_masks_t
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
const access_mask_t optional_access,
- const struct layer_access_masks *const masks);
+ const struct layer_masks *const masks);
int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy);
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index edaa52572cbd..f7c1bc64de20 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -406,15 +406,15 @@ static const struct access_masks any_fs = {
* src_parent would result in having the same or fewer access rights if it were
* moved under new_parent.
*/
-static bool may_refer(const struct layer_access_masks *const src_parent,
- const struct layer_access_masks *const src_child,
- const struct layer_access_masks *const new_parent,
+static bool may_refer(const struct layer_masks *const src_parent,
+ const struct layer_masks *const src_child,
+ const struct layer_masks *const new_parent,
const bool child_is_dir)
{
- for (size_t i = 0; i < ARRAY_SIZE(new_parent->access); i++) {
- access_mask_t child_access = src_parent->access[i] &
- src_child->access[i];
- access_mask_t parent_access = new_parent->access[i];
+ for (size_t i = 0; i < ARRAY_SIZE(new_parent->layers); i++) {
+ access_mask_t child_access = src_parent->layers[i].access &
+ src_child->layers[i].access;
+ access_mask_t parent_access = new_parent->layers[i].access;
if (!child_is_dir) {
child_access &= ACCESS_FILE;
@@ -436,11 +436,11 @@ static bool may_refer(const struct layer_access_masks *const src_parent,
* that child2 may be used from parent2 to parent1 without increasing its access
* rights), false otherwise.
*/
-static bool no_more_access(const struct layer_access_masks *const parent1,
- const struct layer_access_masks *const child1,
+static bool no_more_access(const struct layer_masks *const parent1,
+ const struct layer_masks *const child1,
const bool child1_is_dir,
- const struct layer_access_masks *const parent2,
- const struct layer_access_masks *const child2,
+ const struct layer_masks *const parent2,
+ const struct layer_masks *const child2,
const bool child2_is_dir)
{
if (!may_refer(parent1, child1, parent2, child1_is_dir))
@@ -459,25 +459,25 @@ static bool no_more_access(const struct layer_access_masks *const parent1,
static void test_no_more_access(struct kunit *const test)
{
- const struct layer_access_masks rx0 = {
- .access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
- LANDLOCK_ACCESS_FS_READ_FILE,
+ const struct layer_masks rx0 = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
+ LANDLOCK_ACCESS_FS_READ_FILE,
};
- const struct layer_access_masks mx0 = {
- .access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
- LANDLOCK_ACCESS_FS_MAKE_REG,
+ const struct layer_masks mx0 = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
+ LANDLOCK_ACCESS_FS_MAKE_REG,
};
- const struct layer_access_masks x0 = {
- .access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
+ const struct layer_masks x0 = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
};
- const struct layer_access_masks x1 = {
- .access[1] = LANDLOCK_ACCESS_FS_EXECUTE,
+ const struct layer_masks x1 = {
+ .layers[1].access = LANDLOCK_ACCESS_FS_EXECUTE,
};
- const struct layer_access_masks x01 = {
- .access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
- .access[1] = LANDLOCK_ACCESS_FS_EXECUTE,
+ const struct layer_masks x01 = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
+ .layers[1].access = LANDLOCK_ACCESS_FS_EXECUTE,
};
- const struct layer_access_masks allows_all = {};
+ const struct layer_masks allows_all = {};
/* Checks without restriction. */
NMA_TRUE(&x0, &allows_all, false, &allows_all, NULL, false);
@@ -565,9 +565,13 @@ static void test_no_more_access(struct kunit *const test)
#undef NMA_TRUE
#undef NMA_FALSE
-static bool is_layer_masks_allowed(const struct layer_access_masks *masks)
+static bool is_layer_masks_allowed(const struct layer_masks *masks)
{
- return mem_is_zero(&masks->access, sizeof(masks->access));
+ for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
+ if (masks->layers[i].access)
+ return false;
+ }
+ return true;
}
/*
@@ -576,16 +580,16 @@ static bool is_layer_masks_allowed(const struct layer_access_masks *masks)
* Returns true if the request is allowed, false otherwise.
*/
static bool scope_to_request(const access_mask_t access_request,
- struct layer_access_masks *masks)
+ struct layer_masks *masks)
{
bool saw_unfulfilled_access = false;
if (WARN_ON_ONCE(!masks))
return true;
- for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) {
- masks->access[i] &= access_request;
- if (masks->access[i])
+ for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
+ masks->layers[i].access &= access_request;
+ if (masks->layers[i].access)
saw_unfulfilled_access = true;
}
return !saw_unfulfilled_access;
@@ -596,41 +600,46 @@ static bool scope_to_request(const access_mask_t access_request,
static void test_scope_to_request_with_exec_none(struct kunit *const test)
{
/* Allows everything. */
- struct layer_access_masks masks = {};
+ struct layer_masks masks = {};
/* Checks and scopes with execute. */
KUNIT_EXPECT_TRUE(test,
scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE, &masks));
- KUNIT_EXPECT_EQ(test, 0, masks.access[0]);
+ KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[0].access);
}
static void test_scope_to_request_with_exec_some(struct kunit *const test)
{
/* Denies execute and write. */
- struct layer_access_masks masks = {
- .access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
- .access[1] = LANDLOCK_ACCESS_FS_WRITE_FILE,
+ struct layer_masks masks = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
+ .layers[1].access = LANDLOCK_ACCESS_FS_WRITE_FILE,
};
/* Checks and scopes with execute. */
KUNIT_EXPECT_FALSE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE,
&masks));
- KUNIT_EXPECT_EQ(test, LANDLOCK_ACCESS_FS_EXECUTE, masks.access[0]);
- KUNIT_EXPECT_EQ(test, 0, masks.access[1]);
+ /*
+ * These casts to access_mask_t are needed because typeof(), used in
+ * KUNIT_EXPECT_EQ(), does not work on bitfields.
+ */
+ KUNIT_EXPECT_EQ(test, LANDLOCK_ACCESS_FS_EXECUTE,
+ (access_mask_t)masks.layers[0].access);
+ KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[1].access);
}
static void test_scope_to_request_without_access(struct kunit *const test)
{
/* Denies execute and write. */
- struct layer_access_masks masks = {
- .access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
- .access[1] = LANDLOCK_ACCESS_FS_WRITE_FILE,
+ struct layer_masks masks = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
+ .layers[1].access = LANDLOCK_ACCESS_FS_WRITE_FILE,
};
/* Checks and scopes without access request. */
KUNIT_EXPECT_TRUE(test, scope_to_request(0, &masks));
- KUNIT_EXPECT_EQ(test, 0, masks.access[0]);
- KUNIT_EXPECT_EQ(test, 0, masks.access[1]);
+ KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[0].access);
+ KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[1].access);
}
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
@@ -639,15 +648,15 @@ static void test_scope_to_request_without_access(struct kunit *const test)
* Returns true if there is at least one access right different than
* LANDLOCK_ACCESS_FS_REFER.
*/
-static bool is_eacces(const struct layer_access_masks *masks,
+static bool is_eacces(const struct layer_masks *masks,
const access_mask_t access_request)
{
if (!masks)
return false;
- for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) {
+ for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
/* LANDLOCK_ACCESS_FS_REFER alone must return -EXDEV. */
- if (masks->access[i] & access_request &
+ if (masks->layers[i].access & access_request &
~LANDLOCK_ACCESS_FS_REFER)
return true;
}
@@ -661,7 +670,7 @@ static bool is_eacces(const struct layer_access_masks *masks,
static void test_is_eacces_with_none(struct kunit *const test)
{
- const struct layer_access_masks masks = {};
+ const struct layer_masks masks = {};
IE_FALSE(&masks, 0);
IE_FALSE(&masks, LANDLOCK_ACCESS_FS_REFER);
@@ -671,8 +680,8 @@ static void test_is_eacces_with_none(struct kunit *const test)
static void test_is_eacces_with_refer(struct kunit *const test)
{
- const struct layer_access_masks masks = {
- .access[0] = LANDLOCK_ACCESS_FS_REFER,
+ const struct layer_masks masks = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_REFER,
};
IE_FALSE(&masks, 0);
@@ -683,8 +692,8 @@ static void test_is_eacces_with_refer(struct kunit *const test)
static void test_is_eacces_with_write(struct kunit *const test)
{
- const struct layer_access_masks masks = {
- .access[0] = LANDLOCK_ACCESS_FS_WRITE_FILE,
+ const struct layer_masks masks = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_WRITE_FILE,
};
IE_FALSE(&masks, 0);
@@ -743,11 +752,11 @@ static bool
is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
const struct path *const path,
const access_mask_t access_request_parent1,
- struct layer_access_masks *layer_masks_parent1,
+ struct layer_masks *layer_masks_parent1,
struct landlock_request *const log_request_parent1,
struct dentry *const dentry_child1,
const access_mask_t access_request_parent2,
- struct layer_access_masks *layer_masks_parent2,
+ struct layer_masks *layer_masks_parent2,
struct landlock_request *const log_request_parent2,
struct dentry *const dentry_child2)
{
@@ -755,9 +764,9 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
child1_is_directory = true, child2_is_directory = true;
struct path walker_path;
access_mask_t access_masked_parent1, access_masked_parent2;
- struct layer_access_masks _layer_masks_child1, _layer_masks_child2;
- struct layer_access_masks *layer_masks_child1 = NULL,
- *layer_masks_child2 = NULL;
+ struct layer_masks _layer_masks_child1, _layer_masks_child2;
+ struct layer_masks *layer_masks_child1 = NULL,
+ *layer_masks_child2 = NULL;
if (!access_request_parent1 && !access_request_parent2)
return true;
@@ -797,6 +806,10 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
}
if (unlikely(dentry_child1)) {
+ /*
+ * Get the layer masks for the child dentries for use by domain
+ * check later.
+ */
if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
&_layer_masks_child1,
LANDLOCK_KEY_INODE))
@@ -952,7 +965,7 @@ static int current_check_access_path(const struct path *const path,
};
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), masks, NULL);
- struct layer_access_masks layer_masks;
+ struct layer_masks layer_masks;
struct landlock_request request = {};
if (!subject)
@@ -1029,7 +1042,7 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
const struct dentry *const mnt_root,
struct dentry *dir,
- struct layer_access_masks *layer_masks_dom)
+ struct layer_masks *layer_masks_dom)
{
bool ret = false;
@@ -1135,8 +1148,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
access_mask_t access_request_parent1, access_request_parent2;
struct path mnt_dir;
struct dentry *old_parent;
- struct layer_access_masks layer_masks_parent1 = {},
- layer_masks_parent2 = {};
+ struct layer_masks layer_masks_parent1 = {}, layer_masks_parent2 = {};
struct landlock_request request1 = {}, request2 = {};
if (!subject)
@@ -1202,7 +1214,6 @@ static int current_check_refer_path(struct dentry *const old_dentry,
allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
new_dir->dentry,
&layer_masks_parent2);
-
if (allow_parent1 && allow_parent2)
return 0;
@@ -1580,7 +1591,7 @@ static int hook_path_truncate(const struct path *const path)
*/
static void unmask_scoped_access(const struct landlock_ruleset *const client,
const struct landlock_ruleset *const server,
- struct layer_access_masks *const masks,
+ struct layer_masks *const masks,
const access_mask_t access)
{
int client_layer, server_layer;
@@ -1621,9 +1632,9 @@ static void unmask_scoped_access(const struct landlock_ruleset *const client,
server_walker = server_walker->parent;
for (; client_layer >= 0; client_layer--) {
- if (masks->access[client_layer] & access &&
+ if (masks->layers[client_layer].access & access &&
client_walker == server_walker)
- masks->access[client_layer] &= ~access;
+ masks->layers[client_layer].access &= ~access;
client_walker = client_walker->parent;
server_walker = server_walker->parent;
@@ -1635,7 +1646,7 @@ static int hook_unix_find(const struct path *const path, struct sock *other,
{
const struct landlock_ruleset *dom_other;
const struct landlock_cred_security *subject;
- struct layer_access_masks layer_masks;
+ struct layer_masks layer_masks;
struct landlock_request request = {};
static const struct access_masks fs_resolve_unix = {
.fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
@@ -1739,7 +1750,7 @@ static bool is_device(const struct file *const file)
static int hook_file_open(struct file *const file)
{
- struct layer_access_masks layer_masks = {};
+ struct layer_masks layer_masks = {};
access_mask_t open_access_request, full_access_request, allowed_access,
optional_access;
const struct landlock_cred_security *const subject =
@@ -1780,8 +1791,8 @@ static int hook_file_open(struct file *const file)
* are still unfulfilled in any of the layers.
*/
allowed_access = full_access_request;
- for (size_t i = 0; i < ARRAY_SIZE(layer_masks.access); i++)
- allowed_access &= ~layer_masks.access[i];
+ for (size_t i = 0; i < ARRAY_SIZE(layer_masks.layers); i++)
+ allowed_access &= ~layer_masks.layers[i].access;
}
/*
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index a4d908b240a2..08d5f2f6d321 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -31,6 +31,9 @@
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
+#define LANDLOCK_NUM_ACCESS_MAX \
+ MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE)
+
#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC
#define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
diff --git a/security/landlock/net.c b/security/landlock/net.c
index db2046a89a9a..981a362c24db 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -48,7 +48,7 @@ static int current_check_access_socket(struct socket *const sock,
bool connecting)
{
__be16 port;
- struct layer_access_masks layer_masks = {};
+ struct layer_masks layer_masks = {};
const struct landlock_rule *rule;
struct landlock_id id = {
.type = LANDLOCK_KEY_NET_PORT,
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 181df7736bb9..91948e406e69 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -628,7 +628,7 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
* remaining unfulfilled access rights and masks has no leftover set bits).
*/
bool landlock_unmask_layers(const struct landlock_rule *const rule,
- struct layer_access_masks *masks)
+ struct layer_masks *masks)
{
if (!masks)
return true;
@@ -649,11 +649,17 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
const struct landlock_layer *const layer = &rule->layers[i];
/* Clear the bits where the layer in the rule grants access. */
- masks->access[layer->level - 1] &= ~layer->access;
+ masks->layers[layer->level - 1].access &= ~layer->access;
+
+#ifdef CONFIG_AUDIT
+ /* Collect rule flags for each layer. */
+ if (layer->flags.quiet)
+ masks->layers[layer->level - 1].quiet = true;
+#endif /* CONFIG_AUDIT */
}
- for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) {
- if (masks->access[i])
+ for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
+ if (masks->layers[i].access)
return false;
}
return true;
@@ -668,6 +674,7 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
*
* Populates @masks such that for each access right in @access_request,
* the bits for all the layers are set where this access right is handled.
+ * Rule flags are also zeroed.
*
* @domain: The domain that defines the current restrictions.
* @access_request: The requested access rights to check.
@@ -680,7 +687,7 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
access_mask_t
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
const access_mask_t access_request,
- struct layer_access_masks *const masks,
+ struct layer_masks *const masks,
const enum landlock_key_type key_type)
{
access_mask_t handled_accesses = 0;
@@ -709,11 +716,19 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain,
for (size_t i = 0; i < domain->num_layers; i++) {
const access_mask_t handled = get_access_mask(domain, i);
- masks->access[i] = access_request & handled;
- handled_accesses |= masks->access[i];
+ masks->layers[i].access = access_request & handled;
+ handled_accesses |= masks->layers[i].access;
+#ifdef CONFIG_AUDIT
+ masks->layers[i].quiet = false;
+#endif /* CONFIG_AUDIT */
+ }
+ for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
+ i++) {
+ masks->layers[i].access = 0;
+#ifdef CONFIG_AUDIT
+ masks->layers[i].quiet = false;
+#endif /* CONFIG_AUDIT */
}
- for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->access); i++)
- masks->access[i] = 0;
return handled_accesses;
}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 889f4b30301a..f80ca487d125 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -29,7 +29,18 @@ struct landlock_layer {
/**
* @level: Position of this layer in the layer stack. Starts from 1.
*/
- u16 level;
+ u8 level;
+ /**
+ * @flags: Bitfield for special flags attached to this rule.
+ */
+ struct {
+ /**
+ * @quiet: Suppresses denial audit logs for the object covered by
+ * this rule in this domain. For filesystem rules, this inherits
+ * down the file hierarchy.
+ */
+ bool quiet : 1;
+ } flags;
/**
* @access: Bitfield of allowed actions on the kernel object. They are
* relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ).
@@ -302,12 +313,12 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
}
bool landlock_unmask_layers(const struct landlock_rule *const rule,
- struct layer_access_masks *masks);
+ struct layer_masks *masks);
access_mask_t
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
const access_mask_t access_request,
- struct layer_access_masks *masks,
+ struct layer_masks *masks,
const enum landlock_key_type key_type);
#endif /* _SECURITY_LANDLOCK_RULESET_H */
--
2.54.0
^ permalink raw reply related
* [PATCH v10 0/9] Implement LANDLOCK_ADD_RULE_QUIET
From: Tingmao Wang @ 2026-06-01 0:00 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Tingmao Wang, Günther Noack, Justin Suess, Jan Kara,
Abhinav Saxena, linux-security-module
Hi,
This is the v10 of the "quiet flag" series, implementing the feature as
proposed in [1].
v9: https://lore.kernel.org/all/cover.1779843375.git.m@maowtm.org/
v8: https://lore.kernel.org/all/cover.1775490344.git.m@maowtm.org/
v7: https://lore.kernel.org/all/cover.1766330134.git.m@maowtm.org/
v6: https://lore.kernel.org/all/cover.1765040503.git.m@maowtm.org/
v5: https://lore.kernel.org/all/cover.1763931318.git.m@maowtm.org/
v4: https://lore.kernel.org/all/cover.1763330228.git.m@maowtm.org/
v3: https://lore.kernel.org/all/cover.1761511023.git.m@maowtm.org/
v2: https://lore.kernel.org/all/cover.1759686613.git.m@maowtm.org/
v1: https://lore.kernel.org/all/cover.1757376311.git.m@maowtm.org/
v9..v10:
- clang-format on .h files
- doc changes
- fix grammar in various comments
- remove stray __attribute__((fallthrough));
(Kept ABI version at 10)
All text following this line is unchanged
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
v8..v9:
- Refactor to store the collected rule flags in layer_masks instead
(renamed from layer_access_masks). Got rid of layer_mask_t again.
- Rebase sandboxer and net_tests on top of UDP support, resolving
conflicts
- Additional small changes, noted in each patch
v7..v8:
- Rebase to mic/next
- Re-introduced layer_mask_t due to need in first patch
- Plumb through rule flags in hook_unix_find()
- Some selftests patches were not properly clang-format'd, fixed now.
- Minor env var handling change in sandboxer
- Fix selftests use of audit_count_records() without EXPECT_EQ
v6..v7:
- Remove "landlock: Fix wrong type usage" (merged)
- Revert back to taking rule_flags separately from landlock_request until
we call landlock_log_denial (https://lore.kernel.org/all/20251219.ahn3aiJuKahb@digikod.net/)
- Rebase to mic/next
v5..v6 rebases on top of the new simpler disconnected directory handling,
change some bools into u32, and fix some typo and style.
v4..v5 addresses review feedbacks, most significantly:
- reduces code changes by pushing rule_flags into landlock_request.
- adding test cases for two layers handling different access bits.
v3..v4 is a one-character formatting change, plus more tests.
We now have 5 patches for the selftest - I'm happy to squash it into one
depending on preference (and happy for Mickaël to do the squash if no
other feedback):
- selftests/landlock: Replace hard-coded 16 with a constant
- selftests/landlock: add tests for quiet flag with fs rules
- selftests/landlock: add tests for quiet flag with net rules
- selftests/landlock: Add tests for quiet flag with scope
- selftests/landlock: Add tests for invalid use of quiet flag
v2..v3:
Not much has changed in the actual functionality except various comment,
typing, asserts and general style fixes based on feedback. The major new
thing here is tests (a bit of KUnit squashed into the optional access
commit, a lot of selftests especially in fs_tests.c).
The added fs_tests should exercise code path for optional and non-optional
access, renames, and mountpoint and disconnected directory handling. I
will add the above missing bits to v4.
Removed:
- "Implement quiet for optional accesses"
(squashed into "landlock: Suppress logging when quiet flag is present")
Old feature summary below:
The quiet flag allows a sandboxer to suppress audit logs for uninteresting
denials. The flag can be set on objects and inherits downward in the
filesystem hierarchy. On a denial, the youngest denying layer's quiet
flag setting decides whether to audit. The motivation for this feature is
to reduce audit noise, and also prepare for a future supervisor feature
which will use this bit to suppress supervisor notifications.
This patch introduces a new quiet access mask in the ruleset_attr, which
gets eventually stored in the hierarchy. This allows the user to specify
which access should be affected by quiet bits. One can then, for example,
make it such that read accesses to certain files are not audited (but
still denied), but all writes are still audited, regardless of location.
The sandboxer is extended to show example usage of this feature,
supporting quieting filesystem, network and scope accesses.
Demo:
/# LL_FS_RO=/usr LL_FS_RW= LL_FORCE_LOG=1 LL_FS_QUIET=/dev:/tmp:/etc LL_FS_QUIET_ACCESS=r ./sandboxer bash
...
audit: type=1423 audit(1759680175.562:195): domain=15bb25f6b blockers=fs.write_file,fs.read_file path="/dev/tty" dev="devtmpfs" ino=11
^^^^^^^^
# note: because write is not quieted, we see the above line. blockers
# contains read as well since that's the originally requested access.
audit: type=1424 audit(1759680175.562:195): domain=15bb25f6b status=allocated mode=enforcing pid=616 uid=0 exe="/sandboxer" comm="sandboxer"
audit: type=1300 audit(1759680175.562:195): arch=c000003e syscall=257 success=no exit=-13 a0=ffffffffffffff9c a1=5565c86113d1 a2=802 a3=0 items=0 ppid=605 pid=616 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bash" exe="/usr/bin/bash" key=(null)
audit: type=1327 audit(1759680175.562:195): proctitle="bash"
bash: cannot set terminal process group (605): Inappropriate ioctl for device
bash: no job control in this shell
bash: /etc/bash.bashrc: Permission denied
audit: type=1423 audit(1759680175.570:196): domain=15bb25f6b blockers=fs.read_file path="/.bash_history" dev="virtiofs" ino=36963
^^^^^^^^
# read outside /dev:/tmp:/etc - not quieted
audit: type=1300 audit(1759680175.570:196): arch=c000003e syscall=257 success=no exit=-13 a0=ffffffffffffff9c a1=5565c868e400 a2=0 a3=0 items=0 ppid=605 pid=616 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bash" exe="/usr/bin/bash" key=(null)
audit: type=1327 audit(1759680175.570:196): proctitle="bash"
audit: type=1423 audit(1759680175.570:197): domain=15bb25f6b blockers=fs.read_file path="/.bash_history" dev="virtiofs" ino=36963
audit: type=1300 audit(1759680175.570:197): arch=c000003e syscall=257 success=no exit=-13 a0=ffffffffffffff9c a1=5565c868e400 a2=0 a3=0 items=0 ppid=605 pid=616 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bash" exe="/usr/bin/bash" key=(null)
audit: type=1327 audit(1759680175.570:197): proctitle="bash"
bash-5.2# head /etc/passwd
head: cannot open '/etc/passwd' for reading: Permission denied
^^^^^^^^
# reads to /etc are quieted
bash-5.2# echo evil >> /etc/passwd
bash: /etc/passwd: Permission denied
audit: type=1423 audit(1759680227.030:198): domain=15bb25f6b blockers=fs.write_file path="/etc/passwd" dev="virtiofs" ino=790
^^^^^^^^
# writes are not quieted
audit: type=1300 audit(1759680227.030:198): arch=c000003e syscall=257 success=no exit=-13 a0=ffffffffffffff9c a1=5565c86ab030 a2=441 a3=1b6 items=0 ppid=605 pid=616 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="bash" exe="/usr/bin/bash" key=(null)
audit: type=1327 audit(1759680227.030:198): proctitle="bash"
Design:
- The user can set the quiet flag for a layer on any part of the fs
hierarchy (whether it allows any access on it or not), and the flag
inherits down (no support for "cancelling" the inheritance of the flag
in specific subdirectories).
- The youngest layer that denies a request gets to decide whether the
denial is audited or not. This means that a compromised binary, for
example, cannot "turn off" Landlock auditing when it tries to access
files, unless it denies access to the files itself. There is some
debate to be had on whether, if a parent layer sets the quiet flag, but
the request is denied by a deeper layer, whether Landlock should still
audit anyway (since the rule author of the child layer likely did not
expect the denial, so it would be good diagnostic). The current
approach is to ignore the quiet on the parent layer and audit anyway.
[1]: https://github.com/landlock-lsm/linux/issues/44#issuecomment-2876500918
Kind regards,
Tingmao
Tingmao Wang (9):
landlock: Add a place for flags to layer rules
landlock: Add API support and docs for the quiet flags
landlock: Suppress logging when quiet flag is present
samples/landlock: Add quiet flag support to sandboxer
selftests/landlock: Replace hard-coded 16 with a constant
selftests/landlock: add tests for quiet flag with fs rules
selftests/landlock: add tests for quiet flag with net rules
selftests/landlock: Add tests for quiet flag with scope
selftests/landlock: Add tests for invalid use of quiet flag
Documentation/admin-guide/LSM/landlock.rst | 9 +-
Documentation/userspace-api/landlock.rst | 11 +
include/uapi/linux/landlock.h | 61 +
samples/landlock/sandboxer.c | 133 +-
security/landlock/access.h | 44 +-
security/landlock/audit.c | 281 +-
security/landlock/audit.h | 3 +-
security/landlock/domain.c | 52 +-
security/landlock/domain.h | 11 +-
security/landlock/fs.c | 180 +-
security/landlock/fs.h | 19 +-
security/landlock/limits.h | 3 +
security/landlock/net.c | 22 +-
security/landlock/net.h | 5 +-
security/landlock/ruleset.c | 45 +-
security/landlock/ruleset.h | 29 +-
security/landlock/syscalls.c | 71 +-
tools/testing/selftests/landlock/audit_test.c | 27 +-
tools/testing/selftests/landlock/base_test.c | 59 +-
tools/testing/selftests/landlock/common.h | 2 +
tools/testing/selftests/landlock/fs_test.c | 2449 ++++++++++++++++-
tools/testing/selftests/landlock/net_test.c | 138 +-
.../landlock/scoped_abstract_unix_test.c | 77 +-
23 files changed, 3512 insertions(+), 219 deletions(-)
base-commit: b7d9ce964102b004a90568726dcdf9051602ddd9
--
2.54.0
^ permalink raw reply
* Re: [PATCH] KEYS: fix overflow in keyctl_pkey_params_get_2()
From: Jarkko Sakkinen @ 2026-05-31 17:04 UTC (permalink / raw)
To: Alessandro G
Cc: keyringsy, stable, David Howells, Paul Moore, James Morris,
Serge E. Hallyn, Denis Kenzior, Marcel Holtmann, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <CAJXJp6jMLQvYi6rpt31hO0O5WwLipSihT9PxFJu1bvtfUS2CBw@mail.gmail.com>
On Sun, May 31, 2026 at 09:23:11AM +0200, Alessandro G wrote:
> Hi Jarkko,
>
> The surname is “Groppo” instead of “Grupp”, don’t worry and thanks for asking!
>
> Thanks also for the fix!
>
> BR,
> Alessandro
Thank you! This was a super good bug report.
Since I cannot do it without permission, can I add your tested-by to the
patch?
BR, Jarkko
>
> Il giorno dom 31 mag 2026 alle 05:25 Jarkko Sakkinen <jarkko@kernel.org> ha
> scritto:
>
> On Sun, May 31, 2026 at 05:49:13AM +0300, Jarkko Sakkinen wrote:
> > The length for the internal output buffer is calculated incorrectly,
> which
> > can result overflow when a too small buffer is provided.
> >
> > Fix the bug by allocating internal output with the size of the maximum
> > length of the cryptographic primitive instead of caller provided size.
> >
> > Cc: stable@vger.kernel.org # v4.20+
> > Fixes: 00d60fd3b932 ("KEYS: Provide keyctls to drive the new key type ops
> for asymmetric keys [ver #2]")
> > Reported-by: Alessandro Grupp <ale.grpp@gmail.com>
> > Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
>
> Should be available in -next within a day or along the lines so please
> be quick with tags/feedback. I'll forward a PR as soon as all is good.
>
> BR, Jarkko
>
^ permalink raw reply
* Re: [PATCH v3 1/2] landlock: fix LANDLOCK_SCOPE_SIGNAL bypass via F_SETOWN to invoker's pgid
From: Mickaël Salaün @ 2026-05-31 10:41 UTC (permalink / raw)
To: hexlabsecurity
Cc: Justin Suess, gnoack@google.com,
linux-security-module@vger.kernel.org, stable@vger.kernel.org,
Christian Brauner
In-Reply-To: <7rvmLIHR1Zh8RDF1IY1-SYRHzErgw9gPHq0k98RLYVsmHqAejjxcuJi8V3QaSbW-SnNvY5tfM2Xn_S1dEajKV_f7iyitoPwJgOSTZQ0nytc=@proton.me>
On Fri, May 29, 2026 at 07:07:30PM +0000, hexlabsecurity@proton.me wrote:
> From b5fdc79ce1cb2881d59dfed01d3d9170306be9e8 Mon Sep 17 00:00:00 2001
> From: Bryam Vargas <hexlabsecurity@proton.me>
> Date: Fri, 29 May 2026 12:49:41 -0500
> Subject: [PATCH v3 1/2] landlock: fix LANDLOCK_SCOPE_SIGNAL bypass via
> F_SETOWN to invoker's pgid
Please send proper threaded emails. Sending two unrelated
emails/patches and with additional headers in the body makes it more
difficult to handle and ignored by reviewer bots.
>
> A Landlock-restricted process can bypass LANDLOCK_SCOPE_SIGNAL on the
> SIGIO delivery path and deliver arbitrary signals (including SIGKILL via
> F_SETSIG) to non-Landlocked targets that share its pgid, by exploiting a
> producer-side cache-vs-live evaluation gap.
What does mean: "producer-side cache-vs-live evaluation gap"?
It looks like the "cache" here is the fowner data, which is not a cache.
Could you please make it clear in the commit message what is the threat
(e.g. real-life scenario of a signal control bypass)?
>
> The SIGIO path in hook_file_send_sigiotask() consults a cached subject
> stored in landlock_file(file)->fown_subject at fcntl(F_SETOWN) time
> (via hook_file_set_fowner()), instead of evaluating the live Landlock
> domain of the invoking task at signal-send time. The capture is gated
> by control_current_fowner(), which returns false (skipping capture)
> when pid_task(fown->pid, fown->pid_type) is in current's thread group.
Commit messages should mostly explain the "why", and only the minimal
"what".
A simpler commit message would be useful.
>
> This is correct for PIDTYPE_TGID / PIDTYPE_PID, where the target is a
> single task sharing current's cred. It is unsafe for PIDTYPE_PGID and
By "task" do you mean "process"?
> PIDTYPE_SID: when current is at the head of its pgid hlist -- the
> default placement after fork(), hlist_add_head_rcu() in kernel/fork.c --
> pid_task(pgid, PIDTYPE_PGID) resolves to current itself,
> same_thread_group(current, current) is true, the capture is skipped, and
> fown_subject.domain stays NULL. hook_file_send_sigiotask() then
> short-circuits at "if (!subject->domain) return 0;", letting the kernel
> fan the signal out to every member of the group, including tasks outside
> current's Landlock domain that SCOPE_SIGNAL is supposed to protect.
>
> The direct kill() path (hook_task_kill) is unaffected: it evaluates
> current's live domain on every call. Only the cached SIGIO path is
> broken.
>
> Tighten control_current_fowner() to apply the thread-group exemption
> only when the target identifies a single task whose Landlock cred is
> necessarily shared with current (PIDTYPE_TGID, PIDTYPE_PID). For
> PIDTYPE_PGID and PIDTYPE_SID, always capture the current Landlock
> subject so the consumer's scope check runs against every member of the
> group at delivery time.
PIDTYPE_SID doesn't seem possible.
>
> Stable kernels before the fown_subject conversion store the domain in
> landlock_file(file)->fown_domain; control_current_fowner() is identical
> there, so the same exemption and the same fix apply.
>
> Fixes: 18eb75f3af40 ("landlock: Always allow signals between threads of the same process")
> Cc: stable@vger.kernel.org
> Reported-by: Bryam Vargas <hexlabsecurity@proton.me>
You should remove Reported-by because it is implicit when you also add a
Signed-off-by with the same value.
> Tested-by: Justin Suess <utilityemal77@gmail.com>
> Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
> ---
> security/landlock/fs.c | 12 ++++++++++++
> 1 file changed, 12 insertions(+)
>
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index c1ecfe239032..edaa52572cbd 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -1909,6 +1909,18 @@ static bool control_current_fowner(struct fown_struct *const fown)
> if (!p)
> return true;
>
> + /*
> + * For PIDTYPE_PGID and PIDTYPE_SID, signal delivery fans out to
> + * every member of the group at SIGIO time. Even when pid_task()
> + * resolves to current itself (e.g., current is the pgid hlist
> + * head post-fork), non-current members of the group are still
> + * valid targets that must be checked by hook_file_send_sigiotask().
> + * Always capture the current subject for those types so the
> + * consumer scope check runs against the live fown_subject.
This looks good but could be simplified.
> + */
> + if (fown->pid_type == PIDTYPE_PGID || fown->pid_type == PIDTYPE_SID)
PIDTYPE_SID is not possible for fowner, and I'd prefer a defensive
programming approach:
fown->pid_type != PIDTYPE_PID && fown->pid_type != PIDTYPE_TGID
> + return true;
> +
> return !same_thread_group(p, current);
> }
>
> --
> 2.43.0
>
>
^ permalink raw reply
* Re: [PATCH] KEYS: fix overflow in keyctl_pkey_params_get_2()
From: Jarkko Sakkinen @ 2026-05-31 3:25 UTC (permalink / raw)
To: keyringsy
Cc: stable, Alessandro Grupp, David Howells, Paul Moore, James Morris,
Serge E. Hallyn, Denis Kenzior, Marcel Holtmann, keyrings,
linux-security-module, linux-kernel
In-Reply-To: <20260531024914.3712130-1-jarkko@kernel.org>
On Sun, May 31, 2026 at 05:49:13AM +0300, Jarkko Sakkinen wrote:
> The length for the internal output buffer is calculated incorrectly, which
> can result overflow when a too small buffer is provided.
>
> Fix the bug by allocating internal output with the size of the maximum
> length of the cryptographic primitive instead of caller provided size.
>
> Cc: stable@vger.kernel.org # v4.20+
> Fixes: 00d60fd3b932 ("KEYS: Provide keyctls to drive the new key type ops for asymmetric keys [ver #2]")
> Reported-by: Alessandro Grupp <ale.grpp@gmail.com>
> Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
Should be available in -next within a day or along the lines so please
be quick with tags/feedback. I'll forward a PR as soon as all is good.
BR, Jarkko
^ permalink raw reply
* [PATCH] KEYS: fix overflow in keyctl_pkey_params_get_2()
From: Jarkko Sakkinen @ 2026-05-31 2:49 UTC (permalink / raw)
To: keyringsy
Cc: Jarkko Sakkinen, stable, Alessandro Grupp, David Howells,
Paul Moore, James Morris, Serge E. Hallyn, Denis Kenzior,
Marcel Holtmann, keyrings, linux-security-module, linux-kernel
The length for the internal output buffer is calculated incorrectly, which
can result overflow when a too small buffer is provided.
Fix the bug by allocating internal output with the size of the maximum
length of the cryptographic primitive instead of caller provided size.
Cc: stable@vger.kernel.org # v4.20+
Fixes: 00d60fd3b932 ("KEYS: Provide keyctls to drive the new key type ops for asymmetric keys [ver #2]")
Reported-by: Alessandro Grupp <ale.grpp@gmail.com>
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
Alessandro, please correct if I put the last name correctly (and
sincere apologies if not).
security/keys/keyctl_pkey.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/security/keys/keyctl_pkey.c b/security/keys/keyctl_pkey.c
index 97bc27bbf079..ba150ee2d4a3 100644
--- a/security/keys/keyctl_pkey.c
+++ b/security/keys/keyctl_pkey.c
@@ -138,28 +138,35 @@ static int keyctl_pkey_params_get_2(const struct keyctl_pkey_params __user *_par
if (uparams.in_len > info.max_dec_size ||
uparams.out_len > info.max_enc_size)
return -EINVAL;
+
+ params->out_len = info.max_enc_size;
break;
case KEYCTL_PKEY_DECRYPT:
if (uparams.in_len > info.max_enc_size ||
uparams.out_len > info.max_dec_size)
return -EINVAL;
+
+ params->out_len = info.max_dec_size;
break;
case KEYCTL_PKEY_SIGN:
if (uparams.in_len > info.max_data_size ||
uparams.out_len > info.max_sig_size)
return -EINVAL;
+
+ params->out_len = info.max_sig_size;
break;
case KEYCTL_PKEY_VERIFY:
if (uparams.in_len > info.max_data_size ||
uparams.in2_len > info.max_sig_size)
return -EINVAL;
+
+ params->out_len = info.max_sig_size;
break;
default:
BUG();
}
params->in_len = uparams.in_len;
- params->out_len = uparams.out_len; /* Note: same as in2_len */
return 0;
}
--
2.47.3
^ permalink raw reply related
* Re: [PATCH] keys: Pin request_key_auth payload in instantiate paths
From: Jarkko Sakkinen @ 2026-05-30 16:49 UTC (permalink / raw)
To: eee sss
Cc: David Howells, keyrings, linux-security-module, linux-kernel,
Paul Moore, James Morris, Serge E. Hallyn
In-Reply-To: <ahsUxQryCvPggKBt@kernel.org>
On Sat, May 30, 2026 at 07:48:10PM +0300, Jarkko Sakkinen wrote:
> On Sat, May 30, 2026 at 09:19:33AM -0700, eee sss wrote:
> > Hi Jarkko,
> >
> > Yes, I have a reproducer available as a tar.gz file.
> >
> > I am not attaching it directly here because I am not sure what the preferred
> > way is to share a tar.gz reproducer in this mail context, especially with
> > mailing lists on Cc. Please let me know whether you would prefer a direct
> > attachment, a private upload location, encrypted transfer, or another channel.
> >
> > BR,
> > Shaomin
>
> Drop it to me as private! I actually have now queue of total three
> reproducers but I'll get to this sometime next week :-) Lots of stuff
> apparently happening in sec.
polite request: it is advicable to use plain text in. It's not just me
whining but also sometimes some ML servers simply drop HTML mail i.e.
only non-ML recipients will get it. Just something to be aware of that's
all.
BR, Jarkko
^ permalink raw reply
* Re: [PATCH] keys: Pin request_key_auth payload in instantiate paths
From: Jarkko Sakkinen @ 2026-05-30 16:48 UTC (permalink / raw)
To: eee sss
Cc: David Howells, keyrings, linux-security-module, linux-kernel,
Paul Moore, James Morris, Serge E. Hallyn
In-Reply-To: <CA+TOyfhe=0Ty-FQDZy-9_LWJ6dgakzyjog5rNfQ2NdCc5X+dFQ@mail.gmail.com>
On Sat, May 30, 2026 at 09:19:33AM -0700, eee sss wrote:
> Hi Jarkko,
>
> Yes, I have a reproducer available as a tar.gz file.
>
> I am not attaching it directly here because I am not sure what the preferred
> way is to share a tar.gz reproducer in this mail context, especially with
> mailing lists on Cc. Please let me know whether you would prefer a direct
> attachment, a private upload location, encrypted transfer, or another channel.
>
> BR,
> Shaomin
Drop it to me as private! I actually have now queue of total three
reproducers but I'll get to this sometime next week :-) Lots of stuff
apparently happening in sec.
BR, Jarkko
^ permalink raw reply
* Re: [PATCH 01/11] hornet: fix TOCTOU in signed program verification
From: Fan Wu @ 2026-05-30 1:11 UTC (permalink / raw)
To: Blaise Boscaccy
Cc: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
linux-security-module
In-Reply-To: <20260528030915.2654994-2-bboscaccy@linux.microsoft.com>
On Wed, May 27, 2026 at 8:09 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
>
> The signature verification path was vulnerable to a time-of-check vs
> time-of-use race at both the program load and program run hook sites:
> between the moment a map's contents were hashed for signature
> verification and the moment the program run hook re-verified them, an
> attacker with sufficient privileges could swap or mutate the map
> contents.
>
> Close the race by snapshotting the map hashes during program load,
> attaching them to the program, and re-verifying them from the
> security_bpf_prog hook against prog->aux->used_maps. Because used_maps
> is the same map set the verifier and runtime resolve against, there is
> no longer a window in which the verified set and the executed set can
> diverge.
>
> Since we are no longer targeting the fd_array passed in, drop the map
> index data entirely and check for whether or not the set of requested
> map hashes is a subset of prog->aux->used_maps.
>
> Reported-by: Eric Biggers <ebiggers@kernel.org>
> Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
> ---
> Documentation/admin-guide/LSM/Hornet.rst | 39 +++-----
> scripts/hornet/gen_sig.c | 17 +---
> security/hornet/hornet.asn1 | 1 -
> security/hornet/hornet_lsm.c | 121 +++--------------------
> tools/testing/selftests/hornet/Makefile | 2 +-
> 5 files changed, 35 insertions(+), 145 deletions(-)
>
> diff --git a/Documentation/admin-guide/LSM/Hornet.rst b/Documentation/admin-guide/LSM/Hornet.rst
> index 0ade4c17374c6..a369bc11408f4 100644
> --- a/Documentation/admin-guide/LSM/Hornet.rst
> +++ b/Documentation/admin-guide/LSM/Hornet.rst
> @@ -86,15 +86,14 @@ Hornet protects against the following threats:
>
> - **Tampering with map data**: When map hashes are included in the
> signature, Hornet verifies that frozen BPF maps match their expected
> - SHA-256 hashes at load time. Maps are also re-verified before program
> - execution via ``BPF_PROG_RUN``.
> + SHA-256 hashes at load time after the program is publically exposed.
>
> Hornet does **not** protect against:
>
> - Compromise of the signing key itself.
> - Attacks that occur after a program has been loaded and verified.
> - Programs loaded by the kernel itself (kernel-internal loads bypass
> - the ``BPF_PROG_RUN`` map check).
> + the map check).
>
> Known Limitations
> =================
> @@ -117,6 +116,10 @@ Known Limitations
> data. It does not guarantee positional binding of maps to specific
> fd_array slots.
>
> +- Map hash verification does not enforce any ordering. It simply asserts
> + that the set of map hashes requested to be verified exist in the used
> + array.
> +
> - BPF_MAP_TYPE_PROG_ARRAY maps must be frozen for Hornet to verify
> them. Unfrozen prog array maps are not covered by verification.
>
> @@ -159,24 +162,19 @@ The following describes what happens when a userspace program calls
> 5. Hornet extracts the authenticated attribute identified by
> ``OID_hornet_data`` (OID ``2.25.316487325684022475439036912669789383960``)
> from the PKCS#7 message. This attribute contains an ASN.1-encoded set
> - of map index/hash pairs.
> + of map hash hashes
>
> -6. For each map hash entry, Hornet retrieves the corresponding BPF map
> - via its file descriptor, confirms it is frozen, computes its SHA-256
> - hash, and compares it against the signed hash.
> +6. For each map hash entry, Hornet retrieves stores the target map hash in
> + the program's LSM blob.
>
> 7. The resulting integrity verdict is passed to the
> ``bpf_prog_load_post_integrity`` hook so that downstream LSMs can
> enforce policy.
>
> -Runtime Map Verification
> -------------------------
> -
> -When ``bpf(BPF_PROG_RUN, ...)`` is called from userspace, Hornet
> -re-verifies the hashes of all maps associated with the program. This
> -ensures that map contents have not been modified between program load
> -and execution. If any map hash no longer matches, the ``BPF_PROG_RUN``
> -command is denied.
> +8. After the verifier processes the program, once it's ready to be published,
> + Hornet intercepts the ``bpf_prog`` hook, and verifies that the set of
> + required hashes exist in the programs used maps. If the map hashes are
> + unable to be found, the command is denied.
>
> Userspace Interface
> -------------------
> @@ -199,14 +197,10 @@ the following ASN.1 schema::
> HornetData ::= SET OF Map
>
> Map ::= SEQUENCE {
> - index INTEGER,
> sha OCTET STRING
> }
>
> -Each ``Map`` entry contains the index of the map in the program's
> -``fd_array`` and its expected SHA-256 hash. A zero-length ``sha`` field
> -indicates that the map at that index should be skipped during
> -verification.
> +Each ``Map`` entry contains an expected SHA-256 hash.
>
> Tooling
> =======
> @@ -229,7 +223,7 @@ Usage::
> --key <signer.key> \
> [--pass <passphrase>] \
> --out <signature.p7b> \
> - [--add <mapfile.bin>:<index> ...]
> + [--add <mapfile.bin> ...]
>
> ``--data``
> Path to the binary file containing eBPF program instructions to sign.
> @@ -248,8 +242,7 @@ Usage::
>
> ``--add``
> Attach a map hash as a signed attribute. The argument is a path to a
> - binary map file followed by a colon and the map's index in the
> - ``fd_array``. This option may be specified multiple times.
> + binary map file. This option may be specified multiple times.
>
> extract-skel.sh
> ---------------
> diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
> index 8dd9ed66346a2..b4f983ab24bcd 100644
> --- a/scripts/hornet/gen_sig.c
> +++ b/scripts/hornet/gen_sig.c
> @@ -55,7 +55,6 @@
>
> struct hash_spec {
> char *file;
> - int index;
> };
>
> typedef struct {
> @@ -66,7 +65,6 @@ typedef struct {
>
> DECLARE_ASN1_FUNCTIONS(HORNET_MAP)
> ASN1_SEQUENCE(HORNET_MAP) = {
> - ASN1_SIMPLE(HORNET_MAP, index, ASN1_INTEGER),
> ASN1_SIMPLE(HORNET_MAP, hash, ASN1_OCTET_STRING)
> } ASN1_SEQUENCE_END(HORNET_MAP);
>
> @@ -253,12 +251,11 @@ static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int
> return rc;
> }
>
> -static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len, int index)
> +static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len)
> {
> HORNET_MAP *map = NULL;
>
> map = HORNET_MAP_new();
> - ASN1_INTEGER_set(map->index, index);
> ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len);
> sk_HORNET_MAP_push(set->maps, map);
> }
> @@ -320,14 +317,8 @@ int main(int argc, char **argv)
> data_path = optarg;
> break;
> case 'A':
> - if (strchr(optarg, ':')) {
> - hashes[hash_count].file = strsep(&optarg, ":");
> - hashes[hash_count].index = atoi(optarg);
> - if (++hash_count >= MAX_HASHES) {
> - usage(argv[0]);
> - return EXIT_FAILURE;
> - }
> - } else {
> + hashes[hash_count].file = optarg;
> + if (++hash_count >= MAX_HASHES) {
> usage(argv[0]);
> return EXIT_FAILURE;
> }
> @@ -371,7 +362,7 @@ int main(int argc, char **argv)
> if (sha256(hashes[i].file, hash_buffer, &hash_len) != 0) {
> DIE("failed to hash input");
> }
> - add_hash(set, hash_buffer, hash_len, hashes[i].index);
> + add_hash(set, hash_buffer, hash_len);
> }
>
> oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1);
> diff --git a/security/hornet/hornet.asn1 b/security/hornet/hornet.asn1
> index e60abf451ae23..3cf50379f5e7c 100644
> --- a/security/hornet/hornet.asn1
> +++ b/security/hornet/hornet.asn1
> @@ -7,6 +7,5 @@
> HornetData ::= SET OF Map
>
> Map ::= SEQUENCE {
> - index INTEGER ({ hornet_map_index }),
> sha OCTET STRING ({ hornet_map_hash })
> } ({ hornet_next_map })
> diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
> index a4d11fa5b0889..516038413f321 100644
> --- a/security/hornet/hornet_lsm.c
> +++ b/security/hornet/hornet_lsm.c
> @@ -21,26 +21,18 @@
>
> #define MAX_USED_MAPS 64
>
> -struct hornet_maps {
> - bpfptr_t fd_array;
> -};
> -
> /* The only hashing algorithm available is SHA256 due to it be hardcoded
> * in the bpf subsystem.
> */
> -
> -struct hornet_parse_context {
> - int indexes[MAX_USED_MAPS];
> - bool skips[MAX_USED_MAPS];
> - unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
> - int hash_count;
> -};
> -
> struct hornet_prog_security_struct {
> int signed_hash_count;
> unsigned char signed_hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
> };
>
> +struct hornet_parse_context {
> + struct hornet_prog_security_struct *security;
> +};
> +
> struct lsm_blob_sizes hornet_blob_sizes __ro_after_init = {
> .lbs_bpf_prog = sizeof(struct hornet_prog_security_struct),
> };
> @@ -51,79 +43,17 @@ hornet_bpf_prog_security(struct bpf_prog *prog)
> return prog->aux->security + hornet_blob_sizes.lbs_bpf_prog;
> }
>
> -static int hornet_verify_hashes(struct hornet_maps *maps,
> - struct hornet_parse_context *ctx,
> - struct bpf_prog *prog)
> -{
> - int map_fd;
> - u32 i;
> - struct bpf_map *map;
> - int err = 0;
> - unsigned char hash[SHA256_DIGEST_SIZE];
> - struct hornet_prog_security_struct *security = hornet_bpf_prog_security(prog);
> -
> - for (i = 0; i < ctx->hash_count; i++) {
> - if (ctx->skips[i])
> - continue;
> -
> - err = copy_from_bpfptr_offset(&map_fd, maps->fd_array,
> - ctx->indexes[i] * sizeof(map_fd),
> - sizeof(map_fd));
> - if (err != 0)
> - return LSM_INT_VERDICT_FAULT;
It seems this verdict is no longer used.
> -
> - CLASS(fd, f)(map_fd);
> - if (fd_empty(f))
> - return LSM_INT_VERDICT_FAULT;
> - if (unlikely(fd_file(f)->f_op != &bpf_map_fops))
> - return LSM_INT_VERDICT_FAULT;
> -
> - map = fd_file(f)->private_data;
> - if (!READ_ONCE(map->frozen))
> - return LSM_INT_VERDICT_FAULT;
> -
> - if (!map->ops->map_get_hash)
> - return LSM_INT_VERDICT_FAULT;
> -
> - if (map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash))
> - return LSM_INT_VERDICT_FAULT;
> -
> - err = memcmp(hash, &ctx->hashes[i * SHA256_DIGEST_SIZE],
> - SHA256_DIGEST_SIZE);
> - if (err)
> - return LSM_INT_VERDICT_UNEXPECTED;
similar above, they should be removed for the header and for the ipe policy.
-Fan
> -
> - memcpy(&security->signed_hashes[security->signed_hash_count * SHA256_DIGEST_SIZE],
> - &ctx->hashes[i * SHA256_DIGEST_SIZE], SHA256_DIGEST_SIZE);
> - security->signed_hash_count++;
> - }
> - return LSM_INT_VERDICT_OK;
> -}
> -
> int hornet_next_map(void *context, size_t hdrlen,
> unsigned char tag,
> const void *value, size_t vlen)
> {
> struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
>
> - if (++ctx->hash_count >= MAX_USED_MAPS)
> + if (++ctx->security->signed_hash_count >= MAX_USED_MAPS)
> return -EINVAL;
> return 0;
> }
>
> -int hornet_map_index(void *context, size_t hdrlen,
> - unsigned char tag,
> - const void *value, size_t vlen)
> -{
> - struct hornet_parse_context *ctx = (struct hornet_parse_context *)context;
> -
> - if (vlen != 1)
> - return -EINVAL;
> -
> - ctx->indexes[ctx->hash_count] = *(u8 *)value;
> - return 0;
> -}
> -
> int hornet_map_hash(void *context, size_t hdrlen,
> unsigned char tag,
> const void *value, size_t vlen)
> @@ -134,11 +64,8 @@ int hornet_map_hash(void *context, size_t hdrlen,
> if (vlen != SHA256_DIGEST_SIZE && vlen != 0)
> return -EINVAL;
>
> - if (vlen) {
> - ctx->skips[ctx->hash_count] = false;
> - memcpy(&ctx->hashes[ctx->hash_count * SHA256_DIGEST_SIZE], value, vlen);
> - } else
> - ctx->skips[ctx->hash_count] = true;
> + memcpy(&ctx->security->signed_hashes[ctx->security->signed_hash_count * SHA256_DIGEST_SIZE],
> + value, vlen);
>
> return 0;
> }
> @@ -147,7 +74,6 @@ static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr,
> struct bpf_token *token, bool is_kernel,
> enum lsm_integrity_verdict *verdict)
> {
> - struct hornet_maps maps = {0};
> bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
> struct pkcs7_message *msg;
> struct hornet_parse_context *ctx;
> @@ -172,7 +98,8 @@ static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr,
> if (!ctx)
> return -ENOMEM;
>
> - maps.fd_array = make_bpfptr(attr->fd_array, is_kernel);
> + ctx->security = hornet_bpf_prog_security(prog);
> +
> sig = kzalloc(attr->signature_size, GFP_KERNEL);
> if (!sig) {
> err = -ENOMEM;
> @@ -225,7 +152,7 @@ static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr,
> goto cleanup_msg;
> }
>
> - *verdict = hornet_verify_hashes(&maps, ctx, prog);
> + *verdict = LSM_INT_VERDICT_OK;
> err = 0;
>
> cleanup_msg:
> @@ -257,10 +184,8 @@ static int hornet_bpf_prog_load_integrity(struct bpf_prog *prog, union bpf_attr
> &hornet_lsmid, verdict);
> }
>
> -static int hornet_check_prog_maps(u32 ufd)
> +static int hornet_check_prog_maps(struct bpf_prog *prog)
> {
> - CLASS(fd, f)(ufd);
> - struct bpf_prog *prog;
> struct hornet_prog_security_struct *security;
> unsigned char hash[SHA256_DIGEST_SIZE];
> struct bpf_map *map;
> @@ -268,12 +193,6 @@ static int hornet_check_prog_maps(u32 ufd)
> bool found;
> int covered_count = 0;
>
> - if (fd_empty(f))
> - return -EBADF;
> - if (fd_file(f)->f_op != &bpf_prog_fops)
> - return -EINVAL;
> -
> - prog = fd_file(f)->private_data;
> security = hornet_bpf_prog_security(prog);
>
> if (!security->signed_hash_count)
> @@ -316,26 +235,14 @@ static int hornet_check_prog_maps(u32 ufd)
> return 0;
> }
>
> -static int hornet_bpf(int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
> +static int hornet_bpf_prog(struct bpf_prog *prog)
> {
> - /* in horent_bpf(), anything that had originated from kernel space we assume
> - * has already been checked, in some form or another, so we don't bother
> - * checking the intergity of any maps. In hornet_bpf_prog_load_integrity(),
> - * hornet doesn't make any opinion on that and delegates that to the downstream
> - * policy enforcement.
> - */
> -
> - if (cmd != BPF_PROG_RUN)
> - return 0;
> - if (kernel)
> - return 0;
> -
> - return hornet_check_prog_maps(attr->test.prog_fd);
> + return hornet_check_prog_maps(prog);
> }
>
> static struct security_hook_list hornet_hooks[] __ro_after_init = {
> LSM_HOOK_INIT(bpf_prog_load_integrity, hornet_bpf_prog_load_integrity),
> - LSM_HOOK_INIT(bpf, hornet_bpf),
> + LSM_HOOK_INIT(bpf_prog, hornet_bpf_prog),
> };
>
> static int __init hornet_init(void)
> diff --git a/tools/testing/selftests/hornet/Makefile b/tools/testing/selftests/hornet/Makefile
> index 432bce59f54e7..316364f95f28c 100644
> --- a/tools/testing/selftests/hornet/Makefile
> +++ b/tools/testing/selftests/hornet/Makefile
> @@ -51,7 +51,7 @@ $(OUTPUT)/gen_sig: ../../../../scripts/hornet/gen_sig.c
>
> sig.bin: insn.bin map.bin $(OUTPUT)/gen_sig
> $(OUTPUT)/gen_sig --key $(CERTDIR)/signing_key.pem --cert $(CERTDIR)/signing_key.x509 \
> - --data insn.bin --add map.bin:0 --out sig.bin
> + --data insn.bin --add map.bin --out sig.bin
>
> signed_loader.h: sig.bin
> $(SCRIPTSDIR)/write-sig.sh loader.h sig.bin > $@
> --
> 2.53.0
>
^ permalink raw reply
* Re: [PATCH] KEYS: Use acquire when reading state in keyring search
From: Jarkko Sakkinen @ 2026-05-30 1:02 UTC (permalink / raw)
To: Gui-Dong Han, dhowells
Cc: keyrings, dhowells, ebiggers, linux-security-module, linux-kernel,
baijiaju1990
In-Reply-To: <20260529033406.20673-1-hanguidong02@gmail.com>
On Fri, May 29, 2026 at 11:34:06AM +0800, Gui-Dong Han wrote:
> The negative-key race fix added release/acquire ordering for key use.
>
> Publish payload before state; read state before payload.
>
> keyring_search_iterator() still uses READ_ONCE() before match callbacks.
> An asymmetric match callback calls asymmetric_key_ids(), which reads
> key->payload.data[asym_key_ids].
>
> Use key_read_state() there to complete that ordering.
OK, so... I'm having a bit trouble understanding the exact concurrency
scenario you're trying to describe despite I think I get the fix itself
i.e. it is not pairing with mark_key_instantiated?
I'm a bit puzzled too why this was not done already in the original
commit despite introducing all the primitives.
>
> Fixes: 363b02dab09b ("KEYS: Fix race between updating and finding a negative key")
> Signed-off-by: Gui-Dong Han <hanguidong02@gmail.com>
> ---
> Found by auditing READ_ONCE() used for synchronization.
> A similar fix can be found in 8df672bfe3ec.
> ---
> security/keys/keyring.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/security/keys/keyring.c b/security/keys/keyring.c
> index b39038f7dd31..243fb1636f10 100644
> --- a/security/keys/keyring.c
> +++ b/security/keys/keyring.c
> @@ -576,7 +576,7 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
> struct keyring_search_context *ctx = iterator_data;
> const struct key *key = keyring_ptr_to_key(object);
> unsigned long kflags = READ_ONCE(key->flags);
> - short state = READ_ONCE(key->state);
> + short state = key_read_state(key);
>
> kenter("{%d}", key->serial);
>
> --
> 2.34.1
>
BR, Jarkko
^ permalink raw reply
* Re: [PATCH 02/11] hornet: invert map set check logic
From: Fan Wu @ 2026-05-30 0:57 UTC (permalink / raw)
To: Blaise Boscaccy
Cc: Jonathan Corbet, Shuah Khan, Paul Moore, James Morris,
Serge E. Hallyn, Eric Biggers, Fan Wu, James.Bottomley,
linux-security-module
In-Reply-To: <20260528030915.2654994-3-bboscaccy@linux.microsoft.com>
On Wed, May 27, 2026 at 8:09 PM Blaise Boscaccy
<bboscaccy@linux.microsoft.com> wrote:
>
> In a multi-map hash verification scenario, a logic bug may have
> allowed an attacker to provide duplicate maps to satisfy the hash
> check count. Instead, invert the logic to verify each map discretely
>
> Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
> ---
I just realized there is no audit event if hornet_check_prog_maps()
fails, probably should add one.
-Fan
^ permalink raw reply
* Re: security_task_prctl: why -ENOSYS
From: Serge E. Hallyn @ 2026-05-29 22:58 UTC (permalink / raw)
To: William Roberts; +Cc: Casey Schaufler, LSM, SElinux list
In-Reply-To: <CAFftDdqvvqrbm=+XsDdkJHdm12jxK9799_A=Txz6Q5goYC2ALQ@mail.gmail.com>
On Wed, May 27, 2026 at 11:05:56AM -0500, William Roberts wrote:
> On Tue, May 26, 2026 at 6:42 PM Casey Schaufler <casey@schaufler-ca.com> wrote:
> >
> > On 5/26/2026 4:21 PM, William Roberts wrote:
> > > On Tue, May 26, 2026 at 5:39 PM William Roberts
> > > <bill.c.roberts@gmail.com> wrote:
> > >> Hello,
> > >>
> > >> I am trying to understand the motivation behind having
> > >> security_task_prctl only continue if the return value is -ENOSYS. This
> > >> seems to be very different from other LSM hooks I have investigated.
> > >> For example, in other hooks, the value from SE Linux avc_has_perms is
> > >> used directly. This essentially means that a 0 will cause the check to
> > >> pass, and anything < 0 usually an error.
> > >>
> > >> In commit:
> > >> ----
> > >> commit d84f4f992cbd76e8f39c488cf0c5d123843923b1 ("CRED: Inaugurate COW
> > >> credentials")
> > >>
> > >> (8) security_task_prctl() and cap_task_prctl().
> > >>
> > >> security_task_prctl() has been modified to return -ENOSYS if it doesn't
> > >> want to handle a function, or otherwise return the return
> > >> value directly
> > >> rather than through an argument.
> > >>
> > >> Additionally, cap_task_prctl() now prepares a new set of
> > >> credentials, even
> > >> if it doesn't end up using it.
> > >> ----
> > >>
> > >> The check in kernel/sys.c is currently:
> > >> error = security_task_prctl(option, arg2, arg3, arg4, arg5);
> > >> if (error != -ENOSYS)
> > >> return error;
> > >>
> > >> Should this be something like, "error && error != -ENOSYS"?
> > >>
> > >> I ask because I am looking to leverage this hook in SE Linux, and it's
> > >> annoying to have to coerce all 0 returns to -ENOSYS.
> > > Of course after hours of banging my head and one email sent, it's more clear to
> > > me now WHY. This hook isn't meant for making yes or no decisions on an operation
> > > but rather to also handle special prctl flags for an LSM in question.
> > >
> > > I guess with the said, do we want this interface to be used for both
> > > a, let the lsm handle
> > > this prctl flag directed to me, as well as a yes/no security decision
> > > or do we want to split
> > > this out into two hooks?
> >
> > The task_prctl hook is used in capability and yama. It is only used to
> > provide a place to process LSM specific prctl options. It is not used to
> > make security decisions outside of processing the LSM's options. If you
> > want to make security decisions on general prctl options you will need
> > a new hook.
>
> Yeah I finally saw that after I sent the email, as always :-p, but thanks
> Casey for confirming my understanding.
>
> So now for naming... We have security_task_prctl for handling random
> prctl interface calls into LSMs. So what should this hook be called?
> Perhaps security_task_prctl_check? Should we rename the old hook to
How about security_task_prctl_allowed()? (Mirroring security_uring_*)
Renaming the existing hook security_task_prctl_handle() also wouldn't
be too bad, but that's probably more churn than it's worth.
> something else?
>
> For now, I will just pick a name, but suggestions are welcome.
>
> As an aside, any lore as to why go through generic prctl vs an
> LSM specific filesystem or an lsm specific prctl (we already
> have arch_prctl why not lsm_prctl)?
>
>
>
>
>
> >
> > >
> > >> Thanks,
> > >> Bill
^ permalink raw reply
* Re: [PATCH] keys: Pin request_key_auth payload in instantiate paths
From: Jarkko Sakkinen @ 2026-05-29 22:53 UTC (permalink / raw)
To: Shaomin Chen, David Howells
Cc: keyrings, linux-security-module, linux-kernel, David Howells,
Paul Moore, James Morris, Serge E. Hallyn
In-Reply-To: <20260526024838.3368409-1-eeesssooo020@gmail.com>
On Tue, May 26, 2026 at 10:48:38AM +0800, Shaomin Chen wrote:
> keyctl_instantiate_key_common() reads request_key_auth from the assumed
> auth key before copying an instantiation payload from userspace. The copy
> can fault and sleep. If the request completes and revokes the auth key in
> that window, the auth payload can be detached and freed before the
> instantiate path uses it again.
>
> A request-key helper reproducer can trigger this race. One helper child
> blocks in KEYCTL_INSTANTIATE_IOV while the original helper instantiates the
> requested key and returns. KASAN then reports a use-after-free from the
> stale request_key_auth payload in keyctl_instantiate_key_common().
>
> Give request_key_auth payloads a refcount. Take a payload reference while
> authkey->sem stabilizes the payload and revocation state. Hold that
> reference across the instantiate and reject paths. Drop the auth key
> owning reference from revoke and destroy.
>
> Reported-by: Shaomin Chen <eeesssooo020@gmail.com>
> Closes: https://lore.kernel.org/r/20260519144403.436694-1-eeesssooo020@gmail.com
> Signed-off-by: Shaomin Chen <eeesssooo020@gmail.com>
> ---
> include/keys/request_key_auth-type.h | 2 ++
> security/keys/internal.h | 2 ++
> security/keys/keyctl.c | 24 +++++++++++++++-----
> security/keys/request_key_auth.c | 33 ++++++++++++++++++++++++++--
> 4 files changed, 53 insertions(+), 8 deletions(-)
>
> diff --git a/include/keys/request_key_auth-type.h b/include/keys/request_key_auth-type.h
> index 36b89a933310..01e42ee5f409 100644
> --- a/include/keys/request_key_auth-type.h
> +++ b/include/keys/request_key_auth-type.h
> @@ -9,12 +9,14 @@
> #define _KEYS_REQUEST_KEY_AUTH_TYPE_H
>
> #include <linux/key.h>
> +#include <linux/refcount.h>
>
> /*
> * Authorisation record for request_key().
> */
> struct request_key_auth {
> struct rcu_head rcu;
> + refcount_t usage;
> struct key *target_key;
> struct key *dest_keyring;
> const struct cred *cred;
> diff --git a/security/keys/internal.h b/security/keys/internal.h
> index 2cffa6dc8255..b7b622bc36a1 100644
> --- a/security/keys/internal.h
> +++ b/security/keys/internal.h
> @@ -208,6 +208,8 @@ extern struct key *request_key_auth_new(struct key *target,
> const void *callout_info,
> size_t callout_len,
> struct key *dest_keyring);
> +struct request_key_auth *request_key_auth_get(struct key *authkey);
> +void request_key_auth_put(struct request_key_auth *rka);
>
> extern struct key *key_get_instantiation_authkey(key_serial_t target_id);
>
> diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
> index ef855d69c97a..d14ace88e529 100644
> --- a/security/keys/keyctl.c
> +++ b/security/keys/keyctl.c
> @@ -1197,9 +1197,13 @@ static long keyctl_instantiate_key_common(key_serial_t id,
> if (!instkey)
> goto error;
>
> - rka = instkey->payload.data[0];
> - if (rka->target_key->serial != id)
> + rka = request_key_auth_get(instkey);
> + if (!rka) {
> + ret = -EKEYREVOKED;
> goto error;
> + }
> + if (rka->target_key->serial != id)
> + goto error_put_rka;
>
> /* pull the payload in if one was supplied */
> payload = NULL;
> @@ -1208,7 +1212,7 @@ static long keyctl_instantiate_key_common(key_serial_t id,
> ret = -ENOMEM;
> payload = kvmalloc(plen, GFP_KERNEL);
> if (!payload)
> - goto error;
> + goto error_put_rka;
>
> ret = -EFAULT;
> if (!copy_from_iter_full(payload, plen, from))
> @@ -1234,6 +1238,8 @@ static long keyctl_instantiate_key_common(key_serial_t id,
>
> error2:
> kvfree_sensitive(payload, plen);
> +error_put_rka:
> + request_key_auth_put(rka);
> error:
> return ret;
> }
> @@ -1358,15 +1364,19 @@ long keyctl_reject_key(key_serial_t id, unsigned timeout, unsigned error,
> if (!instkey)
> goto error;
>
> - rka = instkey->payload.data[0];
> - if (rka->target_key->serial != id)
> + rka = request_key_auth_get(instkey);
> + if (!rka) {
> + ret = -EKEYREVOKED;
> goto error;
> + }
> + if (rka->target_key->serial != id)
> + goto error_put_rka;
>
> /* find the destination keyring if present (which must also be
> * writable) */
> ret = get_instantiation_keyring(ringid, rka, &dest_keyring);
> if (ret < 0)
> - goto error;
> + goto error_put_rka;
>
> /* instantiate the key and link it into a keyring */
> ret = key_reject_and_link(rka->target_key, timeout, error,
> @@ -1379,6 +1389,8 @@ long keyctl_reject_key(key_serial_t id, unsigned timeout, unsigned error,
> if (ret == 0)
> keyctl_change_reqkey_auth(NULL);
>
> +error_put_rka:
> + request_key_auth_put(rka);
> error:
> return ret;
> }
> diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c
> index a7d7538c1f70..282e09d8fa46 100644
> --- a/security/keys/request_key_auth.c
> +++ b/security/keys/request_key_auth.c
> @@ -23,6 +23,7 @@ static void request_key_auth_describe(const struct key *, struct seq_file *);
> static void request_key_auth_revoke(struct key *);
> static void request_key_auth_destroy(struct key *);
> static long request_key_auth_read(const struct key *, char *, size_t);
> +static void request_key_auth_rcu_disposal(struct rcu_head *);
>
> /*
> * The request-key authorisation key type definition.
> @@ -115,6 +116,31 @@ static void free_request_key_auth(struct request_key_auth *rka)
> kfree(rka);
> }
>
> +/*
> + * Take a reference to the request-key authorisation payload so callers can
> + * drop authkey->sem before doing operations that may sleep.
> + */
> +struct request_key_auth *request_key_auth_get(struct key *authkey)
> +{
> + struct request_key_auth *rka;
> +
> + down_read(&authkey->sem);
> + rka = dereference_key_locked(authkey);
> + if (rka && !test_bit(KEY_FLAG_REVOKED, &authkey->flags))
> + refcount_inc(&rka->usage);
> + else
> + rka = NULL;
> + up_read(&authkey->sem);
> +
> + return rka;
> +}
> +
> +void request_key_auth_put(struct request_key_auth *rka)
> +{
> + if (rka && refcount_dec_and_test(&rka->usage))
> + call_rcu(&rka->rcu, request_key_auth_rcu_disposal);
> +}
> +
> /*
> * Dispose of the request_key_auth record under RCU conditions
> */
> @@ -136,8 +162,10 @@ static void request_key_auth_revoke(struct key *key)
> struct request_key_auth *rka = dereference_key_locked(key);
>
> kenter("{%d}", key->serial);
> + if (!rka)
> + return;
> rcu_assign_keypointer(key, NULL);
> - call_rcu(&rka->rcu, request_key_auth_rcu_disposal);
> + request_key_auth_put(rka);
> }
>
> /*
> @@ -150,7 +178,7 @@ static void request_key_auth_destroy(struct key *key)
> kenter("{%d}", key->serial);
> if (rka) {
> rcu_assign_keypointer(key, NULL);
> - call_rcu(&rka->rcu, request_key_auth_rcu_disposal);
> + request_key_auth_put(rka);
> }
> }
>
> @@ -174,6 +202,7 @@ struct key *request_key_auth_new(struct key *target, const char *op,
> rka = kzalloc_obj(*rka);
> if (!rka)
> goto error;
> + refcount_set(&rka->usage, 1);
> rka->callout_info = kmemdup(callout_info, callout_len, GFP_KERNEL);
> if (!rka->callout_info)
> goto error_free_rka;
> --
> 2.47.3
I get the approach and for it makes sense but I'd really like to hear
David's feedback on this before moving forward.
In the meanwhile, were you able to do a script or similar as a
reproducer?
BR, Jarkko
^ permalink raw reply
* Re: [PATCH v4 3/3] tpm: tpm_crb_ffa: revert defered_probed when tpm_crb_ffa is built-in
From: Jarkko Sakkinen @ 2026-05-29 22:46 UTC (permalink / raw)
To: Yeoreum Yun
Cc: linux-security-module, linux-kernel, linux-integrity, paul, zohar,
roberto.sassu, noodles, sudeep.holla, jmorris, serge,
dmitry.kasatkin, eric.snowberg, jgg
In-Reply-To: <20260525075404.3480282-4-yeoreum.yun@arm.com>
On Mon, May 25, 2026 at 08:54:04AM +0100, Yeoreum Yun wrote:
> commit 746d9e9f62a6 ("tpm: tpm_crb_ffa: try to probe tpm_crb_ffa when it's build_in")
> probe tpm_crb_ffa forcefully when it's built-in to integrate with IMA.
>
> However, IMA now provides the IMA_INIT_LATE_SYNC build option, which
> initialises IMA at the late_initcall_sync level, so this change is no
> longer required.
>
> Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
> ---
> drivers/char/tpm/tpm_crb_ffa.c | 18 +++---------------
> 1 file changed, 3 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/char/tpm/tpm_crb_ffa.c b/drivers/char/tpm/tpm_crb_ffa.c
> index 99f1c1e5644b..025c4d4b17ca 100644
> --- a/drivers/char/tpm/tpm_crb_ffa.c
> +++ b/drivers/char/tpm/tpm_crb_ffa.c
> @@ -177,23 +177,13 @@ static int tpm_crb_ffa_to_linux_errno(int errno)
> */
> int tpm_crb_ffa_init(void)
> {
> - int ret = 0;
> -
> - if (!IS_MODULE(CONFIG_TCG_ARM_CRB_FFA)) {
> - ret = ffa_register(&tpm_crb_ffa_driver);
> - if (ret) {
> - tpm_crb_ffa = ERR_PTR(-ENODEV);
> - return ret;
> - }
> - }
> -
> if (!tpm_crb_ffa)
> - ret = -ENOENT;
> + return -ENOENT;
>
> if (IS_ERR_VALUE(tpm_crb_ffa))
> - ret = -ENODEV;
> + return -ENODEV;
>
> - return ret;
> + return 0;
> }
> EXPORT_SYMBOL_GPL(tpm_crb_ffa_init);
>
> @@ -405,9 +395,7 @@ static struct ffa_driver tpm_crb_ffa_driver = {
> .id_table = tpm_crb_ffa_device_id,
> };
>
> -#ifdef MODULE
> module_ffa_driver(tpm_crb_ffa_driver);
> -#endif
>
> MODULE_AUTHOR("Arm");
> MODULE_DESCRIPTION("TPM CRB FFA driver");
> --
> LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7}
>
How we would sync up this patch? Through which tree etc.
BR, Jarkko
^ permalink raw reply
* Re: [PATCH] tpm-buf: memory-safe allocations
From: Jarkko Sakkinen @ 2026-05-29 22:37 UTC (permalink / raw)
To: Srish Srinivasan
Cc: linux-integrity, Jarkko Sakkinen, Arun Menon, Daniel P. Smith,
Alec Brown, Ross Philipson, Stefan Berger, Peter Huewe,
Jason Gunthorpe, James Bottomley, Mimi Zohar, David Howells,
Paul Moore, James Morris, Serge E. Hallyn, linux-kernel, keyrings,
linux-security-module
In-Reply-To: <b4e26e4c-d62d-4551-bbdd-b4409316c4a9@linux.ibm.com>
On Mon, May 25, 2026 at 07:16:03PM +0530, Srish Srinivasan wrote:
> Tested on emulated TPM 1.2 and TPM 2.0 backends.
>
> Coverage:
> TPM 1.2: sysfs and trusted-key paths
> TPM 2.0: PCR, random, and trusted-key paths
>
> Tested-by: Srish Srinivasan <ssrish@linux.ibm.com>
>
> On 5/22/26 7:05 AM, Jarkko Sakkinen wrote:
> > From: Jarkko Sakkinen <jarkko.sakkinen@opinsys.com>
> >
> > Decouple kzalloc from buffer creation, so that a managed allocation can be
> > used:
> >
> > struct tpm_buf *buf __free(kfree) buf = kzalloc(TPM_BUFSIZE,
> > GFP_KERNEL);
> > if (!buf)
> > return -ENOMEM;
> >
> > tpm_buf_init(buf, TPM_BUFSIZE);
> >
> > Alternatively, stack allocations are also possible:
> >
> > u8 buf_data[512];
> > struct tpm_buf *buf = (struct tpm_buf *)buf_data;
> > tpm_buf_init(buf, sizeof(buf_data));
> >
> > This is achieved by embedding buffer's header inside the allocated blob,
> > instead of having an outer wrapper.
> >
> > Cc: Arun Menon <armenon@redhat.com>
> > Cc: Daniel P. Smith <dpsmith@apertussolutions.com>
> > Cc: Alec Brown <alec.r.brown@oracle.com>
> > Cc: Ross Philipson <ross.philipson@gmail.com>
> > Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
> > Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@opinsys.com>
> > ---
> > Rebased the managed allocations patch, which has been probably like a
> > year in circulation.
> > drivers/char/tpm/tpm-buf.c | 122 ++++++----
> > drivers/char/tpm/tpm-sysfs.c | 17 +-
> > drivers/char/tpm/tpm.h | 1 -
> > drivers/char/tpm/tpm1-cmd.c | 150 ++++++------
> > drivers/char/tpm/tpm2-cmd.c | 277 +++++++++++-----------
> > drivers/char/tpm/tpm2-sessions.c | 149 ++++++------
> > drivers/char/tpm/tpm2-space.c | 44 ++--
> > drivers/char/tpm/tpm_vtpm_proxy.c | 30 +--
> > include/linux/tpm.h | 18 +-
> > security/keys/trusted-keys/trusted_tpm1.c | 44 ++--
> > security/keys/trusted-keys/trusted_tpm2.c | 165 ++++++-------
> > 11 files changed, 505 insertions(+), 512 deletions(-)
> >
> > diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
> > index dc882fc9fa9e..b16d824ef0af 100644
> > --- a/drivers/char/tpm/tpm-buf.c
> > +++ b/drivers/char/tpm/tpm-buf.c
> > @@ -7,82 +7,110 @@
> > #include <linux/module.h>
> > #include <linux/tpm.h>
> >
> > -/**
> > - * tpm_buf_init() - Allocate and initialize a TPM command
> > - * @buf: A &tpm_buf
> > - * @tag: TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS
> > - * @ordinal: A command ordinal
> > - *
> > - * Return: 0 or -ENOMEM
> > - */
> > -int tpm_buf_init(struct tpm_buf *buf, u16 tag, u32 ordinal)
> > +static void __tpm_buf_size_invariant(struct tpm_buf *buf, u16 buf_size)
> > {
> > - buf->data = (u8 *)__get_free_page(GFP_KERNEL);
> > - if (!buf->data)
> > - return -ENOMEM;
> > -
> > - tpm_buf_reset(buf, tag, ordinal);
> > - return 0;
> > + u32 buf_size_2 = (u32)buf->capacity + (u32)sizeof(*buf);
> > +
> > + if (!buf->capacity) {
> > + if (buf_size > TPM_BUFSIZE) {
> > + WARN(1, "%s: size overflow: %u\n", __func__, buf_size);
> > + buf->flags |= TPM_BUF_OVERFLOW;
> > + }
> > + } else {
> > + if (buf_size != buf_size_2) {
> > + WARN(1, "%s: size mismatch: %u != %u\n", __func__,
> > + buf_size, buf_size_2);
> > + buf->flags |= TPM_BUF_OVERFLOW;
> > + }
> > + }
> > }
> > -EXPORT_SYMBOL_GPL(tpm_buf_init);
> >
> > -/**
> > - * tpm_buf_reset() - Initialize a TPM command
> > - * @buf: A &tpm_buf
> > - * @tag: TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS
> > - * @ordinal: A command ordinal
> > - */
> > -void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal)
> > +static void __tpm_buf_reset(struct tpm_buf *buf, u16 buf_size, u16 tag,
> > + u32 ordinal)
> > {
> > struct tpm_header *head = (struct tpm_header *)buf->data;
> >
> > + __tpm_buf_size_invariant(buf, buf_size);
> > +
> > + if (buf->flags & TPM_BUF_OVERFLOW)
> > + return;
> > +
> > WARN_ON(tag != TPM_TAG_RQU_COMMAND && tag != TPM2_ST_NO_SESSIONS &&
> > tag != TPM2_ST_SESSIONS && tag != 0);
> >
> > buf->flags = 0;
> > buf->length = sizeof(*head);
> > + buf->capacity = buf_size - sizeof(*buf);
> > + buf->handles = 0;
> > head->tag = cpu_to_be16(tag);
> > head->length = cpu_to_be32(sizeof(*head));
> > head->ordinal = cpu_to_be32(ordinal);
> > +}
> > +
> > +static void __tpm_buf_reset_sized(struct tpm_buf *buf, u16 buf_size)
> > +{
> > + __tpm_buf_size_invariant(buf, buf_size);
> > +
> > + if (buf->flags & TPM_BUF_OVERFLOW)
> > + return;
> > +
> > + buf->flags = TPM_BUF_TPM2B;
> > + buf->length = 2;
> > + buf->capacity = buf_size - sizeof(*buf);
> > buf->handles = 0;
> > + buf->data[0] = 0;
> > + buf->data[1] = 0;
> > }
> > -EXPORT_SYMBOL_GPL(tpm_buf_reset);
> >
> > /**
> > - * tpm_buf_init_sized() - Allocate and initialize a sized (TPM2B) buffer
> > - * @buf: A @tpm_buf
> > - *
> > - * Return: 0 or -ENOMEM
> > + * tpm_buf_init() - Initialize a TPM command
> > + * @buf: A &tpm_buf
> > + * @buf_size: Size of the buffer.
> > */
> > -int tpm_buf_init_sized(struct tpm_buf *buf)
> > +void tpm_buf_init(struct tpm_buf *buf, u16 buf_size)
> > {
> > - buf->data = (u8 *)__get_free_page(GFP_KERNEL);
> > - if (!buf->data)
> > - return -ENOMEM;
> > + memset(buf, 0, buf_size);
> > + __tpm_buf_reset(buf, buf_size, TPM_TAG_RQU_COMMAND, 0);
> > +}
> > +EXPORT_SYMBOL_GPL(tpm_buf_init);
> >
> > - tpm_buf_reset_sized(buf);
> > - return 0;
> > +/**
> > + * tpm_buf_init_sized() - Initialize a sized buffer
> > + * @buf: A &tpm_buf
> > + * @buf_size: Size of the buffer.
> > + */
> > +void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size)
> > +{
> > + memset(buf, 0, buf_size);
> > + __tpm_buf_reset_sized(buf, buf_size);
> > }
> > EXPORT_SYMBOL_GPL(tpm_buf_init_sized);
> >
> > /**
> > - * tpm_buf_reset_sized() - Initialize a sized buffer
> > + * tpm_buf_reset() - Re-initialize a TPM command
> > * @buf: A &tpm_buf
> > + * @tag: TPM_TAG_RQU_COMMAND, TPM2_ST_NO_SESSIONS or TPM2_ST_SESSIONS
> > + * @ordinal: A command ordinal
> > */
> > -void tpm_buf_reset_sized(struct tpm_buf *buf)
> > +void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal)
> > {
> > - buf->flags = TPM_BUF_TPM2B;
> > - buf->length = 2;
> > - buf->data[0] = 0;
> > - buf->data[1] = 0;
> > + u16 buf_size = buf->capacity + sizeof(*buf);
> > +
> > + __tpm_buf_reset(buf, buf_size, tag, ordinal);
> > }
> > -EXPORT_SYMBOL_GPL(tpm_buf_reset_sized);
> > +EXPORT_SYMBOL_GPL(tpm_buf_reset);
> >
> > -void tpm_buf_destroy(struct tpm_buf *buf)
> > +/**
> > + * tpm_buf_reset_sized() - Re-initialize a sized buffer
> > + * @buf: A &tpm_buf
> > + */
> > +void tpm_buf_reset_sized(struct tpm_buf *buf)
> > {
> > - free_page((unsigned long)buf->data);
> > + u16 buf_size = buf->capacity + sizeof(*buf);
> > +
> > + __tpm_buf_reset_sized(buf, buf_size);
> > }
> > -EXPORT_SYMBOL_GPL(tpm_buf_destroy);
> > +EXPORT_SYMBOL_GPL(tpm_buf_reset_sized);
> >
> > /**
> > * tpm_buf_length() - Return the number of bytes consumed by the data
> > @@ -90,7 +118,7 @@ EXPORT_SYMBOL_GPL(tpm_buf_destroy);
> > *
> > * Return: The number of bytes consumed by the buffer
> > */
> > -u32 tpm_buf_length(struct tpm_buf *buf)
> > +u16 tpm_buf_length(struct tpm_buf *buf)
> > {
> > return buf->length;
> > }
> > @@ -104,11 +132,13 @@ EXPORT_SYMBOL_GPL(tpm_buf_length);
> > */
> > void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length)
> > {
> > + u32 total_length = (u32)buf->length + (u32)new_length;
> > +
> > /* Return silently if overflow has already happened. */
> > if (buf->flags & TPM_BUF_OVERFLOW)
> > return;
> >
> > - if ((buf->length + new_length) > PAGE_SIZE) {
> > + if (total_length > (u32)buf->capacity) {
> > WARN(1, "tpm_buf: write overflow\n");
> > buf->flags |= TPM_BUF_OVERFLOW;
> > return;
> > diff --git a/drivers/char/tpm/tpm-sysfs.c b/drivers/char/tpm/tpm-sysfs.c
> > index 94231f052ea7..1de03cf340b3 100644
> > --- a/drivers/char/tpm/tpm-sysfs.c
> > +++ b/drivers/char/tpm/tpm-sysfs.c
> > @@ -32,28 +32,31 @@ struct tpm_readpubek_out {
> > static ssize_t pubek_show(struct device *dev, struct device_attribute *attr,
> > char *buf)
> > {
> > - struct tpm_buf tpm_buf;
> > struct tpm_readpubek_out *out;
> > int i;
> > char *str = buf;
> > struct tpm_chip *chip = to_tpm_chip(dev);
> > char anti_replay[20];
> > + struct tpm_buf *tpm_buf __free(kfree) = NULL;
> >
> > memset(&anti_replay, 0, sizeof(anti_replay));
> >
> > if (tpm_try_get_ops(chip))
> > return 0;
> >
> > - if (tpm_buf_init(&tpm_buf, TPM_TAG_RQU_COMMAND, TPM_ORD_READPUBEK))
> > + tpm_buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!tpm_buf)
> > goto out_ops;
> >
> > - tpm_buf_append(&tpm_buf, anti_replay, sizeof(anti_replay));
> > + tpm_buf_init(tpm_buf, TPM_BUFSIZE);
> > + tpm_buf_reset(tpm_buf, TPM_TAG_RQU_COMMAND, TPM_ORD_READPUBEK);
> > + tpm_buf_append(tpm_buf, anti_replay, sizeof(anti_replay));
> >
> > - if (tpm_transmit_cmd(chip, &tpm_buf, READ_PUBEK_RESULT_MIN_BODY_SIZE,
> > + if (tpm_transmit_cmd(chip, tpm_buf, READ_PUBEK_RESULT_MIN_BODY_SIZE,
> > "attempting to read the PUBEK"))
> > - goto out_buf;
> > + goto out_ops;
> >
> > - out = (struct tpm_readpubek_out *)&tpm_buf.data[10];
> > + out = (struct tpm_readpubek_out *)&tpm_buf->data[10];
> > str +=
> > sprintf(str,
> > "Algorithm: %4ph\n"
> > @@ -71,8 +74,6 @@ static ssize_t pubek_show(struct device *dev, struct device_attribute *attr,
> > for (i = 0; i < 256; i += 16)
> > str += sprintf(str, "%16ph\n", &out->modulus[i]);
> >
> > -out_buf:
> > - tpm_buf_destroy(&tpm_buf);
> > out_ops:
> > tpm_put_ops(chip);
> > return str - buf;
> > diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> > index 87d68ddf270a..03f5346343ab 100644
> > --- a/drivers/char/tpm/tpm.h
> > +++ b/drivers/char/tpm/tpm.h
> > @@ -33,7 +33,6 @@
> > #endif
> >
> > #define TPM_MINOR 224 /* officially assigned */
> > -#define TPM_BUFSIZE 4096
> > #define TPM_NUM_DEVICES 65536
> > #define TPM_RETRY 50
> >
> > diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
> > index b49a790f1bd5..6facc3de2c46 100644
> > --- a/drivers/char/tpm/tpm1-cmd.c
> > +++ b/drivers/char/tpm/tpm1-cmd.c
> > @@ -323,20 +323,18 @@ unsigned long tpm1_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal)
> > */
> > static int tpm1_startup(struct tpm_chip *chip)
> > {
> > - struct tpm_buf buf;
> > - int rc;
> > + struct tpm_buf *buf __free(kfree) = NULL;
> >
> > dev_info(&chip->dev, "starting up the TPM manually\n");
> >
> > - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_STARTUP);
> > - if (rc < 0)
> > - return rc;
> > -
> > - tpm_buf_append_u16(&buf, TPM_ST_CLEAR);
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to start the TPM");
> > - tpm_buf_destroy(&buf);
> > - return rc;
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_STARTUP);
> > + tpm_buf_append_u16(buf, TPM_ST_CLEAR);
> > + return tpm_transmit_cmd(chip, buf, 0, "attempting to start the TPM");
> > }
> >
> > int tpm1_get_timeouts(struct tpm_chip *chip)
> > @@ -463,50 +461,47 @@ int tpm1_get_timeouts(struct tpm_chip *chip)
> > int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
> > const char *log_msg)
> > {
> > - struct tpm_buf buf;
> > - int rc;
> > -
> > - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND);
> > - if (rc)
> > - return rc;
> > -
> > - tpm_buf_append_u32(&buf, pcr_idx);
> > - tpm_buf_append(&buf, hash, TPM_DIGEST_SIZE);
> > -
> > - rc = tpm_transmit_cmd(chip, &buf, TPM_DIGEST_SIZE, log_msg);
> > - tpm_buf_destroy(&buf);
> > - return rc;
> > + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND);
> > + tpm_buf_append_u32(buf, pcr_idx);
> > + tpm_buf_append(buf, hash, TPM_DIGEST_SIZE);
> > + return tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE, log_msg);
> > }
> >
> > #define TPM_ORD_GET_CAP 101
> > ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
> > const char *desc, size_t min_cap_length)
> > {
> > - struct tpm_buf buf;
> > int rc;
> >
> > - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_CAP);
> > - if (rc)
> > - return rc;
> > + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_CAP);
> >
> > if (subcap_id == TPM_CAP_VERSION_1_1 ||
> > subcap_id == TPM_CAP_VERSION_1_2) {
> > - tpm_buf_append_u32(&buf, subcap_id);
> > - tpm_buf_append_u32(&buf, 0);
> > + tpm_buf_append_u32(buf, subcap_id);
> > + tpm_buf_append_u32(buf, 0);
> > } else {
> > if (subcap_id == TPM_CAP_FLAG_PERM ||
> > subcap_id == TPM_CAP_FLAG_VOL)
> > - tpm_buf_append_u32(&buf, TPM_CAP_FLAG);
> > + tpm_buf_append_u32(buf, TPM_CAP_FLAG);
> > else
> > - tpm_buf_append_u32(&buf, TPM_CAP_PROP);
> > + tpm_buf_append_u32(buf, TPM_CAP_PROP);
> >
> > - tpm_buf_append_u32(&buf, 4);
> > - tpm_buf_append_u32(&buf, subcap_id);
> > + tpm_buf_append_u32(buf, 4);
> > + tpm_buf_append_u32(buf, subcap_id);
> > }
> > - rc = tpm_transmit_cmd(chip, &buf, min_cap_length, desc);
> > + rc = tpm_transmit_cmd(chip, buf, min_cap_length, desc);
> > if (!rc)
> > - *cap = *(cap_t *)&buf.data[TPM_HEADER_SIZE + 4];
> > - tpm_buf_destroy(&buf);
> > + *cap = *(cap_t *)&buf->data[TPM_HEADER_SIZE + 4];
> > return rc;
> > }
> > EXPORT_SYMBOL_GPL(tpm1_getcap);
> > @@ -530,21 +525,24 @@ struct tpm1_get_random_out {
> > int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
> > {
> > struct tpm1_get_random_out *out;
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > u32 num_bytes = min_t(u32, max, TPM_MAX_RNG_DATA);
> > - struct tpm_buf buf;
> > u32 total = 0;
> > int retries = 5;
> > u32 recd;
> > int rc;
> >
> > - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM);
> > - if (rc)
> > - return rc;
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM);
> >
> > do {
> > - tpm_buf_append_u32(&buf, num_bytes);
> > + tpm_buf_append_u32(buf, num_bytes);
> >
> > - rc = tpm_transmit_cmd(chip, &buf, sizeof(out->rng_data_len),
> > + rc = tpm_transmit_cmd(chip, buf, sizeof(out->rng_data_len),
> > "attempting get random");
> > if (rc) {
> > if (rc > 0)
> > @@ -552,7 +550,7 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
> > goto out;
> > }
> >
> > - out = (struct tpm1_get_random_out *)&buf.data[TPM_HEADER_SIZE];
> > + out = (struct tpm1_get_random_out *)&buf->data[TPM_HEADER_SIZE];
> >
> > recd = be32_to_cpu(out->rng_data_len);
> > if (recd > num_bytes) {
> > @@ -560,8 +558,8 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
> > goto out;
> > }
> >
> > - if (tpm_buf_length(&buf) < TPM_HEADER_SIZE +
> > - sizeof(out->rng_data_len) + recd) {
> > + if (tpm_buf_length(buf) < TPM_HEADER_SIZE +
> > + sizeof(out->rng_data_len) + recd) {
> > rc = -EFAULT;
> > goto out;
> > }
> > @@ -571,41 +569,36 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
> > total += recd;
> > num_bytes -= recd;
> >
> > - tpm_buf_reset(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM);
> > + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM);
> > } while (retries-- && total < max);
> >
> > rc = total ? (int)total : -EIO;
> > out:
> > - tpm_buf_destroy(&buf);
> > return rc;
> > }
> >
> > #define TPM_ORD_PCRREAD 21
> > int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
> > {
> > - struct tpm_buf buf;
> > int rc;
> >
> > - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCRREAD);
> > - if (rc)
> > - return rc;
> > + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> >
> > - tpm_buf_append_u32(&buf, pcr_idx);
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCRREAD);
> > + tpm_buf_append_u32(buf, pcr_idx);
> >
> > - rc = tpm_transmit_cmd(chip, &buf, TPM_DIGEST_SIZE,
> > + rc = tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE,
> > "attempting to read a pcr value");
> > if (rc)
> > - goto out;
> > -
> > - if (tpm_buf_length(&buf) < TPM_DIGEST_SIZE) {
> > - rc = -EFAULT;
> > - goto out;
> > - }
> > + return rc;
> >
> > - memcpy(res_buf, &buf.data[TPM_HEADER_SIZE], TPM_DIGEST_SIZE);
> > + if (tpm_buf_length(buf) < TPM_DIGEST_SIZE)
> > + return -EFAULT;
> >
> > -out:
> > - tpm_buf_destroy(&buf);
> > + memcpy(res_buf, &buf->data[TPM_HEADER_SIZE], TPM_DIGEST_SIZE);
> > return rc;
> > }
> >
> > @@ -619,16 +612,13 @@ int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
> > */
> > static int tpm1_continue_selftest(struct tpm_chip *chip)
> > {
> > - struct tpm_buf buf;
> > - int rc;
> > + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> >
> > - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_CONTINUE_SELFTEST);
> > - if (rc)
> > - return rc;
> > -
> > - rc = tpm_transmit_cmd(chip, &buf, 0, "continue selftest");
> > - tpm_buf_destroy(&buf);
> > - return rc;
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_CONTINUE_SELFTEST);
> > + return tpm_transmit_cmd(chip, buf, 0, "continue selftest");
> > }
> >
> > /**
> > @@ -742,22 +732,24 @@ int tpm1_auto_startup(struct tpm_chip *chip)
> > int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr)
> > {
> > u8 dummy_hash[TPM_DIGEST_SIZE] = { 0 };
> > - struct tpm_buf buf;
> > unsigned int try;
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > int rc;
> >
> > -
> > /* for buggy tpm, flush pcrs with extend to selected dummy */
> > if (tpm_suspend_pcr)
> > rc = tpm1_pcr_extend(chip, tpm_suspend_pcr, dummy_hash,
> > "extending dummy pcr before suspend");
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
> >
> > - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
> > - if (rc)
> > - return rc;
> > /* now do the actual savestate */
> > for (try = 0; try < TPM_RETRY; try++) {
> > - rc = tpm_transmit_cmd(chip, &buf, 0, NULL);
> > + rc = tpm_transmit_cmd(chip, buf, 0, NULL);
> > /*
> > * If the TPM indicates that it is too busy to respond to
> > * this command then retry before giving up. It can take
> > @@ -772,7 +764,7 @@ int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr)
> > break;
> > tpm_msleep(TPM_TIMEOUT_RETRY);
> >
> > - tpm_buf_reset(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
> > + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
> > }
> >
> > if (rc)
> > @@ -782,8 +774,6 @@ int tpm1_pm_suspend(struct tpm_chip *chip, u32 tpm_suspend_pcr)
> > dev_warn(&chip->dev, "TPM savestate took %dms\n",
> > try * TPM_TIMEOUT_RETRY);
> >
> > - tpm_buf_destroy(&buf);
> > -
> > return rc;
> > }
> >
> > diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
> > index 52ee350da867..f619ce390f6d 100644
> > --- a/drivers/char/tpm/tpm2-cmd.c
> > +++ b/drivers/char/tpm/tpm2-cmd.c
> > @@ -119,12 +119,13 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
> > {
> > int i;
> > int rc;
> > - struct tpm_buf buf;
> > struct tpm2_pcr_read_out *out;
> > u8 pcr_select[TPM2_PCR_SELECT_MIN] = {0};
> > u16 digest_size;
> > u16 expected_digest_size = 0;
> >
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > +
> > if (pcr_idx >= TPM2_PLATFORM_PCR)
> > return -EINVAL;
> >
> > @@ -139,36 +140,35 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
> > expected_digest_size = chip->allocated_banks[i].digest_size;
> > }
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_PCR_READ);
> > - if (rc)
> > - return rc;
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_PCR_READ);
> >
> > pcr_select[pcr_idx >> 3] = 1 << (pcr_idx & 0x7);
> >
> > - tpm_buf_append_u32(&buf, 1);
> > - tpm_buf_append_u16(&buf, digest->alg_id);
> > - tpm_buf_append_u8(&buf, TPM2_PCR_SELECT_MIN);
> > - tpm_buf_append(&buf, (const unsigned char *)pcr_select,
> > + tpm_buf_append_u32(buf, 1);
> > + tpm_buf_append_u16(buf, digest->alg_id);
> > + tpm_buf_append_u8(buf, TPM2_PCR_SELECT_MIN);
> > + tpm_buf_append(buf, (const unsigned char *)pcr_select,
> > sizeof(pcr_select));
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to read a pcr value");
> > + rc = tpm_transmit_cmd(chip, buf, 0, "attempting to read a pcr value");
> > if (rc)
> > - goto out;
> > + return rc;
> >
> > - out = (struct tpm2_pcr_read_out *)&buf.data[TPM_HEADER_SIZE];
> > + out = (struct tpm2_pcr_read_out *)&buf->data[TPM_HEADER_SIZE];
> > digest_size = be16_to_cpu(out->digest_size);
> > if (digest_size > sizeof(digest->digest) ||
> > - (!digest_size_ptr && digest_size != expected_digest_size)) {
> > - rc = -EINVAL;
> > - goto out;
> > - }
> > + (!digest_size_ptr && digest_size != expected_digest_size))
> > + return -EINVAL;
> >
> > if (digest_size_ptr)
> > *digest_size_ptr = digest_size;
> >
> > memcpy(digest->digest, out->digest, digest_size);
> > -out:
> > - tpm_buf_destroy(&buf);
> > return rc;
> > }
> >
> > @@ -184,56 +184,54 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
> > int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
> > struct tpm_digest *digests)
> > {
> > - struct tpm_buf buf;
> > int rc;
> > int i;
> >
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > +
> > if (!disable_pcr_integrity) {
> > rc = tpm2_start_auth_session(chip);
> > if (rc)
> > return rc;
> > }
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_EXTEND);
> > - if (rc) {
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf) {
> > if (!disable_pcr_integrity)
> > tpm2_end_auth_session(chip);
> > - return rc;
> > + return -ENOMEM;
> > }
> >
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_PCR_EXTEND);
> > +
> > if (!disable_pcr_integrity) {
> > - rc = tpm_buf_append_name(chip, &buf, pcr_idx, NULL);
> > - if (rc) {
> > - tpm_buf_destroy(&buf);
> > + rc = tpm_buf_append_name(chip, buf, pcr_idx, NULL);
> > + if (rc)
> > return rc;
> > - }
> > - tpm_buf_append_hmac_session(chip, &buf, 0, NULL, 0);
> > + tpm_buf_append_hmac_session(chip, buf, 0, NULL, 0);
> > } else {
> > - tpm_buf_append_handle(chip, &buf, pcr_idx);
> > - tpm_buf_append_auth(chip, &buf, NULL, 0);
> > + tpm_buf_append_handle(chip, buf, pcr_idx);
> > + tpm_buf_append_auth(chip, buf, NULL, 0);
> > }
> >
> > - tpm_buf_append_u32(&buf, chip->nr_allocated_banks);
> > + tpm_buf_append_u32(buf, chip->nr_allocated_banks);
> >
> > for (i = 0; i < chip->nr_allocated_banks; i++) {
> > - tpm_buf_append_u16(&buf, digests[i].alg_id);
> > - tpm_buf_append(&buf, (const unsigned char *)&digests[i].digest,
> > + tpm_buf_append_u16(buf, digests[i].alg_id);
> > + tpm_buf_append(buf, (const unsigned char *)&digests[i].digest,
> > chip->allocated_banks[i].digest_size);
> > }
> >
> > if (!disable_pcr_integrity) {
> > - rc = tpm_buf_fill_hmac_session(chip, &buf);
> > - if (rc) {
> > - tpm_buf_destroy(&buf);
> > + rc = tpm_buf_fill_hmac_session(chip, buf);
> > + if (rc)
> > return rc;
> > - }
> > }
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting extend a PCR value");
> > + rc = tpm_transmit_cmd(chip, buf, 0, "attempting extend a PCR value");
> > if (!disable_pcr_integrity)
> > - rc = tpm_buf_check_hmac_response(chip, &buf, rc);
> > -
> > - tpm_buf_destroy(&buf);
> > + rc = tpm_buf_check_hmac_response(chip, buf, rc);
> >
> > return rc;
> > }
> > @@ -258,7 +256,6 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
> > {
> > struct tpm2_get_random_out *out;
> > struct tpm_header *head;
> > - struct tpm_buf buf;
> > u32 recd;
> > u32 num_bytes = max;
> > int err;
> > @@ -267,6 +264,8 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
> > u8 *dest_ptr = dest;
> > off_t offset;
> >
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > +
> > if (!num_bytes || max > TPM_MAX_RNG_DATA)
> > return -EINVAL;
> >
> > @@ -274,50 +273,52 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
> > if (err)
> > return err;
> >
> > - err = tpm_buf_init(&buf, 0, 0);
> > - if (err) {
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf) {
> > tpm2_end_auth_session(chip);
> > - return err;
> > + return -ENOMEM;
> > }
> >
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > +
> > do {
> > - tpm_buf_reset(&buf, TPM2_ST_SESSIONS, TPM2_CC_GET_RANDOM);
> > + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_GET_RANDOM);
> > if (tpm2_chip_auth(chip)) {
> > - tpm_buf_append_hmac_session(chip, &buf,
> > + tpm_buf_append_hmac_session(chip, buf,
> > TPM2_SA_ENCRYPT |
> > TPM2_SA_CONTINUE_SESSION,
> > NULL, 0);
> > } else {
> > - offset = buf.handles * 4 + TPM_HEADER_SIZE;
> > - head = (struct tpm_header *)buf.data;
> > - if (tpm_buf_length(&buf) == offset)
> > + offset = buf->handles * 4 + TPM_HEADER_SIZE;
> > + head = (struct tpm_header *)buf->data;
> > + if (tpm_buf_length(buf) == offset)
> > head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
> > }
> > - tpm_buf_append_u16(&buf, num_bytes);
> > - err = tpm_buf_fill_hmac_session(chip, &buf);
> > + tpm_buf_append_u16(buf, num_bytes);
> > + err = tpm_buf_fill_hmac_session(chip, buf);
> > if (err)
> > goto out;
> >
> > - err = tpm_transmit_cmd(chip, &buf,
> > + err = tpm_transmit_cmd(chip, buf,
> > offsetof(struct tpm2_get_random_out,
> > buffer),
> > "attempting get random");
> > - err = tpm_buf_check_hmac_response(chip, &buf, err);
> > + err = tpm_buf_check_hmac_response(chip, buf, err);
> > if (err) {
> > if (err > 0)
> > err = -EIO;
> > goto out;
> > }
> >
> > - head = (struct tpm_header *)buf.data;
> > + head = (struct tpm_header *)buf->data;
> > offset = TPM_HEADER_SIZE;
> > /* Skip the parameter size field: */
> > if (be16_to_cpu(head->tag) == TPM2_ST_SESSIONS)
> > offset += 4;
> >
> > - out = (struct tpm2_get_random_out *)&buf.data[offset];
> > + out = (struct tpm2_get_random_out *)&buf->data[offset];
> > recd = min_t(u32, be16_to_cpu(out->size), num_bytes);
> > - if (tpm_buf_length(&buf) <
> > + if (tpm_buf_length(buf) <
> > TPM_HEADER_SIZE +
> > offsetof(struct tpm2_get_random_out, buffer) +
> > recd) {
> > @@ -331,11 +332,8 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
> > num_bytes -= recd;
> > } while (retries-- && total < max);
> >
> > - tpm_buf_destroy(&buf);
> > -
> > return total ? total : -EIO;
> > out:
> > - tpm_buf_destroy(&buf);
> > tpm2_end_auth_session(chip);
> > return err;
> > }
> > @@ -347,20 +345,18 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
> > */
> > void tpm2_flush_context(struct tpm_chip *chip, u32 handle)
> > {
> > - struct tpm_buf buf;
> > - int rc;
> > -
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_FLUSH_CONTEXT);
> > - if (rc) {
> > + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf) {
> > dev_warn(&chip->dev, "0x%08x was not flushed, out of memory\n",
> > handle);
> > return;
> > }
> >
> > - tpm_buf_append_u32(&buf, handle);
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_FLUSH_CONTEXT);
> > + tpm_buf_append_u32(buf, handle);
> >
> > - tpm_transmit_cmd(chip, &buf, 0, "flushing context");
> > - tpm_buf_destroy(&buf);
> > + tpm_transmit_cmd(chip, buf, 0, "flushing context");
> > }
> > EXPORT_SYMBOL_GPL(tpm2_flush_context);
> >
> > @@ -387,19 +383,21 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, u32 *value,
> > const char *desc)
> > {
> > struct tpm2_get_cap_out *out;
> > - struct tpm_buf buf;
> > int rc;
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
> > - if (rc)
> > - return rc;
> > - tpm_buf_append_u32(&buf, TPM2_CAP_TPM_PROPERTIES);
> > - tpm_buf_append_u32(&buf, property_id);
> > - tpm_buf_append_u32(&buf, 1);
> > - rc = tpm_transmit_cmd(chip, &buf, 0, NULL);
> > + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
> > + tpm_buf_append_u32(buf, TPM2_CAP_TPM_PROPERTIES);
> > + tpm_buf_append_u32(buf, property_id);
> > + tpm_buf_append_u32(buf, 1);
> > + rc = tpm_transmit_cmd(chip, buf, 0, NULL);
> > if (!rc) {
> > out = (struct tpm2_get_cap_out *)
> > - &buf.data[TPM_HEADER_SIZE];
> > + &buf->data[TPM_HEADER_SIZE];
> > /*
> > * To prevent failing boot up of some systems, Infineon TPM2.0
> > * returns SUCCESS on TPM2_Startup in field upgrade mode. Also
> > @@ -411,7 +409,6 @@ ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id, u32 *value,
> > else
> > rc = -ENODATA;
> > }
> > - tpm_buf_destroy(&buf);
> > return rc;
> > }
> > EXPORT_SYMBOL_GPL(tpm2_get_tpm_pt);
> > @@ -428,15 +425,14 @@ EXPORT_SYMBOL_GPL(tpm2_get_tpm_pt);
> > */
> > void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type)
> > {
> > - struct tpm_buf buf;
> > - int rc;
> > -
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SHUTDOWN);
> > - if (rc)
> > + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > return;
> > - tpm_buf_append_u16(&buf, shutdown_type);
> > - tpm_transmit_cmd(chip, &buf, 0, "stopping the TPM");
> > - tpm_buf_destroy(&buf);
> > +
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SHUTDOWN);
> > + tpm_buf_append_u16(buf, shutdown_type);
> > + tpm_transmit_cmd(chip, buf, 0, "stopping the TPM");
> > }
> >
> > /**
> > @@ -454,20 +450,21 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type)
> > */
> > static int tpm2_do_selftest(struct tpm_chip *chip)
> > {
> > - struct tpm_buf buf;
> > int full;
> > int rc;
> >
> > for (full = 0; full < 2; full++) {
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SELF_TEST);
> > - if (rc)
> > - return rc;
> > + struct tpm_buf *buf __free(kfree) = NULL;
> >
> > - tpm_buf_append_u8(&buf, full);
> > - rc = tpm_transmit_cmd(chip, &buf, 0,
> > - "attempting the self test");
> > - tpm_buf_destroy(&buf);
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> >
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_SELF_TEST);
> > + tpm_buf_append_u8(buf, full);
> > + rc = tpm_transmit_cmd(chip, buf, 0,
> > + "attempting the self test");
> > if (rc == TPM2_RC_TESTING)
> > rc = TPM2_RC_SUCCESS;
> > if (rc == TPM2_RC_INITIALIZE || rc == TPM2_RC_SUCCESS)
> > @@ -492,23 +489,24 @@ static int tpm2_do_selftest(struct tpm_chip *chip)
> > int tpm2_probe(struct tpm_chip *chip)
> > {
> > struct tpm_header *out;
> > - struct tpm_buf buf;
> > int rc;
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
> > - if (rc)
> > - return rc;
> > - tpm_buf_append_u32(&buf, TPM2_CAP_TPM_PROPERTIES);
> > - tpm_buf_append_u32(&buf, TPM_PT_TOTAL_COMMANDS);
> > - tpm_buf_append_u32(&buf, 1);
> > - rc = tpm_transmit_cmd(chip, &buf, 0, NULL);
> > + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
> > + tpm_buf_append_u32(buf, TPM2_CAP_TPM_PROPERTIES);
> > + tpm_buf_append_u32(buf, TPM_PT_TOTAL_COMMANDS);
> > + tpm_buf_append_u32(buf, 1);
> > + rc = tpm_transmit_cmd(chip, buf, 0, NULL);
> > /* We ignore TPM return codes on purpose. */
> > if (rc >= 0) {
> > - out = (struct tpm_header *)buf.data;
> > + out = (struct tpm_header *)buf->data;
> > if (be16_to_cpu(out->tag) == TPM2_ST_NO_SESSIONS)
> > chip->flags |= TPM_CHIP_FLAG_TPM2;
> > }
> > - tpm_buf_destroy(&buf);
> > return 0;
> > }
> > EXPORT_SYMBOL_GPL(tpm2_probe);
> > @@ -548,7 +546,6 @@ struct tpm2_pcr_selection {
> > ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
> > {
> > struct tpm2_pcr_selection pcr_selection;
> > - struct tpm_buf buf;
> > void *marker;
> > void *end;
> > void *pcr_select_offset;
> > @@ -560,20 +557,22 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
> > int rc;
> > int i = 0;
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
> > - if (rc)
> > - return rc;
> > + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> >
> > - tpm_buf_append_u32(&buf, TPM2_CAP_PCRS);
> > - tpm_buf_append_u32(&buf, 0);
> > - tpm_buf_append_u32(&buf, 1);
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
> > + tpm_buf_append_u32(buf, TPM2_CAP_PCRS);
> > + tpm_buf_append_u32(buf, 0);
> > + tpm_buf_append_u32(buf, 1);
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 9, "get tpm pcr allocation");
> > + rc = tpm_transmit_cmd(chip, buf, 9, "get tpm pcr allocation");
> > if (rc)
> > goto out;
> >
> > nr_possible_banks = be32_to_cpup(
> > - (__be32 *)&buf.data[TPM_HEADER_SIZE + 5]);
> > + (__be32 *)&buf->data[TPM_HEADER_SIZE + 5]);
> > if (nr_possible_banks > TPM2_MAX_PCR_BANKS) {
> > pr_err("tpm: out of bank capacity: %u > %u\n",
> > nr_possible_banks, TPM2_MAX_PCR_BANKS);
> > @@ -581,10 +580,10 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
> > goto out;
> > }
> >
> > - marker = &buf.data[TPM_HEADER_SIZE + 9];
> > + marker = &buf->data[TPM_HEADER_SIZE + 9];
> >
> > - rsp_len = be32_to_cpup((__be32 *)&buf.data[2]);
> > - end = &buf.data[rsp_len];
> > + rsp_len = be32_to_cpup((__be32 *)&buf->data[2]);
> > + end = &buf->data[rsp_len];
> >
> > for (i = 0; i < nr_possible_banks; i++) {
> > pcr_select_offset = marker +
> > @@ -617,20 +616,19 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
> >
> > chip->nr_allocated_banks = nr_alloc_banks;
> > out:
> > - tpm_buf_destroy(&buf);
> > -
> > return rc;
> > }
> >
> > int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip)
> > {
> > - struct tpm_buf buf;
> > u32 nr_commands;
> > __be32 *attrs;
> > u32 cc;
> > int i;
> > int rc;
> >
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > +
> > rc = tpm2_get_tpm_pt(chip, TPM_PT_TOTAL_COMMANDS, &nr_commands, NULL);
> > if (rc)
> > goto out;
> > @@ -647,30 +645,31 @@ int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip)
> > goto out;
> > }
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
> > - if (rc)
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf) {
> > + rc = -ENOMEM;
> > goto out;
> > + }
> >
> > - tpm_buf_append_u32(&buf, TPM2_CAP_COMMANDS);
> > - tpm_buf_append_u32(&buf, TPM2_CC_FIRST);
> > - tpm_buf_append_u32(&buf, nr_commands);
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_GET_CAPABILITY);
> > + tpm_buf_append_u32(buf, TPM2_CAP_COMMANDS);
> > + tpm_buf_append_u32(buf, TPM2_CC_FIRST);
> > + tpm_buf_append_u32(buf, nr_commands);
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 9 + 4 * nr_commands, NULL);
> > - if (rc) {
> > - tpm_buf_destroy(&buf);
> > + rc = tpm_transmit_cmd(chip, buf, 9 + 4 * nr_commands, NULL);
> > + if (rc)
> > goto out;
> > - }
> >
> > if (nr_commands !=
> > - be32_to_cpup((__be32 *)&buf.data[TPM_HEADER_SIZE + 5])) {
> > + be32_to_cpup((__be32 *)&buf->data[TPM_HEADER_SIZE + 5])) {
> > rc = -EFAULT;
> > - tpm_buf_destroy(&buf);
> > goto out;
> > }
> >
> > chip->nr_commands = nr_commands;
> >
> > - attrs = (__be32 *)&buf.data[TPM_HEADER_SIZE + 9];
> > + attrs = (__be32 *)&buf->data[TPM_HEADER_SIZE + 9];
> > for (i = 0; i < nr_commands; i++, attrs++) {
> > chip->cc_attrs_tbl[i] = be32_to_cpup(attrs);
> > cc = chip->cc_attrs_tbl[i] & 0xFFFF;
> > @@ -682,8 +681,6 @@ int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip)
> > }
> > }
> >
> > - tpm_buf_destroy(&buf);
> > -
> > out:
> > if (rc > 0)
> > rc = -ENODEV;
> > @@ -704,20 +701,18 @@ EXPORT_SYMBOL_GPL(tpm2_get_cc_attrs_tbl);
> >
> > static int tpm2_startup(struct tpm_chip *chip)
> > {
> > - struct tpm_buf buf;
> > - int rc;
> > + struct tpm_buf *buf __free(kfree) = NULL;
> >
> > dev_info(&chip->dev, "starting up the TPM manually\n");
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_STARTUP);
> > - if (rc < 0)
> > - return rc;
> > -
> > - tpm_buf_append_u16(&buf, TPM2_SU_CLEAR);
> > - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to start the TPM");
> > - tpm_buf_destroy(&buf);
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> >
> > - return rc;
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_STARTUP);
> > + tpm_buf_append_u16(buf, TPM2_SU_CLEAR);
> > + return tpm_transmit_cmd(chip, buf, 0, "attempting to start the TPM");
> > }
> >
> > /**
> > diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
> > index 795cd99dc6fe..b6a93db5a5ee 100644
> > --- a/drivers/char/tpm/tpm2-sessions.c
> > +++ b/drivers/char/tpm/tpm2-sessions.c
> > @@ -167,8 +167,8 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name)
> > {
> > u32 mso = tpm2_handle_mso(handle);
> > off_t offset = TPM_HEADER_SIZE;
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > int rc, name_size_alg;
> > - struct tpm_buf buf;
> >
> > if (mso != TPM2_MSO_PERSISTENT && mso != TPM2_MSO_VOLATILE &&
> > mso != TPM2_MSO_NVRAM) {
> > @@ -176,50 +176,40 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name)
> > return sizeof(u32);
> > }
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC);
> > - if (rc)
> > - return rc;
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> >
> > - tpm_buf_append_u32(&buf, handle);
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC);
> > + tpm_buf_append_u32(buf, handle);
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 0, "TPM2_ReadPublic");
> > - if (rc) {
> > - tpm_buf_destroy(&buf);
> > + rc = tpm_transmit_cmd(chip, buf, 0, "TPM2_ReadPublic");
> > + if (rc)
> > return tpm_ret_to_err(rc);
> > - }
> >
> > /* Skip TPMT_PUBLIC: */
> > - offset += tpm_buf_read_u16(&buf, &offset);
> > + offset += tpm_buf_read_u16(buf, &offset);
> >
> > /*
> > * Ensure space for the length field of TPM2B_NAME and hashAlg field of
> > * TPMT_HA (the extra four bytes).
> > */
> > - if (offset + 4 > tpm_buf_length(&buf)) {
> > - tpm_buf_destroy(&buf);
> > + if (offset + 4 > tpm_buf_length(buf))
> > return -EIO;
> > - }
> > -
> > - rc = tpm_buf_read_u16(&buf, &offset);
> > - name_size_alg = name_size(&buf.data[offset]);
> >
> > - if (name_size_alg < 0) {
> > - tpm_buf_destroy(&buf);
> > + rc = tpm_buf_read_u16(buf, &offset);
> > + name_size_alg = name_size(&buf->data[offset]);
> > + if (name_size_alg < 0)
> > return name_size_alg;
> > - }
> >
> > - if (rc != name_size_alg) {
> > - tpm_buf_destroy(&buf);
> > + if (rc != name_size_alg)
> > return -EIO;
> > - }
> >
> > - if (offset + rc > tpm_buf_length(&buf)) {
> > - tpm_buf_destroy(&buf);
> > + if (offset + rc > tpm_buf_length(buf))
> > return -EIO;
> > - }
> >
> > - memcpy(name, &buf.data[offset], rc);
> > - tpm_buf_destroy(&buf);
> > + memcpy(name, &buf->data[offset], rc);
> > return name_size_alg;
> > }
> > #endif /* CONFIG_TCG_TPM2_HMAC */
> > @@ -987,8 +977,8 @@ static int tpm2_load_null(struct tpm_chip *chip, u32 *null_key)
> > */
> > int tpm2_start_auth_session(struct tpm_chip *chip)
> > {
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > struct tpm2_auth *auth;
> > - struct tpm_buf buf;
> > u32 null_key;
> > int rc;
> >
> > @@ -1007,41 +997,43 @@ int tpm2_start_auth_session(struct tpm_chip *chip)
> >
> > auth->session = TPM_HEADER_SIZE;
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_START_AUTH_SESS);
> > - if (rc)
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf) {
> > + rc = -ENOMEM;
> > goto out;
> > + }
> >
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_NO_SESSIONS, TPM2_CC_START_AUTH_SESS);
> > /* salt key handle */
> > - tpm_buf_append_u32(&buf, null_key);
> > + tpm_buf_append_u32(buf, null_key);
> > /* bind key handle */
> > - tpm_buf_append_u32(&buf, TPM2_RH_NULL);
> > + tpm_buf_append_u32(buf, TPM2_RH_NULL);
> > /* nonce caller */
> > get_random_bytes(auth->our_nonce, sizeof(auth->our_nonce));
> > - tpm_buf_append_u16(&buf, sizeof(auth->our_nonce));
> > - tpm_buf_append(&buf, auth->our_nonce, sizeof(auth->our_nonce));
> > + tpm_buf_append_u16(buf, sizeof(auth->our_nonce));
> > + tpm_buf_append(buf, auth->our_nonce, sizeof(auth->our_nonce));
> >
> > /* append encrypted salt and squirrel away unencrypted in auth */
> > - tpm_buf_append_salt(&buf, chip, auth);
> > + tpm_buf_append_salt(buf, chip, auth);
> > /* session type (HMAC, audit or policy) */
> > - tpm_buf_append_u8(&buf, TPM2_SE_HMAC);
> > + tpm_buf_append_u8(buf, TPM2_SE_HMAC);
> >
> > /* symmetric encryption parameters */
> > /* symmetric algorithm */
> > - tpm_buf_append_u16(&buf, TPM_ALG_AES);
> > + tpm_buf_append_u16(buf, TPM_ALG_AES);
> > /* bits for symmetric algorithm */
> > - tpm_buf_append_u16(&buf, AES_KEY_BITS);
> > + tpm_buf_append_u16(buf, AES_KEY_BITS);
> > /* symmetric algorithm mode (must be CFB) */
> > - tpm_buf_append_u16(&buf, TPM_ALG_CFB);
> > + tpm_buf_append_u16(buf, TPM_ALG_CFB);
> > /* hash algorithm for session */
> > - tpm_buf_append_u16(&buf, TPM_ALG_SHA256);
> > + tpm_buf_append_u16(buf, TPM_ALG_SHA256);
> >
> > - rc = tpm_ret_to_err(tpm_transmit_cmd(chip, &buf, 0, "StartAuthSession"));
> > + rc = tpm_ret_to_err(tpm_transmit_cmd(chip, buf, 0, "StartAuthSession"));
> > tpm2_flush_context(chip, null_key);
> >
> > if (rc == TPM2_RC_SUCCESS)
> > - rc = tpm2_parse_start_auth_session(auth, &buf);
> > -
> > - tpm_buf_destroy(&buf);
> > + rc = tpm2_parse_start_auth_session(auth, buf);
> >
> > if (rc == TPM2_RC_SUCCESS) {
> > chip->auth = auth;
> > @@ -1262,19 +1254,21 @@ static int tpm2_parse_create_primary(struct tpm_chip *chip, struct tpm_buf *buf,
> > static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy,
> > u32 *handle, u8 *name)
> > {
> > + struct tpm_buf *template __free(kfree) = NULL;
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > int rc;
> > - struct tpm_buf buf;
> > - struct tpm_buf template;
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE_PRIMARY);
> > - if (rc)
> > - return rc;
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> >
> > - rc = tpm_buf_init_sized(&template);
> > - if (rc) {
> > - tpm_buf_destroy(&buf);
> > - return rc;
> > - }
> > + template = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!template)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE_PRIMARY);
> > + tpm_buf_init_sized(template, TPM_BUFSIZE);
> >
> > /*
> > * create the template. Note: in order for userspace to
> > @@ -1286,75 +1280,72 @@ static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy,
> > */
> >
> > /* key type */
> > - tpm_buf_append_u16(&template, TPM_ALG_ECC);
> > + tpm_buf_append_u16(template, TPM_ALG_ECC);
> >
> > /* name algorithm */
> > - tpm_buf_append_u16(&template, TPM_ALG_SHA256);
> > + tpm_buf_append_u16(template, TPM_ALG_SHA256);
> >
> > /* object properties */
> > - tpm_buf_append_u32(&template, TPM2_OA_NULL_KEY);
> > + tpm_buf_append_u32(template, TPM2_OA_NULL_KEY);
> >
> > /* sauth policy (empty) */
> > - tpm_buf_append_u16(&template, 0);
> > + tpm_buf_append_u16(template, 0);
> >
> > /* BEGIN parameters: key specific; for ECC*/
> >
> > /* symmetric algorithm */
> > - tpm_buf_append_u16(&template, TPM_ALG_AES);
> > + tpm_buf_append_u16(template, TPM_ALG_AES);
> >
> > /* bits for symmetric algorithm */
> > - tpm_buf_append_u16(&template, AES_KEY_BITS);
> > + tpm_buf_append_u16(template, AES_KEY_BITS);
> >
> > /* algorithm mode (must be CFB) */
> > - tpm_buf_append_u16(&template, TPM_ALG_CFB);
> > + tpm_buf_append_u16(template, TPM_ALG_CFB);
> >
> > /* scheme (NULL means any scheme) */
> > - tpm_buf_append_u16(&template, TPM_ALG_NULL);
> > + tpm_buf_append_u16(template, TPM_ALG_NULL);
> >
> > /* ECC Curve ID */
> > - tpm_buf_append_u16(&template, TPM2_ECC_NIST_P256);
> > + tpm_buf_append_u16(template, TPM2_ECC_NIST_P256);
> >
> > /* KDF Scheme */
> > - tpm_buf_append_u16(&template, TPM_ALG_NULL);
> > + tpm_buf_append_u16(template, TPM_ALG_NULL);
> >
> > /* unique: key specific; for ECC it is two zero size points */
> > - tpm_buf_append_u16(&template, 0);
> > - tpm_buf_append_u16(&template, 0);
> > + tpm_buf_append_u16(template, 0);
> > + tpm_buf_append_u16(template, 0);
> >
> > /* END parameters */
> >
> > /* primary handle */
> > - tpm_buf_append_u32(&buf, hierarchy);
> > - tpm_buf_append_empty_auth(&buf, TPM2_RS_PW);
> > + tpm_buf_append_u32(buf, hierarchy);
> > + tpm_buf_append_empty_auth(buf, TPM2_RS_PW);
> >
> > /* sensitive create size is 4 for two empty buffers */
> > - tpm_buf_append_u16(&buf, 4);
> > + tpm_buf_append_u16(buf, 4);
> >
> > /* sensitive create auth data (empty) */
> > - tpm_buf_append_u16(&buf, 0);
> > + tpm_buf_append_u16(buf, 0);
> >
> > /* sensitive create sensitive data (empty) */
> > - tpm_buf_append_u16(&buf, 0);
> > + tpm_buf_append_u16(buf, 0);
> >
> > /* the public template */
> > - tpm_buf_append(&buf, template.data, template.length);
> > - tpm_buf_destroy(&template);
> > + tpm_buf_append(buf, template->data, template->length);
> >
> > /* outside info (empty) */
> > - tpm_buf_append_u16(&buf, 0);
> > + tpm_buf_append_u16(buf, 0);
> >
> > /* creation PCR (none) */
> > - tpm_buf_append_u32(&buf, 0);
> > + tpm_buf_append_u32(buf, 0);
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 0,
> > + rc = tpm_transmit_cmd(chip, buf, 0,
> > "attempting to create NULL primary");
> >
> > if (rc == TPM2_RC_SUCCESS)
> > - rc = tpm2_parse_create_primary(chip, &buf, handle, hierarchy,
> > + rc = tpm2_parse_create_primary(chip, buf, handle, hierarchy,
> > name);
> >
> > - tpm_buf_destroy(&buf);
> > -
> > return rc;
> > }
> >
> > diff --git a/drivers/char/tpm/tpm2-space.c b/drivers/char/tpm/tpm2-space.c
> > index 60354cd53b5c..cbf86ff5931f 100644
> > --- a/drivers/char/tpm/tpm2-space.c
> > +++ b/drivers/char/tpm/tpm2-space.c
> > @@ -71,24 +71,25 @@ void tpm2_del_space(struct tpm_chip *chip, struct tpm_space *space)
> > int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
> > unsigned int *offset, u32 *handle)
> > {
> > - struct tpm_buf tbuf;
> > struct tpm2_context *ctx;
> > unsigned int body_size;
> > int rc;
> >
> > - rc = tpm_buf_init(&tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_LOAD);
> > - if (rc)
> > - return rc;
> > + struct tpm_buf *tbuf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!tbuf)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(tbuf, TPM_BUFSIZE);
> > + tpm_buf_reset(tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_LOAD);
> >
> > ctx = (struct tpm2_context *)&buf[*offset];
> > body_size = sizeof(*ctx) + be16_to_cpu(ctx->blob_size);
> > - tpm_buf_append(&tbuf, &buf[*offset], body_size);
> > + tpm_buf_append(tbuf, &buf[*offset], body_size);
> >
> > - rc = tpm_transmit_cmd(chip, &tbuf, 4, NULL);
> > + rc = tpm_transmit_cmd(chip, tbuf, 4, NULL);
> > if (rc < 0) {
> > dev_warn(&chip->dev, "%s: failed with a system error %d\n",
> > __func__, rc);
> > - tpm_buf_destroy(&tbuf);
> > return -EFAULT;
> > } else if (tpm2_rc_value(rc) == TPM2_RC_HANDLE ||
> > rc == TPM2_RC_REFERENCE_H0) {
> > @@ -103,64 +104,55 @@ int tpm2_load_context(struct tpm_chip *chip, u8 *buf,
> > * flushed outside the space
> > */
> > *handle = 0;
> > - tpm_buf_destroy(&tbuf);
> > return -ENOENT;
> > } else if (tpm2_rc_value(rc) == TPM2_RC_INTEGRITY) {
> > - tpm_buf_destroy(&tbuf);
> > return -EINVAL;
> > } else if (rc > 0) {
> > dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n",
> > __func__, rc);
> > - tpm_buf_destroy(&tbuf);
> > return -EFAULT;
> > }
> >
> > - *handle = be32_to_cpup((__be32 *)&tbuf.data[TPM_HEADER_SIZE]);
> > + *handle = be32_to_cpup((__be32 *)&tbuf->data[TPM_HEADER_SIZE]);
> > *offset += body_size;
> > -
> > - tpm_buf_destroy(&tbuf);
> > return 0;
> > }
> >
> > int tpm2_save_context(struct tpm_chip *chip, u32 handle, u8 *buf,
> > unsigned int buf_size, unsigned int *offset)
> > {
> > - struct tpm_buf tbuf;
> > unsigned int body_size;
> > int rc;
> >
> > - rc = tpm_buf_init(&tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_SAVE);
> > - if (rc)
> > - return rc;
> > + struct tpm_buf *tbuf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!tbuf)
> > + return -ENOMEM;
> >
> > - tpm_buf_append_u32(&tbuf, handle);
> > + tpm_buf_init(tbuf, TPM_BUFSIZE);
> > + tpm_buf_reset(tbuf, TPM2_ST_NO_SESSIONS, TPM2_CC_CONTEXT_SAVE);
> > + tpm_buf_append_u32(tbuf, handle);
> >
> > - rc = tpm_transmit_cmd(chip, &tbuf, 0, NULL);
> > + rc = tpm_transmit_cmd(chip, tbuf, 0, NULL);
> > if (rc < 0) {
> > dev_warn(&chip->dev, "%s: failed with a system error %d\n",
> > __func__, rc);
> > - tpm_buf_destroy(&tbuf);
> > return -EFAULT;
> > } else if (tpm2_rc_value(rc) == TPM2_RC_REFERENCE_H0) {
> > - tpm_buf_destroy(&tbuf);
> > return -ENOENT;
> > } else if (rc) {
> > dev_warn(&chip->dev, "%s: failed with a TPM error 0x%04X\n",
> > __func__, rc);
> > - tpm_buf_destroy(&tbuf);
> > return -EFAULT;
> > }
> >
> > - body_size = tpm_buf_length(&tbuf) - TPM_HEADER_SIZE;
> > + body_size = tpm_buf_length(tbuf) - TPM_HEADER_SIZE;
> > if ((*offset + body_size) > buf_size) {
> > dev_warn(&chip->dev, "%s: out of backing storage\n", __func__);
> > - tpm_buf_destroy(&tbuf);
> > return -ENOMEM;
> > }
> >
> > - memcpy(&buf[*offset], &tbuf.data[TPM_HEADER_SIZE], body_size);
> > + memcpy(&buf[*offset], &tbuf->data[TPM_HEADER_SIZE], body_size);
> > *offset += body_size;
> > - tpm_buf_destroy(&tbuf);
> > return 0;
> > }
> >
> > diff --git a/drivers/char/tpm/tpm_vtpm_proxy.c b/drivers/char/tpm/tpm_vtpm_proxy.c
> > index 7bb0f4d4a2ed..b81fd2a537df 100644
> > --- a/drivers/char/tpm/tpm_vtpm_proxy.c
> > +++ b/drivers/char/tpm/tpm_vtpm_proxy.c
> > @@ -395,40 +395,36 @@ static bool vtpm_proxy_tpm_req_canceled(struct tpm_chip *chip, u8 status)
> >
> > static int vtpm_proxy_request_locality(struct tpm_chip *chip, int locality)
> > {
> > - struct tpm_buf buf;
> > int rc;
> > const struct tpm_header *header;
> > struct proxy_dev *proxy_dev = dev_get_drvdata(&chip->dev);
> >
> > + struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > if (chip->flags & TPM_CHIP_FLAG_TPM2)
> > - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS,
> > - TPM2_CC_SET_LOCALITY);
> > + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_SET_LOCALITY);
> > else
> > - rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND,
> > - TPM_ORD_SET_LOCALITY);
> > - if (rc)
> > - return rc;
> > - tpm_buf_append_u8(&buf, locality);
> > + tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SET_LOCALITY);
> > +
> > + tpm_buf_append_u8(buf, locality);
> >
> > proxy_dev->state |= STATE_DRIVER_COMMAND;
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to set locality");
> > + rc = tpm_transmit_cmd(chip, buf, 0, "attempting to set locality");
> >
> > proxy_dev->state &= ~STATE_DRIVER_COMMAND;
> >
> > - if (rc < 0) {
> > - locality = rc;
> > - goto out;
> > - }
> > + if (rc < 0)
> > + return rc;
> >
> > - header = (const struct tpm_header *)buf.data;
> > + header = (const struct tpm_header *)buf->data;
> > rc = be32_to_cpu(header->return_code);
> > if (rc)
> > locality = -1;
> >
> > -out:
> > - tpm_buf_destroy(&buf);
> > -
> > return locality;
> > }
> >
> > diff --git a/include/linux/tpm.h b/include/linux/tpm.h
> > index 202da079d500..14d75c1482d6 100644
> > --- a/include/linux/tpm.h
> > +++ b/include/linux/tpm.h
> > @@ -26,6 +26,7 @@
> > #include <crypto/aes.h>
> >
> > #define TPM_DIGEST_SIZE 20 /* Max TPM v1.2 PCR size */
> > +#define TPM_BUFSIZE 4096
> >
> > #define TPM2_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE
> > #define TPM2_MAX_PCR_BANKS 8
> > @@ -378,13 +379,15 @@ enum tpm_buf_flags {
> > };
> >
> > /*
> > - * A string buffer type for constructing TPM commands.
> > + * A buffer for constructing and parsing TPM commands, responses and sized
> > + * (TPM2B) buffers.
> > */
> > struct tpm_buf {
> > - u32 flags;
> > - u32 length;
> > - u8 *data;
> > + u8 flags;
> > + u16 length;
> > + u16 capacity;
> > u8 handles;
> > + u8 data[];
> > };
> >
> > enum tpm2_object_attributes {
> > @@ -415,12 +418,11 @@ struct tpm2_hash {
> > unsigned int tpm_id;
> > };
> >
> > -int tpm_buf_init(struct tpm_buf *buf, u16 tag, u32 ordinal);
> > +void tpm_buf_init(struct tpm_buf *buf, u16 buf_size);
> > +void tpm_buf_init_sized(struct tpm_buf *buf, u16 buf_size);
> > void tpm_buf_reset(struct tpm_buf *buf, u16 tag, u32 ordinal);
> > -int tpm_buf_init_sized(struct tpm_buf *buf);
> > void tpm_buf_reset_sized(struct tpm_buf *buf);
> > -void tpm_buf_destroy(struct tpm_buf *buf);
> > -u32 tpm_buf_length(struct tpm_buf *buf);
> > +u16 tpm_buf_length(struct tpm_buf *buf);
> > void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length);
> > void tpm_buf_append_u8(struct tpm_buf *buf, const u8 value);
> > void tpm_buf_append_u16(struct tpm_buf *buf, const u16 value);
> > diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
> > index 13513819991e..6e03fa7227e4 100644
> > --- a/security/keys/trusted-keys/trusted_tpm1.c
> > +++ b/security/keys/trusted-keys/trusted_tpm1.c
> > @@ -317,9 +317,8 @@ static int TSS_checkhmac2(unsigned char *buffer,
> > * For key specific tpm requests, we will generate and send our
> > * own TPM command packets using the drivers send function.
> > */
> > -static int trusted_tpm_send(unsigned char *cmd, size_t buflen)
> > +static int trusted_tpm_send(struct tpm_buf *buf)
> > {
> > - struct tpm_buf buf;
> > int rc;
> >
> > if (!chip)
> > @@ -329,12 +328,9 @@ static int trusted_tpm_send(unsigned char *cmd, size_t buflen)
> > if (rc)
> > return rc;
> >
> > - buf.flags = 0;
> > - buf.length = buflen;
> > - buf.data = cmd;
> > - dump_tpm_buf(cmd);
> > - rc = tpm_transmit_cmd(chip, &buf, 4, "sending data");
> > - dump_tpm_buf(cmd);
> > + dump_tpm_buf(buf->data);
> > + rc = tpm_transmit_cmd(chip, buf, 4, "sending data");
> > + dump_tpm_buf(buf->data);
> >
> > if (rc > 0)
> > /* TPM error */
> > @@ -380,7 +376,7 @@ static int osap(struct tpm_buf *tb, struct osapsess *s,
> > tpm_buf_append_u32(tb, handle);
> > tpm_buf_append(tb, ononce, TPM_NONCE_SIZE);
> >
> > - ret = trusted_tpm_send(tb->data, tb->length);
> > + ret = trusted_tpm_send(tb);
> > if (ret < 0)
> > return ret;
> >
> > @@ -404,7 +400,7 @@ static int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce)
> > return -ENODEV;
> >
> > tpm_buf_reset(tb, TPM_TAG_RQU_COMMAND, TPM_ORD_OIAP);
> > - ret = trusted_tpm_send(tb->data, tb->length);
> > + ret = trusted_tpm_send(tb);
> > if (ret < 0)
> > return ret;
> >
> > @@ -513,7 +509,7 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype,
> > tpm_buf_append_u8(tb, cont);
> > tpm_buf_append(tb, td->pubauth, SHA1_DIGEST_SIZE);
> >
> > - ret = trusted_tpm_send(tb->data, tb->length);
> > + ret = trusted_tpm_send(tb);
> > if (ret < 0)
> > goto out;
> >
> > @@ -604,7 +600,7 @@ static int tpm_unseal(struct tpm_buf *tb,
> > tpm_buf_append_u8(tb, cont);
> > tpm_buf_append(tb, authdata2, SHA1_DIGEST_SIZE);
> >
> > - ret = trusted_tpm_send(tb->data, tb->length);
> > + ret = trusted_tpm_send(tb);
> > if (ret < 0) {
> > pr_info("authhmac failed (%d)\n", ret);
> > return ret;
> > @@ -631,23 +627,23 @@ static int tpm_unseal(struct tpm_buf *tb,
> > static int key_seal(struct trusted_key_payload *p,
> > struct trusted_key_options *o)
> > {
> > - struct tpm_buf tb;
> > int ret;
> >
> > - ret = tpm_buf_init(&tb, 0, 0);
> > - if (ret)
> > - return ret;
> > + struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!tb)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(tb, TPM_BUFSIZE);
> >
> > /* include migratable flag at end of sealed key */
> > p->key[p->key_len] = p->migratable;
> >
> > - ret = tpm_seal(&tb, o->keytype, o->keyhandle, o->keyauth,
> > + ret = tpm_seal(tb, o->keytype, o->keyhandle, o->keyauth,
> > p->key, p->key_len + 1, p->blob, &p->blob_len,
> > o->blobauth, o->pcrinfo, o->pcrinfo_len);
> > if (ret < 0)
> > pr_info("srkseal failed (%d)\n", ret);
> >
> > - tpm_buf_destroy(&tb);
> > return ret;
> > }
> >
> > @@ -657,14 +653,15 @@ static int key_seal(struct trusted_key_payload *p,
> > static int key_unseal(struct trusted_key_payload *p,
> > struct trusted_key_options *o)
> > {
> > - struct tpm_buf tb;
> > int ret;
> >
> > - ret = tpm_buf_init(&tb, 0, 0);
> > - if (ret)
> > - return ret;
> > + struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!tb)
> > + return -ENOMEM;
> > +
> > + tpm_buf_init(tb, TPM_BUFSIZE);
> >
> > - ret = tpm_unseal(&tb, o->keyhandle, o->keyauth, p->blob, p->blob_len,
> > + ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len,
> > o->blobauth, p->key, &p->key_len);
> > if (ret < 0)
> > pr_info("srkunseal failed (%d)\n", ret);
> > @@ -672,7 +669,6 @@ static int key_unseal(struct trusted_key_payload *p,
> > /* pull migratable flag out of sealed key */
> > p->migratable = p->key[--p->key_len];
> >
> > - tpm_buf_destroy(&tb);
> > return ret;
> > }
> >
> > diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
> > index 6340823f8b53..6f5c34b885fb 100644
> > --- a/security/keys/trusted-keys/trusted_tpm2.c
> > +++ b/security/keys/trusted-keys/trusted_tpm2.c
> > @@ -234,7 +234,8 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
> > struct trusted_key_options *options)
> > {
> > off_t offset = TPM_HEADER_SIZE;
> > - struct tpm_buf buf, sized;
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > + struct tpm_buf *sized __free(kfree) = NULL;
> > int blob_len = 0;
> > int hash;
> > u32 flags;
> > @@ -255,97 +256,100 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
> > if (rc)
> > goto out_put;
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE);
> > - if (rc) {
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf) {
> > + rc = -ENOMEM;
> > tpm2_end_auth_session(chip);
> > goto out_put;
> > }
> >
> > - rc = tpm_buf_init_sized(&sized);
> > - if (rc) {
> > - tpm_buf_destroy(&buf);
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE);
> > +
> > + sized = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!sized) {
> > + rc = -ENOMEM;
> > tpm2_end_auth_session(chip);
> > goto out_put;
> > }
> >
> > - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
> > + tpm_buf_init_sized(sized, TPM_BUFSIZE);
> > +
> > + rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL);
> > if (rc)
> > goto out;
> >
> > - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT,
> > + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_DECRYPT,
> > options->keyauth, TPM_DIGEST_SIZE);
> >
> > /* sensitive */
> > - tpm_buf_append_u16(&sized, options->blobauth_len);
> > + tpm_buf_append_u16(sized, options->blobauth_len);
> >
> > if (options->blobauth_len)
> > - tpm_buf_append(&sized, options->blobauth, options->blobauth_len);
> > + tpm_buf_append(sized, options->blobauth, options->blobauth_len);
> >
> > - tpm_buf_append_u16(&sized, payload->key_len);
> > - tpm_buf_append(&sized, payload->key, payload->key_len);
> > - tpm_buf_append(&buf, sized.data, sized.length);
> > + tpm_buf_append_u16(sized, payload->key_len);
> > + tpm_buf_append(sized, payload->key, payload->key_len);
> > + tpm_buf_append(buf, sized->data, sized->length);
> >
> > /* public */
> > - tpm_buf_reset_sized(&sized);
> > - tpm_buf_append_u16(&sized, TPM_ALG_KEYEDHASH);
> > - tpm_buf_append_u16(&sized, hash);
> > + tpm_buf_reset_sized(sized);
> > + tpm_buf_append_u16(sized, TPM_ALG_KEYEDHASH);
> > + tpm_buf_append_u16(sized, hash);
> >
> > /* key properties */
> > flags = 0;
> > flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH;
> > flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT);
> > - tpm_buf_append_u32(&sized, flags);
> > + tpm_buf_append_u32(sized, flags);
> >
> > /* policy */
> > - tpm_buf_append_u16(&sized, options->policydigest_len);
> > + tpm_buf_append_u16(sized, options->policydigest_len);
> > if (options->policydigest_len)
> > - tpm_buf_append(&sized, options->policydigest, options->policydigest_len);
> > + tpm_buf_append(sized, options->policydigest, options->policydigest_len);
> >
> > /* public parameters */
> > - tpm_buf_append_u16(&sized, TPM_ALG_NULL);
> > - tpm_buf_append_u16(&sized, 0);
> > + tpm_buf_append_u16(sized, TPM_ALG_NULL);
> > + tpm_buf_append_u16(sized, 0);
> >
> > - tpm_buf_append(&buf, sized.data, sized.length);
> > + tpm_buf_append(buf, sized->data, sized->length);
> >
> > /* outside info */
> > - tpm_buf_append_u16(&buf, 0);
> > + tpm_buf_append_u16(buf, 0);
> >
> > /* creation PCR */
> > - tpm_buf_append_u32(&buf, 0);
> > + tpm_buf_append_u32(buf, 0);
> >
> > - if (buf.flags & TPM_BUF_OVERFLOW) {
> > + if (buf->flags & TPM_BUF_OVERFLOW) {
> > rc = -E2BIG;
> > tpm2_end_auth_session(chip);
> > goto out;
> > }
> >
> > - rc = tpm_buf_fill_hmac_session(chip, &buf);
> > + rc = tpm_buf_fill_hmac_session(chip, buf);
> > if (rc)
> > goto out;
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 4, "sealing data");
> > - rc = tpm_buf_check_hmac_response(chip, &buf, rc);
> > + rc = tpm_transmit_cmd(chip, buf, 4, "sealing data");
> > + rc = tpm_buf_check_hmac_response(chip, buf, rc);
> > if (rc)
> > goto out;
> >
> > - blob_len = tpm_buf_read_u32(&buf, &offset);
> > - if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_BOUNDARY_ERROR) {
> > + blob_len = tpm_buf_read_u32(buf, &offset);
> > + if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_BOUNDARY_ERROR) {
> > rc = -E2BIG;
> > goto out;
> > }
> > - if (buf.length - offset < blob_len) {
> > + if (buf->length - offset < blob_len) {
> > rc = -EFAULT;
> > goto out;
> > }
> >
> > - blob_len = tpm2_key_encode(payload, options, &buf.data[offset], blob_len);
> > + blob_len = tpm2_key_encode(payload, options, &buf->data[offset], blob_len);
> > if (blob_len < 0)
> > rc = blob_len;
> >
> > out:
> > - tpm_buf_destroy(&sized);
> > - tpm_buf_destroy(&buf);
> > -
> > if (!rc)
> > payload->blob_len = blob_len;
> >
> > @@ -373,7 +377,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
> > u32 *blob_handle)
> > {
> > u8 *blob_ref __free(kfree) = NULL;
> > - struct tpm_buf buf;
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > unsigned int private_len;
> > unsigned int public_len;
> > unsigned int blob_len;
> > @@ -427,39 +431,38 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
> > if (rc)
> > return rc;
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD);
> > - if (rc) {
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf) {
> > tpm2_end_auth_session(chip);
> > - return rc;
> > + return -ENOMEM;
> > }
> >
> > - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD);
> > +
> > + rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL);
> > if (rc)
> > - goto out;
> > + return rc;
> >
> > - tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth,
> > + tpm_buf_append_hmac_session(chip, buf, 0, options->keyauth,
> > TPM_DIGEST_SIZE);
> >
> > - tpm_buf_append(&buf, blob, blob_len);
> > + tpm_buf_append(buf, blob, blob_len);
> >
> > - if (buf.flags & TPM_BUF_OVERFLOW) {
> > - rc = -E2BIG;
> > + if (buf->flags & TPM_BUF_OVERFLOW) {
> > tpm2_end_auth_session(chip);
> > - goto out;
> > + return -E2BIG;
> > }
> >
> > - rc = tpm_buf_fill_hmac_session(chip, &buf);
> > + rc = tpm_buf_fill_hmac_session(chip, buf);
> > if (rc)
> > - goto out;
> > + return rc;
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 4, "loading blob");
> > - rc = tpm_buf_check_hmac_response(chip, &buf, rc);
> > + rc = tpm_transmit_cmd(chip, buf, 4, "loading blob");
> > + rc = tpm_buf_check_hmac_response(chip, buf, rc);
> > if (!rc)
> > *blob_handle = be32_to_cpup(
> > - (__be32 *) &buf.data[TPM_HEADER_SIZE]);
> > -
> > -out:
> > - tpm_buf_destroy(&buf);
> > + (__be32 *)&buf->data[TPM_HEADER_SIZE]);
> >
> > return tpm_ret_to_err(rc);
> > }
> > @@ -482,7 +485,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
> > u32 blob_handle)
> > {
> > struct tpm_header *head;
> > - struct tpm_buf buf;
> > + struct tpm_buf *buf __free(kfree) = NULL;
> > u16 data_len;
> > int offset;
> > u8 *data;
> > @@ -492,18 +495,21 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
> > if (rc)
> > return rc;
> >
> > - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
> > - if (rc) {
> > + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> > + if (!buf) {
> > tpm2_end_auth_session(chip);
> > - return rc;
> > + return -ENOMEM;
> > }
> >
> > - rc = tpm_buf_append_name(chip, &buf, blob_handle, NULL);
> > + tpm_buf_init(buf, TPM_BUFSIZE);
> > + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
> > +
> > + rc = tpm_buf_append_name(chip, buf, blob_handle, NULL);
> > if (rc)
> > - goto out;
> > + return rc;
> >
> > if (!options->policyhandle) {
> > - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT,
> > + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT,
> > options->blobauth,
> > options->blobauth_len);
> > } else {
> > @@ -518,39 +524,36 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
> > * could repeat our actions with the exfiltrated
> > * password.
> > */
> > - tpm2_buf_append_auth(&buf, options->policyhandle,
> > + tpm2_buf_append_auth(buf, options->policyhandle,
> > NULL /* nonce */, 0, 0,
> > options->blobauth, options->blobauth_len);
> > if (tpm2_chip_auth(chip)) {
> > - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, NULL, 0);
> > + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT,
> > + NULL, 0);
> > } else {
> > - offset = buf.handles * 4 + TPM_HEADER_SIZE;
> > - head = (struct tpm_header *)buf.data;
> > - if (tpm_buf_length(&buf) == offset)
> > + offset = buf->handles * 4 + TPM_HEADER_SIZE;
> > + head = (struct tpm_header *)buf->data;
> > + if (tpm_buf_length(buf) == offset)
> > head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
> > }
> > }
> >
> > - rc = tpm_buf_fill_hmac_session(chip, &buf);
> > + rc = tpm_buf_fill_hmac_session(chip, buf);
> > if (rc)
> > - goto out;
> > + return rc;
> >
> > - rc = tpm_transmit_cmd(chip, &buf, 6, "unsealing");
> > - rc = tpm_buf_check_hmac_response(chip, &buf, rc);
> > + rc = tpm_transmit_cmd(chip, buf, 6, "unsealing");
> > + rc = tpm_buf_check_hmac_response(chip, buf, rc);
> >
> > if (!rc) {
> > data_len = be16_to_cpup(
> > - (__be16 *) &buf.data[TPM_HEADER_SIZE + 4]);
> > - if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE) {
> > - rc = -EFAULT;
> > - goto out;
> > - }
> > + (__be16 *)&buf->data[TPM_HEADER_SIZE + 4]);
> > + if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE)
> > + return -EFAULT;
> >
> > - if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + 6 + data_len) {
> > - rc = -EFAULT;
> > - goto out;
> > - }
> > - data = &buf.data[TPM_HEADER_SIZE + 6];
> > + if (tpm_buf_length(buf) < TPM_HEADER_SIZE + 6 + data_len)
> > + return -EFAULT;
> > + data = &buf->data[TPM_HEADER_SIZE + 6];
> >
> > if (payload->old_format) {
> > /* migratable flag is at the end of the key */
> > @@ -567,8 +570,6 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
> > }
> > }
> >
> > -out:
> > - tpm_buf_destroy(&buf);
> > return tpm_ret_to_err(rc);
> > }
> >
> > --
> > 2.47.3
> >
> >
Thank you.
BR, Jarkko
^ permalink raw reply
* Re: [PATCH] tpm-buf: memory-safe allocations
From: Jarkko Sakkinen @ 2026-05-29 22:33 UTC (permalink / raw)
To: James Bottomley
Cc: linux-integrity, Jarkko Sakkinen, Arun Menon, Daniel P. Smith,
Alec Brown, Ross Philipson, Stefan Berger, Peter Huewe,
Jason Gunthorpe, Mimi Zohar, David Howells, Paul Moore,
James Morris, Serge E. Hallyn, linux-kernel, keyrings,
linux-security-module
In-Reply-To: <27db53d88a44e057c2f0ed5a637f65e4e18c8c3d.camel@HansenPartnership.com>
On Fri, May 29, 2026 at 10:08:52AM -0400, James Bottomley wrote:
> On Tue, 2026-05-26 at 10:53 +0300, Jarkko Sakkinen wrote:
> > On Mon, May 25, 2026 at 01:50:51PM -0400, James Bottomley wrote:
> > > On Fri, 2026-05-22 at 04:35 +0300, Jarkko Sakkinen wrote:
> > > > Decouple kzalloc from buffer creation, so that a managed
> > > > allocation
> > > > can be
> > > > used:
> > > >
> > > > struct tpm_buf *buf __free(kfree) buf =
> > > > kzalloc(TPM_BUFSIZE,
> > > > GFP_KERNEL);
> > > > if (!buf)
> > > > return -ENOMEM;
> > > >
> > > > tpm_buf_init(buf, TPM_BUFSIZE);
> > > >
> > > > Alternatively, stack allocations are also possible:
> > > >
> > > > u8 buf_data[512];
> > > > struct tpm_buf *buf = (struct tpm_buf *)buf_data;
> > > > tpm_buf_init(buf, sizeof(buf_data));
> > >
> > > This isn't really a good idea from a security point of view.
> > > Remember the buffer has to be big enough for both the sent and the
> > > received data. Today we simply set TPM_BUFSIZE to the maximum
> > > amount a TPM requires and all the send and receives just work. If
> > > we let callers set this size, we're asking for them to get it wrong
> > > (or at least forget about the receive part) and for us to get a DMA
> > > overrun from the TPM ... which might be potentially exploitable
> > > depending on how it occurs (think of an unseal of user chosen data
> > > overrunning).
> >
> > It's one patch so you're free to remark the call sites where this
> > happens. This is not a majorn concern at all.
>
> Nearly twenty years ago, when the kernel was a lot smaller, a then
> kernel luminary called Rusty Russell realized we needed to pay much
> more attention to how we design APIs inside the kernel if we wanted it
> to grow successfully. He published his initial thoughts and gave talks
> at both the kernel summit and OLS on it:
>
> https://ozlabs.org/~rusty/index.cgi/tech/2008-03-18.html
>
> The key point that's always stuck with me is "hard to misuse beats easy
> to use". Later he came up with a rating scale (now known as the Rusty
> API classification):
>
> https://ozlabs.org/~rusty/index.cgi/tech/2008-03-30.html
>
> and for chuckles and grins on April fools day he came up with a
> negative rating ridiculing some of our dafter API choices:
>
> https://ozlabs.org/~rusty/index.cgi/tech/2008-04-01.html
>
> The point for this patch set is that the sizing of the original tpm_buf
> interface scores 10/10 on the Rusty scale (it's impossible to get
> wrong). Simply threading size through the whole API, as this patch
> does, may look like the right answer, but it causes a massive reduction
> in API score. In fact, since the buffer has to be sized not only
> according to what goes in, but also what gets returned and this is
> nowhere mentioned in the new documentation it scores -3 (read the
> documentation and you can still get it wrong). Now by mentioning the
> sizing problems in the doc, you can probably get it up to +3 (read the
> documentation and you'll get it right) but my question was not if you
> got it wrong somewhere in the patch but whether we couldn't do a whole
> lot better in terms of API score by designing a better API.
>
> A key point about the 185 version of the TPM spec is that it's really
> only a few commands that need larger buffers (the Post Quantum ML-KEM
> keys) which doesn't apply to most of the in-kernel TPM callsites.
> Since tpm_buf_init takes the ordinal, we can actually tell at runtime
> (or compile time if the ordinal is a constant) if the command would
> need a larger buffer. We can also tell from the TPM properties whether
> the TPM itself can take a larger buffer, so for every current TPM we
> could retain the original score 10/10 API and warn at runtime if there
> might be a problem. Then the larger keys seem to fit into 8k, so we
> could still retain most of the original API properties of being
> difficult to misuse simply by having an 8k size flag (which we could
> ignore if the TPM doesn't support it) and warn at runtime if
> tpm_buf_init sends an ordinal which might need a larger buffer. At
> worst we should be able to get to an API which scores 5/10 (do it right
> or it will break at runtime).
>
> Regards,
>
> James
This patch has pre-existed long before any of this post-quantum stuff,
and there are good reasons so to have buffers managed given e.g.,
complexity of tpm2-sessions code. It prevents any major risk for
memory leaks.
Trenchboot extends the use buffers to early boot and we want a robust
structure. I'm not going to spend my time reading about philosophical
aspects of API design. There are quantitative reasons to decrease
the risk of memory leaks.
BR, Jarkko
^ permalink raw reply
* Re: [PATCH 00/11] hornet: security, tooling and selftest fixes
From: Paul Moore @ 2026-05-29 20:56 UTC (permalink / raw)
To: Blaise Boscaccy
Cc: Jonathan Corbet, Shuah Khan, James Morris, Serge E. Hallyn,
Eric Biggers, Fan Wu, James.Bottomley, linux-security-module
In-Reply-To: <CAHC9VhSQzV0xZPFwkNdUJ5Lz8Mw6gppBhR6QyRdrY8xCTohHDw@mail.gmail.com>
On Thu, May 28, 2026 at 9:39 PM Paul Moore <paul@paul-moore.com> wrote:
> On Wed, May 27, 2026 at 11:09 PM Blaise Boscaccy
> <bboscaccy@linux.microsoft.com> wrote:
> >
> > Patch 1 closes a TOCTOU race in signature verification. Map
> > contents were hashed at the program-load hook and re-hashed at
> > the program-run hook, leaving a window in which a sufficiently
> > privileged attacker could mutate a map between the two checks
> > and run a program whose maps no longer matched what was signed.
> > The fix records the verified hashes on the prog at load time
> > and, in security_bpf_prog, checks them against
> > prog->aux->used_maps — the same map set the verifier and
> > runtime resolve against — so the verified and executed sets
> > cannot diverge. The per-map index in the signature format is no
> > longer needed and is dropped; the check becomes a subset test.
> > Reported by Eric Biggers.
> >
> > Patches 2-3 fix two counting bugs in the same area: duplicate maps
> > could satisfy the required hash count, and an off-by-one capped
> > accepted maps at MAX_USED_MAPS.
> >
> > Patches 4-11 are in response to sashiko feedback found here:
> > https://sashiko.dev/#/patchset/20260507191416.2984054-1-bboscaccy%40linux.microsoft.com
> >
> > They provide some correctness fixes in the hornet tooling along with
> > making the selftest behave under cross-compilation and skip cleanly
> > when signing keys / bpftool / vmlinux BTF are unavailable, instead of
> > breaking the global selftest build.
> >
> > Blaise Boscaccy (11):
> > hornet: fix TOCTOU in signed program verification
> > hornet: invert map set check logic
> > hornet: fix off-by-one bug in max used maps check
> > selftests: hornet: handle cross compilation and test skipping
> > hornet: gen_sig: fix off-by-one check for used maps
> > hornet: gen_sig: fix error string allocations
> > hornet: gen_sig: check for bad allocations
> > hornet: gen_sig: fix missing command line switches
> > hornet: scripts: set a non-zero error code for usage
> > hornet: scripts: harden scripts to handle trailing whitespace
> > hornet: scripts: Improve argument handling and error messages
> >
> > Documentation/admin-guide/LSM/Hornet.rst | 39 +++---
> > scripts/hornet/extract-insn.sh | 24 ++--
> > scripts/hornet/extract-map.sh | 25 ++--
> > scripts/hornet/extract-skel.sh | 35 ++++--
> > scripts/hornet/gen_sig.c | 61 ++++++----
> > scripts/hornet/write-sig.sh | 10 +-
> > security/hornet/hornet.asn1 | 1 -
> > security/hornet/hornet_lsm.c | 148 ++++-------------------
> > tools/testing/selftests/hornet/Makefile | 114 +++++++++++++----
> > 9 files changed, 235 insertions(+), 222 deletions(-)
>
> Aside from a possible (?) typo in patch 5/11, this patchset looks okay
> to me so I'm going to merge it to lsm/dev-staging now with the idea of
> moving it to lsm/dev once Blaise provides some clarity on patch-5.
With the typo in 5/11 sorted out, I've gone ahead and moved these
fixes over to lsm/dev. I also took the liberty of adding the
associated 'Fixes:' tags, not critical in this case, but nice to have.
--
paul-moore.com
^ permalink raw reply
* [PATCH] selftests/landlock: explicitly disable audit
From: Maximilian Heyne @ 2026-05-29 20:03 UTC (permalink / raw)
To: stable
Cc: Maximilian Heyne, Mickaël Salaün, Günther Noack,
Shuah Khan, linux-security-module, linux-kselftest, linux-kernel
I'm seeing sporadic selftest failures, such as
# RUN scoped_audit.connect_to_child ...
# scoped_abstract_unix_test.c:314:connect_to_child:Expected 0 (0) == records.access (8)
# connect_to_child: Test failed
# FAIL scoped_audit.connect_to_child
not ok 19 scoped_audit.connect_to_child
This seems similar to what commit 3647a4977fb73d ("selftests/landlock:
Drain stale audit records on init") tried to fix. However, the added
drain loop is not effective. When setting the AUDIT_STATUS_PID, the
kauditd_thread is woken up starting to send messages from the hold queue
to the netlink. Depending on scheduling of this kthread not all messages
might be send via the netlink in the 1 us interval.
Therefore, instead of trying to drain the queue, let's just disable
audit when running non-audit tests or more precisely disable it after
audit-tests. This way we won't generate any new audit message that could
interfere with the other tests.
The comment saying that on process exit audit will be disabled is wrong.
The closed file descriptor just causes an auditd_reset(), not a
disablement. So future messages will be queued in the hold queue.
Cc: stable@vger.kernel.org
Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs")
Signed-off-by: Maximilian Heyne <mheyne@amazon.de>
---
I've seen the failures on the 6.18 kernels but haven't tested on latest
upstream. However, I still think this is an issue.
---
tools/testing/selftests/landlock/audit.h | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h
index 834005b2b0f09..7842330875f53 100644
--- a/tools/testing/selftests/landlock/audit.h
+++ b/tools/testing/selftests/landlock/audit.h
@@ -494,10 +494,9 @@ static int audit_init_filter_exe(struct audit_filter *filter, const char *path)
static int audit_cleanup(int audit_fd, struct audit_filter *filter)
{
struct audit_filter new_filter;
+ int err;
if (audit_fd < 0 || !filter) {
- int err;
-
/*
* Simulates audit_init_with_exe_filter() when called from
* FIXTURE_TEARDOWN_PARENT().
@@ -518,12 +517,10 @@ static int audit_cleanup(int audit_fd, struct audit_filter *filter)
audit_filter_exe(audit_fd, filter, AUDIT_DEL_RULE);
audit_filter_drop(audit_fd, AUDIT_DEL_RULE);
- /*
- * Because audit_cleanup() might not be called by the test auditd
- * process, it might not be possible to explicitly set it. Anyway,
- * AUDIT_STATUS_ENABLED will implicitly be set to 0 when the auditd
- * process will exit.
- */
+ err = audit_set_status(audit_fd, AUDIT_STATUS_ENABLED, 0);
+ if (err)
+ return err;
+
return close(audit_fd);
}
--
2.50.1
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christof Hellmis, Andreas Stieger
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox