Linux Security Modules development
 help / color / mirror / Atom feed
* [PATCH v4 0/5] Implement LANDLOCK_ADD_RULE_NO_INHERIT
From: Justin Suess @ 2025-12-07  1:51 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 version 4 of the LANDLOCK_ADD_RULE_NO_INHERIT series, which
implements a new flag to suppress inheritance of access rights and
flags from parent objects.

This series is rebased on v6 of Tingmao Wang's "quiet flag" series.

The new flag enables policies where a parent directory needs broader
access than its children. For example, a sandbox may permit read-write
access to /home/user but still prohibit writes to ~/.bashrc or
~/.ssh, even though they are nested beneath the parent. Today this is
not possible because access rights always propagate from parent to
child within a layer.

When a rule is added with LANDLOCK_ADD_RULE_NO_INHERIT:

  * access rights on parent inodes are ignored for that inode and its
    descendants; and
  * operations that change the filesystem ancestors of such objects
    (via rename, rmdir, link) are denied up to the VFS root
    (new in v4); and
  * parent flags do not propagate below a NO_INHERIT rule.

These parent-directory restrictions help mitigate sandbox-restart
attacks: a sandboxed process could otherwise move a protected
directory before exit, causing the next sandbox instance to apply its
policy to the wrong path.

Changes since v3:

  1. Trimmed core implementation in fs.c by removing redundant functions.
  2. Fixed placement/inclusion of prototypes.
  3. Added 4 new selftests for bind mount cases.
  4. Protections now apply up to the VFS root instead of the mountpoint
     root.

Changes since v2:

  1. Add six new selftests for the new flag.
  2. Add an optimization to stop permission harvesting when all
     relevant layers are tagged with NO_INHERIT.
  3. Suppress inheritance of parent flags.
  4. Rebase onto v5 of the quiet-flag series.
  5. Remove the xarray structure used for flag tracking in favor of
     blank rule insertion, simplifying the implementation.
  6. Fix edge cases involving flag inheritance across multiple
     NO_INHERIT layers.
  7. Add documenting comments to new functions.

Links:

v1:
  https://lore.kernel.org/linux-security-module/20251105180019.1432367-1-utilityemal77@gmail.com/T/#t
v2:
  https://lore.kernel.org/linux-security-module/20251120222346.1157004-1-utilityemal77@gmail.com/T/#t
v3:
  https://lore.kernel.org/linux-security-module/20251126122039.3832162-1-utilityemal77@gmail.com/
quiet-flag v6:
  https://lore.kernel.org/linux-security-module/cover.1765040503.git.m@maowtm.org/

Example usage:

  # LL_FS_RO="/a/b/c" LL_FS_RW="/" LL_FS_NO_INHERIT="/a/b/c"
    landlock-sandboxer sh
  # touch /a/b/c/fi                    # denied; / RW does not inherit
  # rmdir /a/b/c                       # denied by ancestor protections
  # mv /a /bad                         # denied
  # mkdir /a/good; touch /a/good/fi    # allowed; unrelated path

Again, if preferred, I'm happy to split the selftests into multiple
commits.

This version simplifies a lot of the code in the core implementation.
Special thanks to Tingmao Wang for your valuable feedback.

Thank you for your time and review.

Regards,
Justin Suess

Justin Suess (5):
  landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT
  landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT userspace api
  samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to
    landlock-sandboxer
  selftests/landlock: Implement selftests for
    LANDLOCK_ADD_RULE_NO_INHERIT
  landlock: Implement KUnit test for LANDLOCK_ADD_RULE_NO_INHERIT

 include/uapi/linux/landlock.h              |  29 +
 samples/landlock/sandboxer.c               |  13 +-
 security/landlock/fs.c                     | 389 ++++++++++-
 security/landlock/ruleset.c                | 108 +++-
 security/landlock/ruleset.h                |  29 +-
 security/landlock/syscalls.c               |  16 +-
 tools/testing/selftests/landlock/fs_test.c | 710 +++++++++++++++++++++
 7 files changed, 1285 insertions(+), 9 deletions(-)


base-commit: 92f98eb2cc08c6e2d093d4682f1cd1204728e97e
-- 
2.51.0


^ permalink raw reply

* Re: [PATCH] ipe: remove headers that are included but not used
From: Yicong Hui @ 2025-12-06 21:04 UTC (permalink / raw)
  To: Fan Wu
  Cc: paul, jmorris, serge, linux-security-module, linux-kernel, skhan,
	david.hunter.linux
In-Reply-To: <CAKtyLkEyD9UVxqbmODVOAymE32aE7X4Xdbqj6H3BMGyhn_PQqw@mail.gmail.com>

On 12/3/25 10:25 PM, Fan Wu wrote:
> On Wed, Dec 3, 2025 at 11:37 AM Yicong Hui <yiconghui@gmail.com> wrote:
> 
> Hi Yicong,
> 
> Thanks for the patch. This kind of cleanup is appreciated.
> 
> Commit message typo: "audit. c, audit.c, policy. c" - audit. c is listed
> twice.

Hi! Thank you for the reply! Yes! My bad, this typo will be fixed in v2.

> I was trying to verify whether ipe.h is really not needed and found
> that these files are missing explicit dependencies. policy.c and
> policy_fs.c use rcu, mutex, and slab functions but rely on transitive
> includes.  After removing ipe.h, they still compile because eval.h
> also happens to provide these dependencies indirectly.
> 
> I'm happy to merge a patch removing unused headers like ipe.h, but
> would like to see the implicit dependencies resolved as well. Would
> you mind tracing the complete dependencies and adding the explicit
> includes in v2?

I have manually read through the functions/macros/filetypes in policy.c, 
policy_fs.c and audit.c and found a few dependencies that are used but 
not explicitly included, like minmax.h, sha2.h, lockdep.h, string.h, 
capability.h, kstrtox.h, sprintf.h, array_size.h and err.h.

This might be a stupid question, but how explicit should I be in my v2 
patch with the dependencies? There's headers like 
"asm-generic/int-ll64.h" "uidgid.h", "gfp_types.h", "rwonce.h", 
"compiler_types.h" or "errno-base.h" but I'm not sure to what extent I 
need to import them, because I shouldn't be including them all, right?

Thank you!
- Yicong

^ permalink raw reply

* Re: [GIT PULL] Landlock update for v6.19-rc1
From: pr-tracker-bot @ 2025-12-06 19:03 UTC (permalink / raw)
  To: Mickaël Salaün
  Cc: Linus Torvalds, Mickaël Salaün, Günther Noack,
	Matthieu Buffet, Song Liu, Tingmao Wang, linux-kernel,
	linux-security-module
In-Reply-To: <20251205183825.737361-1-mic@digikod.net>

The pull request you sent on Fri,  5 Dec 2025 19:38:25 +0100:

> https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git tags/landlock-6.19-rc1

has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/eee654ca9a55fd1e8632afb119975cba6af7d4ad

Thank you!

-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html

^ permalink raw reply

* [PATCH v6 10/10] selftests/landlock: Add tests for invalid use of quiet flag
From: Tingmao Wang @ 2025-12-06 17:11 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.1765040503.git.m@maowtm.org>

Signed-off-by: Tingmao Wang <m@maowtm.org>
---

Changes in v4:
- New patch

 tools/testing/selftests/landlock/base_test.c | 57 ++++++++++++++++++++
 1 file changed, 57 insertions(+)

diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index b34b340c52a5..055d416508a0 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -526,4 +526,61 @@ TEST(cred_transfer)
 	EXPECT_EQ(EACCES, errno);
 }
 
+TEST(useless_quiet_rule)
+{
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
+		.quiet_access_fs = 0,
+	};
+	struct landlock_path_beneath_attr path_beneath_attr = {
+		.allowed_access = LANDLOCK_ACCESS_FS_READ_DIR,
+	};
+	int ruleset_fd, root_fd;
+
+	drop_caps(_metadata);
+	ruleset_fd =
+		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+	ASSERT_LE(0, ruleset_fd);
+
+	root_fd = open("/", O_PATH | O_CLOEXEC);
+	ASSERT_LE(0, root_fd);
+	path_beneath_attr.parent_fd = root_fd;
+	ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
+					&path_beneath_attr,
+					LANDLOCK_ADD_RULE_QUIET));
+	ASSERT_EQ(EINVAL, errno);
+
+	/* Check that the rule had not been added. */
+	ASSERT_EQ(0, close(root_fd));
+	enforce_ruleset(_metadata, ruleset_fd);
+	ASSERT_EQ(0, close(ruleset_fd));
+
+	ASSERT_EQ(-1, open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC));
+	ASSERT_EQ(EACCES, errno);
+}
+
+TEST(invalid_quiet_bits_1)
+{
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
+		.quiet_access_fs = LANDLOCK_ACCESS_FS_WRITE_FILE,
+	};
+
+	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr,
+					      sizeof(ruleset_attr), 0));
+	ASSERT_EQ(EINVAL, errno);
+}
+
+TEST(invalid_quiet_bits_2)
+{
+	struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR,
+		.quiet_access_fs = 1ULL << 63,
+	};
+
+	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr,
+					      sizeof(ruleset_attr), 0));
+	ASSERT_EQ(EINVAL, errno);
+}
+
 TEST_HARNESS_MAIN
-- 
2.52.0

^ permalink raw reply related

* [PATCH v6 09/10] selftests/landlock: Add tests for quiet flag with scope
From: Tingmao Wang @ 2025-12-06 17:11 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.1765040503.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 4417cdedeadd..818ce485ecd9 100644
--- a/tools/testing/selftests/landlock/audit_test.c
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -289,30 +289,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)
@@ -356,12 +368,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;
 
@@ -378,8 +394,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));
@@ -406,8 +421,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);
@@ -431,8 +445,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) {
 		EXPECT_EQ(-EAGAIN,
 			  matches_log_domain_deallocated(self->audit_fd, 0,
 							 &deallocated_dom));
diff --git a/tools/testing/selftests/landlock/scoped_abstract_unix_test.c b/tools/testing/selftests/landlock/scoped_abstract_unix_test.c
index 6825082c079c..d6da9c20bde3 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.52.0

^ permalink raw reply related

* [PATCH v6 08/10] selftests/landlock: add tests for quiet flag with net rules
From: Tingmao Wang @ 2025-12-06 17:11 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.1765040503.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>
---

Changes in v3:
- New patch

 tools/testing/selftests/landlock/net_test.c | 121 ++++++++++++++++++--
 1 file changed, 111 insertions(+), 10 deletions(-)

diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 2a45208551e6..0fd0ec85faff 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -1871,21 +1871,22 @@ TEST_F(port_specific, bind_connect_1023)
 
 static int matches_log_tcp(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_template[] = REGEX_LANDLOCK_PREFIX
-		" blockers=%s %s=%s %s=1024$";
+		" blockers=%s %s=%s %s=%u$";
 	/*
 	 * Max strlen(blockers): 16
 	 * Max strlen(dir_addr): 5
 	 * Max strlen(addr): 12
 	 * Max strlen(dir_port): 4
+	 * Max strlen(%d port): 5
 	 */
-	char log_match[sizeof(log_template) + 37];
+	char log_match[sizeof(log_template) + 42];
 	int log_match_len;
 
 	log_match_len = snprintf(log_match, sizeof(log_match), log_template,
-				 blockers, dir_addr, addr, dir_port);
+				 blockers, dir_addr, addr, dir_port, port);
 	if (log_match_len > sizeof(log_match))
 		return -E2BIG;
 
@@ -1895,7 +1896,8 @@ static int matches_log_tcp(const int audit_fd, const char *const blockers,
 
 FIXTURE(audit)
 {
-	struct service_fixture srv0;
+	/* srv1 has a rule with no access but quiet bit set, srv0 does not. */
+	struct service_fixture srv0, srv1;
 	struct audit_filter audit_filter;
 	int audit_fd;
 };
@@ -1929,6 +1931,7 @@ FIXTURE_VARIANT_ADD(audit, ipv6) {
 FIXTURE_SETUP(audit)
 {
 	ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
+	ASSERT_EQ(0, set_service(&self->srv1, variant->prot, 1));
 	setup_loopback(_metadata);
 
 	set_cap(_metadata, CAP_AUDIT_CONTROL);
@@ -1949,6 +1952,12 @@ TEST_F(audit, bind)
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
 				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.quiet_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP |
+				    LANDLOCK_ACCESS_NET_BIND_TCP,
+	};
+	const struct landlock_net_port_attr quiet_rule = {
+		.allowed_access = 0,
+		.port = self->srv1.port,
 	};
 	struct audit_records records;
 	int ruleset_fd, sock_fd;
@@ -1956,6 +1965,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));
 
@@ -1963,11 +1974,21 @@ TEST_F(audit, bind)
 	ASSERT_LE(0, sock_fd);
 	EXPECT_EQ(-EACCES, bind_variant(sock_fd, &self->srv0));
 	EXPECT_EQ(0, matches_log_tcp(self->audit_fd, "net\\.bind_tcp", "saddr",
-				     variant->addr, "src"));
+				     variant->addr, "src", self->srv0.port));
 
+	/* No other logs expected. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+
+	EXPECT_EQ(0, close(sock_fd));
+
+	sock_fd = socket_variant(&self->srv1);
+	ASSERT_LE(0, sock_fd);
+	EXPECT_EQ(-EACCES, bind_variant(sock_fd, &self->srv1));
+
+	/* No log expected due to quiet rule. */
 	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));
 }
@@ -1977,6 +1998,12 @@ TEST_F(audit, connect)
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
 				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.quiet_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP |
+				    LANDLOCK_ACCESS_NET_BIND_TCP,
+	};
+	const struct landlock_net_port_attr quiet_rule = {
+		.allowed_access = 0,
+		.port = self->srv1.port,
 	};
 	struct audit_records records;
 	int ruleset_fd, sock_fd;
@@ -1984,18 +2011,92 @@ TEST_F(audit, connect)
 	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->srv0);
 	ASSERT_LE(0, sock_fd);
 	EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv0));
-	EXPECT_EQ(0, matches_log_tcp(self->audit_fd, "net\\.connect_tcp",
-				     "daddr", variant->addr, "dest"));
+	EXPECT_EQ(0,
+		  matches_log_tcp(self->audit_fd, "net\\.connect_tcp", "daddr",
+				  variant->addr, "dest", self->srv0.port));
+
+	/* No other logs expected. */
+	EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
+	EXPECT_EQ(0, records.access);
+
+	EXPECT_EQ(0, close(sock_fd));
+
+	sock_fd = socket_variant(&self->srv1);
+	ASSERT_LE(0, sock_fd);
+	EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv1));
+
+	/* 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));
+}
+
+/* Quieting bind access has no effect on connect. */
+TEST_F(audit, connect_quiet_bind)
+{
+	const struct landlock_ruleset_attr ruleset_attr = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.quiet_access_net = LANDLOCK_ACCESS_NET_BIND_TCP,
+	};
+	const struct landlock_ruleset_attr ruleset_attr_2 = {
+		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
+		.quiet_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+	};
+	const struct landlock_net_port_attr quiet_rule = {
+		.allowed_access = 0,
+		.port = self->srv1.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->srv1);
+	ASSERT_LE(0, sock_fd);
+	EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv1));
+	EXPECT_EQ(0,
+		  matches_log_tcp(self->audit_fd, "net\\.connect_tcp", "daddr",
+				  variant->addr, "dest", self->srv1.port));
+
+	/* No other logs expected. */
+	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->srv1);
+	ASSERT_LE(0, sock_fd);
+	EXPECT_EQ(-EACCES, connect_variant(sock_fd, &self->srv1));
 
+	/* Quieted - no logs expected. */
 	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));
 }
-- 
2.52.0

^ permalink raw reply related

* [PATCH v6 07/10] selftests/landlock: add tests for quiet flag with fs rules
From: Tingmao Wang @ 2025-12-06 17:11 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.1765040503.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.

Signed-off-by: Tingmao Wang <m@maowtm.org>
---

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 | 2454 +++++++++++++++++++-
 1 file changed, 2443 insertions(+), 11 deletions(-)

diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
index 418a66ffcefe..44e131957fba 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -718,7 +718,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,
@@ -731,7 +731,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));
@@ -786,7 +786,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;
 }
@@ -1364,7 +1364,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
@@ -1396,7 +1396,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. */
@@ -1419,7 +1419,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));
 
@@ -1472,7 +1472,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);
 	ASSERT_EQ(0, close(ruleset_fd));
 
@@ -4211,7 +4211,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
@@ -4226,13 +4226,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;
 }
@@ -4579,6 +4580,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";
@@ -7074,8 +7076,8 @@ static int matches_log_fs_extra(struct __test_metadata *const _metadata,
 		return -E2BIG;
 
 	/*
-	 * It is assume that absolute_path does not contain control characters nor
-	 * spaces, see audit_string_contains_control().
+	 * It is assumed that absolute_path does not contain control
+	 * characters nor spaces, see audit_string_contains_control().
 	 */
 	absolute_path = realpath(path, NULL);
 	if (!absolute_path)
@@ -7647,4 +7649,2434 @@ 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.
+		 *
+		 * We explicitly do not check records.domain here because sometimes, a
+		 * domain deallocation log from a previous test (or even a previous
+		 * run of the test binary when running in a loop) might run over and
+		 * show up here.  Since this is not a test about domain alloc/dealloc
+		 * messages, we ignore them.
+		 */
+		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\\+"));
+
+			/* No other logs. records.domain not checked per reasoning above. */
+			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 is 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 says
+ */
+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 says
+ */
+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 is 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 is 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 is 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.
+	 */
+	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 */
+	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 */
+	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 */
+	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);
+
+	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 */
+	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 */
+	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 */
+	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).
+	 */
+	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.
+	 */
+	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. */
+	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. */
+	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);
+
+	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);
+
+	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));
+
+	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);
+
+	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);
+
+	audit_count_records(self->audit_fd, &records);
+	ASSERT_EQ(0, records.access);
+}
+
 TEST_HARNESS_MAIN
-- 
2.52.0

^ permalink raw reply related

* [PATCH v6 06/10] selftests/landlock: Replace hard-coded 16 with a constant
From: Tingmao Wang @ 2025-12-06 17:11 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.1765040503.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 46d02d49835a..4417cdedeadd 100644
--- a/tools/testing/selftests/landlock/audit_test.c
+++ b/tools/testing/selftests/landlock/audit_test.c
@@ -76,7 +76,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 9acecae36f51..c7ce92342e7c 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 eee814e09dd7..418a66ffcefe 100644
--- a/tools/testing/selftests/landlock/fs_test.c
+++ b/tools/testing/selftests/landlock/fs_test.c
@@ -1497,7 +1497,7 @@ TEST_F_FORK(layout0, max_layers)
 	const int ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules);
 
 	ASSERT_LE(0, ruleset_fd);
-	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.52.0

^ permalink raw reply related

* [PATCH v6 05/10] samples/landlock: Add quiet flag support to sandboxer
From: Tingmao Wang @ 2025-12-06 17:11 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.1765040503.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 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 | 129 ++++++++++++++++++++++++++++++++---
 1 file changed, 119 insertions(+), 10 deletions(-)

diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index e7af02f98208..07dc0013ff19 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_DELIMITER ":"
 
@@ -116,7 +121,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;
@@ -166,7 +171,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));
@@ -184,7 +189,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;
@@ -212,7 +217,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));
@@ -299,7 +304,55 @@ static bool check_ruleset_scope(const char *const env_var,
 
 /* clang-format on */
 
-#define LANDLOCK_ABI_LAST 7
+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, "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 8
 
 #define XSTR(s) #s
 #define STR(s) XSTR(s)
@@ -328,6 +381,20 @@ 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 bind denials\n"
+	"  - \"c\" to quiet connect 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\" "
@@ -357,7 +424,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
 				      LANDLOCK_ACCESS_NET_CONNECT_TCP,
 		.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;
 
@@ -444,6 +516,11 @@ int main(const int argc, char *const argv[], char *const *const envp)
 			"provided by ABI version %d (instead of %d).\n",
 			LANDLOCK_ABI_LAST, abi);
 		__attribute__((fallthrough));
+	case 7:
+		/* Don't add quiet flags for ABI < 8 later on. */
+		quiet_supported = false;
+
+		__attribute__((fallthrough));
 	case LANDLOCK_ABI_LAST:
 		break;
 	default:
@@ -490,6 +567,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) {
@@ -497,22 +593,35 @@ 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;
 	}
 
+	/* Don't require this env to be present. */
+	if (quiet_supported && getenv(ENV_NET_QUIET_NAME)) {
+		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.52.0

^ permalink raw reply related

* [PATCH v6 04/10] landlock: Fix wrong type usage
From: Tingmao Wang @ 2025-12-06 17:11 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.1765040503.git.m@maowtm.org>

I think, based on my best understanding, that this type is likely a typo
(even though in the end both are u16)

Signed-off-by: Tingmao Wang <m@maowtm.org>
Fixes: 2fc80c69df82 ("landlock: Log file-related denials")
---

Changes in v2:
- Added Fixes tag

 security/landlock/audit.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 1a9d3f4e3369..d51563712325 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -191,7 +191,7 @@ static size_t get_denied_layer(const struct landlock_ruleset *const domain,
 	long youngest_layer = -1;
 
 	for_each_set_bit(access_bit, &access_req, layer_masks_size) {
-		const access_mask_t mask = (*layer_masks)[access_bit];
+		const layer_mask_t mask = (*layer_masks)[access_bit];
 		long layer;
 
 		if (!mask)
-- 
2.52.0

^ permalink raw reply related

* [PATCH v6 03/10] landlock: Suppress logging when quiet flag is present
From: Tingmao Wang @ 2025-12-06 17:11 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.1765040503.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.

Signed-off-by: Tingmao Wang <m@maowtm.org>
---

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  | 257 +++++++++++++++++++++++++++++++++++--
 security/landlock/audit.h  |   1 +
 security/landlock/domain.c |  33 +++++
 security/landlock/domain.h |   5 +
 security/landlock/fs.c     |  29 +++++
 security/landlock/fs.h     |  17 ++-
 security/landlock/net.c    |   2 +-
 8 files changed, 327 insertions(+), 22 deletions(-)

diff --git a/security/landlock/access.h b/security/landlock/access.h
index 7961c6630a2d..db47edc88afa 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -97,4 +97,9 @@ landlock_upgrade_handled_access_masks(struct access_masks access_masks)
 	return access_masks;
 }
 
+/* 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 c52d079cdb77..1a9d3f4e3369 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -273,7 +273,8 @@ 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,
+			  u8 quiet_optional_accesses, bool *quiet)
 {
 	const unsigned long access_opt = all_existing_optional_access;
 	const unsigned long access_req = *access_request;
@@ -281,6 +282,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);
@@ -291,18 +293,29 @@ get_layer_from_deny_masks(access_mask_t *const access_request,
 			const size_t layer =
 				(deny_masks >> (access_index * 4)) &
 				(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;
 }
 
@@ -312,42 +325,188 @@ static void test_get_layer_from_deny_masks(struct kunit *const test)
 {
 	deny_masks_t deny_mask;
 	access_mask_t access;
+	u8 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 */
@@ -381,11 +540,29 @@ 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
  *
  * @subject: The Landlock subject's credential denying an action.
  * @request: Detail of the user space request.
+ * @rule_flags: The flags for the matched rule, or no_rule_flags (zero) if
+ * this is a scope request (no particular object involved).
  */
 void landlock_log_denial(const struct landlock_cred_security *const subject,
 			 const struct landlock_request *const request)
@@ -394,6 +571,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))
@@ -409,10 +587,14 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
 			youngest_layer = get_denied_layer(
 				subject->domain, &missing, request->layer_masks,
 				request->layer_masks_size);
+			object_quiet_flag = !!(request->rule_flags.quiet_masks &
+					       BIT(youngest_layer));
 		} else {
 			youngest_layer = get_layer_from_deny_masks(
 				&missing, request->all_existing_optional_access,
-				request->deny_masks);
+				request->deny_masks,
+				request->quiet_optional_accesses,
+				&object_quiet_flag);
 		}
 		youngest_denied =
 			get_hierarchy(subject->domain, youngest_layer);
@@ -447,6 +629,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 d66c6d936438..f0647ee6052c 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -49,6 +49,7 @@ struct landlock_request {
 	/* Required fields for requests with deny masks. */
 	const access_mask_t all_existing_optional_access;
 	deny_masks_t deny_masks;
+	u8 quiet_optional_accesses;
 
 	struct collected_rule_flags rule_flags;
 };
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index a647b68e8d06..8caf07250328 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -212,6 +212,39 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
 	return deny_masks;
 }
 
+/**
+ * landlock_get_quiet_optional_accesses - Get optional accesses which are
+ * "covered" by quiet rule flags.
+ *
+ * Returns a bitmask of which optional access are denied by layers for
+ * which rule_flags.quiet_masks has the corresponding bit set.
+ */
+optional_access_t landlock_get_quiet_optional_accesses(
+	const access_mask_t all_existing_optional_access,
+	const deny_masks_t deny_masks,
+	const struct collected_rule_flags rule_flags)
+{
+	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 * 4)) &
+				 (LANDLOCK_MAX_NUM_LAYERS - 1);
+		const bool is_quiet = !!(rule_flags.quiet_masks & BIT(layer));
+
+		if (is_quiet)
+			quiet_optional_accesses |= BIT(access_index);
+		access_index++;
+	}
+	return quiet_optional_accesses;
+}
+
 #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
 
 static void test_landlock_get_deny_masks(struct kunit *const test)
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index aadbf53505c0..ea487315580a 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -130,6 +130,11 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
 			const layer_mask_t (*const layer_masks)[],
 			size_t layer_masks_size);
 
+optional_access_t landlock_get_quiet_optional_accesses(
+	const access_mask_t all_existing_optional_access,
+	const deny_masks_t deny_masks,
+	const struct collected_rule_flags rule_flags);
+
 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 1bab31f3188d..0b589263ea42 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1626,8 +1626,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.
@@ -1711,6 +1734,10 @@ static int hook_file_open(struct file *const file)
 	landlock_file(file)->deny_masks = landlock_get_deny_masks(
 		_LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks,
 		ARRAY_SIZE(layer_masks));
+	landlock_file(file)->quiet_optional_accesses =
+		landlock_get_quiet_optional_accesses(
+			_LANDLOCK_ACCESS_FS_OPTIONAL,
+			landlock_file(file)->deny_masks, request.rule_flags);
 #endif /* CONFIG_AUDIT */
 
 	if ((open_access_request & allowed_access) == open_access_request)
@@ -1747,6 +1774,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;
@@ -1786,6 +1814,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..ac6e50216f87 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 bddbe93d69fd..a3a90a4de70a 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -193,7 +193,7 @@ static int current_check_access_socket(struct socket *const sock,
 				    .access = access_request,
 				    .layer_masks = &layer_masks,
 				    .layer_masks_size = ARRAY_SIZE(layer_masks),
-			    });
+				    .rule_flags = rule_flags });
 	return -EACCES;
 }
 
-- 
2.52.0

^ permalink raw reply related

* [PATCH v6 02/10] landlock: Add API support and docs for the quiet flags
From: Tingmao Wang @ 2025-12-06 17:11 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.1765040503.git.m@maowtm.org>

Adds the UAPI for the quiet flags feature (but not the implementation
yet).

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 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.

 include/uapi/linux/landlock.h                | 64 +++++++++++++++++
 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                 | 72 +++++++++++++++-----
 tools/testing/selftests/landlock/base_test.c |  4 +-
 10 files changed, 155 insertions(+), 30 deletions(-)

diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index f030adc462ee..d4f47d20361a 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 8.
+ *
+ * @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,24 @@ struct landlock_ruleset_attr {
 	 * resources (e.g. IPCs).
 	 */
 	__u64 scoped;
+
+	/* Since ABI 8: */
+
+	/**
+	 * @quiet_access_fs: Bitmask of filesystem actions which should not be
+	 * audit logged if per-object quiet flag is set.
+	 */
+	__u64 quiet_access_fs;
+	/**
+	 * @quiet_access_net: Bitmask of network actions which should not be
+	 * audit logged if per-object quiet flag is set.
+	 */
+	__u64 quiet_access_net;
+	/**
+	 * @quiet_scoped: Bitmask of scoped actions which should not be audit
+	 * logged.
+	 */
+	__u64 quiet_scoped;
 };
 
 /**
@@ -69,6 +100,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 7fb70b25f85a..aadbf53505c0 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -114,6 +114,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 ce5fceb87657..1bab31f3188d 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -322,7 +322,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 = {
@@ -343,7 +343,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 fc6369dffa51..bddbe93d69fd 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 81cdf87d1c79..750a444e1983 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 "audit.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 9790c60c0c00..eb60db646422 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -166,8 +166,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 {
@@ -193,6 +193,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.
@@ -223,7 +229,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 0116e9f93ffe..5cf1183bb596 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -102,8 +102,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);
@@ -161,7 +164,7 @@ static const struct file_operations ruleset_fops = {
  * Documentation/userspace-api/landlock.rst should be updated to reflect the
  * UAPI change.
  */
-const int landlock_abi_version = 7;
+const int landlock_abi_version = 8;
 
 /**
  * sys_landlock_create_ruleset - Create a new ruleset
@@ -185,6 +188,8 @@ const int landlock_abi_version = 7;
  *
  * - %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 or quiet_access_net is not a subset of the
+ *   corresponding handled_access_fs or handled_access_net;
  * - %E2BIG: @attr or @size inconsistencies;
  * - %EFAULT: @attr or @size inconsistencies;
  * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
@@ -241,6 +246,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,
@@ -248,6 +268,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);
@@ -312,7 +336,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;
@@ -327,9 +351,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. */
@@ -337,6 +362,10 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
 	if ((path_beneath_attr.allowed_access | mask) != mask)
 		return -EINVAL;
 
+	/* Check 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)
@@ -344,13 +373,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;
@@ -363,9 +392,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. */
@@ -373,13 +403,17 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
 	if ((net_port_attr.allowed_access | mask) != mask)
 		return -EINVAL;
 
+	/* Check 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);
 }
 
 /**
@@ -390,7 +424,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.
@@ -400,20 +434,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,
@@ -424,8 +463,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. */
@@ -435,9 +473,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 7b69002239d7..b34b340c52a5 100644
--- a/tools/testing/selftests/landlock/base_test.c
+++ b/tools/testing/selftests/landlock/base_test.c
@@ -76,7 +76,7 @@ TEST(abi_version)
 	const struct landlock_ruleset_attr ruleset_attr = {
 		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
 	};
-	ASSERT_EQ(7, landlock_create_ruleset(NULL, 0,
+	ASSERT_EQ(8, landlock_create_ruleset(NULL, 0,
 					     LANDLOCK_CREATE_RULESET_VERSION));
 
 	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
@@ -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.52.0

^ permalink raw reply related

* [PATCH v6 01/10] landlock: Add a place for flags to layer rules
From: Tingmao Wang @ 2025-12-06 17:11 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.1765040503.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.

Cc: Justin Suess <utilityemal77@gmail.com>
Signed-off-by: Tingmao Wang <m@maowtm.org>
---

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.

 security/landlock/audit.h   |  2 ++
 security/landlock/fs.c      | 53 ++++++++++++++++++++++++-------------
 security/landlock/net.c     |  3 ++-
 security/landlock/ruleset.c |  7 ++++-
 security/landlock/ruleset.h | 26 ++++++++++++++++--
 5 files changed, 69 insertions(+), 22 deletions(-)

diff --git a/security/landlock/audit.h b/security/landlock/audit.h
index 92428b7fc4d8..d66c6d936438 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -49,6 +49,8 @@ struct landlock_request {
 	/* Required fields for requests with deny masks. */
 	const access_mask_t all_existing_optional_access;
 	deny_masks_t deny_masks;
+
+	struct collected_rule_flags rule_flags;
 };
 
 #ifdef CONFIG_AUDIT
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 6fadb54496a0..ce5fceb87657 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -772,6 +772,8 @@ static bool is_access_to_paths_allowed(
 		_layer_masks_child2[LANDLOCK_NUM_ACCESS_FS];
 	layer_mask_t(*layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS] = NULL,
 	(*layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS] = NULL;
+	struct collected_rule_flags *rule_flags_parent1 = &log_request_parent1->rule_flags;
+	struct collected_rule_flags *rule_flags_parent2 = &log_request_parent2->rule_flags;
 
 	if (!access_request_parent1 && !access_request_parent2)
 		return true;
@@ -811,22 +813,32 @@ static bool is_access_to_paths_allowed(
 	}
 
 	if (unlikely(dentry_child1)) {
+		/*
+		 * Get the layer masks for the child dentries for use by domain
+		 * check later.  The rule_flags for child1 should have been
+		 * included in rule_flags_parent1 already (cf.
+		 * collect_domain_accesses), and is not relevant for domain check,
+		 * so we don't have to pass it to landlock_unmask_layers.
+		 */
 		landlock_unmask_layers(
 			find_rule(domain, dentry_child1),
 			landlock_init_layer_masks(
 				domain, LANDLOCK_MASK_ACCESS_FS,
 				&_layer_masks_child1, LANDLOCK_KEY_INODE),
-			&_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1));
+			&_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1),
+			NULL);
 		layer_masks_child1 = &_layer_masks_child1;
 		child1_is_directory = d_is_dir(dentry_child1);
 	}
 	if (unlikely(dentry_child2)) {
+		/* See above comment for why NULL is passed as rule_flags_masks. */
 		landlock_unmask_layers(
 			find_rule(domain, dentry_child2),
 			landlock_init_layer_masks(
 				domain, LANDLOCK_MASK_ACCESS_FS,
 				&_layer_masks_child2, LANDLOCK_KEY_INODE),
-			&_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2));
+			&_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2),
+			NULL);
 		layer_masks_child2 = &_layer_masks_child2;
 		child2_is_directory = d_is_dir(dentry_child2);
 	}
@@ -881,16 +893,18 @@ static bool is_access_to_paths_allowed(
 		}
 
 		rule = find_rule(domain, walker_path.dentry);
-		allowed_parent1 = allowed_parent1 ||
-				  landlock_unmask_layers(
-					  rule, access_masked_parent1,
-					  layer_masks_parent1,
-					  ARRAY_SIZE(*layer_masks_parent1));
-		allowed_parent2 = allowed_parent2 ||
-				  landlock_unmask_layers(
-					  rule, access_masked_parent2,
-					  layer_masks_parent2,
-					  ARRAY_SIZE(*layer_masks_parent2));
+		allowed_parent1 =
+			allowed_parent1 ||
+			landlock_unmask_layers(rule, access_masked_parent1,
+					       layer_masks_parent1,
+					       ARRAY_SIZE(*layer_masks_parent1),
+					       rule_flags_parent1);
+		allowed_parent2 =
+			allowed_parent2 ||
+			landlock_unmask_layers(rule, access_masked_parent2,
+					       layer_masks_parent2,
+					       ARRAY_SIZE(*layer_masks_parent2),
+					       rule_flags_parent2);
 
 		/* Stops when a rule from each layer grants access. */
 		if (allowed_parent1 && allowed_parent2)
@@ -1047,7 +1061,8 @@ 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,
-	layer_mask_t (*const layer_masks_dom)[LANDLOCK_NUM_ACCESS_FS])
+	layer_mask_t (*const layer_masks_dom)[LANDLOCK_NUM_ACCESS_FS],
+	struct collected_rule_flags *const rule_flags)
 {
 	unsigned long access_dom;
 	bool ret = false;
@@ -1066,9 +1081,9 @@ static bool collect_domain_accesses(
 		struct dentry *parent_dentry;
 
 		/* Gets all layers allowing all domain accesses. */
-		if (landlock_unmask_layers(find_rule(domain, dir), access_dom,
-					   layer_masks_dom,
-					   ARRAY_SIZE(*layer_masks_dom))) {
+		if (landlock_unmask_layers(
+			    find_rule(domain, dir), access_dom, layer_masks_dom,
+			    ARRAY_SIZE(*layer_masks_dom), rule_flags)) {
 			/*
 			 * Stops when all handled accesses are allowed by at
 			 * least one rule in each layer.
@@ -1219,10 +1234,12 @@ static int current_check_refer_path(struct dentry *const old_dentry,
 	/* new_dir->dentry is equal to new_dentry->d_parent */
 	allow_parent1 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
 						old_parent,
-						&layer_masks_parent1);
+						&layer_masks_parent1,
+						&request1.rule_flags);
 	allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
 						new_dir->dentry,
-						&layer_masks_parent2);
+						&layer_masks_parent2,
+						&request2.rule_flags);
 
 	if (allow_parent1 && allow_parent2)
 		return 0;
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 1f3915a90a80..fc6369dffa51 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -48,6 +48,7 @@ static int current_check_access_socket(struct socket *const sock,
 {
 	__be16 port;
 	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
+	struct collected_rule_flags rule_flags = {};
 	const struct landlock_rule *rule;
 	struct landlock_id id = {
 		.type = LANDLOCK_KEY_NET_PORT,
@@ -179,7 +180,7 @@ static int current_check_access_socket(struct socket *const sock,
 						   access_request, &layer_masks,
 						   LANDLOCK_KEY_NET_PORT);
 	if (landlock_unmask_layers(rule, access_request, &layer_masks,
-				   ARRAY_SIZE(layer_masks)))
+				   ARRAY_SIZE(layer_masks), &rule_flags))
 		return 0;
 
 	audit_net.family = address->sa_family;
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index dfcdc19ea268..81cdf87d1c79 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -624,7 +624,8 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
 bool landlock_unmask_layers(const struct landlock_rule *const rule,
 			    const access_mask_t access_request,
 			    layer_mask_t (*const layer_masks)[],
-			    const size_t masks_array_size)
+			    const size_t masks_array_size,
+			    struct collected_rule_flags *const rule_flags)
 {
 	size_t layer_level;
 
@@ -651,6 +652,10 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
 		unsigned long access_bit;
 		bool is_empty;
 
+		/* Collect rule flags for each layer. */
+		if (rule_flags && layer->flags.quiet)
+			rule_flags->quiet_masks |= layer_bit;
+
 		/*
 		 * Records in @layer_masks which layer grants access to each requested
 		 * access: bit cleared if the related layer grants access.
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 1a78cba662b2..9790c60c0c00 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).
@@ -37,6 +48,16 @@ struct landlock_layer {
 	access_mask_t access;
 };
 
+/**
+ * struct collected_rule_flags - Hold accumulated flags for each layer.
+ */
+struct collected_rule_flags {
+	/**
+	 * @quiet_masks: Layers for which the quiet flag is effective.
+	 */
+	layer_mask_t quiet_masks;
+};
+
 /**
  * union landlock_key - Key of a ruleset's red-black tree
  */
@@ -304,7 +325,8 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
 bool landlock_unmask_layers(const struct landlock_rule *const rule,
 			    const access_mask_t access_request,
 			    layer_mask_t (*const layer_masks)[],
-			    const size_t masks_array_size);
+			    const size_t masks_array_size,
+			    struct collected_rule_flags *const rule_flags);
 
 access_mask_t
 landlock_init_layer_masks(const struct landlock_ruleset *const domain,
-- 
2.52.0

^ permalink raw reply related

* [PATCH v6 00/10] Implement LANDLOCK_ADD_RULE_QUIET
From: Tingmao Wang @ 2025-12-06 17:11 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 v6 of the "quiet flag" series, implementing the feature as
proposed in [1].

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/

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 (10):
  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
  landlock: Fix wrong type usage
  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

 include/uapi/linux/landlock.h                 |   64 +
 samples/landlock/sandboxer.c                  |  129 +-
 security/landlock/access.h                    |    5 +
 security/landlock/audit.c                     |  259 +-
 security/landlock/audit.h                     |    3 +
 security/landlock/domain.c                    |   33 +
 security/landlock/domain.h                    |   10 +
 security/landlock/fs.c                        |   86 +-
 security/landlock/fs.h                        |   19 +-
 security/landlock/net.c                       |   10 +-
 security/landlock/net.h                       |    5 +-
 security/landlock/ruleset.c                   |   19 +-
 security/landlock/ruleset.h                   |   38 +-
 security/landlock/syscalls.c                  |   72 +-
 tools/testing/selftests/landlock/audit_test.c |   27 +-
 tools/testing/selftests/landlock/base_test.c  |   61 +-
 tools/testing/selftests/landlock/common.h     |    2 +
 tools/testing/selftests/landlock/fs_test.c    | 2456 ++++++++++++++++-
 tools/testing/selftests/landlock/net_test.c   |  121 +-
 .../landlock/scoped_abstract_unix_test.c      |   77 +-
 20 files changed, 3373 insertions(+), 123 deletions(-)


base-commit: 54f9baf537b0a091adad860ec92e3e18e0a0754c
-- 
2.52.0


^ permalink raw reply

* Re: [PATCH v3 1/5] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT
From: Tingmao Wang @ 2025-12-06 17:07 UTC (permalink / raw)
  To: Justin Suess; +Cc: gnoack, jack, linux-security-module, mic, xandfury
In-Reply-To: <20251206152611.442312-1-utilityemal77@gmail.com>

On 12/6/25 15:26, Justin Suess wrote:
> Thank you for the review.
>
> I agree with the bind mount limitations for this flag. I think it's
> reasonable to expect the sandboxer to provide protections for pre-
> existing bind mounts as opposed to the kernel.
>
> This limitation is a tradeoff between safety and complexity.
>
> I looked into doing in automatically, and it ends up being sort of a
> mess, and you end up having to iterate through the bind mounts, and I
> suspect it would be a major performance hit, especially if we have to
> account for changes outside the sandbox after the policy is already
> enforced.
>
> We can note this limitation in the docs.
>
>> On 11/26/25 12:20, Justin Suess wrote:
>>> Implements a flag to prevent access grant inheritance within the filesys
> tem hierarchy

Minor nit but please try not to wrap lines this way, as when the text is
colored / indented by quote level, it makes it seem like you're adding a
reply.

> [...]
>>> @@ -1232,6 +1656,121 @@ static bool collect_domain_accesses(
>>>  	return ret;
>>>  }
>>>
>>> +/**
>>> + * collect_topology_sealed_layers - collect layers sealed against topol
> ogy changes
>>> + * @domain: Ruleset to consult.
>>> + * @dentry: Starting dentry for the upward walk.
>>> + * @override_layers: Optional out parameter filled with layers that are
>>> + *                   present on ancestors but considered overrides (not
>>> + *                   sealing the topology for descendants).
>>> + *
>>> + * Walk upwards from @dentry and return a mask of layers where either t
> he
>>> + * visited dentry contains a no_inherit rule or ancestors were previous
> ly
>>> + * marked as having a descendant with no_inherit.  @override_layers, if
>  not
>>> + * NULL, is filled with layers that would normally be overridden by mor
> e
>>> + * specific descendant rules.
>>> + *
>>> + * Returns a layer mask where set bits indicate layers that are "sealed
> "
>>> + * (topology changes like rename/rmdir are denied) for the subtree root
> ed at
>>> + * @dentry.
>>> + *
>>> + * Useful for LANDLOCK_ADD_RULE_NO_INHERIT parent directory enforcement
>  to ensure
>>> + * that topology changes do not violate the no_inherit constraints.
>>> + */
>>> +static layer_mask_t
>>> +collect_topology_sealed_layers(const struct landlock_ruleset *const dom
> ain,
>>> +			       struct dentry *dentry,
>>> +			       layer_mask_t *const override_layers)
>>> +{
>>> +	struct dentry *cursor, *parent;
>>> +	bool include_descendants = true;
>>> +	layer_mask_t sealed_layers = 0;
>>> +
>>> +	if (override_layers)
>>> +		*override_layers = 0;
>>> +
>>> +	if (!domain || !dentry || d_is_negative(dentry))
>>> +		return 0;
>>> +
>>> +	cursor = dget(dentry);
>>> +	while (cursor) {
>>> +		const struct landlock_rule *rule;
>>> +		u32 layer_index;
>>> +
>>> +		rule = find_rule(domain, cursor);
>>> +		if (rule) {
>>> +			for (layer_index = 0; layer_index < rule->num_layer
> s;
>>> +			     layer_index++) {
>>> +				const struct landlock_layer *layer =
>>> +					&rule->layers[layer_index];
>>> +				const int level = layer->level ? layer->lev
> el :
>>> +								 layer_inde
> x + 1;
>> Wouldn't layer->level always be >= 1 here?  Using layer_index doesn't mak
> e
>> sense since layer_index is just the index that the struct landlock_layer
>> happened to be in that rule's array.
>
> Hmm good catch. I was replicating logic from other places while sometimes not realizing
> exactly why the code is doing what it does and didn't make that realization. Seems to work
> fine and pass after I fixed it in my working tree so it will be in the next version.

Well actually this function would become unused after removing the "Apply
descendant no-inherit masking" code above.

> [...]
>
> Thank you for the review of the patch. Trimming the code was very
> satisfying. I see that the disconnected directory handling was
> simplified in linux-next, which is gonna make things easier but also
> make rebasing require some more careful attention. I assume your next
> patch series for LANDLOCK_ADD_RULE_QUIET is going to be rebased off
> those disconnected directory changes as well.
>
> I'll implement these fixes in my working tree and be ready when you drop
> your next version to do a rebase on top of your series again. If you
> have a base commit SHA you could give me that you are building your next
> patch series version on top of already, that would be helpful.

Will send v6 now, but you can also take it from
https://github.com/micromaomao/linux-dev/tree/landlock-quiet-flag :)

^ permalink raw reply

* Re: [PATCH 3/3] tpm-buf: Implement managed allocations
From: Jarkko Sakkinen @ 2025-12-06 17:00 UTC (permalink / raw)
  To: linux-integrity
  Cc: Jarkko Sakkinen, Stefan Berger, Peter Huewe, Jason Gunthorpe,
	James Bottomley, Mimi Zohar, David Howells, Paul Moore,
	James Morris, Serge E. Hallyn, open list, open list:KEYS-TRUSTED,
	open list:SECURITY SUBSYSTEM, ross.philipson, daniel.kiper,
	dpsmith
In-Reply-To: <20251206113110.1793407-4-jarkko@kernel.org>

On Sat, Dec 06, 2025 at 01:31:10PM +0200, 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.
> 
> Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@opinsys.com>
> Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
> ---
>  drivers/char/tpm/tpm-buf.c                | 124 +++++----
>  drivers/char/tpm/tpm-sysfs.c              |  21 +-
>  drivers/char/tpm/tpm.h                    |   1 -
>  drivers/char/tpm/tpm1-cmd.c               | 166 +++++------
>  drivers/char/tpm/tpm2-cmd.c               | 320 ++++++++++------------
>  drivers/char/tpm/tpm2-sessions.c          | 142 +++++-----
>  drivers/char/tpm/tpm2-space.c             |  44 ++-
>  drivers/char/tpm/tpm_vtpm_proxy.c         |  30 +-
>  include/linux/tpm.h                       |  20 +-
>  security/keys/trusted-keys/trusted_tpm1.c |  36 +--
>  security/keys/trusted-keys/trusted_tpm2.c | 170 ++++++------
>  11 files changed, 508 insertions(+), 566 deletions(-)
> 
> diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
> index 1b9dee0d0681..73be8a87b472 100644
> --- a/drivers/char/tpm/tpm-buf.c
> +++ b/drivers/char/tpm/tpm-buf.c
> @@ -7,82 +7,109 @@
>  #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_INVALID;
> +		}
> +	} 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_INVALID;
> +		}
> +	}
>  }
> -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_INVALID)
> +		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_INVALID)
> +		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,8 +117,11 @@ 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)
>  {
> +	if (buf->flags & TPM_BUF_INVALID)
> +		return 0;
> +
>  	return buf->length;
>  }
>  EXPORT_SYMBOL_GPL(tpm_buf_length);
> @@ -104,10 +134,12 @@ 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;
> +
>  	if (buf->flags & TPM_BUF_INVALID)
>  		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_INVALID;
>  		return;
> diff --git a/drivers/char/tpm/tpm-sysfs.c b/drivers/char/tpm/tpm-sysfs.c
> index 94231f052ea7..f5dcadb1ab3c 100644
> --- a/drivers/char/tpm/tpm-sysfs.c
> +++ b/drivers/char/tpm/tpm-sysfs.c
> @@ -32,28 +32,29 @@ 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) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!tpm_buf)
> +		return -ENOMEM;
> +
>  	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))
> -		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,
> -			     "attempting to read the PUBEK"))
> -		goto out_buf;
> +	if (tpm_transmit_cmd(chip, tpm_buf, READ_PUBEK_RESULT_MIN_BODY_SIZE, "TPM_ReadPubek"))
> +		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 +72,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 02c07fef41ba..5395927c62fc 100644
> --- a/drivers/char/tpm/tpm.h
> +++ b/drivers/char/tpm/tpm.h
> @@ -32,7 +32,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..11090053ef54 100644
> --- a/drivers/char/tpm/tpm1-cmd.c
> +++ b/drivers/char/tpm/tpm1-cmd.c
> @@ -323,20 +323,14 @@ 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;
> -
> -	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);
> -
> -	rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to start the TPM");
> -	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_STARTUP);
> +	tpm_buf_append_u16(buf, TPM_ST_CLEAR);
> +	return tpm_transmit_cmd(chip, buf, 0, "TPM_Startup");
>  }
>  
>  int tpm1_get_timeouts(struct tpm_chip *chip)
> @@ -463,50 +457,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);
> @@ -531,81 +522,69 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
>  {
>  	struct tpm1_get_random_out *out;
>  	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;
> +	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
>  
> +	tpm_buf_init(buf, TPM_BUFSIZE);
>  	do {
> -		tpm_buf_append_u32(&buf, num_bytes);
> +		tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM);
> +		tpm_buf_append_u32(buf, num_bytes);
>  
> -		rc = tpm_transmit_cmd(chip, &buf, sizeof(out->rng_data_len),
> -				      "attempting get random");
> +		rc = tpm_transmit_cmd(chip, buf, sizeof(out->rng_data_len), "TPM_GetRandom");
>  		if (rc) {
>  			if (rc > 0)
>  				rc = -EIO;
> -			goto out;
> +			return rc;
>  		}
>  
> -		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) {
> -			rc = -EFAULT;
> -			goto out;
> -		}
> +		if (recd > num_bytes)
> +			return -EFAULT;
> +
> +		if (buf->length < TPM_HEADER_SIZE +
> +				  sizeof(out->rng_data_len) + recd)
> +			return -EFAULT;
>  
> -		if (tpm_buf_length(&buf) < TPM_HEADER_SIZE +
> -					   sizeof(out->rng_data_len) + recd) {
> -			rc = -EFAULT;
> -			goto out;
> -		}
>  		memcpy(dest, out->rng_data, recd);
>  
>  		dest += recd;
>  		total += recd;
>  		num_bytes -= recd;
> -
> -		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,
> -			      "attempting to read a pcr value");
> +	rc = tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE, "TPM_PCRRead");
>  	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 (buf->length < 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 +598,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;
> -
> -	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_CONTINUE_SELFTEST);
> -	if (rc)
> -		return rc;
> +	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
>  
> -	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, "TPM_ContinueSelfTest");
>  }
>  
>  /**
> @@ -742,22 +718,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;
>  	int rc;
>  
> +	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
>  
>  	/* 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");
>  
> -	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
> -	if (rc)
> -		return rc;
> +	tpm_buf_init(buf, TPM_BUFSIZE);
> +	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
> +
>  	/* 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 +750,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 +760,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 82076b6cfcf7..2682f5ec09cb 100644
> --- a/drivers/char/tpm/tpm2-cmd.c
> +++ b/drivers/char/tpm/tpm2-cmd.c
> @@ -119,12 +119,15 @@ 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) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
>  	if (pcr_idx >= TPM2_PLATFORM_PCR)
>  		return -EINVAL;
>  
> @@ -139,36 +142,31 @@ 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;
> +	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, "TPM2_PCR_Read");
>  	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 rc;
>  
>  	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 +182,52 @@ 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) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
>  	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) {
> -		if (!disable_pcr_integrity)
> -			tpm2_end_auth_session(chip);
> -		return rc;
> -	}
> +	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(&buf, pcr_idx);
> -		tpm_buf_append_auth(chip, &buf, NULL, 0);
> +		tpm_buf_append_handle(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 (buf->flags & TPM_BUF_INVALID)
> +		return -EINVAL;
>  
>  	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");
> -	if (!disable_pcr_integrity)
> -		rc = tpm_buf_check_hmac_response(chip, &buf, rc);
> +	rc = tpm_transmit_cmd(chip, buf, 0, "TPM2_PCR_Extend");
>  
> -	tpm_buf_destroy(&buf);
> +	if (!disable_pcr_integrity)
> +		rc = tpm_buf_check_hmac_response(chip, buf, rc);
>  
>  	return rc;
>  }
> @@ -258,7 +252,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;
> @@ -270,61 +263,58 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
>  	if (!num_bytes || max > TPM_MAX_RNG_DATA)
>  		return -EINVAL;
>  
> +	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
>  	err = tpm2_start_auth_session(chip);
>  	if (err)
>  		return err;
>  
> -	err = tpm_buf_init(&buf, 0, 0);
> -	if (err) {
> -		tpm2_end_auth_session(chip);
> -		return err;
> -	}
> -
> +	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);
> -		if (err) {
> -			tpm_buf_destroy(&buf);
> +		tpm_buf_append_u16(buf, num_bytes);
> +
> +		err = tpm_buf_fill_hmac_session(chip, buf);
> +		if (err)
>  			return err;
> -		}
>  
> -		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);
> +				       "TPM2_GetRandom");
> +		err = tpm_buf_check_hmac_response(chip, buf, err);
>  		if (err) {
>  			if (err > 0)
>  				err = -EIO;
> -			goto out;
> +			return err;
>  		}
>  
> -		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) {
> -			err = -EFAULT;
> -			goto out;
> +			return -EFAULT;
>  		}
>  		memcpy(dest_ptr, out->buffer, recd);
>  
> @@ -333,13 +323,7 @@ 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;
>  }
>  
>  /**
> @@ -349,20 +333,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, "TPM2_FlushContext");
>  }
>  EXPORT_SYMBOL_GPL(tpm2_flush_context);
>  
> @@ -389,19 +371,20 @@ 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];
> +		out = (struct tpm2_get_cap_out *)&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
> @@ -413,7 +396,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);
> @@ -430,15 +412,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, "TPM2_Shutdown");
>  }
>  
>  /**
> @@ -456,20 +437,19 @@ 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;
> -
> -		tpm_buf_append_u8(&buf, full);
> -		rc = tpm_transmit_cmd(chip, &buf, 0,
> -				      "attempting the self test");
> -		tpm_buf_destroy(&buf);
> +	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_SELF_TEST);
> +	for (full = 0; full < 2; full++) {
> +		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, "TPM2_SelfTest");
>  		if (rc == TPM2_RC_TESTING)
>  			rc = TPM2_RC_SUCCESS;
>  		if (rc == TPM2_RC_INITIALIZE || rc == TPM2_RC_SUCCESS)
> @@ -494,23 +474,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);
> @@ -550,7 +531,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;
> @@ -562,39 +542,37 @@ 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, "TPM2_GetCapability(PCRS)");
>  	if (rc)
> -		goto out;
> +		return rc;
>  
> -	nr_possible_banks = be32_to_cpup(
> -		(__be32 *)&buf.data[TPM_HEADER_SIZE + 5]);
> +	nr_possible_banks = be32_to_cpup((__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);
> -		rc = -ENOMEM;
> -		goto out;
> +		return -ENOMEM;
>  	}
>  
> -	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 +
>  			offsetof(struct tpm2_pcr_selection, size_of_select);
> -		if (pcr_select_offset >= end) {
> -			rc = -EFAULT;
> -			break;
> -		}
> +		if (pcr_select_offset >= end)
> +			return -EFAULT;
>  
>  		memcpy(&pcr_selection, marker, sizeof(pcr_selection));
>  		hash_alg = be16_to_cpu(pcr_selection.hash_alg);
> @@ -606,7 +584,7 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
>  
>  			rc = tpm2_init_bank_info(chip, nr_alloc_banks);
>  			if (rc < 0)
> -				break;
> +				return rc;
>  
>  			nr_alloc_banks++;
>  		}
> @@ -618,21 +596,21 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
>  	}
>  
>  	chip->nr_allocated_banks = nr_alloc_banks;
> -out:
> -	tpm_buf_destroy(&buf);
> -
> -	return rc;
> +	return 0;
>  }
>  
>  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) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
>  	rc = tpm2_get_tpm_pt(chip, TPM_PT_TOTAL_COMMANDS, &nr_commands, NULL);
>  	if (rc)
>  		goto out;
> @@ -649,30 +627,24 @@ 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)
> -		goto out;
> +	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);
>  
> -	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])) {
> +	if (nr_commands != 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;
> @@ -684,8 +656,6 @@ int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip)
>  		}
>  	}
>  
> -	tpm_buf_destroy(&buf);
> -
>  out:
>  	if (rc > 0)
>  		rc = -ENODEV;
> @@ -706,20 +676,14 @@ EXPORT_SYMBOL_GPL(tpm2_get_cc_attrs_tbl);
>  
>  static int tpm2_startup(struct tpm_chip *chip)
>  {
> -	struct tpm_buf buf;
> -	int rc;
> -
> -	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);
> -
> -	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, TPM2_ST_NO_SESSIONS, TPM2_CC_STARTUP);
> +	tpm_buf_append_u16(buf, TPM2_SU_CLEAR);
> +	return tpm_transmit_cmd(chip, buf, 0, "TPM2_Startup");
>  }
>  
>  /**
> diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
> index 92d116d73c6f..79f27a46bd7f 100644
> --- a/drivers/char/tpm/tpm2-sessions.c
> +++ b/drivers/char/tpm/tpm2-sessions.c
> @@ -168,7 +168,6 @@ 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;
>  	int rc, name_size_alg;
> -	struct tpm_buf buf;
>  
>  	if (mso != TPM2_MSO_PERSISTENT && mso != TPM2_MSO_VOLATILE &&
>  	    mso != TPM2_MSO_NVRAM) {
> @@ -176,47 +175,41 @@ 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;
> +	struct tpm_buf *buf __free(kfree) = 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]);
> +	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);
> +	memcpy(name, &buf->data[offset], rc);
>  	return name_size_alg;
>  }
>  #endif /* CONFIG_TCG_TPM2_HMAC */
> @@ -982,7 +975,6 @@ static int tpm2_load_null(struct tpm_chip *chip, u32 *null_key)
>  int tpm2_start_auth_session(struct tpm_chip *chip)
>  {
>  	struct tpm2_auth *auth;
> -	struct tpm_buf buf;
>  	u32 null_key;
>  	int rc;
>  
> @@ -991,6 +983,10 @@ int tpm2_start_auth_session(struct tpm_chip *chip)
>  		return 0;
>  	}
>  
> +	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
>  	auth = kzalloc(sizeof(*auth), GFP_KERNEL);
>  	if (!auth)
>  		return -ENOMEM;
> @@ -1001,41 +997,37 @@ 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)
> -		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, "TPM2_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;
> @@ -1257,18 +1249,18 @@ static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy,
>  			       u32 *handle, u8 *name)
>  {
>  	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;
> +	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
>  
> -	rc = tpm_buf_init_sized(&template);
> -	if (rc) {
> -		tpm_buf_destroy(&buf);
> -		return rc;
> -	}
> +	struct tpm_buf *template __free(kfree) = 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
> @@ -1280,75 +1272,71 @@ 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,
> -			      "attempting to create NULL primary");
> +	rc = tpm_transmit_cmd(chip, buf, 0, "TPM2_CreatePrimary");
>  
>  	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 0818bb517805..682dfc93845d 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 1e0585e45371..42e2a091f43d 100644
> --- a/include/linux/tpm.h
> +++ b/include/linux/tpm.h
> @@ -25,7 +25,8 @@
>  #include <crypto/hash_info.h>
>  #include <crypto/aes.h>
>  
> -#define TPM_DIGEST_SIZE 20	/* Max TPM v1.2 PCR size */
> +#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
> @@ -376,13 +377,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;
>  	u8 handles;
> +	u16 length;
> +	u16 capacity;
> +	u8 data[];
>  };
>  
>  enum tpm2_object_attributes {
> @@ -413,12 +416,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 636acb66a4f6..759c1ecb0435 100644
> --- a/security/keys/trusted-keys/trusted_tpm1.c
> +++ b/security/keys/trusted-keys/trusted_tpm1.c
> @@ -310,9 +310,10 @@ 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(void *cmd, size_t cmd_len)
>  {
> -	struct tpm_buf buf;
> +	u8 buf_data[512];
> +	struct tpm_buf *buf = (struct tpm_buf *)buf_data;
>  	int rc;
>  
>  	if (!chip)
> @@ -322,11 +323,10 @@ static int trusted_tpm_send(unsigned char *cmd, size_t buflen)
>  	if (rc)
>  		return rc;
>  
> -	buf.flags = 0;
> -	buf.length = buflen;
> -	buf.data = cmd;
> +	tpm_buf_init(buf, sizeof(buf_data));
> +	tpm_buf_append(buf, cmd, cmd_len);
>  	dump_tpm_buf(cmd);
> -	rc = tpm_transmit_cmd(chip, &buf, 4, "sending data");
> +	rc = tpm_transmit_cmd(chip, buf, 4, "sending data");
>  	dump_tpm_buf(cmd);
>  
>  	if (rc > 0)
> @@ -624,23 +624,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;
>  }
>  
> @@ -650,14 +650,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);
> @@ -665,7 +666,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 fdba641fefdb..fb76c4ea496f 100644
> --- a/security/keys/trusted-keys/trusted_tpm2.c
> +++ b/security/keys/trusted-keys/trusted_tpm2.c
> @@ -234,7 +234,6 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
>  		      struct trusted_key_options *options)
>  {
>  	off_t offset = TPM_HEADER_SIZE;
> -	struct tpm_buf buf, sized;
>  	int blob_len = 0;
>  	int hash;
>  	u32 flags;
> @@ -255,97 +254,99 @@ 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) {
> +	struct tpm_buf *buf __free(kfree) = 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);
> -		tpm2_end_auth_session(chip);
> -		goto out_put;
> -	}
> +	tpm_buf_init(buf, TPM_BUFSIZE);
> +	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE);
>  
> -	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
> +	rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL);
>  	if (rc)
> -		goto out;
> +		goto out_put;
>  
> -	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);
>  
> +	struct tpm_buf *sized __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!sized) {
> +		rc = -ENOMEM;
> +		tpm2_end_auth_session(chip);
> +		goto out_put;
> +	}
> +
>  	/* sensitive */
> -	tpm_buf_append_u16(&sized, options->blobauth_len);
> +	tpm_buf_init_sized(sized, TPM_BUFSIZE);
> +	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_init_sized(sized, TPM_BUFSIZE);
> +	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_INVALID) {
> +	if (buf->flags & TPM_BUF_INVALID) {
>  		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_INVALID) {
> +	blob_len = tpm_buf_read_u32(buf, &offset);
> +	if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_INVALID) {
>  		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 +374,6 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
>  			 u32 *blob_handle)
>  {
>  	u8 *blob_ref __free(kfree) = NULL;
> -	struct tpm_buf buf;
>  	unsigned int private_len;
>  	unsigned int public_len;
>  	unsigned int blob_len;
> @@ -427,39 +427,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) {
> +	struct tpm_buf *buf __free(kfree) = 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_INVALID) {
> -		rc = -E2BIG;
> +	if (buf->flags & TPM_BUF_INVALID) {
>  		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,28 +481,28 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
>  			   u32 blob_handle)
>  {
>  	struct tpm_header *head;
> -	struct tpm_buf buf;
>  	u16 data_len;
>  	int offset;
>  	u8 *data;
>  	int rc;
>  
> +	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
> +	if (!buf)
> +		return -ENOMEM;
> +
>  	rc = tpm2_start_auth_session(chip);
>  	if (rc)
>  		return rc;
>  
> -	rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
> -	if (rc) {
> -		tpm2_end_auth_session(chip);
> -		return rc;
> -	}
> +	tpm_buf_init(buf, TPM_BUFSIZE);
> +	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
>  
> -	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
> +	rc = tpm_buf_append_name(chip, buf, options->keyhandle, 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 +517,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 +563,6 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
>  		}
>  	}
>  
> -out:
> -	tpm_buf_destroy(&buf);
>  	return tpm_ret_to_err(rc);
>  }
>  
> -- 
> 2.39.5
> 

I applied these patches to my master branch for these reasons:

1. It is a change for better, as managed allocations objectively
   deliver safer memory semantics overall.
2. The patch has been in my testing cycle for months now already.
   I've tested *everything* with it and without it in order to
   stress the change a bit.
3. Despite being large change it does fit to the "single logical
   change" category. It would only make things more dissolved if
   it was split in any possible way.
4. It's a great idea roughly base it on 6.19-rc1 so that it can
   be applied easily top of the mainline release (if wanted).

I will appreciate any testing results etc. and stay put on fixing
any possible issues. Also, e.g. tested-by tags can be added up
until the first pull request for 6.20.

BR, Jarkko

^ permalink raw reply

* [PATCH v5 2/2] KEYS: trusted: Store parent's name to the encoded keys
From: Jarkko Sakkinen @ 2025-12-06 16:13 UTC (permalink / raw)
  To: linux-integrity
  Cc: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe, James Bottomley,
	Mimi Zohar, David Howells, Paul Moore, James Morris,
	Serge E. Hallyn, open list, open list:KEYS-TRUSTED,
	open list:SECURITY SUBSYSTEM
In-Reply-To: <20251206161345.2038730-1-jarkko@kernel.org>

Extend TPMKey ASN.1 speciication [1] with an optional 'parentName'
attribute containing TPM name of the parent key (in other words, TPMT_HA
blob).

The life-cycle for trusted keys will now proceed as follows:

1. Encode parent's name to the 'paretName' during tpm2_key_encode().
2. During tpm2_unseal_trusted, read parent's name from 'parentName'. When
   the attribute is not available, fallback on doing tpm2_read_public().

In other words, in the common (i.e., not loading a legacy key blob),
tpm2_read_public() will now only happen at the time when a key is first
created.

In addition, move tpm2_read_public() to 'tpm2-cmd.c' and make its body
unconditional so that the binary format of the saved keys is not dependent
on kernel configuration.

[1] https://www.hansenpartnership.com/draft-bottomley-tpm2-keys.txt

Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
v5:
- Rebased to the mainline.
v4:
- No changes.
v3:
- Fixed three low-hanging fruit issues I found myself.
v2:
- A new patch.
---
 drivers/char/tpm/tpm2-cmd.c               | 90 ++++++++++++++++++++++
 drivers/char/tpm/tpm2-sessions.c          | 92 ----------------------
 include/linux/tpm.h                       |  9 +--
 security/keys/trusted-keys/tpm2key.asn1   | 17 ++++-
 security/keys/trusted-keys/trusted_tpm2.c | 93 ++++++++++++++++-------
 5 files changed, 173 insertions(+), 128 deletions(-)

diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index 1f561ad3bdcf..360bc014d895 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -793,3 +793,93 @@ int tpm2_find_cc(struct tpm_chip *chip, u32 cc)
 
 	return -1;
 }
+
+/*
+ * Name Size based on TPM algorithm (assumes no hash bigger than 255)
+ */
+static int name_size(const u8 *name)
+{
+	u16 hash_alg = get_unaligned_be16(name);
+
+	switch (hash_alg) {
+	case TPM_ALG_SHA1:
+		return SHA1_DIGEST_SIZE + 2;
+	case TPM_ALG_SHA256:
+		return SHA256_DIGEST_SIZE + 2;
+	case TPM_ALG_SHA384:
+		return SHA384_DIGEST_SIZE + 2;
+	case TPM_ALG_SHA512:
+		return SHA512_DIGEST_SIZE + 2;
+	default:
+		pr_warn("tpm: unsupported name algorithm: 0x%04x\n", hash_alg);
+		return -EINVAL;
+	}
+}
+
+/**
+ * tpm2_read_public: Resolve TPM name for a handle
+ * @chip:		TPM chip to use.
+ * @handle:		TPM handle.
+ * @name:		A buffer for returning the name blob. Must have a
+ *			capacity of 'SHA512_DIGET_SIZE + 2' bytes at minimum
+ *
+ * Returns size of TPM handle name of success.
+ * Returns tpm_transmit_cmd error codes when TPM2_ReadPublic fails.
+ */
+int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name)
+{
+	u32 mso = tpm2_handle_mso(handle);
+	off_t offset = TPM_HEADER_SIZE;
+	int rc, name_size_alg;
+	struct tpm_buf buf;
+
+	if (mso != TPM2_MSO_PERSISTENT && mso != TPM2_MSO_VOLATILE &&
+	    mso != TPM2_MSO_NVRAM) {
+		memcpy(name, &handle, sizeof(u32));
+		return sizeof(u32);
+	}
+
+	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC);
+	if (rc)
+		return rc;
+
+	tpm_buf_append_u32(&buf, handle);
+
+	rc = tpm_transmit_cmd(chip, &buf, 0, "TPM2_ReadPublic");
+	if (rc) {
+		tpm_buf_destroy(&buf);
+		return tpm_ret_to_err(rc);
+	}
+
+	/* Skip TPMT_PUBLIC: */
+	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);
+		return -EIO;
+	}
+
+	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);
+		return -EIO;
+	}
+
+	if (offset + rc > tpm_buf_length(&buf)) {
+		tpm_buf_destroy(&buf);
+		return -EIO;
+	}
+
+	memcpy(name, &buf.data[offset], rc);
+	return name_size_alg;
+}
+EXPORT_SYMBOL_GPL(tpm2_read_public);
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
index e33be09446ff..c8b44b1a6cb6 100644
--- a/drivers/char/tpm/tpm2-sessions.c
+++ b/drivers/char/tpm/tpm2-sessions.c
@@ -140,98 +140,6 @@ struct tpm2_auth {
 	u16 name_size_tbl[AUTH_MAX_NAMES];
 };
 
-#ifdef CONFIG_TCG_TPM2_HMAC
-/*
- * Name Size based on TPM algorithm (assumes no hash bigger than 255)
- */
-static int name_size(const u8 *name)
-{
-	u16 hash_alg = get_unaligned_be16(name);
-
-	switch (hash_alg) {
-	case TPM_ALG_SHA1:
-		return SHA1_DIGEST_SIZE + 2;
-	case TPM_ALG_SHA256:
-		return SHA256_DIGEST_SIZE + 2;
-	case TPM_ALG_SHA384:
-		return SHA384_DIGEST_SIZE + 2;
-	case TPM_ALG_SHA512:
-		return SHA512_DIGEST_SIZE + 2;
-	default:
-		pr_warn("tpm: unsupported name algorithm: 0x%04x\n", hash_alg);
-		return -EINVAL;
-	}
-}
-
-/**
- * tpm2_read_public: Resolve TPM name for a handle
- * @chip:		TPM chip to use.
- * @handle:		TPM handle.
- * @name:		A buffer for returning the name blob. Must have a
- *			capacity of 'SHA512_DIGET_SIZE + 2' bytes at minimum
- *
- * Returns size of TPM handle name of success.
- * Returns tpm_transmit_cmd error codes when TPM2_ReadPublic fails.
- */
-int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name)
-{
-	u32 mso = tpm2_handle_mso(handle);
-	off_t offset = TPM_HEADER_SIZE;
-	int rc, name_size_alg;
-	struct tpm_buf buf;
-
-	if (mso != TPM2_MSO_PERSISTENT && mso != TPM2_MSO_VOLATILE &&
-	    mso != TPM2_MSO_NVRAM) {
-		memcpy(name, &handle, sizeof(u32));
-		return sizeof(u32);
-	}
-
-	rc = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_READ_PUBLIC);
-	if (rc)
-		return rc;
-
-	tpm_buf_append_u32(&buf, handle);
-
-	rc = tpm_transmit_cmd(chip, &buf, 0, "TPM2_ReadPublic");
-	if (rc) {
-		tpm_buf_destroy(&buf);
-		return tpm_ret_to_err(rc);
-	}
-
-	/* Skip TPMT_PUBLIC: */
-	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);
-		return -EIO;
-	}
-
-	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);
-		return -EIO;
-	}
-
-	if (offset + rc > tpm_buf_length(&buf)) {
-		tpm_buf_destroy(&buf);
-		return -EIO;
-	}
-
-	memcpy(name, &buf.data[offset], rc);
-	return name_size_alg;
-}
-EXPORT_SYMBOL_GPL(tpm2_read_public);
-#endif /* CONFIG_TCG_TPM2_HMAC */
-
 /**
  * tpm_buf_append_name() - Append a handle and store TPM name
  * @chip:		TPM chip to use.
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 319ba75dd79a..704fceb46a40 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -478,6 +478,7 @@ extern int tpm_get_random(struct tpm_chip *chip, u8 *data, size_t max);
 extern struct tpm_chip *tpm_default_chip(void);
 void tpm2_flush_context(struct tpm_chip *chip, u32 handle);
 int tpm2_find_hash_alg(unsigned int crypto_id);
+int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name);
 
 static inline void tpm_buf_append_empty_auth(struct tpm_buf *buf, u32 handle)
 {
@@ -544,7 +545,6 @@ int tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf);
 int tpm_buf_check_hmac_response(struct tpm_chip *chip, struct tpm_buf *buf,
 				int rc);
 void tpm2_end_auth_session(struct tpm_chip *chip);
-int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name);
 #else
 #include <linux/unaligned.h>
 
@@ -568,13 +568,6 @@ static inline int tpm_buf_check_hmac_response(struct tpm_chip *chip,
 {
 	return rc;
 }
-
-static inline int tpm2_read_public(struct tpm_chip *chip, u32 handle,
-				   void *name)
-{
-	memcpy(name, &handle, sizeof(u32));
-	return sizeof(u32);
-}
 #endif	/* CONFIG_TCG_TPM2_HMAC */
 
 #endif
diff --git a/security/keys/trusted-keys/tpm2key.asn1 b/security/keys/trusted-keys/tpm2key.asn1
index f57f869ad600..70cca3b7c3d4 100644
--- a/security/keys/trusted-keys/tpm2key.asn1
+++ b/security/keys/trusted-keys/tpm2key.asn1
@@ -1,11 +1,26 @@
 ---
 --- ASN.1 for TPM 2.0 keys
 ---
+TPMPolicy ::= SEQUENCE {
+	commandCode	[0] EXPLICIT INTEGER,
+	commandPolicy	[1] EXPLICIT OCTET STRING
+}
+
+TPMAuthPolicy ::= SEQUENCE {
+	name		[0] EXPLICIT UTF8String OPTIONAL,
+	policy		[1] EXPLICIT SEQUENCE OF TPMPolicy
+}
 
 TPMKey ::= SEQUENCE {
 	type		OBJECT IDENTIFIER ({tpm2_key_type}),
 	emptyAuth	[0] EXPLICIT BOOLEAN OPTIONAL,
+	policy		[1] EXPLICIT SEQUENCE OF TPMPolicy OPTIONAL,
+	secret		[2] EXPLICIT OCTET STRING OPTIONAL,
+	authPolicy	[3] EXPLICIT SEQUENCE OF TPMAuthPolicy OPTIONAL,
+	description	[4] EXPLICIT UTF8String OPTIONAL,
+	rsaParent	[5] EXPLICIT BOOLEAN OPTIONAL,
+	parentName	[6] EXPLICIT OCTET STRING OPTIONAL ({tpm2_key_parent_name}),
 	parent		INTEGER ({tpm2_key_parent}),
 	pubkey		OCTET STRING ({tpm2_key_pub}),
 	privkey		OCTET STRING ({tpm2_key_priv})
-	}
+}
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index 88bafbcc011a..07f04cc010bc 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -20,16 +20,26 @@
 
 static u32 tpm2key_oid[] = { 2, 23, 133, 10, 1, 5 };
 
+enum tpm_key_tag {
+	TPM_KEY_TAG_EMPTY_AUTH	= 0,
+	TPM_KEY_TAG_POLICY	= 1,
+	TPM_KEY_TAG_SECRET	= 2,
+	TPM_KEY_TAG_AUTH_POLICY	= 3,
+	TPM_KEY_TAG_DESCRIPTION = 4,
+	TPM_KEY_TAG_RSA_PARENT	= 5,
+	TPM_KEY_TAG_PARENT_NAME	= 6,
+};
+
 static int tpm2_key_encode(struct trusted_key_payload *payload,
 			   struct trusted_key_options *options,
-			   u8 *src, u32 len)
+			   u8 *src, u32 len, u8 *parent_name,
+			   u16 parent_name_size)
 {
 	const int SCRATCH_SIZE = PAGE_SIZE;
-	u8 *scratch = kmalloc(SCRATCH_SIZE, GFP_KERNEL);
-	u8 *work = scratch, *work1;
-	u8 *end_work = scratch + SCRATCH_SIZE;
-	u8 *priv, *pub;
+	u8 *end_work, *name_end;
 	u16 priv_len, pub_len;
+	u8 *work, *work1;
+	u8 *priv, *pub;
 	int ret;
 
 	priv_len = get_unaligned_be16(src) + 2;
@@ -40,23 +50,41 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
 	pub_len = get_unaligned_be16(src) + 2;
 	pub = src;
 
+	u8 *scratch __free(kfree) = kmalloc(SCRATCH_SIZE, GFP_KERNEL);
 	if (!scratch)
 		return -ENOMEM;
 
+	work = scratch;
+	end_work = scratch + SCRATCH_SIZE;
+
 	work = asn1_encode_oid(work, end_work, tpm2key_oid,
 			       asn1_oid_len(tpm2key_oid));
 
 	if (options->blobauth_len == 0) {
-		unsigned char bool[3], *w = bool;
-		/* tag 0 is emptyAuth */
-		w = asn1_encode_boolean(w, w + sizeof(bool), true);
-		if (WARN(IS_ERR(w), "BUG: Boolean failed to encode")) {
-			ret = PTR_ERR(w);
-			goto err;
+		u8 *bool_end;
+		u8 bool[3];
+
+		bool_end = asn1_encode_boolean(&bool[0], &bool[sizeof(bool)],
+					       true);
+		if (IS_ERR(bool_end)) {
+			pr_err("BUG: Boolean failed to encode\n");
+			return PTR_ERR(bool_end);
 		}
-		work = asn1_encode_tag(work, end_work, 0, bool, w - bool);
+
+		work = asn1_encode_tag(work, end_work, TPM_KEY_TAG_EMPTY_AUTH,
+				       bool, bool_end - bool);
 	}
 
+	u8 *name_encoded __free(kfree) = kmalloc(SCRATCH_SIZE, GFP_KERNEL);
+	if (!name_encoded)
+		return -ENOMEM;
+
+	name_end = asn1_encode_octet_string(&name_encoded[0],
+					    &name_encoded[SCRATCH_SIZE],
+					    parent_name, parent_name_size);
+	work = asn1_encode_tag(work, end_work, TPM_KEY_TAG_PARENT_NAME,
+			       name_encoded, name_end - name_encoded);
+
 	/*
 	 * Assume both octet strings will encode to a 2 byte definite length
 	 *
@@ -65,8 +93,7 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
 	 */
 	if (WARN(work - scratch + pub_len + priv_len + 14 > SCRATCH_SIZE,
 		 "BUG: scratch buffer is too small")) {
-		ret = -EINVAL;
-		goto err;
+		return -EINVAL;
 	}
 
 	work = asn1_encode_integer(work, end_work, options->keyhandle);
@@ -79,15 +106,10 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
 	if (IS_ERR(work1)) {
 		ret = PTR_ERR(work1);
 		pr_err("BUG: ASN.1 encoder failed with %d\n", ret);
-		goto err;
+		return ret;
 	}
 
-	kfree(scratch);
 	return work1 - payload->blob;
-
-err:
-	kfree(scratch);
-	return ret;
 }
 
 struct tpm2_key_context {
@@ -96,11 +118,13 @@ struct tpm2_key_context {
 	u32 pub_len;
 	const u8 *priv;
 	u32 priv_len;
+	const u8 *name;
+	u32 name_len;
 };
 
 static int tpm2_key_decode(struct trusted_key_payload *payload,
 			   struct trusted_key_options *options,
-			   u8 **buf)
+			   u8 **buf, u8 *parent_name, u16 *parent_name_size)
 {
 	int ret;
 	struct tpm2_key_context ctx;
@@ -127,6 +151,8 @@ static int tpm2_key_decode(struct trusted_key_payload *payload,
 	blob += ctx.priv_len;
 
 	memcpy(blob, ctx.pub, ctx.pub_len);
+	memcpy(parent_name, ctx.name, ctx.name_len);
+	*parent_name_size = ctx.name_len;
 
 	return 0;
 }
@@ -190,6 +216,16 @@ int tpm2_key_priv(void *context, size_t hdrlen,
 	return 0;
 }
 
+int tpm2_key_parent_name(void *context, size_t hdrlen, unsigned char tag,
+			 const void *value, size_t vlen)
+{
+	struct tpm2_key_context *ctx = context;
+
+	ctx->name = value;
+	ctx->name_len = vlen;
+
+	return 0;
+}
 /**
  * tpm2_buf_append_auth() - append TPMS_AUTH_COMMAND to the buffer.
  *
@@ -347,7 +383,8 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 		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, parent_name, parent_name_size);
 	if (blob_len < 0)
 		rc = blob_len;
 
@@ -602,7 +639,7 @@ int tpm2_unseal_trusted(struct tpm_chip *chip,
 	 * Try to decode the provided blob as an ASN.1 blob. Assume that the
 	 * blob is in the legacy format if decoding does not end successfully.
 	 */
-	rc = tpm2_key_decode(payload, options, &blob);
+	rc = tpm2_key_decode(payload, options, &blob, &parent_name[0], &parent_name_size);
 	if (rc) {
 		blob = payload->blob;
 		payload->old_format = 1;
@@ -617,11 +654,13 @@ int tpm2_unseal_trusted(struct tpm_chip *chip,
 	if (rc)
 		return rc;
 
-	rc = tpm2_read_public(chip, options->keyhandle, parent_name);
-	if (rc < 0)
-		goto out;
+	if (!parent_name_size) {
+		rc = tpm2_read_public(chip, options->keyhandle, parent_name);
+		if (rc < 0)
+			goto out;
 
-	parent_name_size = rc;
+		parent_name_size = rc;
+	}
 
 	rc = tpm2_load_cmd(chip, payload, options, parent_name,
 			   parent_name_size, blob, &blob_handle);
-- 
2.39.5


^ permalink raw reply related

* [PATCH v5 1/2] KEYS: trusted: Re-orchestrate tpm2_read_public() calls
From: Jarkko Sakkinen @ 2025-12-06 16:13 UTC (permalink / raw)
  To: linux-integrity
  Cc: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe, James Bottomley,
	Mimi Zohar, David Howells, Paul Moore, James Morris,
	Serge E. Hallyn, open list, open list:KEYS-TRUSTED,
	open list:SECURITY SUBSYSTEM
In-Reply-To: <20251206161345.2038730-1-jarkko@kernel.org>

tpm2_load_cmd() and tpm2_unseal_cmd() use the same parent, and calls to
tpm_buf_append_name() cause the exact same TPM2_ReadPublic command to be
sent to the chip, causing unnecessary traffic.

1. Export tpm2_read_public in order to make it callable from
   'trusted_tpm2'.
2. Re-orchestrate tpm2_seal_trusted() and tpm2_unseal_trusted() in order to
   halve the name resolutions required:
2a. Move tpm2_read_public() calls into trusted_tpm2.
2b. Pass TPM name to tpm_buf_append_name().
2c. Rework tpm_buf_append_name() to use the pre-resolved name.

Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
v5:
- Rebased to the mainline.
v4:
- No changes
v3:
- No changes.
v2:
- No changes.
---
 drivers/char/tpm/tpm2-cmd.c               |   3 +-
 drivers/char/tpm/tpm2-sessions.c          |  95 +++++------------
 include/linux/tpm.h                       |  10 +-
 security/keys/trusted-keys/trusted_tpm2.c | 124 ++++++++++++++--------
 4 files changed, 118 insertions(+), 114 deletions(-)

diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index 3a77be7ebf4a..1f561ad3bdcf 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -202,7 +202,8 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
 	}
 
 	if (!disable_pcr_integrity) {
-		rc = tpm_buf_append_name(chip, &buf, pcr_idx, NULL);
+		rc = tpm_buf_append_name(chip, &buf, pcr_idx, (u8 *)&pcr_idx,
+					 sizeof(u32));
 		if (rc) {
 			tpm_buf_destroy(&buf);
 			return rc;
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
index 4149379665c4..e33be09446ff 100644
--- a/drivers/char/tpm/tpm2-sessions.c
+++ b/drivers/char/tpm/tpm2-sessions.c
@@ -136,8 +136,8 @@ struct tpm2_auth {
 	 * handle, but they are part of the session by name, which
 	 * we must compute and remember
 	 */
-	u32 name_h[AUTH_MAX_NAMES];
 	u8 name[AUTH_MAX_NAMES][2 + SHA512_DIGEST_SIZE];
+	u16 name_size_tbl[AUTH_MAX_NAMES];
 };
 
 #ifdef CONFIG_TCG_TPM2_HMAC
@@ -163,7 +163,17 @@ static int name_size(const u8 *name)
 	}
 }
 
-static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name)
+/**
+ * tpm2_read_public: Resolve TPM name for a handle
+ * @chip:		TPM chip to use.
+ * @handle:		TPM handle.
+ * @name:		A buffer for returning the name blob. Must have a
+ *			capacity of 'SHA512_DIGET_SIZE + 2' bytes at minimum
+ *
+ * Returns size of TPM handle name of success.
+ * Returns tpm_transmit_cmd error codes when TPM2_ReadPublic fails.
+ */
+int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name)
 {
 	u32 mso = tpm2_handle_mso(handle);
 	off_t offset = TPM_HEADER_SIZE;
@@ -219,14 +229,16 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name)
 	memcpy(name, &buf.data[offset], rc);
 	return name_size_alg;
 }
+EXPORT_SYMBOL_GPL(tpm2_read_public);
 #endif /* CONFIG_TCG_TPM2_HMAC */
 
 /**
- * tpm_buf_append_name() - add a handle area to the buffer
- * @chip: the TPM chip structure
- * @buf: The buffer to be appended
- * @handle: The handle to be appended
- * @name: The name of the handle (may be NULL)
+ * tpm_buf_append_name() - Append a handle and store TPM name
+ * @chip:		TPM chip to use.
+ * @buf:		TPM buffer containing the TPM command in-transit.
+ * @handle:		TPM handle to be appended.
+ * @name:		TPM name of the handle
+ * @name_size:		Size of the TPM name.
  *
  * In order to compute session HMACs, we need to know the names of the
  * objects pointed to by the handles.  For most objects, this is simply
@@ -243,15 +255,14 @@ static int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name)
  * will be caused by an incorrect programming model and indicated by a
  * kernel message.
  *
- * Ends the authorization session on failure.
+ * Returns zero on success.
+ * Returns -EIO when the authorization area state is malformed.
  */
 int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
-			u32 handle, u8 *name)
+			u32 handle, u8 *name, u16 name_size)
 {
 #ifdef CONFIG_TCG_TPM2_HMAC
-	enum tpm2_mso_type mso = tpm2_handle_mso(handle);
 	struct tpm2_auth *auth;
-	u16 name_size_alg;
 	int slot;
 	int ret;
 #endif
@@ -276,36 +287,15 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
 	}
 	tpm_buf_append_u32(buf, handle);
 	auth->session += 4;
-
-	if (mso == TPM2_MSO_PERSISTENT ||
-	    mso == TPM2_MSO_VOLATILE ||
-	    mso == TPM2_MSO_NVRAM) {
-		if (!name) {
-			ret = tpm2_read_public(chip, handle, auth->name[slot]);
-			if (ret < 0)
-				goto err;
-
-			name_size_alg = ret;
-		}
-	} else {
-		if (name) {
-			dev_err(&chip->dev, "handle 0x%08x does not use a name\n",
-				handle);
-			ret = -EIO;
-			goto err;
-		}
-	}
-
-	auth->name_h[slot] = handle;
-	if (name)
-		memcpy(auth->name[slot], name, name_size_alg);
+	memcpy(auth->name[slot], name, name_size);
+	auth->name_size_tbl[slot] = name_size;
 #endif
 	return 0;
 
 #ifdef CONFIG_TCG_TPM2_HMAC
 err:
 	tpm2_end_auth_session(chip);
-	return tpm_ret_to_err(ret);
+	return ret;
 #endif
 }
 EXPORT_SYMBOL_GPL(tpm_buf_append_name);
@@ -613,22 +603,8 @@ int tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf)
 	attrs = chip->cc_attrs_tbl[i];
 
 	handles = (attrs >> TPM2_CC_ATTR_CHANDLES) & GENMASK(2, 0);
+	offset_s += handles * sizeof(u32);
 
-	/*
-	 * just check the names, it's easy to make mistakes.  This
-	 * would happen if someone added a handle via
-	 * tpm_buf_append_u32() instead of tpm_buf_append_name()
-	 */
-	for (i = 0; i < handles; i++) {
-		u32 handle = tpm_buf_read_u32(buf, &offset_s);
-
-		if (auth->name_h[i] != handle) {
-			dev_err(&chip->dev, "invalid handle 0x%08x\n", handle);
-			ret = -EIO;
-			goto err;
-		}
-	}
-	/* point offset_s to the start of the sessions */
 	val = tpm_buf_read_u32(buf, &offset_s);
 	/* point offset_p to the start of the parameters */
 	offset_p = offset_s + val;
@@ -689,23 +665,8 @@ int tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf)
 	/* ordinal is already BE */
 	sha256_update(&sctx, (u8 *)&head->ordinal, sizeof(head->ordinal));
 	/* add the handle names */
-	for (i = 0; i < handles; i++) {
-		enum tpm2_mso_type mso = tpm2_handle_mso(auth->name_h[i]);
-
-		if (mso == TPM2_MSO_PERSISTENT ||
-		    mso == TPM2_MSO_VOLATILE ||
-		    mso == TPM2_MSO_NVRAM) {
-			ret = name_size(auth->name[i]);
-			if (ret < 0)
-				goto err;
-
-			sha256_update(&sctx, auth->name[i], ret);
-		} else {
-			__be32 h = cpu_to_be32(auth->name_h[i]);
-
-			sha256_update(&sctx, (u8 *)&h, 4);
-		}
-	}
+	for (i = 0; i < handles; i++)
+		sha256_update(&sctx, auth->name[i], auth->name_size_tbl[i]);
 	if (offset_s != tpm_buf_length(buf))
 		sha256_update(&sctx, &buf->data[offset_s],
 			      tpm_buf_length(buf) - offset_s);
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 202da079d500..319ba75dd79a 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -530,7 +530,7 @@ static inline struct tpm2_auth *tpm2_chip_auth(struct tpm_chip *chip)
 }
 
 int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
-			u32 handle, u8 *name);
+			u32 handle, u8 *name, u16 name_size);
 void tpm_buf_append_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf,
 				 u8 attributes, u8 *passphrase,
 				 int passphraselen);
@@ -544,6 +544,7 @@ int tpm_buf_fill_hmac_session(struct tpm_chip *chip, struct tpm_buf *buf);
 int tpm_buf_check_hmac_response(struct tpm_chip *chip, struct tpm_buf *buf,
 				int rc);
 void tpm2_end_auth_session(struct tpm_chip *chip);
+int tpm2_read_public(struct tpm_chip *chip, u32 handle, void *name);
 #else
 #include <linux/unaligned.h>
 
@@ -567,6 +568,13 @@ static inline int tpm_buf_check_hmac_response(struct tpm_chip *chip,
 {
 	return rc;
 }
+
+static inline int tpm2_read_public(struct tpm_chip *chip, u32 handle,
+				   void *name)
+{
+	memcpy(name, &handle, sizeof(u32));
+	return sizeof(u32);
+}
 #endif	/* CONFIG_TCG_TPM2_HMAC */
 
 #endif
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index a7ea4a1c3bed..88bafbcc011a 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -233,8 +233,10 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 		      struct trusted_key_payload *payload,
 		      struct trusted_key_options *options)
 {
+	u8 parent_name[2 + SHA512_DIGEST_SIZE];
 	off_t offset = TPM_HEADER_SIZE;
 	struct tpm_buf buf, sized;
+	u16 parent_name_size;
 	int blob_len = 0;
 	int hash;
 	u32 flags;
@@ -251,6 +253,12 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 	if (rc)
 		return rc;
 
+	rc = tpm2_read_public(chip, options->keyhandle, parent_name);
+	if (rc < 0)
+		goto out_put;
+
+	parent_name_size = rc;
+
 	rc = tpm2_start_auth_session(chip);
 	if (rc)
 		goto out_put;
@@ -268,7 +276,8 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 		goto out_put;
 	}
 
-	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, parent_name,
+				 parent_name_size);
 	if (rc)
 		goto out;
 
@@ -355,21 +364,25 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 }
 
 /**
- * tpm2_load_cmd() - execute a TPM2_Load command
- *
- * @chip: TPM chip to use
- * @payload: the key data in clear and encrypted form
- * @options: authentication values and other options
- * @blob_handle: returned blob handle
+ * tpm2_load_cmd() - Execute TPM2_Load
+ * @chip:		TPM chip to use.
+ * @payload:		Key data in clear text.
+ * @options:		Trusted key options.
+ * @parent_name:	A cryptographic name, i.e. a TPMT_HA blob, of the
+ *			parent key.
+ * @blob:		The decoded payload for the key.
+ * @blob_handle:	On success, will contain handle to the loaded keyedhash
+ *			blob.
  *
- * Return: 0 on success.
- *        -E2BIG on wrong payload size.
- *        -EPERM on tpm error status.
- *        < 0 error from tpm_send.
+ * Return -E2BIG when the blob size is too small for all the data.
+ * Returns tpm_transmit_cmd() error codes when either TPM2_Load fails.
  */
 static int tpm2_load_cmd(struct tpm_chip *chip,
 			 struct trusted_key_payload *payload,
 			 struct trusted_key_options *options,
+			 u8 *parent_name,
+			 u16 parent_name_size,
+			 const u8 *blob,
 			 u32 *blob_handle)
 {
 	u8 *blob_ref __free(kfree) = NULL;
@@ -377,27 +390,13 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
 	unsigned int private_len;
 	unsigned int public_len;
 	unsigned int blob_len;
-	u8 *blob, *pub;
+	const u8 *pub;
 	int rc;
 	u32 attrs;
 
-	rc = tpm2_key_decode(payload, options, &blob);
-	if (rc) {
-		/* old form */
-		blob = payload->blob;
-		payload->old_format = 1;
-	} else {
-		/* Bind for cleanup: */
-		blob_ref = blob;
-	}
-
-	/* new format carries keyhandle but old format doesn't */
-	if (!options->keyhandle)
-		return -EINVAL;
-
 	/* must be big enough for at least the two be16 size counts */
 	if (payload->blob_len < 4)
-		return -EINVAL;
+		return -E2BIG;
 
 	private_len = get_unaligned_be16(blob);
 
@@ -433,7 +432,8 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
 		return rc;
 	}
 
-	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, parent_name,
+				 parent_name_size);
 	if (rc)
 		goto out;
 
@@ -465,20 +465,23 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
 }
 
 /**
- * tpm2_unseal_cmd() - execute a TPM2_Unload command
+ * tpm2_unseal_cmd() - Execute TPM2_Unload
  *
- * @chip: TPM chip to use
- * @payload: the key data in clear and encrypted form
- * @options: authentication values and other options
- * @blob_handle: blob handle
+ * @chip:		TPM chip to use
+ * @payload:		Key data in clear text.
+ * @options:		Trusted key options.
+ * @parent_name:	A cryptographic name, i.e. a TPMT_HA blob, of the
+ *			parent key.
+ * @blob_handle:	Handle to the loaded keyedhash blob.
  *
- * Return: 0 on success
- *         -EPERM on tpm error status
- *         < 0 error from tpm_send
+ * Return -E2BIG when the blob size is too small for all the data.
+ * Returns tpm_transmit_cmd() error codes when either TPM2_Load fails.
  */
 static int tpm2_unseal_cmd(struct tpm_chip *chip,
 			   struct trusted_key_payload *payload,
 			   struct trusted_key_options *options,
+			   u8 *parent_name,
+			   u16 parent_name_size,
 			   u32 blob_handle)
 {
 	struct tpm_header *head;
@@ -498,7 +501,8 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
 		return rc;
 	}
 
-	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, parent_name,
+				 parent_name_size);
 	if (rc)
 		goto out;
 
@@ -573,30 +577,60 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
 }
 
 /**
- * tpm2_unseal_trusted() - unseal the payload of a trusted key
+ * tpm2_unseal_trusted() - Unseal a trusted key
+ * @chip:	TPM chip to use.
+ * @payload:	Key data in clear text.
+ * @options:	Trusted key options.
  *
- * @chip: TPM chip to use
- * @payload: the key data in clear and encrypted form
- * @options: authentication values and other options
- *
- * Return: Same as with tpm_send.
+ * Return -E2BIG when the blob size is too small for all the data.
+ * Return -EINVAL when parent's key handle has not been set.
+ * Returns tpm_transmit_cmd() error codes when either TPM2_Load or TPM2_Unseal
+ * fails.
  */
 int tpm2_unseal_trusted(struct tpm_chip *chip,
 			struct trusted_key_payload *payload,
 			struct trusted_key_options *options)
 {
+	u8 *blob_ref __free(kfree) = NULL;
+	u8 parent_name[2 + SHA512_DIGEST_SIZE];
+	u16 parent_name_size;
 	u32 blob_handle;
+	u8 *blob;
 	int rc;
 
+	/*
+	 * Try to decode the provided blob as an ASN.1 blob. Assume that the
+	 * blob is in the legacy format if decoding does not end successfully.
+	 */
+	rc = tpm2_key_decode(payload, options, &blob);
+	if (rc) {
+		blob = payload->blob;
+		payload->old_format = 1;
+	} else {
+		blob_ref = blob;
+	}
+
+	if (!options->keyhandle)
+		return -EINVAL;
+
 	rc = tpm_try_get_ops(chip);
 	if (rc)
 		return rc;
 
-	rc = tpm2_load_cmd(chip, payload, options, &blob_handle);
+	rc = tpm2_read_public(chip, options->keyhandle, parent_name);
+	if (rc < 0)
+		goto out;
+
+	parent_name_size = rc;
+
+	rc = tpm2_load_cmd(chip, payload, options, parent_name,
+			   parent_name_size, blob, &blob_handle);
 	if (rc)
 		goto out;
 
-	rc = tpm2_unseal_cmd(chip, payload, options, blob_handle);
+	rc = tpm2_unseal_cmd(chip, payload, options, parent_name,
+			     parent_name_size, blob_handle);
+
 	tpm2_flush_context(chip, blob_handle);
 
 out:
-- 
2.39.5


^ permalink raw reply related

* [PATCH v5 0/2] Optimize tpm2_read_public() calls
From: Jarkko Sakkinen @ 2025-12-06 16:13 UTC (permalink / raw)
  To: linux-integrity
  Cc: Jarkko Sakkinen, James Bottomley, Mimi Zohar, David Howells,
	Paul Moore, James Morris, Serge E. Hallyn, open list:KEYS-TRUSTED,
	open list:SECURITY SUBSYSTEM, open list

The main goal is fairly straight-forwrd here.

The aim of these patches is optimize the number of tpm2_read_public()
calls to the bare minimum. The last patch is a bit out of scope but
I included because it builds on top of this work.

Jarkko Sakkinen (2):
  KEYS: trusted: Re-orchestrate tpm2_read_public() calls
  KEYS: trusted: Store parent's name to the encoded keys

 drivers/char/tpm/tpm2-cmd.c               |  93 +++++++++-
 drivers/char/tpm/tpm2-sessions.c          | 163 ++---------------
 include/linux/tpm.h                       |   3 +-
 security/keys/trusted-keys/tpm2key.asn1   |  17 +-
 security/keys/trusted-keys/trusted_tpm2.c | 207 +++++++++++++++-------
 5 files changed, 266 insertions(+), 217 deletions(-)

-- 
2.39.5


^ permalink raw reply

* Re: [PATCH v3 3/5] samples/landlock: Add LANDLOCK_ADD_RULE_NO_INHERIT to landlock-sandboxer
From: Justin Suess @ 2025-12-06 15:33 UTC (permalink / raw)
  To: m; +Cc: gnoack, jack, linux-security-module, mic, utilityemal77, xandfury
In-Reply-To: <664b4f51-afaa-4dbf-a83f-95a2167eeedb@maowtm.org>

It totally does. I think that would be more way more clear and reduce LOC.

It'll be in the next version.

>
>>  	"* " ENV_TCP_BIND_NAME ": ports allowed to bind (server)\n"
>>  	"* " ENV_TCP_CONNECT_NAME ": ports allowed to connect (client)\n"
>>  	"* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n"
>> @@ -596,17 +604,28 @@ int main(const int argc, char *const argv[], char *const *const envp)
>>  	}
>>  
>>  	if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro,
>> -				false)) {
>> +			0, true)) {
>>  		goto err_close_ruleset;
>>  	}
>>  	if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw,
>> +			0, true)) {
>> +		goto err_close_ruleset;
>> +	}
>> +	/* Optional no-inherit rules mirror the regular read-only/read-write sets. */
>> +	if (populate_ruleset_fs(ENV_FS_RO_NO_INHERIT_NAME, ruleset_fd,
>> +				access_fs_ro, LANDLOCK_ADD_RULE_NO_INHERIT,
>> +				false)) {
>> +		goto err_close_ruleset;
>> +	}
>> +	if (populate_ruleset_fs(ENV_FS_RW_NO_INHERIT_NAME, ruleset_fd,
>> +				access_fs_rw, LANDLOCK_ADD_RULE_NO_INHERIT,
>
> These need to be under an ABI version check like the quiet one - this
> sandboxer is designed to "downgrade" what it tries to do gracefully if
> running on older kernel.
>
> However, there is an argument that maybe if deny rules aren't supported in
> the current running kernel, it should just refuse to run the program at
> all, otherwise by running the sandboxed program without the deny rules it
> might expose the user to risks they might mitigate via some other means.
> But in that case we should still have a better error message when running
> on an older kernel than "Failed to update the ruleset with ...: Invalid
> argument"

Gotcha. I'll put it under the ABI check. 

I think it'd be better to let the user decide if they wanna continue
with the policy if it's using an unsupported feature.

Kind Regards,

Justin Suess


^ permalink raw reply

* Re: [PATCH v3 1/5] landlock: Implement LANDLOCK_ADD_RULE_NO_INHERIT
From: Justin Suess @ 2025-12-06 15:26 UTC (permalink / raw)
  To: m; +Cc: gnoack, jack, linux-security-module, mic, utilityemal77, xandfury
In-Reply-To: <c314e302-d4ca-4a39-81ba-d4a1e21b9391@maowtm.org>

Thank you for the review.

I agree with the bind mount limitations for this flag. I think it's
reasonable to expect the sandboxer to provide protections for pre-
existing bind mounts as opposed to the kernel.

This limitation is a tradeoff between safety and complexity.

I looked into doing in automatically, and it ends up being sort of a
mess, and you end up having to iterate through the bind mounts, and I
suspect it would be a major performance hit, especially if we have to
account for changes outside the sandbox after the policy is already
enforced.

We can note this limitation in the docs.

> On 11/26/25 12:20, Justin Suess wrote:
>> Implements a flag to prevent access grant inheritance within the filesys
tem hierarchy
>> for landlock rules.
>>
>> If a landlock rule on an inode has this flag, any access grants on paren
t inodes will be
>> ignored. Moreover, operations that involve altering the direct parent tr
ee of the subject with
> I feel like the wording "direct parent tree" is a bit unclear - I think
> what you meant is "altering the parent of the subject or its ancestors",
> right?
>

Yes. I think that wording is more clear and use that from now on.

It just means if we have /a/b/c, the protected ancestors are /a/b and /a,
but not /a/x/ for instance.

>> LANDLOCK_ADD_RULE_NO_INHERIT will be denied up to the mountpoint.
> Tbh I'm not entirely clear on why we only deny rename of parents up to th
e
> mountpoint.

I agree.

My initial reasoning for not doing so was the fact that a mountpoint
doesn't let you move any of its ancestors.

So I thought going past the mountpoint was redundant.


    $ sudo mount --bind test test2
    $ mv test2 test3
    mv: cannot move 'test2' to 'test3': Device or resource busy

But I did not consider the fact that you can still link to a parent and
bypass restrictions that way. So the next patch version will fix this issue.

I implemented protections beyond the mountpoint in my current tree using
follow_up.

>> Additionally (new in v3) parent flag inheritance is blocked by this flag
, allowing fine
>> grained access control over LANDLOCK_ADD_RULE_QUIET.
>>
>> For example, if /a/b/c/ = read only + LANDLOCK_ADD_RULE_NO_INHERIT and /
 = read write, writes to
>> files in /a/b/c will be denied. Moreover, moving /a to /bad, removing /a
/b/c, or creating links to
>> /a will be prohibited.
>>
>> And if / has LANDLOCK_ADD_RULE_QUIET, /a/b/c will still audit (handled)
>> accesses. This is because LANDLOCK_ADD_RULE_NO_INHERIT also
>> suppresses flag inheritance from parent objects.
>>
>> The parent directory restrictions mitigate sandbox-restart attacks. For 
example, if a sandboxed program
>> is able to move a LANDLOCK_ADD_RULE_NO_INHERIT restricted directory, upo
n sandbox restart, the policy
>> applied naively on the same filenames would be invalid. Preventing these
 operations mitigates these attacks.
>>
>> v2..v3 changes:
>>
>>   * Parent directory topology protections now work by lazily
>>     inserting blank rules on parent inodes if they do not
>>     exist. This replaces the previous xarray implementation
>>     with simplified logic.
>>   * Added an optimization to skip further processing if all layers colle
cted
>>     no inherit.
>>   * Added support to block flag inheritance.
>>
>> Signed-off-by: Justin Suess <utilityemal77@gmail.com>
>> ---
>>  security/landlock/audit.c   |   4 +-
>>  security/landlock/domain.c  |   4 +-
>>  security/landlock/fs.c      | 592 +++++++++++++++++++++++++++++++++++-
>>  security/landlock/ruleset.c |  27 +-
>>  security/landlock/ruleset.h |  36 ++-
>>  5 files changed, 645 insertions(+), 18 deletions(-)
>>
>> diff --git a/security/landlock/audit.c b/security/landlock/audit.c
>> index d51563712325..4da97dd6985c 100644
>> --- a/security/landlock/audit.c
>> +++ b/security/landlock/audit.c
>> @@ -588,7 +588,9 @@ void landlock_log_denial(const struct landlock_cred_
security *const subject,
>>  				subject->domain, &missing, request->layer_m
asks,
>>  				request->layer_masks_size);
>>  			object_quiet_flag = !!(request->rule_flags.quiet_ma
sks &
>> -					       BIT(youngest_layer));
>> +				       BIT(youngest_layer)) &&
>> +				!(request->rule_flags.blocked_flag_masks &
>> +				  BIT(youngest_layer));
>>  		} else {
>>  			youngest_layer = get_layer_from_deny_masks(
>>  				&missing, request->all_existing_optional_ac
cess,
>> diff --git a/security/landlock/domain.c b/security/landlock/domain.c
>> index 8caf07250328..5bd83865c87d 100644
>> --- a/security/landlock/domain.c
>> +++ b/security/landlock/domain.c
>> @@ -236,7 +236,9 @@ optional_access_t landlock_get_quiet_optional_access
es(
>>  			 BITS_PER_TYPE(access_mask_t)) {
>>  		const u8 layer = (deny_masks >> (access_index * 4)) &
>>  				 (LANDLOCK_MAX_NUM_LAYERS - 1);
>> -		const bool is_quiet = !!(rule_flags.quiet_masks & BIT(layer
));
>> +		const layer_mask_t layer_bit = BIT(layer);
>> +		const bool is_quiet = !!(rule_flags.quiet_masks & layer_bit
) &&
>> +				  !(rule_flags.blocked_flag_masks & layer_b
it);
>>
>>  		if (is_quiet)
>>  			quiet_optional_accesses |= BIT(access_index);
>> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
>> index 29f10da32141..0a5c73f18f26 100644
>> --- a/security/landlock/fs.c
>> +++ b/security/landlock/fs.c
>> @@ -317,6 +317,206 @@ static struct landlock_object *get_inode_object(st
ruct inode *const inode)
>>  	LANDLOCK_ACCESS_FS_IOCTL_DEV)
>>  /* clang-format on */
>>
>> +static const struct landlock_rule *find_rule(const struct landlock_rule
set *const domain,
>> +					     const struct dentry *const den
try);
>> +
>> +/**
>> + * landlock_domain_layers_mask - Build a mask covering all layers of a 
domain
>> + * @domain: The ruleset (domain) to inspect.
>> + *
>> + * Return a layer mask with a 1 bit for each existing layer of @domain.
>> + * If @domain has no layers 0 is returned.  If the number of layers is
>> + * greater than or equal to the number of bits in layer_mask_t, all bit
s
>> + * are set.
>> + */
>> +static layer_mask_t landlock_domain_layers_mask(const struct landlock_r
uleset
>> +						*const domain)
>> +{
>> +	if (!domain || !domain->num_layers)
>> +		return 0;
>> +
>> +	if (domain->num_layers >= sizeof(layer_mask_t) * BITS_PER_BYTE)
>> +		return (layer_mask_t)~0ULL;
>> +
>> +	return GENMASK_ULL(domain->num_layers - 1, 0);
>> +}
>> +
>> +/**
>> + * rule_blocks_all_layers_no_inherit - check whether a rule disables in
heritance
>> + * @domain_layers_mask: Mask describing the domain's active layers.
>> + * @rule: Rule to inspect.
>> + *
>> + * Return true if every layer present in @rule has its no_inherit flag 
set
>> + * and the set of layers covered by the rule equals @domain_layers_mask
.
>> + * This indicates that the rule prevents inheritance on all layers of t
he
>> + * domain and thus further walking for inheritance checks can stop.
>> + */
>> +static bool rule_blocks_all_layers_no_inherit(const layer_mask_t domain
_layers_mask,
>> +					      const struct landlock_rule *c
onst rule)
>> +{
>> +	layer_mask_t rule_layers = 0;
>> +	u32 layer_index;
>> +
>> +	if (!domain_layers_mask || !rule)
>> +		return false;
>> +
>> +	for (layer_index = 0; layer_index < rule->num_layers; layer_index++
) {
>> +		const struct landlock_layer *const layer =
>> +			&rule->layers[layer_index];
>> +		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
>> +
>> +		if (!layer->flags.no_inherit)
>> +			return false;
>> +
>> +		rule_layers |= layer_bit;
>> +	}
>> +
>> +	return rule_layers && rule_layers == domain_layers_mask;
>> +}
>> +
>> +/**
>> + * landlock_collect_no_inherit_layers - Collects layers with no_inherit
 flags
>> + */
> Note likely misplaced comment here

Gotcha. Probably a mistake while rebasing.

>
>
>> +/**
>> + * landlock_collect_no_inherit_layers - collect effective no_inherit la
yers
>> + * @ruleset: Ruleset to consult.
>> + * @dentry: Dentry used as a starting point for the upward walk.
>> + *
>> + * Walk upwards from @dentry and return a layer mask containing the lay
ers
>> + * for which either a rule on the visited dentry has the no_inherit fla
g set
>> + * or where an ancestor was previously marked as having a descendant wi
th
>> + * a no_inherit rule.  The search prefers the closest matching dentry a
nd
>> + * stops once any relevant layer bits are found or the root is reached.
>> + *
>> + * Returns a layer_mask_t where each set bit corresponds to a layer wit
h an
>> + * effective no_inherit influence for @dentry.  Returns 0 if none apply
 or if
>> + * inputs are invalid.
>> + */
>> +static layer_mask_t landlock_collect_no_inherit_layers(const struct lan
dlock_ruleset
>> +						       *const ruleset,
>> +						       struct dentry *const
 dentry)
>> +{
>> +	struct dentry *cursor, *parent;
>> +	layer_mask_t layers = 0;
>> +	bool include_descendants = true;
>> +
>> +	if (!ruleset || !dentry || d_is_negative(dentry))
>> +		return 0;
>> +
>> +	cursor = dget(dentry);
>> +	while (true) {
>> +		const struct landlock_rule *rule;
>> +		u32 layer_index;
>> +
>> +		rule = find_rule(ruleset, cursor);
>> +		if (rule) {
>> +			for (layer_index = 0; layer_index < rule->num_layer
s; layer_index++) {
>> +				const struct landlock_layer *layer = &rule-
>layers[layer_index];
>> +
>> +				if (layer->flags.no_inherit ||
>> +				    (include_descendants &&
>> +				     layer->flags.has_no_inherit_descendant
))
>> +					layers |= BIT_ULL((layer->level ?
>> +						layer->level : layer_index 
+ 1) - 1);
>> +			}
>> +		}
>> +
>> +		if (layers) {
>> +			dput(cursor);
>> +			return layers;
>> +		}
>> +
>> +		if (IS_ROOT(cursor)) {
>> +			dput(cursor);
>> +			break;
>> +		}
>> +
>> +		parent = dget_parent(cursor);
>> +		dput(cursor);
>> +		if (!parent)
>> +			break;
>> +
>> +		cursor = parent;
>> +		include_descendants = false;
>> +	}
>> +	return 0;
>> +}
>> +
>> +static int mark_no_inherit_ancestors(struct landlock_ruleset *ruleset,
>> +				     struct dentry *dentry,
>> +				     layer_mask_t descendant_layers);
> Wouldn't you be able to avoid this declaration by moving the definition
> for ensure_rule_for_dentry and mark_no_inherit_ancestors up a bit, before
> mask_no_inherit_descendant_layers?

Good catch. I've fixed this in my working tree.

>
>
>> +
>> +/**
>> + * mask_no_inherit_descendant_layers - apply descendant no_inherit mask
ing
>> + * @domain: The ruleset (domain) to consult.
>> + * @dentry: The dentry whose descendants are considered.
>> + * @child_layers: Layers present on the child that may be subject to ma
sking.
>> + * @access_request: Accesses being requested (bitmask).
>> + * @layer_masks: Per-access layer masks to be modified in-place.
>> + * @rule_flags: Collected flags which will be updated accordingly.
>> + *
>> + * If descendant dentries have no_inherit, clear that
>> + * layer's bit from @layer_masks. Also updates @rule_flags to reflect
>> + * which layers were blocked.  Returns true if any of the @layer_masks 
were
>> + * modified, false otherwise.
>> + */
>> +static bool mask_no_inherit_descendant_layers(const struct landlock_rul
eset
>> +					      *const domain,
>> +					      struct dentry *const dentry,
>> +					      layer_mask_t child_layers,
>> +					      const access_mask_t access_re
quest,
>> +					      layer_mask_t
>> +					      (*const layer_masks)
>> +					      [LANDLOCK_NUM_ACCESS_FS],
>> +					      struct collected_rule_flags
>> +					      *const rule_flags)
>> +{
>> +	layer_mask_t descendant_layers;
>> +	const unsigned long access_req = access_request;
>> +	unsigned long access_bit;
>> +	bool changed = false;
>> +
>> +	if (!access_request || !layer_masks || !rule_flags || !dentry)
>> +		return false;
>> +	if (d_is_negative(dentry))
>> +		return false;
>> +
>> +	descendant_layers = landlock_collect_no_inherit_layers(domain, dent
ry);
>> +	{
>> +		layer_mask_t shared_layers = descendant_layers & child_laye
rs;
>> +
>> +		if (shared_layers) {
>> +			rule_flags->no_inherit_masks |= shared_layers;
>> +			rule_flags->no_inherit_desc_masks |= shared_layers;
>> +			rule_flags->blocked_flag_masks |= shared_layers;
>> +		}
>> +	}
>> +	descendant_layers &= ~child_layers;
>> +	descendant_layers &= ~rule_flags->no_inherit_masks;
>> +	if (!descendant_layers)
>> +		return false;
>> +
>> +	rule_flags->blocked_flag_masks |= descendant_layers;
>> +
>> +	for_each_set_bit(access_bit, &access_req,
>> +			 ARRAY_SIZE(*layer_masks)) {
>> +		layer_mask_t *const layer_mask = &(*layer_masks)[access_bit
];
>> +
>> +		if (*layer_mask & descendant_layers) {
>> +			*layer_mask &= ~descendant_layers;
>> +			changed = true;
>> +		}
>> +	}
>> +
>> +	if (!changed)
>> +		return false;
>> +
>> +	rule_flags->no_inherit_masks |= descendant_layers;
>> +	rule_flags->no_inherit_desc_masks |= descendant_layers;
>> +
>> +	return true;
>> +}
>> +
>>  /*
>>   * @path: Should have been checked by get_path_from_fd().
>>   */
>> @@ -325,12 +525,13 @@ int landlock_append_fs_rule(struct landlock_rulese
t *const ruleset,
>>  			    access_mask_t access_rights, const int flags)
>>  {
>>  	int err;
>> +	const bool is_dir = d_is_dir(path->dentry);
>>  	struct landlock_id id = {
>>  		.type = LANDLOCK_KEY_INODE,
>>  	};
>>
>>  	/* Files only get access rights that make sense. */
>> -	if (!d_is_dir(path->dentry) &&
>> +	if (!is_dir &&
>>  	    (access_rights | ACCESS_FILE) != ACCESS_FILE)
>>  		return -EINVAL;
>>  	if (WARN_ON_ONCE(ruleset->num_layers != 1))
>> @@ -344,13 +545,43 @@ int landlock_append_fs_rule(struct landlock_rulese
t *const ruleset,
>>  		return PTR_ERR(id.key.object);
>>  	mutex_lock(&ruleset->lock);
>>  	err = landlock_insert_rule(ruleset, id, access_rights, flags);
>> +	if (!err && (flags & LANDLOCK_ADD_RULE_NO_INHERIT)) {
>> +		const struct landlock_rule *rule;
>> +		layer_mask_t descendant_layers = 0;
>> +		u32 layer_index;
>> +
>> +		rule = find_rule(ruleset, path->dentry);
>> +		if (rule) {
>> +			for (layer_index = 0; layer_index < rule->num_layer
s; layer_index++) {
> This function is only called to add rules to an "unmerged" ruleset, which
> will always only have one layer.  So probably this can just be a
>
> 	err = landlock_insert_rule(ruleset, id, access_rights, flags);
> 	if (err)
> 		goto out_unlock;
> 	if (flags & LANDLOCK_ADD_RULE_NO_INHERIT) {
> 		err = mark_no_inherit_ancestors(ruleset, path->dentry);
> 		if (err)
> 			goto out_unlock;
> 	}
>
> And for a similar reason, you don't have to do a for(layer_index) in
> mark_no_inherit_ancestors either.  (Basically I think you would just set
> rule->layers[0].flags.has_no_inherit_descendant to true)
>
> It might be helpful to validate / document this reasoning by adding
> WARN_ONCE(rule->num_layers != 1, "unmerged rulesets should only have one
> layer") either here or in mark_no_inherit_ancestors.

Ah ok I see. I assume WARN_ONCE just is a kernel indicator of "hey this
state shouldn't happen".

And I got rid of the for loop and fixed it to use logic like the above,
and all tests passed. It will be in the next version.

>
>
>> +				const struct landlock_layer *layer =
>> +					&rule->layers[layer_index];
>> +
>> +				if (layer->flags.no_inherit ||
>> +				    layer->flags.has_no_inherit_descendant)
>> +					descendant_layers |=
>> +						BIT_ULL((layer->level ?
>> +							 layer->level : lay
er_index + 1) - 1);
>> +			}
>> +			if (descendant_layers) {
>> +				err = mark_no_inherit_ancestors(ruleset, pa
th->dentry,
>> +								descendant_
layers);
>> +				if (err)
>> +					goto out_unlock;
>> +			}
>> +		}
>> +	}
>>  	mutex_unlock(&ruleset->lock);
>> +out:
>>  	/*
>>  	 * No need to check for an error because landlock_insert_rule()
>>  	 * increments the refcount for the new object if needed.
>>  	 */
>>  	landlock_put_object(id.key.object);
>>  	return err;
>> +
>> +out_unlock:
>> +	mutex_unlock(&ruleset->lock);
>> +	goto out;
>>  }
>>
>>  /* Access-control management */
>> @@ -382,6 +613,134 @@ find_rule(const struct landlock_ruleset *const dom
ain,
>>  	return rule;
>>  }
>>
>> +/**
>> + * ensure_rule_for_dentry - ensure a ruleset contains a rule entry for 
dentry,
>> + * inserting a blank rule if needed.
>> + * @ruleset: Ruleset to modify/inspect.  Caller must hold @ruleset->loc
k.
>> + * @dentry: Dentry to ensure a rule exists for.
>> + *
>> + * If no rule is currently associated with @dentry, insert an empty rul
e
>> + * (with zero access) tied to the backing inode.  Returns a pointer to 
the
>> + * rule associated with @dentry on success, NULL when @dentry is negati
ve, or
>> + * an ERR_PTR()-encoded error if the rule cannot be created.
>> + *
>> + * This is useful for LANDLOCK_ADD_RULE_NO_INHERIT processing, where a 
rule
>> + * may need to be created for an ancestor dentry that does not yet have
 one
>> + * to properly track no_inherit flags.
>> + *
>> + * The flags are set to zero if a rule is newly created, and the caller
>> + * is responsible for setting them appropriately.
>> + *
>> + * The returned rule pointer's lifetime is tied to @ruleset.
>> + */
>> +static const struct landlock_rule *
>> +ensure_rule_for_dentry(struct landlock_ruleset *const ruleset,
>> +		       struct dentry *const dentry)
>> +{
>> +	struct landlock_id id = {
>> +		.type = LANDLOCK_KEY_INODE,
>> +	};
>> +	const struct landlock_rule *rule;
>> +	int err;
>> +
>> +	if (!ruleset || !dentry || d_is_negative(dentry))
>> +		return NULL;
>> +
>> +	lockdep_assert_held(&ruleset->lock);
>> +
>> +	rule = find_rule(ruleset, dentry);
>> +	if (rule)
>> +		return rule;
>> +
>> +	id.key.object = get_inode_object(d_backing_inode(dentry));
>> +	if (IS_ERR(id.key.object))
>> +		return ERR_CAST(id.key.object);
>> +
>> +	err = landlock_insert_rule(ruleset, id, 0, 0);
>> +	landlock_put_object(id.key.object);
>> +	if (err)
>> +		return ERR_PTR(err);
>> +
>> +	rule = find_rule(ruleset, dentry);
>> +	return rule ? rule : ERR_PTR(-ENOENT);
> I feel like this deserves a WARN_ON_ONCE(!rule) before this line - we
> don't really expect to not find a rule right after adding it.
>
> On the other hand, the only reason why we need to re-lookup the rule seem
s
> to be because landlock_insert_rule() does not return the newly added rule
.
> We could change it to do so, and not have to do this extra lookup.
>
> This also nicely solves the constness issue - landlock_insert_rule could
> return the mutable pointer.

I agree, but I'll hold off on changing landlock_insert_rule to keep the
patch scoped. The mutable cast thing seemed like a hack to me too when I
was doing it. Maybe returning a pointer or an ERR_PTR in landlock_insert_rule?

I'll add the WARN_ON_ONCE too.

>> +}
>> +
>> +/**
>> + * mark_no_inherit_ancestors - mark ancestors as having no_inherit desc
endants
>> + * @ruleset: Ruleset to modify.  Caller must hold @ruleset->lock.
>> + * @dentry: Dentry representing the descendant that carries no_inherit 
bits.
>> + * @descendant_layers: Mask of layers from the descendant that should b
e
>> + *                     advertised to ancestors via has_no_inherit_desce
ndant.
>> + *
>> + * Walks upward from @dentry and ensures that any ancestor rule contain
s the
>> + * has_no_inherit_descendant marker for the specified @descendant_layer
s so
>> + * parent lookups can quickly detect descendant no_inherit influence.
>> + *
>> + * Returns 0 on success or a negative errno if ancestor bookkeeping fai
ls.
>> + */
>> +static int mark_no_inherit_ancestors(struct landlock_ruleset *ruleset,
>> +				     struct dentry *dentry,
>> +				     layer_mask_t descendant_layers)
>> +{
>> +	struct dentry *cursor;
>> +	u32 layer_index;
>> +	int err = 0;
>> +
>> +	if (!ruleset || !dentry || !descendant_layers)
>> +		return -EINVAL;
>> +
>> +	lockdep_assert_held(&ruleset->lock);
>> +
>> +	cursor = dget(dentry);
>> +	while (cursor) {
>> +		struct dentry *parent;
>> +
>> +		if (IS_ROOT(cursor)) {
>> +			dput(cursor);
>> +			break;
>> +		}
>> +
>> +		parent = dget_parent(cursor);
>> +		dput(cursor);
>> +		if (!parent)
>> +			break;
>> +
>> +		if (!d_is_negative(parent)) {
> My understanding is that if the child is not negative (which is required
> for us to actually get here), as long as we always have a reference to it
,
> none of its parents should be negative as well.  This should probably be 
a
> WARN_ON_ONCE(d_is_negative(parent)).
>
> I think some of the other d_is_negative() checks in this patch also have 
a
> similar argument (i.e. turn into WARN_ON_ONCE or be removed if not
> necessary), but I've not looked at them all.
>

I'll do a pass and see. Again some of my understanding of the code is still
in the "I think I know what it's doing but not why" stage so missing that
is on me.

>> +			const struct landlock_rule *rule;
>> +			/* Ensures a rule exists for the parent dentry,
>> +			 * inserting a blank one if needed
>> +			 */
>> +			rule = ensure_rule_for_dentry(ruleset, parent);
>> +			if (IS_ERR(rule)) {
>> +				err = PTR_ERR(rule);
>> +				dput(parent);
>> +				cursor = NULL;
>> +				break;
>> +			}
>> +			if (rule) {
>> +				struct landlock_rule *mutable_rule =
>> +					(struct landlock_rule *)rule;
>> +
>> +				for (layer_index = 0;
>> +				     layer_index < mutable_rule->num_layers
;
>> +				     layer_index++) {
>> +					struct landlock_layer *layer =
>> +						&mutable_rule->layers[layer
_index];
>> +					layer_mask_t layer_bit =
>> +						BIT_ULL((layer->level ?
>> +							layer->level : laye
r_index + 1) - 1);
>> +
>> +					if (descendant_layers & layer_bit)
>> +						layer->flags.has_no_inherit
_descendant = true;
>> +				}
>> +			}
>> +		}
>> +
>> +		cursor = parent;
>> +	}
>> +	return err;
>> +}
>> +
>>  /*
>>   * Allows access to pseudo filesystems that will never be mountable (e.
g.
>>   * sockfs, pipefs), but can still be reachable through
>> @@ -764,6 +1123,8 @@ static bool is_access_to_paths_allowed(
>>  	struct landlock_request *const log_request_parent2,
>>  	struct dentry *const dentry_child2)
>>  {
>> +	const layer_mask_t domain_layers_mask =
>> +		landlock_domain_layers_mask(domain);
>>  	bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check
,
>>  	     is_dom_check_bkp, child1_is_directory = true,
>>  	     child2_is_directory = true;
>> @@ -778,6 +1139,13 @@ static bool is_access_to_paths_allowed(
>>  	struct collected_rule_flags *rule_flags_parent1 = &log_request_pare
nt1->rule_flags;
>>  	struct collected_rule_flags *rule_flags_parent2 = &log_request_pare
nt2->rule_flags;
>>  	struct collected_rule_flags _rule_flag_parent1_bkp, _rule_flag_pare
nt2_bkp;
>> +	layer_mask_t child1_layers = 0;
>> +	layer_mask_t child2_layers = 0;
>> +
>> +	if (dentry_child1)
>> +		child1_layers = landlock_collect_no_inherit_layers(domain, 
dentry_child1);
>> +	if (dentry_child2)
>> +		child2_layers = landlock_collect_no_inherit_layers(domain, 
dentry_child2);
>>
>>  	if (!access_request_parent1 && !access_request_parent2)
>>  		return true;
>> @@ -931,6 +1299,10 @@ static bool is_access_to_paths_allowed(
>>  					       ARRAY_SIZE(*layer_masks_pare
nt2),
>>  					       rule_flags_parent2);
>>
>> +		if (rule &&
>> +		    rule_blocks_all_layers_no_inherit(domain_layers_mask, r
ule))
>> +			break;
>> +
>>  		/* Stops when a rule from each layer grants access. */
>>  		if (allowed_parent1 && allowed_parent2) {
>>  			/*
>> @@ -976,8 +1348,13 @@ static bool is_access_to_paths_allowed(
>>  					memcpy(&_rule_flag_parent2_bkp,
>>  					       rule_flags_parent2,
>>  					       sizeof(_rule_flag_parent2_bk
p));
>> -					is_dom_check_bkp = is_dom_check;
>>  				}
>> +				is_dom_check_bkp = is_dom_check;
>> +				child1_layers = landlock_collect_no_inherit
_layers(domain,
>> +									
	   walker_path
>> +									
	   .dentry);
>> +				if (layer_masks_parent2)
>> +					child2_layers = child1_layers;
>>
>>  				/* Ignores hidden mount points. */
>>  				goto jump_up;
>> @@ -1001,15 +1378,50 @@ static bool is_access_to_paths_allowed(
>>  				break;
>>  			}
>>
>> -			/*
>> -			 * We reached a disconnected root directory from a 
bind mount, and
>> -			 * we need to reset the walk to the current mount r
oot.
>> -			 */
>> -			goto reset_to_mount_root;
>> -		}
>> -		parent_dentry = dget_parent(walker_path.dentry);
>> -		dput(walker_path.dentry);
>> -		walker_path.dentry = parent_dentry;
>> +		/*
>> +		 * We reached a disconnected root directory from a bind mou
nt, and
>> +		 * we need to reset the walk to the current mount root.
>> +		 */
> Accidental change of indentation?

I'll fix that. Guess checkpatch.pl didn't catch it.

>
>
>> +		goto reset_to_mount_root;
>> +	}
>> +	if (likely(!d_is_negative(walker_path.dentry))) {
>> +		child1_layers = landlock_collect_no_inherit_layers(domain,
>> +								   walker_p
ath.dentry);
>> +		if (layer_masks_parent2)
>> +			child2_layers = child1_layers;
>> +	} else {
>> +		child1_layers = 0;
>> +		if (layer_masks_parent2)
>> +			child2_layers = 0;
>> +	}
>> +	parent_dentry = dget_parent(walker_path.dentry);
>> +	dput(walker_path.dentry);
>> +	walker_path.dentry = parent_dentry;
>> +	/*
>> +	 * Apply descendant no-inherit masking now that we've moved to the
>> +	 * parent. This ensures the parent respects any no-inherit rules fr
om
>> +	 * the child we just left. Only applies to refer operations (rename
/link).
>> +	 */
>> +	if (unlikely(layer_masks_parent2)) {
>> +		if (mask_no_inherit_descendant_layers(domain, walker_path.d
entry,
>> +						      child1_layers,
>> +						      access_masked_parent1
,
>> +						      layer_masks_parent1,
>> +						      rule_flags_parent1))
>> +			allowed_parent1 =
>> +				allowed_parent1 ||
>> +				is_layer_masks_allowed(layer_masks_parent1)
;
>> +
>> +		if (rule_flags_parent2 &&
>> +		    mask_no_inherit_descendant_layers(domain, walker_path.d
entry,
>> +						      child2_layers,
>> +						      access_masked_parent2
,
>> +						      layer_masks_parent2,
>> +						      rule_flags_parent2))
>> +			allowed_parent2 =
>> +				allowed_parent2 ||
>> +				is_layer_masks_allowed(layer_masks_parent2)
;
>> +	}
> Maybe I'm missing something, but I can't tell what's the purpose of this
> block, or in fact what mask_no_inherit_descendant_layers and
> landlock_collect_no_inherit_layers is for.  The doc comment for
> mask_no_inherit_descendant_layers seems to suggest that it's supposed to
> walk descendents, but landlock_collect_no_inherit_layers is actually
> walking ancestors.  Removing these checks doesn't seem to break any tests
,
> and sandboxer still seems to work as expected wrt. no_inherit rules and
> denial of renaming denied dentries and its parents.
>
> Note that the special "reverting" style disconnected directory handling
> has been removed in Mickaël's next branch (i.e. the "backup" logic is
> removed), which should hopefully simplify reasoning about this.
>

Gotcha. I think this came from some of my initial confusion going
through the codebase and making multiple changes before testing. So I
would add checks to be sure before doing a rebuild, and had a fear of
breaking it and having to wait around for a recompile.

I am still figuring out kernel debugging so sometimes I wouldn't know
what code was actually breaking the tests.

This is a big catch and reduces the LOC considerably so thank you! I
removed the checks and all tests passed, so it seems  like these were
indeed redundant.

>>  		continue;
>>
>>  reset_to_mount_root:
>> @@ -1057,6 +1469,10 @@ static bool is_access_to_paths_allowed(
>>  		dput(walker_path.dentry);
>>  		walker_path.dentry = walker_path.mnt->mnt_root;
>>  		dget(walker_path.dentry);
>> +		child1_layers = landlock_collect_no_inherit_layers(domain,
>> +								   walker_p
ath.dentry);
>> +		if (layer_masks_parent2)
>> +			child2_layers = child1_layers;
>>  	}
>>  	path_put(&walker_path);
>>
>> @@ -1172,6 +1588,8 @@ static bool collect_domain_accesses(
>>  	struct collected_rule_flags *const rule_flags)
>>  {
>>  	access_mask_t access_dom;
>> +	const layer_mask_t domain_layers_mask =
>> +		landlock_domain_layers_mask(domain);
>>  	bool ret = false;
>>
>>  	if (WARN_ON_ONCE(!domain || !mnt_dir || !dir || !layer_masks_dom))
>> @@ -1187,9 +1605,11 @@ static bool collect_domain_accesses(
>>  	while (true) {
>>  		struct dentry *parent_dentry;
>>
>> +		const struct landlock_rule *rule = find_rule(domain, dir);
>> +
>>  		/* Gets all layers allowing all domain accesses. */
>>  		if (landlock_unmask_layers(
>> -			    find_rule(domain, dir), access_dom, layer_masks
_dom,
>> +			    rule, access_dom, layer_masks_dom,
>>  			    ARRAY_SIZE(*layer_masks_dom), rule_flags)) {
>>  			/*
>>  			 * Before allowing this side of the access request,
 checks that the
>> @@ -1206,6 +1626,10 @@ static bool collect_domain_accesses(
>>  			break;
>>  		}
>>
>> +		if (rule &&
>> +		    rule_blocks_all_layers_no_inherit(domain_layers_mask, r
ule))
>> +			break;
>> +
>>  		/* Stops at the mount point. */
>>  		if (dir == mnt_dir->dentry)
>>  			break;
>> @@ -1232,6 +1656,121 @@ static bool collect_domain_accesses(
>>  	return ret;
>>  }
>>
>> +/**
>> + * collect_topology_sealed_layers - collect layers sealed against topol
ogy changes
>> + * @domain: Ruleset to consult.
>> + * @dentry: Starting dentry for the upward walk.
>> + * @override_layers: Optional out parameter filled with layers that are
>> + *                   present on ancestors but considered overrides (not
>> + *                   sealing the topology for descendants).
>> + *
>> + * Walk upwards from @dentry and return a mask of layers where either t
he
>> + * visited dentry contains a no_inherit rule or ancestors were previous
ly
>> + * marked as having a descendant with no_inherit.  @override_layers, if
 not
>> + * NULL, is filled with layers that would normally be overridden by mor
e
>> + * specific descendant rules.
>> + *
>> + * Returns a layer mask where set bits indicate layers that are "sealed
"
>> + * (topology changes like rename/rmdir are denied) for the subtree root
ed at
>> + * @dentry.
>> + *
>> + * Useful for LANDLOCK_ADD_RULE_NO_INHERIT parent directory enforcement
 to ensure
>> + * that topology changes do not violate the no_inherit constraints.
>> + */
>> +static layer_mask_t
>> +collect_topology_sealed_layers(const struct landlock_ruleset *const dom
ain,
>> +			       struct dentry *dentry,
>> +			       layer_mask_t *const override_layers)
>> +{
>> +	struct dentry *cursor, *parent;
>> +	bool include_descendants = true;
>> +	layer_mask_t sealed_layers = 0;
>> +
>> +	if (override_layers)
>> +		*override_layers = 0;
>> +
>> +	if (!domain || !dentry || d_is_negative(dentry))
>> +		return 0;
>> +
>> +	cursor = dget(dentry);
>> +	while (cursor) {
>> +		const struct landlock_rule *rule;
>> +		u32 layer_index;
>> +
>> +		rule = find_rule(domain, cursor);
>> +		if (rule) {
>> +			for (layer_index = 0; layer_index < rule->num_layer
s;
>> +			     layer_index++) {
>> +				const struct landlock_layer *layer =
>> +					&rule->layers[layer_index];
>> +				const int level = layer->level ? layer->lev
el :
>> +								 layer_inde
x + 1;
> Wouldn't layer->level always be >= 1 here?  Using layer_index doesn't mak
e
> sense since layer_index is just the index that the struct landlock_layer
> happened to be in that rule's array.

Hmm good catch. I was replicating logic from other places while sometimes not realizing 
exactly why the code is doing what it does and didn't make that realization. Seems to work
fine and pass after I fixed it in my working tree so it will be in the next version.

>
>> +				layer_mask_t layer_bit = BIT_ULL(level - 1)
;
>> +
>> +				if (include_descendants &&
>> +				    (layer->flags.no_inherit ||
>> +				     layer->flags.has_no_inherit_descendant
)) {
>> +					sealed_layers |= layer_bit;
>> +				} else if (override_layers) {
>> +					*override_layers |= layer_bit;
>> +				}
>> +			}
>> +		}
>> +
>> +		if (sealed_layers || IS_ROOT(cursor))
>> +			break;
>> +
>> +		parent = dget_parent(cursor);
>> +		dput(cursor);
>> +		if (!parent)
>> +			return sealed_layers;
>> +
>> +		cursor = parent;
>> +		include_descendants = false;
>> +	}
>> +	dput(cursor);
>> +	return sealed_layers;
>> +}
>> +
>> +/**
>> + * deny_no_inherit_topology_change - deny topology changes on sealed la
yers
>> + * @subject: Subject performing the operation (contains the domain).
>> + * @dentry: Dentry that is the target of the topology modification.
>> + *
>> + * Checks whether any domain layers are sealed against topology changes
 at
>> + * @dentry (via collect_topology_sealed_layers).  If so, emit an audit 
record
>> + * and return -EACCES.  Otherwise return 0.
>> + */
>> +static int deny_no_inherit_topology_change(const struct landlock_cred_s
ecurity
>> +					   *subject,
>> +					   struct dentry *dentry)
>> +{
>> +	layer_mask_t sealed_layers;
>> +	layer_mask_t override_layers;
>> +	unsigned long layer_index;
>> +
>> +	if (!subject || !dentry || d_is_negative(dentry))
>> +		return 0;
>> +	sealed_layers = collect_topology_sealed_layers(subject->domain,
>> +						       dentry, &override_la
yers);
>> +	sealed_layers &= ~override_layers;
>> +
>> +	if (!sealed_layers)
>> +		return 0;
>> +
>> +	layer_index = __ffs((unsigned long)sealed_layers);
>> +	landlock_log_denial(subject, &(struct landlock_request) {
>> +		.type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
>> +		.audit = {
>> +			.type = LSM_AUDIT_DATA_DENTRY,
>> +			.u.dentry = dentry,
>> +		},
>> +		.layer_plus_one = layer_index + 1,
>> +	});
>> +
>> +	return -EACCES;
>> +}
>> +
>>  /**
>>   * current_check_refer_path - Check if a rename or link action is allow
ed
>>   *
>> @@ -1316,6 +1855,16 @@ static int current_check_refer_path(struct dentry
 *const old_dentry,
>>  	access_request_parent2 =
>>  		get_mode_access(d_backing_inode(old_dentry)->i_mode);
>>  	if (removable) {
>> +		int err;
>> +
>> +		err = deny_no_inherit_topology_change(subject, old_dentry);
>> +		if (err)
>> +			return err;
>> +		if (exchange) {
>> +			err = deny_no_inherit_topology_change(subject, new_
dentry);
>> +			if (err)
>> +				return err;
>> +		}
>>  		access_request_parent1 |= maybe_remove(old_dentry);
>>  		access_request_parent2 |= maybe_remove(new_dentry);
>>  	}
>> @@ -1707,12 +2256,31 @@ static int hook_path_symlink(const struct path *
const dir,
>>  static int hook_path_unlink(const struct path *const dir,
>>  			    struct dentry *const dentry)
>>  {
>> +	const struct landlock_cred_security *const subject =
>> +		landlock_get_applicable_subject(current_cred(), any_fs, NUL
L);
>> +	int err;
>> +
>> +	if (subject) {
>> +		err = deny_no_inherit_topology_change(subject, dentry);
>> +		if (err)
>> +			return err;
>> +	}
>>  	return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FIL
E);
>>  }
>>
>>  static int hook_path_rmdir(const struct path *const dir,
>>  			   struct dentry *const dentry)
>>  {
>> +	const struct landlock_cred_security *const subject =
>> +		landlock_get_applicable_subject(current_cred(), any_fs, NUL
L);
>> +	int err;
>> +
>> +	if (subject) {
>> +		err = deny_no_inherit_topology_change(subject, dentry);
>> +		if (err)
>> +			return err;
>> +	}
>> +
>>  	return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR
);
>>  }
>>
>> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
>> index 750a444e1983..f7b6a48bbf39 100644
>> --- a/security/landlock/ruleset.c
>> +++ b/security/landlock/ruleset.c
>> @@ -255,8 +255,13 @@ static int insert_rule(struct landlock_ruleset *con
st ruleset,
>>  				return -EINVAL;
>>  			if (WARN_ON_ONCE(this->layers[0].level != 0))
>>  				return -EINVAL;
>> +			/* Merge the flags into the rules */
>>  			this->layers[0].access |= (*layers)[0].access;
>>  			this->layers[0].flags.quiet |= (*layers)[0].flags.q
uiet;
>> +			this->layers[0].flags.no_inherit |=
>> +				(*layers)[0].flags.no_inherit;
>> +			this->layers[0].flags.has_no_inherit_descendant |=
>> +				(*layers)[0].flags.has_no_inherit_descendan
t;
>>  			return 0;
>>  		}
>>
>> @@ -315,7 +320,10 @@ int landlock_insert_rule(struct landlock_ruleset *c
onst ruleset,
>>  		.level = 0,
>>  		.flags = {
>>  			.quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET),
>> -		},
>> +			.no_inherit = !!(flags & LANDLOCK_ADD_RULE_NO_INHER
IT),
>> +			.has_no_inherit_descendant =
>> +				!!(flags & LANDLOCK_ADD_RULE_NO_INHERIT),
>> +		}
>>  	} };
>>
>>  	build_check_layer();
>> @@ -662,9 +670,22 @@ bool landlock_unmask_layers(const struct landlock_r
ule *const rule,
>>  		unsigned long access_bit;
>>  		bool is_empty;
>>
>> -		/* Collect rule flags for each layer. */
>> -		if (rule_flags && layer->flags.quiet)
>> +		/* Skip layers that already have no inherit flags. */
>> +		if (rule_flags &&
>> +		    (rule_flags->no_inherit_masks & layer_bit))
>> +			continue;
>> +
>> +		/* Collect rule flags for each layer.
>> +		 * We block flag inheritance if needed
>> +		 * because of a no_inherit rule.
>> +		 */
>> +		if (rule_flags && layer->flags.quiet &&
>> +		    !(rule_flags->blocked_flag_masks & layer_bit))
> I don't quite understand the purpose of blocked_flag_masks - wouldn't the
> "continue;" above naturally prevent flag inheritance?

Yes, initially I thought I needed to have special handling / a marker
for this. But as you pointed out the continue skips it anyways. I'll
remove that and add a comment saying it skips over the flag inheritance
as well as a side effect.

>
>
>>  			rule_flags->quiet_masks |= layer_bit;
>> +		if (rule_flags && layer->flags.no_inherit)
>> +			rule_flags->no_inherit_masks |= layer_bit;
>> +		if (rule_flags && layer->flags.has_no_inherit_descendant)
>> +			rule_flags->no_inherit_desc_masks |= layer_bit;
>>
>>  		/*
>>  		 * Records in @layer_masks which layer grants access to eac
h requested
>> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
>> index eb60db646422..8b46ab14e995 100644
>> --- a/security/landlock/ruleset.h
>> +++ b/security/landlock/ruleset.h
>> @@ -40,6 +40,21 @@ struct landlock_layer {
>>  		 * down the file hierarchy.
>>  		 */
>>  		bool quiet:1;
>> +		/**
>> +		 * @no_inherit: Prevents this rule from being inherited by
>> +		 * descendant directories in the filesystem layer.  Only us
ed
>> +		 * for filesystem rules.
>> +		 */
>> +		bool no_inherit:1;
>> +		/**
>> +		 * @has_no_inherit_descendant: Marker to indicate that this
 layer
>> +		 * has at least one descendant directory with a rule having
 the
>> +		 * no_inherit flag.  Only used for filesystem rules.
>> +		 * This "flag" is not set by the user, but by Landlock on
>> +		 * parent directories of rules when the child rule has
>> +		 * a rule with the no_inherit flag.
>> +		 */
>> +		bool has_no_inherit_descendant:1;
>>  	} flags;
>>  	/**
>>  	 * @access: Bitfield of allowed actions on the kernel object.  They
 are
>> @@ -49,13 +64,32 @@ struct landlock_layer {
>>  };
>>
>>  /**
>> - * struct collected_rule_flags - Hold accumulated flags for each layer.
>> + * struct collected_rule_flags - Hold accumulated flags and their marke
rs for each layer.
>>   */
>>  struct collected_rule_flags {
>>  	/**
>>  	 * @quiet_masks: Layers for which the quiet flag is effective.
>>  	 */
>>  	layer_mask_t quiet_masks;
>> +	/**
>> +	 * @no_inherit_masks: Layers for which the no_inherit flag is effec
tive.
>> +	 */
>> +	layer_mask_t no_inherit_masks;
>> +	/**
>> +	 * @no_inherit_desc_masks: Layers for which the
>> +	 * has_no_inherit_descendant tag is effective.
>> +	 * This is not a flag itself, but a marker set on ancestors
>> +	 * of rules with the no_inherit flag to deny topology changes
>> +	 * in the direct parent path.
>> +	 */
>> +	layer_mask_t no_inherit_desc_masks;
>> +	/**
>> +	 * @blocked_flag_masks: Layers where flag inheritance must be block
ed
>> +	 * because of a no_inherit rule. This is not a flag itself, but a m
arker
>> +	 * for layers that have their flags blocked due to no_inherit rule
>> +	 * propagation.
>> +	 */
>> +	layer_mask_t blocked_flag_masks;
>>  };
>>
>>  /**

Thank you for the review of the patch. Trimming the code was very
satisfying. I see that the disconnected directory handling was
simplified in linux-next, which is gonna make things easier but also
make rebasing require some more careful attention. I assume your next
patch series for LANDLOCK_ADD_RULE_QUIET is going to be rebased off
those disconnected directory changes as well.

I'll implement these fixes in my working tree and be ready when you drop
your next version to do a rebase on top of your series again. If you
have a base commit SHA you could give me that you are building your next
patch series version on top of already, that would be helpful.

Kind Regards,
Justin Suess

^ permalink raw reply

* [PATCH 3/3] tpm-buf: Implement managed allocations
From: Jarkko Sakkinen @ 2025-12-06 11:31 UTC (permalink / raw)
  To: linux-integrity
  Cc: Jarkko Sakkinen, Stefan Berger, Peter Huewe, Jarkko Sakkinen,
	Jason Gunthorpe, James Bottomley, Mimi Zohar, David Howells,
	Paul Moore, James Morris, Serge E. Hallyn, open list,
	open list:KEYS-TRUSTED, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251206113110.1793407-1-jarkko@kernel.org>

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.

Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@opinsys.com>
Reviewed-by: Stefan Berger <stefanb@linux.ibm.com>
---
 drivers/char/tpm/tpm-buf.c                | 124 +++++----
 drivers/char/tpm/tpm-sysfs.c              |  21 +-
 drivers/char/tpm/tpm.h                    |   1 -
 drivers/char/tpm/tpm1-cmd.c               | 166 +++++------
 drivers/char/tpm/tpm2-cmd.c               | 320 ++++++++++------------
 drivers/char/tpm/tpm2-sessions.c          | 142 +++++-----
 drivers/char/tpm/tpm2-space.c             |  44 ++-
 drivers/char/tpm/tpm_vtpm_proxy.c         |  30 +-
 include/linux/tpm.h                       |  20 +-
 security/keys/trusted-keys/trusted_tpm1.c |  36 +--
 security/keys/trusted-keys/trusted_tpm2.c | 170 ++++++------
 11 files changed, 508 insertions(+), 566 deletions(-)

diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
index 1b9dee0d0681..73be8a87b472 100644
--- a/drivers/char/tpm/tpm-buf.c
+++ b/drivers/char/tpm/tpm-buf.c
@@ -7,82 +7,109 @@
 #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_INVALID;
+		}
+	} 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_INVALID;
+		}
+	}
 }
-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_INVALID)
+		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_INVALID)
+		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,8 +117,11 @@ 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)
 {
+	if (buf->flags & TPM_BUF_INVALID)
+		return 0;
+
 	return buf->length;
 }
 EXPORT_SYMBOL_GPL(tpm_buf_length);
@@ -104,10 +134,12 @@ 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;
+
 	if (buf->flags & TPM_BUF_INVALID)
 		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_INVALID;
 		return;
diff --git a/drivers/char/tpm/tpm-sysfs.c b/drivers/char/tpm/tpm-sysfs.c
index 94231f052ea7..f5dcadb1ab3c 100644
--- a/drivers/char/tpm/tpm-sysfs.c
+++ b/drivers/char/tpm/tpm-sysfs.c
@@ -32,28 +32,29 @@ 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) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!tpm_buf)
+		return -ENOMEM;
+
 	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))
-		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,
-			     "attempting to read the PUBEK"))
-		goto out_buf;
+	if (tpm_transmit_cmd(chip, tpm_buf, READ_PUBEK_RESULT_MIN_BODY_SIZE, "TPM_ReadPubek"))
+		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 +72,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 02c07fef41ba..5395927c62fc 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -32,7 +32,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..11090053ef54 100644
--- a/drivers/char/tpm/tpm1-cmd.c
+++ b/drivers/char/tpm/tpm1-cmd.c
@@ -323,20 +323,14 @@ 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;
-
-	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);
-
-	rc = tpm_transmit_cmd(chip, &buf, 0, "attempting to start the TPM");
-	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_STARTUP);
+	tpm_buf_append_u16(buf, TPM_ST_CLEAR);
+	return tpm_transmit_cmd(chip, buf, 0, "TPM_Startup");
 }
 
 int tpm1_get_timeouts(struct tpm_chip *chip)
@@ -463,50 +457,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);
@@ -531,81 +522,69 @@ int tpm1_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
 {
 	struct tpm1_get_random_out *out;
 	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;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
 
+	tpm_buf_init(buf, TPM_BUFSIZE);
 	do {
-		tpm_buf_append_u32(&buf, num_bytes);
+		tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_RANDOM);
+		tpm_buf_append_u32(buf, num_bytes);
 
-		rc = tpm_transmit_cmd(chip, &buf, sizeof(out->rng_data_len),
-				      "attempting get random");
+		rc = tpm_transmit_cmd(chip, buf, sizeof(out->rng_data_len), "TPM_GetRandom");
 		if (rc) {
 			if (rc > 0)
 				rc = -EIO;
-			goto out;
+			return rc;
 		}
 
-		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) {
-			rc = -EFAULT;
-			goto out;
-		}
+		if (recd > num_bytes)
+			return -EFAULT;
+
+		if (buf->length < TPM_HEADER_SIZE +
+				  sizeof(out->rng_data_len) + recd)
+			return -EFAULT;
 
-		if (tpm_buf_length(&buf) < TPM_HEADER_SIZE +
-					   sizeof(out->rng_data_len) + recd) {
-			rc = -EFAULT;
-			goto out;
-		}
 		memcpy(dest, out->rng_data, recd);
 
 		dest += recd;
 		total += recd;
 		num_bytes -= recd;
-
-		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,
-			      "attempting to read a pcr value");
+	rc = tpm_transmit_cmd(chip, buf, TPM_DIGEST_SIZE, "TPM_PCRRead");
 	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 (buf->length < 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 +598,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;
-
-	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_CONTINUE_SELFTEST);
-	if (rc)
-		return rc;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
 
-	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, "TPM_ContinueSelfTest");
 }
 
 /**
@@ -742,22 +718,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;
 	int rc;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
 
 	/* 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");
 
-	rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
-	if (rc)
-		return rc;
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM_TAG_RQU_COMMAND, TPM_ORD_SAVESTATE);
+
 	/* 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 +750,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 +760,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 82076b6cfcf7..2682f5ec09cb 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -119,12 +119,15 @@ 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) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	if (pcr_idx >= TPM2_PLATFORM_PCR)
 		return -EINVAL;
 
@@ -139,36 +142,31 @@ 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;
+	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, "TPM2_PCR_Read");
 	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 rc;
 
 	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 +182,52 @@ 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) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	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) {
-		if (!disable_pcr_integrity)
-			tpm2_end_auth_session(chip);
-		return rc;
-	}
+	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(&buf, pcr_idx);
-		tpm_buf_append_auth(chip, &buf, NULL, 0);
+		tpm_buf_append_handle(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 (buf->flags & TPM_BUF_INVALID)
+		return -EINVAL;
 
 	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");
-	if (!disable_pcr_integrity)
-		rc = tpm_buf_check_hmac_response(chip, &buf, rc);
+	rc = tpm_transmit_cmd(chip, buf, 0, "TPM2_PCR_Extend");
 
-	tpm_buf_destroy(&buf);
+	if (!disable_pcr_integrity)
+		rc = tpm_buf_check_hmac_response(chip, buf, rc);
 
 	return rc;
 }
@@ -258,7 +252,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;
@@ -270,61 +263,58 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
 	if (!num_bytes || max > TPM_MAX_RNG_DATA)
 		return -EINVAL;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	err = tpm2_start_auth_session(chip);
 	if (err)
 		return err;
 
-	err = tpm_buf_init(&buf, 0, 0);
-	if (err) {
-		tpm2_end_auth_session(chip);
-		return err;
-	}
-
+	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);
-		if (err) {
-			tpm_buf_destroy(&buf);
+		tpm_buf_append_u16(buf, num_bytes);
+
+		err = tpm_buf_fill_hmac_session(chip, buf);
+		if (err)
 			return err;
-		}
 
-		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);
+				       "TPM2_GetRandom");
+		err = tpm_buf_check_hmac_response(chip, buf, err);
 		if (err) {
 			if (err > 0)
 				err = -EIO;
-			goto out;
+			return err;
 		}
 
-		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) {
-			err = -EFAULT;
-			goto out;
+			return -EFAULT;
 		}
 		memcpy(dest_ptr, out->buffer, recd);
 
@@ -333,13 +323,7 @@ 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;
 }
 
 /**
@@ -349,20 +333,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, "TPM2_FlushContext");
 }
 EXPORT_SYMBOL_GPL(tpm2_flush_context);
 
@@ -389,19 +371,20 @@ 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];
+		out = (struct tpm2_get_cap_out *)&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
@@ -413,7 +396,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);
@@ -430,15 +412,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, "TPM2_Shutdown");
 }
 
 /**
@@ -456,20 +437,19 @@ 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;
-
-		tpm_buf_append_u8(&buf, full);
-		rc = tpm_transmit_cmd(chip, &buf, 0,
-				      "attempting the self test");
-		tpm_buf_destroy(&buf);
+	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_SELF_TEST);
+	for (full = 0; full < 2; full++) {
+		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, "TPM2_SelfTest");
 		if (rc == TPM2_RC_TESTING)
 			rc = TPM2_RC_SUCCESS;
 		if (rc == TPM2_RC_INITIALIZE || rc == TPM2_RC_SUCCESS)
@@ -494,23 +474,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);
@@ -550,7 +531,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;
@@ -562,39 +542,37 @@ 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, "TPM2_GetCapability(PCRS)");
 	if (rc)
-		goto out;
+		return rc;
 
-	nr_possible_banks = be32_to_cpup(
-		(__be32 *)&buf.data[TPM_HEADER_SIZE + 5]);
+	nr_possible_banks = be32_to_cpup((__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);
-		rc = -ENOMEM;
-		goto out;
+		return -ENOMEM;
 	}
 
-	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 +
 			offsetof(struct tpm2_pcr_selection, size_of_select);
-		if (pcr_select_offset >= end) {
-			rc = -EFAULT;
-			break;
-		}
+		if (pcr_select_offset >= end)
+			return -EFAULT;
 
 		memcpy(&pcr_selection, marker, sizeof(pcr_selection));
 		hash_alg = be16_to_cpu(pcr_selection.hash_alg);
@@ -606,7 +584,7 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
 
 			rc = tpm2_init_bank_info(chip, nr_alloc_banks);
 			if (rc < 0)
-				break;
+				return rc;
 
 			nr_alloc_banks++;
 		}
@@ -618,21 +596,21 @@ ssize_t tpm2_get_pcr_allocation(struct tpm_chip *chip)
 	}
 
 	chip->nr_allocated_banks = nr_alloc_banks;
-out:
-	tpm_buf_destroy(&buf);
-
-	return rc;
+	return 0;
 }
 
 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) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	rc = tpm2_get_tpm_pt(chip, TPM_PT_TOTAL_COMMANDS, &nr_commands, NULL);
 	if (rc)
 		goto out;
@@ -649,30 +627,24 @@ 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)
-		goto out;
+	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);
 
-	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])) {
+	if (nr_commands != 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;
@@ -684,8 +656,6 @@ int tpm2_get_cc_attrs_tbl(struct tpm_chip *chip)
 		}
 	}
 
-	tpm_buf_destroy(&buf);
-
 out:
 	if (rc > 0)
 		rc = -ENODEV;
@@ -706,20 +676,14 @@ EXPORT_SYMBOL_GPL(tpm2_get_cc_attrs_tbl);
 
 static int tpm2_startup(struct tpm_chip *chip)
 {
-	struct tpm_buf buf;
-	int rc;
-
-	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);
-
-	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, TPM2_ST_NO_SESSIONS, TPM2_CC_STARTUP);
+	tpm_buf_append_u16(buf, TPM2_SU_CLEAR);
+	return tpm_transmit_cmd(chip, buf, 0, "TPM2_Startup");
 }
 
 /**
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
index 92d116d73c6f..79f27a46bd7f 100644
--- a/drivers/char/tpm/tpm2-sessions.c
+++ b/drivers/char/tpm/tpm2-sessions.c
@@ -168,7 +168,6 @@ 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;
 	int rc, name_size_alg;
-	struct tpm_buf buf;
 
 	if (mso != TPM2_MSO_PERSISTENT && mso != TPM2_MSO_VOLATILE &&
 	    mso != TPM2_MSO_NVRAM) {
@@ -176,47 +175,41 @@ 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;
+	struct tpm_buf *buf __free(kfree) = 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]);
+	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);
+	memcpy(name, &buf->data[offset], rc);
 	return name_size_alg;
 }
 #endif /* CONFIG_TCG_TPM2_HMAC */
@@ -982,7 +975,6 @@ static int tpm2_load_null(struct tpm_chip *chip, u32 *null_key)
 int tpm2_start_auth_session(struct tpm_chip *chip)
 {
 	struct tpm2_auth *auth;
-	struct tpm_buf buf;
 	u32 null_key;
 	int rc;
 
@@ -991,6 +983,10 @@ int tpm2_start_auth_session(struct tpm_chip *chip)
 		return 0;
 	}
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	auth = kzalloc(sizeof(*auth), GFP_KERNEL);
 	if (!auth)
 		return -ENOMEM;
@@ -1001,41 +997,37 @@ 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)
-		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, "TPM2_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;
@@ -1257,18 +1249,18 @@ static int tpm2_create_primary(struct tpm_chip *chip, u32 hierarchy,
 			       u32 *handle, u8 *name)
 {
 	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;
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
 
-	rc = tpm_buf_init_sized(&template);
-	if (rc) {
-		tpm_buf_destroy(&buf);
-		return rc;
-	}
+	struct tpm_buf *template __free(kfree) = 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
@@ -1280,75 +1272,71 @@ 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,
-			      "attempting to create NULL primary");
+	rc = tpm_transmit_cmd(chip, buf, 0, "TPM2_CreatePrimary");
 
 	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 0818bb517805..682dfc93845d 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 1e0585e45371..42e2a091f43d 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -25,7 +25,8 @@
 #include <crypto/hash_info.h>
 #include <crypto/aes.h>
 
-#define TPM_DIGEST_SIZE 20	/* Max TPM v1.2 PCR size */
+#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
@@ -376,13 +377,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;
 	u8 handles;
+	u16 length;
+	u16 capacity;
+	u8 data[];
 };
 
 enum tpm2_object_attributes {
@@ -413,12 +416,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 636acb66a4f6..759c1ecb0435 100644
--- a/security/keys/trusted-keys/trusted_tpm1.c
+++ b/security/keys/trusted-keys/trusted_tpm1.c
@@ -310,9 +310,10 @@ 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(void *cmd, size_t cmd_len)
 {
-	struct tpm_buf buf;
+	u8 buf_data[512];
+	struct tpm_buf *buf = (struct tpm_buf *)buf_data;
 	int rc;
 
 	if (!chip)
@@ -322,11 +323,10 @@ static int trusted_tpm_send(unsigned char *cmd, size_t buflen)
 	if (rc)
 		return rc;
 
-	buf.flags = 0;
-	buf.length = buflen;
-	buf.data = cmd;
+	tpm_buf_init(buf, sizeof(buf_data));
+	tpm_buf_append(buf, cmd, cmd_len);
 	dump_tpm_buf(cmd);
-	rc = tpm_transmit_cmd(chip, &buf, 4, "sending data");
+	rc = tpm_transmit_cmd(chip, buf, 4, "sending data");
 	dump_tpm_buf(cmd);
 
 	if (rc > 0)
@@ -624,23 +624,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;
 }
 
@@ -650,14 +650,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);
@@ -665,7 +666,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 fdba641fefdb..fb76c4ea496f 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -234,7 +234,6 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 		      struct trusted_key_options *options)
 {
 	off_t offset = TPM_HEADER_SIZE;
-	struct tpm_buf buf, sized;
 	int blob_len = 0;
 	int hash;
 	u32 flags;
@@ -255,97 +254,99 @@ 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) {
+	struct tpm_buf *buf __free(kfree) = 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);
-		tpm2_end_auth_session(chip);
-		goto out_put;
-	}
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE);
 
-	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+	rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL);
 	if (rc)
-		goto out;
+		goto out_put;
 
-	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);
 
+	struct tpm_buf *sized __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!sized) {
+		rc = -ENOMEM;
+		tpm2_end_auth_session(chip);
+		goto out_put;
+	}
+
 	/* sensitive */
-	tpm_buf_append_u16(&sized, options->blobauth_len);
+	tpm_buf_init_sized(sized, TPM_BUFSIZE);
+	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_init_sized(sized, TPM_BUFSIZE);
+	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_INVALID) {
+	if (buf->flags & TPM_BUF_INVALID) {
 		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_INVALID) {
+	blob_len = tpm_buf_read_u32(buf, &offset);
+	if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_INVALID) {
 		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 +374,6 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
 			 u32 *blob_handle)
 {
 	u8 *blob_ref __free(kfree) = NULL;
-	struct tpm_buf buf;
 	unsigned int private_len;
 	unsigned int public_len;
 	unsigned int blob_len;
@@ -427,39 +427,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) {
+	struct tpm_buf *buf __free(kfree) = 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_INVALID) {
-		rc = -E2BIG;
+	if (buf->flags & TPM_BUF_INVALID) {
 		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,28 +481,28 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
 			   u32 blob_handle)
 {
 	struct tpm_header *head;
-	struct tpm_buf buf;
 	u16 data_len;
 	int offset;
 	u8 *data;
 	int rc;
 
+	struct tpm_buf *buf __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
 	rc = tpm2_start_auth_session(chip);
 	if (rc)
 		return rc;
 
-	rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
-	if (rc) {
-		tpm2_end_auth_session(chip);
-		return rc;
-	}
+	tpm_buf_init(buf, TPM_BUFSIZE);
+	tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
 
-	rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+	rc = tpm_buf_append_name(chip, buf, options->keyhandle, 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 +517,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 +563,6 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
 		}
 	}
 
-out:
-	tpm_buf_destroy(&buf);
 	return tpm_ret_to_err(rc);
 }
 
-- 
2.39.5


^ permalink raw reply related

* [PATCH 2/3] tpm-buf: Remove chip parameter from tpm_buf_append_handle
From: Jarkko Sakkinen @ 2025-12-06 11:31 UTC (permalink / raw)
  To: linux-integrity
  Cc: Jarkko Sakkinen, Jonathan McDowell, Peter Huewe, Jarkko Sakkinen,
	Jason Gunthorpe, James Bottomley, Mimi Zohar, David Howells,
	Paul Moore, James Morris, Serge E. Hallyn, open list,
	open list:KEYS-TRUSTED, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251206113110.1793407-1-jarkko@kernel.org>

From: Jarkko Sakkinen <jarkko.sakkinen@opinsys.com>

Remove @chip from tpm_buf_append_handle() in order to decouple it from
driver. This allows tpm-buf.c to be compiled as part of early boot code,
thus providing a mechanism to build and parse TPM data.

E.g., Trenchboot can use this to manage TPM2_PcrExtend protocol data as
part of its early boot blob simply by compiling and linking the object
file.

Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@opinsys.com>
Reviewed-by: Jonathan McDowell <noodles@meta.com>
---
 drivers/char/tpm/tpm-buf.c       | 5 ++---
 drivers/char/tpm/tpm2-cmd.c      | 2 +-
 drivers/char/tpm/tpm2-sessions.c | 2 +-
 include/linux/tpm.h              | 2 +-
 4 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
index 69ee77400539..1b9dee0d0681 100644
--- a/drivers/char/tpm/tpm-buf.c
+++ b/drivers/char/tpm/tpm-buf.c
@@ -147,20 +147,19 @@ EXPORT_SYMBOL_GPL(tpm_buf_append_u32);
 
 /**
  * tpm_buf_append_handle() - Add a handle
- * @chip:	&tpm_chip instance
  * @buf:	&tpm_buf instance
  * @handle:	a TPM object handle
  *
  * Add a handle to the buffer, and increase the count tracking the number of
  * handles in the command buffer. Works only for command buffers.
  */
-void tpm_buf_append_handle(struct tpm_chip *chip, struct tpm_buf *buf, u32 handle)
+void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle)
 {
 	if (buf->flags & TPM_BUF_INVALID)
 		return;
 
 	if (buf->flags & TPM_BUF_TPM2B) {
-		dev_err(&chip->dev, "Invalid buffer type (TPM2B)\n");
+		WARN(1, "tpm-buf: invalid type: TPM2B\n");
 		buf->flags |= TPM_BUF_INVALID;
 		return;
 	}
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index 3a77be7ebf4a..82076b6cfcf7 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -209,7 +209,7 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
 		}
 		tpm_buf_append_hmac_session(chip, &buf, 0, NULL, 0);
 	} else {
-		tpm_buf_append_handle(chip, &buf, pcr_idx);
+		tpm_buf_append_handle(&buf, pcr_idx);
 		tpm_buf_append_auth(chip, &buf, NULL, 0);
 	}
 
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
index 4149379665c4..92d116d73c6f 100644
--- a/drivers/char/tpm/tpm2-sessions.c
+++ b/drivers/char/tpm/tpm2-sessions.c
@@ -257,7 +257,7 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
 #endif
 
 	if (!tpm2_chip_auth(chip)) {
-		tpm_buf_append_handle(chip, buf, handle);
+		tpm_buf_append_handle(buf, handle);
 		return 0;
 	}
 
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index aed34f119e8c..1e0585e45371 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -426,7 +426,7 @@ void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value);
 u8 tpm_buf_read_u8(struct tpm_buf *buf, off_t *offset);
 u16 tpm_buf_read_u16(struct tpm_buf *buf, off_t *offset);
 u32 tpm_buf_read_u32(struct tpm_buf *buf, off_t *offset);
-void tpm_buf_append_handle(struct tpm_chip *chip, struct tpm_buf *buf, u32 handle);
+void tpm_buf_append_handle(struct tpm_buf *buf, u32 handle);
 
 /*
  * Check if TPM device is in the firmware upgrade mode.
-- 
2.39.5


^ permalink raw reply related

* [PATCH 1/3] tpm-buf: Merge TPM_BUF_BOUNDARY_ERROR and TPM_BUF_OVERFLOW
From: Jarkko Sakkinen @ 2025-12-06 11:31 UTC (permalink / raw)
  To: linux-integrity
  Cc: Jarkko Sakkinen, Jonathan McDowell, Peter Huewe, Jarkko Sakkinen,
	Jason Gunthorpe, James Bottomley, Mimi Zohar, David Howells,
	Paul Moore, James Morris, Serge E. Hallyn, open list,
	open list:KEYS-TRUSTED, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251206113110.1793407-1-jarkko@kernel.org>

From: Jarkko Sakkinen <jarkko.sakkinen@opinsys.com>

Merge TPM_BUF_BOUNDARY_ERROR and TPM_BUF_OVERFLOW into TPM_BUF_INVALID,
given that they are identical. The only difference are the log messages.

In addition, add a missing TPM_BUF_INVALID check to tpm_buf_append_handle()
following the pattern from other functions in tpm-buf.c.

Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@opinsys.com>
Reviewed-by: Jonathan McDowell <noodles@meta.com>
---
 drivers/char/tpm/tpm-buf.c                | 14 ++++++++------
 include/linux/tpm.h                       |  8 +++-----
 security/keys/trusted-keys/trusted_tpm2.c |  6 +++---
 3 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
index dc882fc9fa9e..69ee77400539 100644
--- a/drivers/char/tpm/tpm-buf.c
+++ b/drivers/char/tpm/tpm-buf.c
@@ -104,13 +104,12 @@ EXPORT_SYMBOL_GPL(tpm_buf_length);
  */
 void tpm_buf_append(struct tpm_buf *buf, const u8 *new_data, u16 new_length)
 {
-	/* Return silently if overflow has already happened. */
-	if (buf->flags & TPM_BUF_OVERFLOW)
+	if (buf->flags & TPM_BUF_INVALID)
 		return;
 
 	if ((buf->length + new_length) > PAGE_SIZE) {
 		WARN(1, "tpm_buf: write overflow\n");
-		buf->flags |= TPM_BUF_OVERFLOW;
+		buf->flags |= TPM_BUF_INVALID;
 		return;
 	}
 
@@ -157,8 +156,12 @@ EXPORT_SYMBOL_GPL(tpm_buf_append_u32);
  */
 void tpm_buf_append_handle(struct tpm_chip *chip, struct tpm_buf *buf, u32 handle)
 {
+	if (buf->flags & TPM_BUF_INVALID)
+		return;
+
 	if (buf->flags & TPM_BUF_TPM2B) {
 		dev_err(&chip->dev, "Invalid buffer type (TPM2B)\n");
+		buf->flags |= TPM_BUF_INVALID;
 		return;
 	}
 
@@ -177,14 +180,13 @@ static void tpm_buf_read(struct tpm_buf *buf, off_t *offset, size_t count, void
 {
 	off_t next_offset;
 
-	/* Return silently if overflow has already happened. */
-	if (buf->flags & TPM_BUF_BOUNDARY_ERROR)
+	if (buf->flags & TPM_BUF_INVALID)
 		return;
 
 	next_offset = *offset + count;
 	if (next_offset > buf->length) {
 		WARN(1, "tpm_buf: read out of boundary\n");
-		buf->flags |= TPM_BUF_BOUNDARY_ERROR;
+		buf->flags |= TPM_BUF_INVALID;
 		return;
 	}
 
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 202da079d500..aed34f119e8c 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -369,12 +369,10 @@ struct tpm_header {
 } __packed;
 
 enum tpm_buf_flags {
-	/* the capacity exceeded: */
-	TPM_BUF_OVERFLOW	= BIT(0),
 	/* TPM2B format: */
-	TPM_BUF_TPM2B		= BIT(1),
-	/* read out of boundary: */
-	TPM_BUF_BOUNDARY_ERROR	= BIT(2),
+	TPM_BUF_TPM2B		= BIT(0),
+	/* The buffer is in invalid and unusable state: */
+	TPM_BUF_INVALID		= BIT(1),
 };
 
 /*
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index a7ea4a1c3bed..fdba641fefdb 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -313,7 +313,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 	/* creation PCR */
 	tpm_buf_append_u32(&buf, 0);
 
-	if (buf.flags & TPM_BUF_OVERFLOW) {
+	if (buf.flags & TPM_BUF_INVALID) {
 		rc = -E2BIG;
 		tpm2_end_auth_session(chip);
 		goto out;
@@ -329,7 +329,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
 		goto out;
 
 	blob_len = tpm_buf_read_u32(&buf, &offset);
-	if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_BOUNDARY_ERROR) {
+	if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_INVALID) {
 		rc = -E2BIG;
 		goto out;
 	}
@@ -442,7 +442,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
 
 	tpm_buf_append(&buf, blob, blob_len);
 
-	if (buf.flags & TPM_BUF_OVERFLOW) {
+	if (buf.flags & TPM_BUF_INVALID) {
 		rc = -E2BIG;
 		tpm2_end_auth_session(chip);
 		goto out;
-- 
2.39.5


^ permalink raw reply related

* [PATCH 0/3] Implement managed allocations
From: Jarkko Sakkinen @ 2025-12-06 11:31 UTC (permalink / raw)
  To: linux-integrity
  Cc: Jarkko Sakkinen, James Bottomley, Mimi Zohar, David Howells,
	Paul Moore, James Morris, Serge E. Hallyn, open list:KEYS-TRUSTED,
	open list:SECURITY SUBSYSTEM, open list

Jarkko Sakkinen (3):
  tpm-buf: Merge TPM_BUF_BOUNDARY_ERROR and TPM_BUF_OVERFLOW
  tpm-buf: Remove chip parameter from tpm_buf_append_handle
  tpm-buf: Implement managed allocations

 drivers/char/tpm/tpm-buf.c                | 143 ++++++----
 drivers/char/tpm/tpm-sysfs.c              |  21 +-
 drivers/char/tpm/tpm.h                    |   1 -
 drivers/char/tpm/tpm1-cmd.c               | 166 +++++------
 drivers/char/tpm/tpm2-cmd.c               | 320 ++++++++++------------
 drivers/char/tpm/tpm2-sessions.c          | 144 +++++-----
 drivers/char/tpm/tpm2-space.c             |  44 ++-
 drivers/char/tpm/tpm_vtpm_proxy.c         |  30 +-
 include/linux/tpm.h                       |  30 +-
 security/keys/trusted-keys/trusted_tpm1.c |  36 +--
 security/keys/trusted-keys/trusted_tpm2.c | 170 ++++++------
 11 files changed, 523 insertions(+), 582 deletions(-)

-- 
2.39.5


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox