* [RFC 02/11] oid_registry: allow arbitrary size OIDs
From: Blaise Boscaccy @ 2025-12-11 2:11 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
The current OID registry parser uses 64 bit arithmetic which limits us
to supporting 64 bit or smaller OIDs. This isn't usually a problem
except that it prevents us from representing the 2.25. prefix OIDs
which are the OID representation of UUIDs and have a 128 bit number
following the prefix. Rather than import not often used perl
arithmetic modules, replace the current perl 64 bit arithmetic with a
callout to bc, which is arbitrary precision, for decimal to base 2
conversion, then do pure string operations on the base 2 number.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
lib/build_OID_registry | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/lib/build_OID_registry b/lib/build_OID_registry
index 8267e8d71338b..30493ac190c0c 100755
--- a/lib/build_OID_registry
+++ b/lib/build_OID_registry
@@ -60,10 +60,12 @@ for (my $i = 0; $i <= $#names; $i++) {
# Determine the encoded length of this OID
my $size = $#components;
for (my $loop = 2; $loop <= $#components; $loop++) {
- my $c = $components[$loop];
+ $ENV{'BC_LINE_LENGTH'} = "0";
+ my $c = `echo "ibase=10; obase=2; $components[$loop]" | bc`;
+ chomp($c);
# We will base128 encode the number
- my $tmp = ($c == 0) ? 0 : int(log($c)/log(2));
+ my $tmp = length($c) - 1;
$tmp = int($tmp / 7);
$size += $tmp;
}
@@ -100,16 +102,24 @@ for (my $i = 0; $i <= $#names; $i++) {
push @octets, $components[0] * 40 + $components[1];
for (my $loop = 2; $loop <= $#components; $loop++) {
- my $c = $components[$loop];
+ # get the base 2 representation of the component
+ $ENV{'BC_LINE_LENGTH'} = "0";
+ my $c = `echo "ibase=10; obase=2; $components[$loop]" | bc`;
+ chomp($c);
- # Base128 encode the number
- my $tmp = ($c == 0) ? 0 : int(log($c)/log(2));
+ my $tmp = length($c) - 1;
$tmp = int($tmp / 7);
- for (; $tmp > 0; $tmp--) {
- push @octets, (($c >> $tmp * 7) & 0x7f) | 0x80;
+ # zero pad upto length multiple of 7
+ $c = substr("0000000", 0, ($tmp + 1) * 7 - length($c)).$c;
+
+ # Base128 encode the number
+ for (my $j = 0; $j < $tmp; $j++) {
+ my $b = oct("0b".substr($c, $j * 7, 7));
+
+ push @octets, $b | 0x80;
}
- push @octets, $c & 0x7f;
+ push @octets, oct("0b".substr($c, $tmp * 7, 7));
}
push @encoded_oids, \@octets;
--
2.52.0
^ permalink raw reply related
* [RFC 01/11] lsm: framework for BPF integrity verification
From: Blaise Boscaccy @ 2025-12-11 2:11 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
From: Paul Moore <paul@paul-moore.com>
Add a new LSM hook and two new LSM hook callbacks to support LSMs that
perform integrity verification, e.g. digital signature verification,
of BPF programs.
While the BPF subsystem does implement a signature verification scheme,
it does not satisfy a number of existing requirements, adding support
for BPF program integrity verification to the LSM framework allows
administrators to select additional integrity verification mechanisms
to meet these needs while also providing a mechanism for future
expansion. Additional on why this is necessary can be found at the
lore archive link below:
https://lore.kernel.org/linux-security-module/CAHC9VhTQ_DR=ANzoDBjcCtrimV7XcCZVUsANPt=TjcvM4d-vjg@mail.gmail.com/
The LSM-based BPF integrity verification mechanism works within the
existing security_bpf_prog_load() hook called by the BPF subsystem.
It adds an additional dedicated integrity callback and a new LSM
hook/callback to be called from within LSMs implementing integrity
verification.
The first new callback, bpf_prog_load_integrity(), located within the
security_bpf_prog_load() hook, is necessary to ensure that the integrity
verification callbacks are executed before any of the existing LSMs
are executed via the bpf_prog_load() callback. Reusing the existing
bpf_prog_load() callback for integrity verification could result in LSMs
not having access to the integrity verification results when asked to
authorize the BPF program load in the bpf_prog_load() callback.
The new LSM hook, security_bpf_prog_load_post_integrity(), is intended
to be called from within LSMs performing BPF program integrity
verification. It is used to report the verdict of the integrity
verification to other LSMs enforcing access control policy on BPF
program loads. LSMs enforcing such access controls should register a
bpf_prog_load_post_integrity() callback to receive integrity verdicts.
More information on these new callbacks and hook can be found in the
code comments in this patch.
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
include/linux/lsm_hook_defs.h | 5 +++
include/linux/security.h | 25 ++++++++++++
security/security.c | 75 +++++++++++++++++++++++++++++++++--
3 files changed, 102 insertions(+), 3 deletions(-)
diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
index 8c42b4bde09c0..4971d3c36d5b4 100644
--- a/include/linux/lsm_hook_defs.h
+++ b/include/linux/lsm_hook_defs.h
@@ -434,6 +434,11 @@ LSM_HOOK(int, 0, bpf_prog, struct bpf_prog *prog)
LSM_HOOK(int, 0, bpf_map_create, struct bpf_map *map, union bpf_attr *attr,
struct bpf_token *token, bool kernel)
LSM_HOOK(void, LSM_RET_VOID, bpf_map_free, struct bpf_map *map)
+LSM_HOOK(int, 0, bpf_prog_load_post_integrity, struct bpf_prog *prog,
+ union bpf_attr *attr, struct bpf_token *token, bool kernel,
+ const struct lsm_id *lsmid, enum lsm_integrity_verdict verdict)
+LSM_HOOK(int, 0, bpf_prog_load_integrity, struct bpf_prog *prog,
+ union bpf_attr *attr, struct bpf_token *token, bool kernel)
LSM_HOOK(int, 0, bpf_prog_load, struct bpf_prog *prog, union bpf_attr *attr,
struct bpf_token *token, bool kernel)
LSM_HOOK(void, LSM_RET_VOID, bpf_prog_free, struct bpf_prog *prog)
diff --git a/include/linux/security.h b/include/linux/security.h
index 92ac3f27b9733..86bb4337d6e81 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -67,6 +67,7 @@ enum fs_value_type;
struct watch;
struct watch_notification;
struct lsm_ctx;
+struct lsm_id;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@@ -99,6 +100,14 @@ enum lsm_integrity_type {
LSM_INT_FSVERITY_BUILTINSIG_VALID,
};
+enum lsm_integrity_verdict {
+ LSM_INT_VERDICT_NONE = 0,
+ LSM_INT_VERDICT_OK,
+ LSM_INT_VERDICT_UNSIGNED,
+ LSM_INT_VERDICT_PARTIALSIG,
+ LSM_INT_VERDICT_BADSIG,
+};
+
/*
* These are reasons that can be passed to the security_locked_down()
* LSM hook. Lockdown reasons that protect kernel integrity (ie, the
@@ -2272,6 +2281,12 @@ extern int security_bpf_prog(struct bpf_prog *prog);
extern int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
struct bpf_token *token, bool kernel);
extern void security_bpf_map_free(struct bpf_map *map);
+extern int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict);
extern int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
struct bpf_token *token, bool kernel);
extern void security_bpf_prog_free(struct bpf_prog *prog);
@@ -2306,6 +2321,16 @@ static inline int security_bpf_map_create(struct bpf_map *map, union bpf_attr *a
static inline void security_bpf_map_free(struct bpf_map *map)
{ }
+static inline int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict)
+{
+ return 0;
+}
+
static inline int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
struct bpf_token *token, bool kernel)
{
diff --git a/security/security.c b/security/security.c
index 4d3c03a4524c5..e3f4ad45d24f0 100644
--- a/security/security.c
+++ b/security/security.c
@@ -5779,6 +5779,50 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
return rc;
}
+/**
+ * security_bpf_prog_load_post_integrity() - Check if the BPF prog is allowed
+ * @prog: BPF program object
+ * @attr: BPF syscall attributes used to create BPF program
+ * @token: BPF token used to grant user access to BPF subsystem
+ * @kernel: whether or not call originated from kernel
+ * @lsmid: LSM ID of the LSM providing @verdict
+ * @verdict: result of the integrity verification
+ *
+ * See the comment block for the security_bpf_prog_load() LSM hook.
+ *
+ * This LSM hook is intended to be called from within the
+ * bpf_prog_load_integrity() callback that is part of the
+ * security_bpf_prog_load() hook; kernel subsystems outside the scope of the
+ * LSM framework should not call this hook directly.
+ *
+ * If the LSM calling into this hook receives a non-zero error code, it should
+ * return the same error code back to its caller. If this hook returns a zero,
+ * it does not necessarily mean that all of the enabled LSMs have authorized
+ * the BPF program load, as there may be other LSMs implementing BPF integrity
+ * checks which have yet to execute. However, if a zero is returned, the LSM
+ * calling into this hook should continue and return zero back to its caller.
+ *
+ * LSMs which implement the bpf_prog_load_post_integrity() callback and
+ * determine that a particular BPF program load is not authorized may choose to
+ * either return an error code for immediate rejection, or store their decision
+ * in their own LSM state attached to @prog, later returning an error code in
+ * the bpf_prog_load() callback. An immediate error code return is in keeping
+ * with the "fail fast" practice, but waiting until the bpf_prog_load()
+ * callback allows the LSM to consider multiple different integrity verdicts.
+ *
+ * Return: Returns 0 on success, error on failure.
+ */
+int security_bpf_prog_load_post_integrity(struct bpf_prog *prog,
+ union bpf_attr *attr,
+ struct bpf_token *token,
+ bool kernel,
+ const struct lsm_id *lsmid,
+ enum lsm_integrity_verdict verdict)
+{
+ return call_int_hook(bpf_prog_load_post_integrity, prog, attr, token,
+ kernel, lsmid, verdict);
+}
+
/**
* security_bpf_prog_load() - Check if loading of BPF program is allowed
* @prog: BPF program object
@@ -5787,8 +5831,24 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
* @kernel: whether or not call originated from kernel
*
* Perform an access control check when the kernel loads a BPF program and
- * allocates associated BPF program object. This hook is also responsible for
- * allocating any required LSM state for the BPF program.
+ * allocates the associated BPF program object. This hook is also responsible
+ * for allocating any required LSM state for the BPF program.
+ *
+ * This hook calls two LSM callbacks: bpf_prog_load_integrity() and
+ * bpf_prog_load(). The bpf_prog_load_integrity() callback is for those LSMs
+ * that wish to implement integrity verifications of BPF programs, e.g.
+ * signature verification, while the bpf_prog_load() callback is for general
+ * authorization of the BPF program load. Performing both verification and
+ * authorization in a single callback, with arbitrary LSM ordering, would be
+ * a challenge.
+ *
+ * LSMs which implement the bpf_prog_load_integrity() callback should call into
+ * the security_bpf_prog_load_post_integrity() hook with their integrity
+ * verdict. LSMs which implement BPF program integrity policy can register a
+ * callback for the security_bpf_prog_load_post_integrity() hook and
+ * either update their own internal state based on the verdict, or immediately
+ * reject the BPF program load with an error code. See the comment block for
+ * security_bpf_prog_load_post_integrity() for more information.
*
* Return: Returns 0 on success, error on failure.
*/
@@ -5801,9 +5861,18 @@ int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
if (unlikely(rc))
return rc;
+ rc = call_int_hook(bpf_prog_load_integrity, prog, attr, token, kernel);
+ if (unlikely(rc))
+ goto err;
+
rc = call_int_hook(bpf_prog_load, prog, attr, token, kernel);
if (unlikely(rc))
- security_bpf_prog_free(prog);
+ goto err;
+
+ return rc;
+
+err:
+ security_bpf_prog_free(prog);
return rc;
}
--
2.52.0
^ permalink raw reply related
* [RFC 00/11] Reintroduce Hornet LSM
From: Blaise Boscaccy @ 2025-12-11 2:11 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
This patch series introduces the next iteration of the Hornet LSM.
Hornet’s goal is to provide a secure and extensible in-kernel
signature verification mechanism for eBPF programs. The purpose of
this RFC is to gather feedback on the LSM design and the newly added
downstream LSM hooks, as well as gauge community sentiment. The
userspace tooling still needs some refinement. The currently accepted
loader-plus-map signature verification scheme, mandated by Alexei and
KP, is simple to implement and generally acceptable if users and
administrators are satisfied with it. However, verifying both the
loader and the maps offers additional benefits beyond verifying the
loader alone:
1. Security and Audit Integrity
A key advantage is that the LSM hook for authorizing BPF program loads
can operate after signature verification. This ensures:
* Access control decisions are based on verified signature status.
* Accurate system state measurement and logging.
* Log entries claiming a verified signature are truthful, avoiding
misleading records where only the loader was verified while the actual
BPF program verification occurs later without logging.
2. TOCTOU Attack Prevention
The current map hash implementation may be vulnerable to a TOCTOU
attack because it allows unfrozen maps to cache a previously
calculated hash. The accepted “trusted loader” scheme cannot detect
this and may permit loading altered maps.
This approach addresses concerns from users who require strict audit
trails and verification guarantees, especially in security-sensitive
environments. Map hashes for extended verification are passed via the
existing PKCS#7 UAPI and verified by the crypto subsystem. Hornet then
calculates the program’s verification state (full, partial, bad, etc.)
and invokes a new downstream LSM hook to delegate policy decisions.
Blaise Boscaccy (4):
security: Hornet LSM
hornet: Introduce gen_sig
hornet: Add a light skeleton data extractor scripts
selftests/hornet: Add a selftest for the Hornet LSM
James Bottomley (6):
oid_registry: allow arbitrary size OIDs
certs: break out pkcs7 check into its own function
crypto: pkcs7: add flag for validated trust on a signed info block
crypto: pkcs7: allow pkcs7_digest() to be called from pkcs7_trust
crypto: pkcs7: add ability to extract signed attributes by OID
crypto: pkcs7: add tests for pkcs7_get_authattr
Paul Moore (1):
lsm: framework for BPF integrity verification
Documentation/admin-guide/LSM/Hornet.rst | 38 ++
Documentation/admin-guide/LSM/index.rst | 1 +
MAINTAINERS | 9 +
certs/system_keyring.c | 76 ++--
crypto/asymmetric_keys/Makefile | 4 +-
crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 +
crypto/asymmetric_keys/pkcs7_key_type.c | 42 +-
crypto/asymmetric_keys/pkcs7_parser.c | 87 ++++
crypto/asymmetric_keys/pkcs7_parser.h | 4 +
crypto/asymmetric_keys/pkcs7_trust.c | 9 +
crypto/asymmetric_keys/pkcs7_verify.c | 13 +-
include/crypto/pkcs7.h | 4 +
include/linux/lsm_hook_defs.h | 5 +
include/linux/oid_registry.h | 3 +
include/linux/security.h | 25 ++
include/linux/verification.h | 2 +
include/uapi/linux/lsm.h | 1 +
lib/build_OID_registry | 26 +-
scripts/Makefile | 1 +
scripts/hornet/Makefile | 5 +
scripts/hornet/extract-insn.sh | 27 ++
scripts/hornet/extract-map.sh | 27 ++
scripts/hornet/extract-skel.sh | 27 ++
scripts/hornet/gen_sig.c | 392 +++++++++++++++++++
scripts/hornet/write-sig.sh | 27 ++
security/Kconfig | 3 +-
security/Makefile | 1 +
security/hornet/Kconfig | 11 +
security/hornet/Makefile | 7 +
security/hornet/hornet.asn1 | 13 +
security/hornet/hornet_lsm.c | 201 ++++++++++
security/security.c | 75 +++-
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/hornet/Makefile | 63 +++
tools/testing/selftests/hornet/loader.c | 21 +
tools/testing/selftests/hornet/trivial.bpf.c | 33 ++
36 files changed, 1253 insertions(+), 49 deletions(-)
create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
create mode 100644 scripts/hornet/Makefile
create mode 100755 scripts/hornet/extract-insn.sh
create mode 100755 scripts/hornet/extract-map.sh
create mode 100755 scripts/hornet/extract-skel.sh
create mode 100644 scripts/hornet/gen_sig.c
create mode 100755 scripts/hornet/write-sig.sh
create mode 100644 security/hornet/Kconfig
create mode 100644 security/hornet/Makefile
create mode 100644 security/hornet/hornet.asn1
create mode 100644 security/hornet/hornet_lsm.c
create mode 100644 tools/testing/selftests/hornet/Makefile
create mode 100644 tools/testing/selftests/hornet/loader.c
create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c
--
2.52.0
^ permalink raw reply
* Re: [RFC][PATCH] ima: Add support for staging measurements for deletion
From: steven chen @ 2025-12-11 0:03 UTC (permalink / raw)
To: Roberto Sassu, corbet, zohar, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, nramas, Roberto Sassu, steven chen
In-Reply-To: <20251209101725.3680225-1-roberto.sassu@huaweicloud.com>
On 12/9/2025 2:17 AM, Roberto Sassu wrote:
> From: Roberto Sassu <roberto.sassu@huawei.com>
>
> Introduce the ability of staging the entire of the IMA measurement list, or
> a portion, for deletion. Staging means moving the current content of the
> measurement list to a separate location, and allowing users to read and
> delete it. This causes the measurement list to be atomically truncated
> before new measurements can be added. Staging can be done only once at a
> time.
>
> User space is responsible to concatenate the staged IMA measurements list
> portions following the temporal order in which the operations were done,
> together with the current measurement list. Then, it can send the collected
> data to the remote verifiers.
>
> The benefit of this solution is the ability to free precious kernel memory,
> in exchange of delegating user space to reconstruct the full measurement
> list from the chunks. No trust needs to be given to user space, since the
> integrity of the measurement list is protected by the TPM.
>
> By default, staging the measurements list for deletion does not alter the
> hash table. When staging is done, IMA is still able to detect collisions on
> the staged and later deleted measurement entries, by keeping the entry
> digests (only template data are freed).
>
> However, since during the measurements list serialization only the SHA1
> digest is passed, and since there are no template data to recalculate the
> other digests from, the hash table is currently not populated with digests
> from staged/deleted entries after kexec().
>
> Introduce the new kernel option ima_flush_htable to decide whether or not
> the digests of staged measurement entries are flushed from the hash table.
>
> Then, introduce ascii_runtime_measurements_staged_<algo> and
> binary_runtime_measurement_staged_<algo> interfaces to stage/delete the
> measurements. Use 'echo A > <IMA interface>' and 'echo D > <IMA interface>'
> to respectively stage and delete the entire measurements list. Use
> 'echo N > <IMA interface>', with N between 1 and ULONG_MAX, to stage the
> selected portion of the measurements list.
>
> The ima_measure_users counter (protected by the ima_measure_lock mutex) has
> been introduced to protect access to the measurement list and the staged
> part. The open method of all the measurement interfaces has been extended
> to allow only one writer at a time or, in alternative, multiple readers.
> The write permission is used to stage/delete the measurements, the read
> permission to read them. Write requires also the CAP_SYS_ADMIN capability.
Hi Roberto,
I released version 2 of trim N entries patch as bellow:
[PATCH v2 0/1] Trim N entries of IMA event logs
<https://lore.kernel.org/linux-integrity/20251210235314.3341-1-chenste@linux.microsoft.com/T/#t>
I adapted some of your idea and I think trim N has following advantages:
1: less measurement list hold time than your current implementation
2. operation much simple for user space
3. less kernel code change
4. no potential issue as Gregory mentioned.
Thanks,
Steven
> Finally, introduce the _notrim version of the run-time measurements count
> and the binary measurements list size, to display them in the kexec-related
> critical data records.
>
> Note: This code derives from the Alt-IMA Huawei project, and is being
> released under the dual license model (GPL-2.0 OR MIT).
>
> Link: https://github.com/linux-integrity/linux/issues/1
> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> ---
> .../admin-guide/kernel-parameters.txt | 4 +
> security/integrity/ima/ima.h | 10 +-
> security/integrity/ima/ima_fs.c | 222 +++++++++++++++++-
> security/integrity/ima/ima_kexec.c | 13 +-
> security/integrity/ima/ima_queue.c | 111 ++++++++-
> 5 files changed, 340 insertions(+), 20 deletions(-)
>
> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> index 6c42061ca20e..355d8930e3ac 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -2215,6 +2215,10 @@
> Use the canonical format for the binary runtime
> measurements, instead of host native format.
>
> + ima_flush_htable [IMA]
> + Flush the measurement list hash table when staging all
> + or a part of it for deletion.
> +
> ima_hash= [IMA]
> Format: { md5 | sha1 | rmd160 | sha256 | sha384
> | sha512 | ... }
> diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> index e3d71d8d56e3..d7aa4a0f79b1 100644
> --- a/security/integrity/ima/ima.h
> +++ b/security/integrity/ima/ima.h
> @@ -117,6 +117,8 @@ struct ima_queue_entry {
> struct ima_template_entry *entry;
> };
> extern struct list_head ima_measurements; /* list of all measurements */
> +extern struct list_head ima_measurements_staged; /* list of staged meas. */
> +extern bool ima_measurements_staged_exist; /* If there are staged meas. */
>
> /* Some details preceding the binary serialized measurement list */
> struct ima_kexec_hdr {
> @@ -281,10 +283,12 @@ struct ima_template_desc *ima_template_desc_current(void);
> struct ima_template_desc *ima_template_desc_buf(void);
> struct ima_template_desc *lookup_template_desc(const char *name);
> bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
> +int ima_queue_stage(unsigned long req_value);
> +int ima_queue_delete_staged(void);
> int ima_restore_measurement_entry(struct ima_template_entry *entry);
> int ima_restore_measurement_list(loff_t bufsize, void *buf);
> int ima_measurements_show(struct seq_file *m, void *v);
> -unsigned long ima_get_binary_runtime_size(void);
> +unsigned long ima_get_binary_runtime_size(bool notrim);
> int ima_init_template(void);
> void ima_init_template_list(void);
> int __init ima_init_digests(void);
> @@ -298,11 +302,13 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
> extern spinlock_t ima_queue_lock;
>
> struct ima_h_table {
> - atomic_long_t len; /* number of stored measurements in the list */
> + atomic_long_t len; /* current num of stored meas. in the list */
> + atomic_long_t len_notrim; /* total num of stored meas. in the list */
> atomic_long_t violations;
> struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE];
> };
> extern struct ima_h_table ima_htable;
> +extern struct mutex ima_extend_list_mutex;
>
> static inline unsigned int ima_hash_key(u8 *digest)
> {
> diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> index 87045b09f120..321c98ae0e55 100644
> --- a/security/integrity/ima/ima_fs.c
> +++ b/security/integrity/ima/ima_fs.c
> @@ -24,7 +24,12 @@
>
> #include "ima.h"
>
> +/* Requests: ('A', [1, ULONG_MAX])\n (stage all/N) or D\n (delete staged) */
> +#define STAGED_REQ_LENGTH 21
> +
> static DEFINE_MUTEX(ima_write_mutex);
> +static DEFINE_MUTEX(ima_measure_lock);
> +static long ima_measure_users;
>
> bool ima_canonical_fmt;
> static int __init default_canonical_fmt_setup(char *str)
> @@ -74,14 +79,15 @@ static const struct file_operations ima_measurements_count_ops = {
> };
>
> /* returns pointer to hlist_node */
> -static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
> +static void *_ima_measurements_start(struct seq_file *m, loff_t *pos,
> + struct list_head *head)
> {
> loff_t l = *pos;
> struct ima_queue_entry *qe;
>
> /* we need a lock since pos could point beyond last element */
> rcu_read_lock();
> - list_for_each_entry_rcu(qe, &ima_measurements, later) {
> + list_for_each_entry_rcu(qe, head, later) {
> if (!l--) {
> rcu_read_unlock();
> return qe;
> @@ -91,7 +97,18 @@ static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
> return NULL;
> }
>
> -static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
> +static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
> +{
> + return _ima_measurements_start(m, pos, &ima_measurements);
> +}
> +
> +static void *ima_measurements_staged_start(struct seq_file *m, loff_t *pos)
> +{
> + return _ima_measurements_start(m, pos, &ima_measurements_staged);
> +}
> +
> +static void *_ima_measurements_next(struct seq_file *m, void *v, loff_t *pos,
> + struct list_head *head)
> {
> struct ima_queue_entry *qe = v;
>
> @@ -103,7 +120,18 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
> rcu_read_unlock();
> (*pos)++;
>
> - return (&qe->later == &ima_measurements) ? NULL : qe;
> + return (&qe->later == head) ? NULL : qe;
> +}
> +
> +static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
> +{
> + return _ima_measurements_next(m, v, pos, &ima_measurements);
> +}
> +
> +static void *ima_measurements_staged_next(struct seq_file *m, void *v,
> + loff_t *pos)
> +{
> + return _ima_measurements_next(m, v, pos, &ima_measurements_staged);
> }
>
> static void ima_measurements_stop(struct seq_file *m, void *v)
> @@ -202,16 +230,138 @@ static const struct seq_operations ima_measurments_seqops = {
> .show = ima_measurements_show
> };
>
> +static int _ima_measurements_open(struct inode *inode, struct file *file,
> + const struct seq_operations *seq_ops)
> +{
> + bool write = !!(file->f_mode & FMODE_WRITE);
> + int ret;
> +
> + if (write && !capable(CAP_SYS_ADMIN))
> + return -EPERM;
> +
> + mutex_lock(&ima_measure_lock);
> + if ((write && ima_measure_users != 0) ||
> + (!write && ima_measure_users < 0)) {
> + mutex_unlock(&ima_measure_lock);
> + return -EBUSY;
> + }
> +
> + ret = seq_open(file, seq_ops);
> + if (ret < 0) {
> + mutex_unlock(&ima_measure_lock);
> + return ret;
> + }
> +
> + if (write)
> + ima_measure_users--;
> + else
> + ima_measure_users++;
> +
> + mutex_unlock(&ima_measure_lock);
> + return ret;
> +}
> +
> static int ima_measurements_open(struct inode *inode, struct file *file)
> {
> - return seq_open(file, &ima_measurments_seqops);
> + return _ima_measurements_open(inode, file, &ima_measurments_seqops);
> +}
> +
> +static int ima_measurements_release(struct inode *inode, struct file *file)
> +{
> + bool write = !!(file->f_mode & FMODE_WRITE);
> + int ret;
> +
> + mutex_lock(&ima_measure_lock);
> + ret = seq_release(inode, file);
> + if (!ret) {
> + if (write)
> + ima_measure_users++;
> + else
> + ima_measure_users--;
> + }
> +
> + mutex_unlock(&ima_measure_lock);
> + return ret;
> }
>
> static const struct file_operations ima_measurements_ops = {
> .open = ima_measurements_open,
> .read = seq_read,
> .llseek = seq_lseek,
> - .release = seq_release,
> + .release = ima_measurements_release,
> +};
> +
> +static const struct seq_operations ima_measurments_staged_seqops = {
> + .start = ima_measurements_staged_start,
> + .next = ima_measurements_staged_next,
> + .stop = ima_measurements_stop,
> + .show = ima_measurements_show
> +};
> +
> +static int ima_measurements_staged_open(struct inode *inode, struct file *file)
> +{
> + return _ima_measurements_open(inode, file,
> + &ima_measurments_staged_seqops);
> +}
> +
> +static ssize_t ima_measurements_staged_read(struct file *file, char __user *buf,
> + size_t size, loff_t *ppos)
> +{
> + if (!ima_measurements_staged_exist)
> + return -ENOENT;
> +
> + return seq_read(file, buf, size, ppos);
> +}
> +
> +static ssize_t ima_measurements_staged_write(struct file *file,
> + const char __user *buf,
> + size_t datalen, loff_t *ppos)
> +{
> + char req[STAGED_REQ_LENGTH], *req_ptr = req;
> + unsigned long req_value;
> + int ret;
> +
> + if (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH)
> + return -EINVAL;
> +
> + ret = copy_from_user(req, buf, datalen);
> + if (ret < 0)
> + return ret;
> +
> + if (strsep(&req_ptr, "\n") == NULL)
> + return -EINVAL;
> +
> + switch (req[0]) {
> + case 'A':
> + if (datalen != 2 || req[1] != '\0')
> + return -EINVAL;
> +
> + ret = ima_queue_stage(ULONG_MAX);
> + break;
> + case 'D':
> + if (datalen != 2 || req[1] != '\0')
> + return -EINVAL;
> +
> + ret = ima_queue_delete_staged();
> + break;
> + default:
> + ret = kstrtoul(req, 0, &req_value);
> + if (!ret)
> + ret = ima_queue_stage(req_value);
> + }
> +
> + if (ret < 0)
> + return ret;
> +
> + return datalen;
> +}
> +
> +static const struct file_operations ima_measurements_staged_ops = {
> + .open = ima_measurements_staged_open,
> + .read = ima_measurements_staged_read,
> + .write = ima_measurements_staged_write,
> + .llseek = seq_lseek,
> + .release = ima_measurements_release,
> };
>
> void ima_print_digest(struct seq_file *m, u8 *digest, u32 size)
> @@ -279,14 +429,37 @@ static const struct seq_operations ima_ascii_measurements_seqops = {
>
> static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
> {
> - return seq_open(file, &ima_ascii_measurements_seqops);
> + return _ima_measurements_open(inode, file,
> + &ima_ascii_measurements_seqops);
> }
>
> static const struct file_operations ima_ascii_measurements_ops = {
> .open = ima_ascii_measurements_open,
> .read = seq_read,
> .llseek = seq_lseek,
> - .release = seq_release,
> + .release = ima_measurements_release,
> +};
> +
> +static const struct seq_operations ima_ascii_measurements_staged_seqops = {
> + .start = ima_measurements_staged_start,
> + .next = ima_measurements_staged_next,
> + .stop = ima_measurements_stop,
> + .show = ima_ascii_measurements_show
> +};
> +
> +static int ima_ascii_measurements_staged_open(struct inode *inode,
> + struct file *file)
> +{
> + return _ima_measurements_open(inode, file,
> + &ima_ascii_measurements_staged_seqops);
> +}
> +
> +static const struct file_operations ima_ascii_measurements_staged_ops = {
> + .open = ima_ascii_measurements_staged_open,
> + .read = ima_measurements_staged_read,
> + .write = ima_measurements_staged_write,
> + .llseek = seq_lseek,
> + .release = ima_measurements_release,
> };
>
> static ssize_t ima_read_policy(char *path)
> @@ -419,6 +592,25 @@ static int __init create_securityfs_measurement_lists(void)
> &ima_measurements_ops);
> if (IS_ERR(dentry))
> return PTR_ERR(dentry);
> +
> + sprintf(file_name, "ascii_runtime_measurements_staged_%s",
> + hash_algo_name[algo]);
> + dentry = securityfs_create_file(file_name,
> + S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP,
> + ima_dir, (void *)(uintptr_t)i,
> + &ima_ascii_measurements_staged_ops);
> + if (IS_ERR(dentry))
> + return PTR_ERR(dentry);
> +
> + sprintf(file_name, "binary_runtime_measurements_staged_%s",
> + hash_algo_name[algo]);
> + dentry = securityfs_create_file(file_name,
> + S_IRUSR | S_IRGRP |
> + S_IWUSR | S_IWGRP,
> + ima_dir, (void *)(uintptr_t)i,
> + &ima_measurements_staged_ops);
> + if (IS_ERR(dentry))
> + return PTR_ERR(dentry);
> }
>
> return 0;
> @@ -528,6 +720,20 @@ int __init ima_fs_init(void)
> goto out;
> }
>
> + dentry = securityfs_create_symlink("binary_runtime_measurements_staged",
> + ima_dir, "binary_runtime_measurements_staged_sha1", NULL);
> + if (IS_ERR(dentry)) {
> + ret = PTR_ERR(dentry);
> + goto out;
> + }
> +
> + dentry = securityfs_create_symlink("ascii_runtime_measurements_staged",
> + ima_dir, "ascii_runtime_measurements_staged_sha1", NULL);
> + if (IS_ERR(dentry)) {
> + ret = PTR_ERR(dentry);
> + goto out;
> + }
> +
> dentry = securityfs_create_file("runtime_measurements_count",
> S_IRUSR | S_IRGRP, ima_dir, NULL,
> &ima_measurements_count_ops);
> diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
> index 7362f68f2d8b..23a20300da7b 100644
> --- a/security/integrity/ima/ima_kexec.c
> +++ b/security/integrity/ima/ima_kexec.c
> @@ -40,8 +40,8 @@ void ima_measure_kexec_event(const char *event_name)
> long len;
> int n;
>
> - buf_size = ima_get_binary_runtime_size();
> - len = atomic_long_read(&ima_htable.len);
> + buf_size = ima_get_binary_runtime_size(true);
> + len = atomic_long_read(&ima_htable.len_notrim);
>
> n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
> "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
> @@ -93,8 +93,10 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
>
> memset(&khdr, 0, sizeof(khdr));
> khdr.version = 1;
> - /* This is an append-only list, no need to hold the RCU read lock */
> - list_for_each_entry_rcu(qe, &ima_measurements, later, true) {
> +
> + /* It can race with ima_queue_stage(). */
> + mutex_lock(&ima_extend_list_mutex);
> + list_for_each_entry(qe, &ima_measurements, later) {
> if (ima_kexec_file.count < ima_kexec_file.size) {
> khdr.count++;
> ima_measurements_show(&ima_kexec_file, qe);
> @@ -103,6 +105,7 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
> break;
> }
> }
> + mutex_unlock(&ima_extend_list_mutex);
>
> /*
> * fill in reserved space with some buffer details
> @@ -157,7 +160,7 @@ void ima_add_kexec_buffer(struct kimage *image)
> else
> extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024;
>
> - binary_runtime_size = ima_get_binary_runtime_size() + extra_memory;
> + binary_runtime_size = ima_get_binary_runtime_size(false) + extra_memory;
>
> if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE)
> kexec_segment_size = ULONG_MAX;
> diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
> index 590637e81ad1..868f216ac343 100644
> --- a/security/integrity/ima/ima_queue.c
> +++ b/security/integrity/ima/ima_queue.c
> @@ -22,19 +22,32 @@
>
> #define AUDIT_CAUSE_LEN_MAX 32
>
> +bool ima_flush_htable;
> +static int __init ima_flush_htable_setup(char *str)
> +{
> + ima_flush_htable = true;
> + return 1;
> +}
> +__setup("ima_flush_htable", ima_flush_htable_setup);
> +
> /* pre-allocated array of tpm_digest structures to extend a PCR */
> static struct tpm_digest *digests;
>
> LIST_HEAD(ima_measurements); /* list of all measurements */
> +LIST_HEAD(ima_measurements_staged); /* list of staged measurements */
> +bool ima_measurements_staged_exist; /* If there are staged measurements */
> #ifdef CONFIG_IMA_KEXEC
> static unsigned long binary_runtime_size;
> +static unsigned long binary_runtime_size_notrim;
> #else
> static unsigned long binary_runtime_size = ULONG_MAX;
> +static unsigned long binary_runtime_size_notrim = ULONG_MAX;
> #endif
>
> /* key: inode (before secure-hashing a file) */
> struct ima_h_table ima_htable = {
> .len = ATOMIC_LONG_INIT(0),
> + .len_notrim = ATOMIC_LONG_INIT(0),
> .violations = ATOMIC_LONG_INIT(0),
> .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
> };
> @@ -43,7 +56,7 @@ struct ima_h_table ima_htable = {
> * and extending the TPM PCR aggregate. Since tpm_extend can take
> * long (and the tpm driver uses a mutex), we can't use the spinlock.
> */
> -static DEFINE_MUTEX(ima_extend_list_mutex);
> +DEFINE_MUTEX(ima_extend_list_mutex);
>
> /*
> * Used internally by the kernel to suspend measurements.
> @@ -114,15 +127,19 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
> list_add_tail_rcu(&qe->later, &ima_measurements);
>
> atomic_long_inc(&ima_htable.len);
> + atomic_long_inc(&ima_htable.len_notrim);
> if (update_htable) {
> key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
> hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]);
> }
>
> - if (binary_runtime_size != ULONG_MAX) {
> + if (binary_runtime_size_notrim != ULONG_MAX) {
> int size;
>
> size = get_binary_runtime_size(entry);
> + binary_runtime_size_notrim =
> + (binary_runtime_size_notrim < ULONG_MAX - size) ?
> + binary_runtime_size_notrim + size : ULONG_MAX;
> binary_runtime_size = (binary_runtime_size < ULONG_MAX - size) ?
> binary_runtime_size + size : ULONG_MAX;
> }
> @@ -134,12 +151,18 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
> * entire binary_runtime_measurement list, including the ima_kexec_hdr
> * structure.
> */
> -unsigned long ima_get_binary_runtime_size(void)
> +unsigned long ima_get_binary_runtime_size(bool notrim)
> {
> - if (binary_runtime_size >= (ULONG_MAX - sizeof(struct ima_kexec_hdr)))
> + unsigned long *val;
> +
> + mutex_lock(&ima_extend_list_mutex);
> + val = (notrim) ? &binary_runtime_size_notrim : &binary_runtime_size;
> + mutex_unlock(&ima_extend_list_mutex);
> +
> + if (*val >= (ULONG_MAX - sizeof(struct ima_kexec_hdr)))
> return ULONG_MAX;
> else
> - return binary_runtime_size + sizeof(struct ima_kexec_hdr);
> + return *val + sizeof(struct ima_kexec_hdr);
> }
>
> static int ima_pcr_extend(struct tpm_digest *digests_arg, int pcr)
> @@ -220,6 +243,84 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
> return result;
> }
>
> +int ima_queue_stage(unsigned long req_value)
> +{
> + unsigned long req_value_copy = req_value, to_remove = 0;
> + struct ima_queue_entry *qe;
> +
> + if (ima_measurements_staged_exist)
> + return -EEXIST;
> +
> + mutex_lock(&ima_extend_list_mutex);
> + if (list_empty(&ima_measurements)) {
> + mutex_unlock(&ima_extend_list_mutex);
> + return -ENOENT;
> + }
> +
> + if (req_value == ULONG_MAX) {
> + list_replace(&ima_measurements, &ima_measurements_staged);
> + INIT_LIST_HEAD(&ima_measurements);
> + atomic_long_set(&ima_htable.len, 0);
> + if (IS_ENABLED(CONFIG_IMA_KEXEC))
> + binary_runtime_size = 0;
> + } else {
> + list_for_each_entry(qe, &ima_measurements, later) {
> + to_remove += get_binary_runtime_size(qe->entry);
> + if (--req_value_copy == 0)
> + break;
> + }
> +
> + if (req_value_copy > 0) {
> + mutex_unlock(&ima_extend_list_mutex);
> + return -ENOENT;
> + }
> +
> + __list_cut_position(&ima_measurements_staged, &ima_measurements,
> + &qe->later);
> + atomic_long_sub(req_value, &ima_htable.len);
> + if (IS_ENABLED(CONFIG_IMA_KEXEC))
> + binary_runtime_size -= to_remove;
> + }
> +
> + if (ima_flush_htable) {
> + list_for_each_entry(qe, &ima_measurements_staged, later)
> + /* It can race with ima_lookup_digest_entry(). */
> + hlist_del_rcu(&qe->hnext);
> + }
> +
> + mutex_unlock(&ima_extend_list_mutex);
> + ima_measurements_staged_exist = true;
> + return 0;
> +}
> +
> +int ima_queue_delete_staged(void)
> +{
> + struct ima_queue_entry *qe, *qe_tmp;
> + unsigned int i;
> +
> + if (!ima_measurements_staged_exist)
> + return -ENOENT;
> +
> + list_for_each_entry_safe(qe, qe_tmp, &ima_measurements_staged, later) {
> + for (i = 0; i < qe->entry->template_desc->num_fields; i++) {
> + kfree(qe->entry->template_data[i].data);
> + qe->entry->template_data[i].data = NULL;
> + qe->entry->template_data[i].len = 0;
> + }
> +
> + list_del(&qe->later);
> +
> + if (ima_flush_htable) {
> + kfree(qe->entry->digests);
> + kfree(qe->entry);
> + kfree(qe);
> + }
> + }
> +
> + ima_measurements_staged_exist = false;
> + return 0;
> +}
> +
> int ima_restore_measurement_entry(struct ima_template_entry *entry)
> {
> int result = 0;
^ permalink raw reply
* [PATCH V2 1/1] IMA event log trimming
From: steven chen @ 2025-12-10 23:53 UTC (permalink / raw)
To: linux-integrity
Cc: zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg, corbet,
serge, paul, jmorris, linux-security-module, anirudhve, chenste,
gregorylumen, nramas, sushring, linux-doc
In-Reply-To: <20251210235314.3341-1-chenste@linux.microsoft.com>
This patch is for trimming N entries of the IMA event logs. It will also
cleaning the hash table if ima_flush_htable is set.
It provides a userspace interface ima_trim_log that can be used to input
number N to let kernel to trim N entries of IMA event logs. When read
this interface, it returns number of entries trimmed last time.
Signed-off-by: steven chen <chenste@linux.microsoft.com>
---
.../admin-guide/kernel-parameters.txt | 4 +
security/integrity/ima/ima.h | 2 +
security/integrity/ima/ima_fs.c | 175 +++++++++++++++++-
security/integrity/ima/ima_queue.c | 64 +++++++
4 files changed, 241 insertions(+), 4 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index e92c0056e4e0..cd1a1d0bf0e2 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -2197,6 +2197,10 @@
Use the canonical format for the binary runtime
measurements, instead of host native format.
+ ima_flush_htable [IMA]
+ Flush the measurement list hash table when trim all
+ or a part of it for deletion.
+
ima_hash= [IMA]
Format: { md5 | sha1 | rmd160 | sha256 | sha384
| sha512 | ... }
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index e3d71d8d56e3..ab0e30ee25ea 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -246,8 +246,10 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key,
#ifdef CONFIG_IMA_KEXEC
void ima_measure_kexec_event(const char *event_name);
+long ima_purge_event_log(long number_logs);
#else
static inline void ima_measure_kexec_event(const char *event_name) {}
+static inline long ima_purge_event_log(long number_logs) { return 0; }
#endif
/*
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 87045b09f120..410f7d03c43f 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -21,6 +21,9 @@
#include <linux/rcupdate.h>
#include <linux/parser.h>
#include <linux/vmalloc.h>
+#include <linux/ktime.h>
+#include <linux/timekeeping.h>
+#include <linux/ima.h>
#include "ima.h"
@@ -38,6 +41,14 @@ __setup("ima_canonical_fmt", default_canonical_fmt_setup);
static int valid_policy = 1;
+#define IMA_LOG_TRIM_REQ_LENGTH 11
+#define IMA_LOG_TRIM_EVENT_LEN 256
+
+static long trimcount;
+/* mutex protects atomicity of trimming measurement list requests */
+static DEFINE_MUTEX(ima_measure_lock);
+static long ima_measure_users;
+
static ssize_t ima_show_htable_value(char __user *buf, size_t count,
loff_t *ppos, atomic_long_t *val)
{
@@ -202,16 +213,65 @@ static const struct seq_operations ima_measurments_seqops = {
.show = ima_measurements_show
};
+static int _ima_measurements_open(struct inode *inode, struct file *file,
+ const struct seq_operations *seq_ops)
+{
+ bool write = !!(file->f_mode & FMODE_WRITE);
+ int ret;
+
+ if (write && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ mutex_lock(&ima_measure_lock);
+ if ((write && ima_measure_users != 0) ||
+ (!write && ima_measure_users < 0)) {
+ mutex_unlock(&ima_measure_lock);
+ return -EBUSY;
+ }
+
+ ret = seq_open(file, seq_ops);
+ if (ret < 0) {
+ mutex_unlock(&ima_measure_lock);
+ return ret;
+ }
+
+ if (write)
+ ima_measure_users--;
+ else
+ ima_measure_users++;
+
+ mutex_unlock(&ima_measure_lock);
+ return ret;
+}
+
static int ima_measurements_open(struct inode *inode, struct file *file)
{
- return seq_open(file, &ima_measurments_seqops);
+ return _ima_measurements_open(inode, file, &ima_measurments_seqops);
+}
+
+static int ima_measurements_release(struct inode *inode, struct file *file)
+{
+ bool write = !!(file->f_mode & FMODE_WRITE);
+ int ret;
+
+ mutex_lock(&ima_measure_lock);
+ ret = seq_release(inode, file);
+ if (!ret) {
+ if (write)
+ ima_measure_users++;
+ else
+ ima_measure_users--;
+ }
+
+ mutex_unlock(&ima_measure_lock);
+ return ret;
}
static const struct file_operations ima_measurements_ops = {
.open = ima_measurements_open,
.read = seq_read,
.llseek = seq_lseek,
- .release = seq_release,
+ .release = ima_measurements_release,
};
void ima_print_digest(struct seq_file *m, u8 *digest, u32 size)
@@ -279,14 +339,111 @@ static const struct seq_operations ima_ascii_measurements_seqops = {
static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
{
- return seq_open(file, &ima_ascii_measurements_seqops);
+ return _ima_measurements_open(inode, file, &ima_ascii_measurements_seqops);
}
static const struct file_operations ima_ascii_measurements_ops = {
.open = ima_ascii_measurements_open,
.read = seq_read,
.llseek = seq_lseek,
- .release = seq_release,
+ .release = ima_measurements_release,
+};
+
+static void ima_measure_trim_event(const long number_logs)
+{
+ char ima_log_trim_event[IMA_LOG_TRIM_EVENT_LEN];
+ struct timespec64 ts;
+ u64 time_ns;
+ int n;
+
+ ktime_get_real_ts64(&ts);
+ time_ns = (u64)ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+ n = scnprintf(ima_log_trim_event, IMA_LOG_TRIM_EVENT_LEN,
+ "time=%llu; log trim this time=%lu;",
+ time_ns, number_logs);
+
+ ima_measure_critical_data("ima_log_trim", "trim ima event logs", ima_log_trim_event, n, false, NULL, 0);
+}
+
+static int ima_log_trim_open(struct inode *inode, struct file *file)
+{
+ bool write = !!(file->f_mode & FMODE_WRITE);
+
+ if (!write && capable(CAP_SYS_ADMIN))
+ return 0;
+ else if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ return _ima_measurements_open(inode, file, &ima_measurments_seqops);
+}
+
+static ssize_t ima_log_trim_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
+{
+ char tmpbuf[IMA_LOG_TRIM_REQ_LENGTH]; /* greater than largest 'long' string value */
+ ssize_t len;
+
+ len = scnprintf(tmpbuf, sizeof(tmpbuf), "%li\n", trimcount);
+ return simple_read_from_buffer(buf, size, ppos, tmpbuf, len);
+}
+
+static ssize_t ima_log_trim_write(struct file *file,
+ const char __user *buf, size_t datalen, loff_t *ppos)
+{
+ unsigned char req[IMA_LOG_TRIM_REQ_LENGTH];
+ long count, n;
+ int ret;
+
+ if (*ppos > 0 || datalen > IMA_LOG_TRIM_REQ_LENGTH || datalen < 2) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ n = (int)datalen;
+
+ ret = copy_from_user(req, buf, datalen);
+ if (ret < 0)
+ goto out;
+
+ count = 0;
+ for (int i = 0; i < n; ++i) {
+ if (req[i] < '0' || req[i] > '9') {
+ ret = -EINVAL;
+ goto out;
+ }
+ count = count * 10 + req[i] - '0';
+ }
+ ret = ima_purge_event_log(count);
+
+ if (ret < 0)
+ goto out;
+
+ trimcount = ret;
+
+ if (trimcount > 0)
+ ima_measure_trim_event(trimcount);
+
+ ret = datalen;
+out:
+ return ret;
+}
+
+static int ima_log_trim_release(struct inode *inode, struct file *file)
+{
+ bool write = !!(file->f_mode & FMODE_WRITE);
+ if (!write && capable(CAP_SYS_ADMIN))
+ return 0;
+ else if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ return ima_measurements_release(inode, file);
+}
+
+static const struct file_operations ima_log_trim_ops = {
+ .open = ima_log_trim_open,
+ .read = ima_log_trim_read,
+ .write = ima_log_trim_write,
+ .llseek = generic_file_llseek,
+ .release = ima_log_trim_release
};
static ssize_t ima_read_policy(char *path)
@@ -528,6 +685,16 @@ int __init ima_fs_init(void)
goto out;
}
+ dentry = securityfs_create_file("ima_trim_log",
+ S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP,
+ ima_dir, NULL, &ima_log_trim_ops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto out;
+ }
+
+ trimcount = 0;
+
dentry = securityfs_create_file("runtime_measurements_count",
S_IRUSR | S_IRGRP, ima_dir, NULL,
&ima_measurements_count_ops);
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 590637e81ad1..77ab52469727 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -22,6 +22,14 @@
#define AUDIT_CAUSE_LEN_MAX 32
+bool ima_flush_htable;
+static int __init ima_flush_htable_setup(char *str)
+{
+ ima_flush_htable = true;
+ return 1;
+}
+__setup("ima_flush_htable", ima_flush_htable_setup);
+
/* pre-allocated array of tpm_digest structures to extend a PCR */
static struct tpm_digest *digests;
@@ -220,6 +228,62 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
return result;
}
+/* Delete the IMA event logs */
+long ima_purge_event_log(long number_logs)
+{
+ struct ima_queue_entry *qe, *qe_tmp;
+ LIST_HEAD(ima_measurements_staged);
+ unsigned int i;
+ long cur = number_logs;
+
+ if (number_logs <= 0)
+ return number_logs;
+
+ mutex_lock(&ima_extend_list_mutex);
+
+
+ list_for_each_entry(qe, &ima_measurements, later) {
+ if (--number_logs == 0)
+ break;
+ }
+
+ if (number_logs > 0) {
+ mutex_unlock(&ima_extend_list_mutex);
+ return -ENOENT;
+ }
+
+ __list_cut_position(&ima_measurements_staged, &ima_measurements,
+ &qe->later);
+ atomic_long_sub(cur, &ima_htable.len);
+
+ if (!IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE) && ima_flush_htable) {
+ list_for_each_entry(qe, &ima_measurements_staged, later)
+ /* It can race with ima_lookup_digest_entry(). */
+ hlist_del_rcu(&qe->hnext);
+ }
+
+ mutex_unlock(&ima_extend_list_mutex);
+
+
+ list_for_each_entry_safe(qe, qe_tmp, &ima_measurements_staged, later) {
+ for (i = 0; i < qe->entry->template_desc->num_fields; i++) {
+ kfree(qe->entry->template_data[i].data);
+ qe->entry->template_data[i].data = NULL;
+ qe->entry->template_data[i].len = 0;
+ }
+
+ list_del(&qe->later);
+
+ if (ima_flush_htable) {
+ kfree(qe->entry->digests);
+ kfree(qe->entry);
+ kfree(qe);
+ }
+ }
+
+ return cur;
+}
+
int ima_restore_measurement_entry(struct ima_template_entry *entry)
{
int result = 0;
--
2.43.0
^ permalink raw reply related
* [PATCH v2 0/1] Trim N entries of IMA event logs
From: steven chen @ 2025-12-10 23:53 UTC (permalink / raw)
To: linux-integrity
Cc: zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg, corbet,
serge, paul, jmorris, linux-security-module, anirudhve, chenste,
gregorylumen, nramas, sushring, linux-doc
The Integrity Measurement Architecture (IMA) maintains a measurement list
—a record of system events used for integrity verification. The IMA event
logs are the entries within this measurement list, each representing a
specific event or measurement that contributes to the system's integrity
assessment.
This update introduces the ability to trim, or remove, N entries from the
current measurement list. Trimming involves deleting N entries from the
list and clearing the corresponding entries from the hash table. This
action atomically truncates the measurement list, ensuring that no new
measurements can be added until the operation is complete. Importantly,
only one writer can initiate this trimming process at a time, maintaining
consistency and preventing race conditions.
A userspace interface, ima_trim_log, has been provided for this purpose.
By writing a number N to this interface, userspace can request the kernel
to trim N entries from the IMA event logs. When this interface is read,
it returns the number of entries trimmed during the last operation. This
value is not preserved across kexec soft reboots, as it is not considered
important information.
To maintain a complete record, userspace is responsible for concatenating
and storing the logs before initiating trimming. Userspace can then send
the collected data to remote verifiers for validation. After receiving
confirmation from the remote verifiers, userspace may instruct the kernel
to proceed with trimming the IMA event logs accordingly.
The primary benefit of this solution is the ability to free valuable
kernel memory by delegating the task of reconstructing the full
measurement list from log chunks to userspace. Trust is not required in
userspace for the integrity of the measurement list, as its integrity is
cryptographically protected by the Trusted Platform Module (TPM).
Multiple readers are allowed to access the ima_trim_log interface
concurrently, while only one writer can trigger log trimming at any time.
During trimming, readers do not see the list and cannot access it while
deletion is in progress, ensuring atomicity.
Introduce the new kernel option ima_flush_htable to decide whether or not
the digests of measurement entries are flushed from the hash table. (from
reference [2])
The ima_measure_users counter (protected by the ima_measure_lock mutex) has
been introduced to protect access to the measurement list part. The open
method of all the measurement interfaces has been extended to allow only
one writer at a time or, in alternative, multiple readers. The write
permission is used to delete the measurements, the read permission
to read them. Write requires also the CAP_SYS_ADMIN capability. (from
reference [2])
New IMA log trim event is added when trimming finish.
The time required for trimming is minimal, and IMA event logs are briefly
on hold during this process, preventing read or add operations. This short
interruption has no impact on the overall functionality of IMA.
V1 of this series is available here[1] for reference.
References:
-----------
[1] [PATCH 0/1] Trim N entries of IMA event logs
https://lore.kernel.org/linux-integrity/20251202232857.8211-1-chenste@linux.microsoft.com/T/#t
[2] [RFC][PATCH] ima: Add support for staging measurements for deletion
https://lore.kernel.org/linux-integrity/207fd6d7-53c-57bb-36d8-13a0902052d1@linux.microsoft.com/T/#t
Change Log v2:
- Incorporated feedback from the Roberto on v1 series.
- Adapted code from Roberto's RFC [Reference 2]
- Add IMA log trim event log to record trim event
- Updated patch descriptions as necessary.
steven chen (1):
IMA event log trimming
.../admin-guide/kernel-parameters.txt | 4 +
security/integrity/ima/ima.h | 2 +
security/integrity/ima/ima_fs.c | 175 +++++++++++++++++-
security/integrity/ima/ima_queue.c | 64 +++++++
4 files changed, 241 insertions(+), 4 deletions(-)
--
2.43.0
^ permalink raw reply
* Re: [PATCH v4 14/35] rcu: Support Clang's context analysis
From: Paul E. McKenney @ 2025-12-10 22:49 UTC (permalink / raw)
To: Marco Elver
Cc: Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon,
David S. Miller, Luc Van Oostenryck, Chris Li,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, Jonathan Corbet,
Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda,
Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda,
Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers,
Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf,
Uladzislau Rezki, Waiman Long, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <CANpmjNNsR_+Mx=H6+4zxJHwpRuM7vKUakS8X+edBD521=w4y_g@mail.gmail.com>
On Wed, Dec 10, 2025 at 10:50:11PM +0100, Marco Elver wrote:
> On Wed, 10 Dec 2025 at 20:30, Paul E. McKenney <paulmck@kernel.org> wrote:
> > On Thu, Nov 20, 2025 at 04:09:39PM +0100, Marco Elver wrote:
> > > Improve the existing annotations to properly support Clang's context
> > > analysis.
> > >
> > > The old annotations distinguished between RCU, RCU_BH, and RCU_SCHED;
> > > however, to more easily be able to express that "hold the RCU read lock"
> > > without caring if the normal, _bh(), or _sched() variant was used we'd
> > > have to remove the distinction of the latter variants: change the _bh()
> > > and _sched() variants to also acquire "RCU".
> > >
> > > When (and if) we introduce context guards to denote more generally that
> > > "IRQ", "BH", "PREEMPT" contexts are disabled, it would make sense to
> > > acquire these instead of RCU_BH and RCU_SCHED respectively.
>
> ^
"I can't read!" ;-)
> > > The above change also simplified introducing __guarded_by support, where
> > > only the "RCU" context guard needs to be held: introduce __rcu_guarded,
> > > where Clang's context analysis warns if a pointer is dereferenced
> > > without any of the RCU locks held, or updated without the appropriate
> > > helpers.
> > >
> > > The primitives rcu_assign_pointer() and friends are wrapped with
> > > context_unsafe(), which enforces using them to update RCU-protected
> > > pointers marked with __rcu_guarded.
> > >
> > > Signed-off-by: Marco Elver <elver@google.com>
> >
> > Good reminder! I had lost track of this series.
> >
> > My big questions here are:
> >
> > o What about RCU readers using (say) preempt_disable() instead
> > of rcu_read_lock_sched()?
>
> The infrastructure that is being built up in this series will be able
> to support this, it's "just" a matter of enhancing our various
> interfaces/macros to use the right annotations, and working out which
> kinds of contexts we want to support. There are the obvious
> candidates, which this series is being applied to, as a starting
> point, but longer-term there are other kinds of context rules that can
> be checked with this context analysis. However, I think we have to
> start somewhere.
>
> > o What about RCU readers using local_bh_disable() instead of
> > rcu_read_lock_sched()?
>
> Same as above; this requires adding the necessary annotations to the
> BH-disabling/enabling primitives.
>
> > And keeping in mind that such readers might start in assembly language.
>
> We can handle this by annotating the C functions invoked from assembly
> with attributes like __must_hold_shared(RCU) or
> __releases_shared(RCU) (if the callee is expected to release the RCU
> read lock / re-enable preemption / etc.) or similar.
>
> > One reasonable approach is to require such readers to use something like
> > rcu_dereference_all() or rcu_dereference_all_check(), which could then
> > have special dispensation to instead rely on run-time checks.
>
> Agree. The current infrastructure encourages run-time checks where the
> static analysis cannot be helped sufficiently otherwise (see patch:
> "lockdep: Annotate lockdep assertions for context analysis").
OK, very good.
> > Another more powerful approach would be to make this facility also
> > track preemption, interrupt, NMI, and BH contexts.
> >
> > Either way could be a significant improvement over what we have now.
> >
> > Thoughts?
>
> The current infrastructure is powerful enough to allow for tracking
> more contexts, such as interrupt, NMI, and BH contexts, and as I
> hinted above, would be nice to eventually get to! But I think this is
> also a question of how much do we want to front-load for this to be
> useful, and what should incrementally be enhanced while the baseline
> infrastructure is already available.
>
> I think the current series is the baseline required support to be
> useful to a large fraction of "normal" code in the kernel.
Makes sense to me!
> On a whole, my strategy was to get to a point where maintainers and
> developers can start using context analysis where appropriate, but at
> the same time build up and incrementally add more supported contexts
> in parallel. There's also a good chance that, once baseline support
> lands, more interested parties contribute and things progress faster
> (or so I'd hope :-)).
I know that feelling! ;-)
OK, for this patch and the SRCU patch based on a quick once-over:
Acked-by: Paul E. McKenney <paulmck@kernel.org>
Thanx, Paul
^ permalink raw reply
* Re: [PATCH v4 14/35] rcu: Support Clang's context analysis
From: Marco Elver @ 2025-12-10 21:50 UTC (permalink / raw)
To: paulmck
Cc: Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon,
David S. Miller, Luc Van Oostenryck, Chris Li,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, Jonathan Corbet,
Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda,
Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda,
Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers,
Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf,
Uladzislau Rezki, Waiman Long, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <98453e19-7df2-43cb-8f05-87632f360028@paulmck-laptop>
On Wed, 10 Dec 2025 at 20:30, Paul E. McKenney <paulmck@kernel.org> wrote:
> On Thu, Nov 20, 2025 at 04:09:39PM +0100, Marco Elver wrote:
> > Improve the existing annotations to properly support Clang's context
> > analysis.
> >
> > The old annotations distinguished between RCU, RCU_BH, and RCU_SCHED;
> > however, to more easily be able to express that "hold the RCU read lock"
> > without caring if the normal, _bh(), or _sched() variant was used we'd
> > have to remove the distinction of the latter variants: change the _bh()
> > and _sched() variants to also acquire "RCU".
> >
> > When (and if) we introduce context guards to denote more generally that
> > "IRQ", "BH", "PREEMPT" contexts are disabled, it would make sense to
> > acquire these instead of RCU_BH and RCU_SCHED respectively.
^
> > The above change also simplified introducing __guarded_by support, where
> > only the "RCU" context guard needs to be held: introduce __rcu_guarded,
> > where Clang's context analysis warns if a pointer is dereferenced
> > without any of the RCU locks held, or updated without the appropriate
> > helpers.
> >
> > The primitives rcu_assign_pointer() and friends are wrapped with
> > context_unsafe(), which enforces using them to update RCU-protected
> > pointers marked with __rcu_guarded.
> >
> > Signed-off-by: Marco Elver <elver@google.com>
>
> Good reminder! I had lost track of this series.
>
> My big questions here are:
>
> o What about RCU readers using (say) preempt_disable() instead
> of rcu_read_lock_sched()?
The infrastructure that is being built up in this series will be able
to support this, it's "just" a matter of enhancing our various
interfaces/macros to use the right annotations, and working out which
kinds of contexts we want to support. There are the obvious
candidates, which this series is being applied to, as a starting
point, but longer-term there are other kinds of context rules that can
be checked with this context analysis. However, I think we have to
start somewhere.
> o What about RCU readers using local_bh_disable() instead of
> rcu_read_lock_sched()?
Same as above; this requires adding the necessary annotations to the
BH-disabling/enabling primitives.
> And keeping in mind that such readers might start in assembly language.
We can handle this by annotating the C functions invoked from assembly
with attributes like __must_hold_shared(RCU) or
__releases_shared(RCU) (if the callee is expected to release the RCU
read lock / re-enable preemption / etc.) or similar.
> One reasonable approach is to require such readers to use something like
> rcu_dereference_all() or rcu_dereference_all_check(), which could then
> have special dispensation to instead rely on run-time checks.
Agree. The current infrastructure encourages run-time checks where the
static analysis cannot be helped sufficiently otherwise (see patch:
"lockdep: Annotate lockdep assertions for context analysis").
> Another more powerful approach would be to make this facility also
> track preemption, interrupt, NMI, and BH contexts.
>
> Either way could be a significant improvement over what we have now.
>
> Thoughts?
The current infrastructure is powerful enough to allow for tracking
more contexts, such as interrupt, NMI, and BH contexts, and as I
hinted above, would be nice to eventually get to! But I think this is
also a question of how much do we want to front-load for this to be
useful, and what should incrementally be enhanced while the baseline
infrastructure is already available.
I think the current series is the baseline required support to be
useful to a large fraction of "normal" code in the kernel.
On a whole, my strategy was to get to a point where maintainers and
developers can start using context analysis where appropriate, but at
the same time build up and incrementally add more supported contexts
in parallel. There's also a good chance that, once baseline support
lands, more interested parties contribute and things progress faster
(or so I'd hope :-)).
Thanks,
-- Marco
^ permalink raw reply
* Re: An opinion about Linux security
From: Casey Schaufler @ 2025-12-10 20:08 UTC (permalink / raw)
To: Timur Chernykh, torvalds; +Cc: linux-security-module, Casey Schaufler
In-Reply-To: <CABZOZnS4im-wNK4jtGKvp3YT9hPobA503rgiptutOF8rZEwt_w@mail.gmail.com>
On 12/9/2025 4:15 PM, Timur Chernykh wrote:
> Hello Linus,
>
> I’m writing to ask for your opinion. What do you think about Linux’s
> current readiness for security-focused commercial products? I’m
> particularly interested in several areas.
>
> First, in today’s 3rd-party (out-of-tree) EDR development — EDR being
> the most common commercial class of security products — eBPF has
> effectively become the main option. Yet eBPF is extremely restrictive.
eBPF has been accepted because of these restrictions. Without them
an eBPF program could very easily reek havoc on the system's behavior.
> It is not possible to write fully expressive real-time analysis code:
> the verifier is overly strict,
This is of necessity. It is the only protection against malicious and
badly written eBPF programs.
> non-deterministic loops are not
> allowed,
Without this restriction denial of service attacks become trivial.
> and older kernels lack BTF support.
Yes. That's true for many kernel features.
> These issues create real
> limitations.
>
> Second, the removal of the out-of-tree LSM API in the 4.x kernel
> series caused significant problems for many AV/EDR vendors. I was
> unable to find an explanation in the mailing lists that convincingly
> justified that decision.
To the best of my understanding, and I have been working with LSMs
and the infrastructure for quite some time, there has never been an
out-of-tree LSM API. We have been very clear that the LSM interfaces
are fluid. We have also been very clear that supporting out-of-tree
security modules is not a requirement. There are members of the
community who would prefer that they be completely prohibited.
> The next closest mechanism, fanotify, was a genuine improvement.
> However, it does not allow an AV/EDR vendor to protect the integrity
> of its own product. Is Linux truly expecting modern AV/EDR solutions
> to rely on fanotify alone?
You will need to provide more detail about why you believe that the
integrity of an AV/EDR product cannot be protected.
> My main question is: what are the future plans?
Security remains a dynamic technology. There are quite a few plans,
from a variety of sources, with no shortage of security goals. Trying
to keep up with the concern/crisis of the day is more than sufficient
to consume the resources committed to Linux security.
> Linux provides very
> few APIs for security and dynamic analysis.
What APIs do you want? It's possible that some exist that you haven't
discovered.
> eBPF is still immature,
And it will be for some time. That is, until it is mature.
> fanotify is insufficient,
Without specifics it is quite difficult to do anything about that.
And, as we like to say, patches are welcome.
> and driver workarounds that bypass kernel
> restrictions are risky — they introduce both stability and security
> problems.
Yes. Don't do that.
> At the same time, properly implemented in-tree LSMs are not
> inherently dangerous and remain the safer, supported path for
> extending security functionality. Without safe, supported interfaces,
> however, commercial products struggle to be competitive. At the
> moment, macOS with its Endpoint Security Framework is significantly
> ahead.
Please propose the interfaces you want. An API definition would be
good, and patches even better.
> Yes, the kernel includes multiple in-tree LSM modules, but in practice
> SELinux does not simplify operations — it often complicates them,
> despite its long-standing presence. Many of the other LSMs are rarely
> used in production. As an EDR developer, I seldom encounter them, and
> when I do, they usually provide little practical value. Across
> numerous real-world server intrusions, none of these LSM modules have
> meaningfully prevented attacks, despite many years of kernel
> development.
There are numerous cases where LSMs have mitigated attacks. One case:
https://mihail-milev.medium.com/mitigating-malware-risks-with-selinux-e37cf1db7c56
Your assertion that LSMs don't provide "real" value is not substantiated
in the literature.
> Perhaps it is time for Linux to focus on more than a theoretical model
> of security.
Each of the existing LSMs addresses one or more real world issues. I would
hardly call any of the Linux security mechanisms, from mode bits and setuid
through SELinux, landlock and EVM, "theoretical".
> P.S.
> Everything above reflects only my personal opinion. I would greatly
> appreciate your response and any criticism you may have.
We are always ready to improve the security infrastructure of Linux.
If it does not meet your needs, help us improve it. The best way to
accomplish this is to provide the changes required.
> Best regards,
> Timur Chernykh
>
^ permalink raw reply
* Re: [PATCH v4 14/35] rcu: Support Clang's context analysis
From: Paul E. McKenney @ 2025-12-10 19:30 UTC (permalink / raw)
To: Marco Elver
Cc: Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon,
David S. Miller, Luc Van Oostenryck, Chris Li,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, Jonathan Corbet,
Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda,
Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda,
Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers,
Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf,
Uladzislau Rezki, Waiman Long, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120151033.3840508-15-elver@google.com>
On Thu, Nov 20, 2025 at 04:09:39PM +0100, Marco Elver wrote:
> Improve the existing annotations to properly support Clang's context
> analysis.
>
> The old annotations distinguished between RCU, RCU_BH, and RCU_SCHED;
> however, to more easily be able to express that "hold the RCU read lock"
> without caring if the normal, _bh(), or _sched() variant was used we'd
> have to remove the distinction of the latter variants: change the _bh()
> and _sched() variants to also acquire "RCU".
>
> When (and if) we introduce context guards to denote more generally that
> "IRQ", "BH", "PREEMPT" contexts are disabled, it would make sense to
> acquire these instead of RCU_BH and RCU_SCHED respectively.
>
> The above change also simplified introducing __guarded_by support, where
> only the "RCU" context guard needs to be held: introduce __rcu_guarded,
> where Clang's context analysis warns if a pointer is dereferenced
> without any of the RCU locks held, or updated without the appropriate
> helpers.
>
> The primitives rcu_assign_pointer() and friends are wrapped with
> context_unsafe(), which enforces using them to update RCU-protected
> pointers marked with __rcu_guarded.
>
> Signed-off-by: Marco Elver <elver@google.com>
Good reminder! I had lost track of this series.
My big questions here are:
o What about RCU readers using (say) preempt_disable() instead
of rcu_read_lock_sched()?
o What about RCU readers using local_bh_disable() instead of
rcu_read_lock_sched()?
And keeping in mind that such readers might start in assembly language.
One reasonable approach is to require such readers to use something like
rcu_dereference_all() or rcu_dereference_all_check(), which could then
have special dispensation to instead rely on run-time checks.
Another more powerful approach would be to make this facility also
track preemption, interrupt, NMI, and BH contexts.
Either way could be a significant improvement over what we have now.
Thoughts?
Thanx, Paul
> ---
> v3:
> * Properly support reentrancy via new compiler support.
>
> v2:
> * Reword commit message and point out reentrancy caveat.
> ---
> Documentation/dev-tools/context-analysis.rst | 2 +-
> include/linux/rcupdate.h | 77 ++++++++++++------
> lib/test_context-analysis.c | 85 ++++++++++++++++++++
> 3 files changed, 139 insertions(+), 25 deletions(-)
>
> diff --git a/Documentation/dev-tools/context-analysis.rst b/Documentation/dev-tools/context-analysis.rst
> index a3d925ce2df4..05164804a92a 100644
> --- a/Documentation/dev-tools/context-analysis.rst
> +++ b/Documentation/dev-tools/context-analysis.rst
> @@ -81,7 +81,7 @@ Supported Kernel Primitives
>
> Currently the following synchronization primitives are supported:
> `raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`,
> -`bit_spinlock`.
> +`bit_spinlock`, RCU.
>
> For context guards with an initialization function (e.g., `spin_lock_init()`),
> calling this function before initializing any guarded members or globals
> diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
> index c5b30054cd01..5cddb9019a99 100644
> --- a/include/linux/rcupdate.h
> +++ b/include/linux/rcupdate.h
> @@ -31,6 +31,16 @@
> #include <asm/processor.h>
> #include <linux/context_tracking_irq.h>
>
> +token_context_guard(RCU, __reentrant_ctx_guard);
> +token_context_guard_instance(RCU, RCU_SCHED);
> +token_context_guard_instance(RCU, RCU_BH);
> +
> +/*
> + * A convenience macro that can be used for RCU-protected globals or struct
> + * members; adds type qualifier __rcu, and also enforces __guarded_by(RCU).
> + */
> +#define __rcu_guarded __rcu __guarded_by(RCU)
> +
> #define ULONG_CMP_GE(a, b) (ULONG_MAX / 2 >= (a) - (b))
> #define ULONG_CMP_LT(a, b) (ULONG_MAX / 2 < (a) - (b))
>
> @@ -425,7 +435,8 @@ static inline void rcu_preempt_sleep_check(void) { }
>
> // See RCU_LOCKDEP_WARN() for an explanation of the double call to
> // debug_lockdep_rcu_enabled().
> -static inline bool lockdep_assert_rcu_helper(bool c)
> +static inline bool lockdep_assert_rcu_helper(bool c, const struct __ctx_guard_RCU *ctx)
> + __assumes_shared_ctx_guard(RCU) __assumes_shared_ctx_guard(ctx)
> {
> return debug_lockdep_rcu_enabled() &&
> (c || !rcu_is_watching() || !rcu_lockdep_current_cpu_online()) &&
> @@ -438,7 +449,7 @@ static inline bool lockdep_assert_rcu_helper(bool c)
> * Splats if lockdep is enabled and there is no rcu_read_lock() in effect.
> */
> #define lockdep_assert_in_rcu_read_lock() \
> - WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_lock_map)))
> + WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_lock_map), RCU))
>
> /**
> * lockdep_assert_in_rcu_read_lock_bh - WARN if not protected by rcu_read_lock_bh()
> @@ -448,7 +459,7 @@ static inline bool lockdep_assert_rcu_helper(bool c)
> * actual rcu_read_lock_bh() is required.
> */
> #define lockdep_assert_in_rcu_read_lock_bh() \
> - WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_bh_lock_map)))
> + WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_bh_lock_map), RCU_BH))
>
> /**
> * lockdep_assert_in_rcu_read_lock_sched - WARN if not protected by rcu_read_lock_sched()
> @@ -458,7 +469,7 @@ static inline bool lockdep_assert_rcu_helper(bool c)
> * instead an actual rcu_read_lock_sched() is required.
> */
> #define lockdep_assert_in_rcu_read_lock_sched() \
> - WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_sched_lock_map)))
> + WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_sched_lock_map), RCU_SCHED))
>
> /**
> * lockdep_assert_in_rcu_reader - WARN if not within some type of RCU reader
> @@ -476,17 +487,17 @@ static inline bool lockdep_assert_rcu_helper(bool c)
> WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_lock_map) && \
> !lock_is_held(&rcu_bh_lock_map) && \
> !lock_is_held(&rcu_sched_lock_map) && \
> - preemptible()))
> + preemptible(), RCU))
>
> #else /* #ifdef CONFIG_PROVE_RCU */
>
> #define RCU_LOCKDEP_WARN(c, s) do { } while (0 && (c))
> #define rcu_sleep_check() do { } while (0)
>
> -#define lockdep_assert_in_rcu_read_lock() do { } while (0)
> -#define lockdep_assert_in_rcu_read_lock_bh() do { } while (0)
> -#define lockdep_assert_in_rcu_read_lock_sched() do { } while (0)
> -#define lockdep_assert_in_rcu_reader() do { } while (0)
> +#define lockdep_assert_in_rcu_read_lock() __assume_shared_ctx_guard(RCU)
> +#define lockdep_assert_in_rcu_read_lock_bh() __assume_shared_ctx_guard(RCU_BH)
> +#define lockdep_assert_in_rcu_read_lock_sched() __assume_shared_ctx_guard(RCU_SCHED)
> +#define lockdep_assert_in_rcu_reader() __assume_shared_ctx_guard(RCU)
>
> #endif /* #else #ifdef CONFIG_PROVE_RCU */
>
> @@ -506,11 +517,11 @@ static inline bool lockdep_assert_rcu_helper(bool c)
> #endif /* #else #ifdef __CHECKER__ */
>
> #define __unrcu_pointer(p, local) \
> -({ \
> +context_unsafe( \
> typeof(*p) *local = (typeof(*p) *__force)(p); \
> rcu_check_sparse(p, __rcu); \
> - ((typeof(*p) __force __kernel *)(local)); \
> -})
> + ((typeof(*p) __force __kernel *)(local)) \
> +)
> /**
> * unrcu_pointer - mark a pointer as not being RCU protected
> * @p: pointer needing to lose its __rcu property
> @@ -586,7 +597,7 @@ static inline bool lockdep_assert_rcu_helper(bool c)
> * other macros that it invokes.
> */
> #define rcu_assign_pointer(p, v) \
> -do { \
> +context_unsafe( \
> uintptr_t _r_a_p__v = (uintptr_t)(v); \
> rcu_check_sparse(p, __rcu); \
> \
> @@ -594,7 +605,7 @@ do { \
> WRITE_ONCE((p), (typeof(p))(_r_a_p__v)); \
> else \
> smp_store_release(&p, RCU_INITIALIZER((typeof(p))_r_a_p__v)); \
> -} while (0)
> +)
>
> /**
> * rcu_replace_pointer() - replace an RCU pointer, returning its old value
> @@ -861,9 +872,10 @@ do { \
> * only when acquiring spinlocks that are subject to priority inheritance.
> */
> static __always_inline void rcu_read_lock(void)
> + __acquires_shared(RCU)
> {
> __rcu_read_lock();
> - __acquire(RCU);
> + __acquire_shared(RCU);
> rcu_lock_acquire(&rcu_lock_map);
> RCU_LOCKDEP_WARN(!rcu_is_watching(),
> "rcu_read_lock() used illegally while idle");
> @@ -891,11 +903,12 @@ static __always_inline void rcu_read_lock(void)
> * See rcu_read_lock() for more information.
> */
> static inline void rcu_read_unlock(void)
> + __releases_shared(RCU)
> {
> RCU_LOCKDEP_WARN(!rcu_is_watching(),
> "rcu_read_unlock() used illegally while idle");
> rcu_lock_release(&rcu_lock_map); /* Keep acq info for rls diags. */
> - __release(RCU);
> + __release_shared(RCU);
> __rcu_read_unlock();
> }
>
> @@ -914,9 +927,11 @@ static inline void rcu_read_unlock(void)
> * was invoked from some other task.
> */
> static inline void rcu_read_lock_bh(void)
> + __acquires_shared(RCU) __acquires_shared(RCU_BH)
> {
> local_bh_disable();
> - __acquire(RCU_BH);
> + __acquire_shared(RCU);
> + __acquire_shared(RCU_BH);
> rcu_lock_acquire(&rcu_bh_lock_map);
> RCU_LOCKDEP_WARN(!rcu_is_watching(),
> "rcu_read_lock_bh() used illegally while idle");
> @@ -928,11 +943,13 @@ static inline void rcu_read_lock_bh(void)
> * See rcu_read_lock_bh() for more information.
> */
> static inline void rcu_read_unlock_bh(void)
> + __releases_shared(RCU) __releases_shared(RCU_BH)
> {
> RCU_LOCKDEP_WARN(!rcu_is_watching(),
> "rcu_read_unlock_bh() used illegally while idle");
> rcu_lock_release(&rcu_bh_lock_map);
> - __release(RCU_BH);
> + __release_shared(RCU_BH);
> + __release_shared(RCU);
> local_bh_enable();
> }
>
> @@ -952,9 +969,11 @@ static inline void rcu_read_unlock_bh(void)
> * rcu_read_lock_sched() was invoked from an NMI handler.
> */
> static inline void rcu_read_lock_sched(void)
> + __acquires_shared(RCU) __acquires_shared(RCU_SCHED)
> {
> preempt_disable();
> - __acquire(RCU_SCHED);
> + __acquire_shared(RCU);
> + __acquire_shared(RCU_SCHED);
> rcu_lock_acquire(&rcu_sched_lock_map);
> RCU_LOCKDEP_WARN(!rcu_is_watching(),
> "rcu_read_lock_sched() used illegally while idle");
> @@ -962,9 +981,11 @@ static inline void rcu_read_lock_sched(void)
>
> /* Used by lockdep and tracing: cannot be traced, cannot call lockdep. */
> static inline notrace void rcu_read_lock_sched_notrace(void)
> + __acquires_shared(RCU) __acquires_shared(RCU_SCHED)
> {
> preempt_disable_notrace();
> - __acquire(RCU_SCHED);
> + __acquire_shared(RCU);
> + __acquire_shared(RCU_SCHED);
> }
>
> /**
> @@ -973,22 +994,27 @@ static inline notrace void rcu_read_lock_sched_notrace(void)
> * See rcu_read_lock_sched() for more information.
> */
> static inline void rcu_read_unlock_sched(void)
> + __releases_shared(RCU) __releases_shared(RCU_SCHED)
> {
> RCU_LOCKDEP_WARN(!rcu_is_watching(),
> "rcu_read_unlock_sched() used illegally while idle");
> rcu_lock_release(&rcu_sched_lock_map);
> - __release(RCU_SCHED);
> + __release_shared(RCU_SCHED);
> + __release_shared(RCU);
> preempt_enable();
> }
>
> /* Used by lockdep and tracing: cannot be traced, cannot call lockdep. */
> static inline notrace void rcu_read_unlock_sched_notrace(void)
> + __releases_shared(RCU) __releases_shared(RCU_SCHED)
> {
> - __release(RCU_SCHED);
> + __release_shared(RCU_SCHED);
> + __release_shared(RCU);
> preempt_enable_notrace();
> }
>
> static __always_inline void rcu_read_lock_dont_migrate(void)
> + __acquires_shared(RCU)
> {
> if (IS_ENABLED(CONFIG_PREEMPT_RCU))
> migrate_disable();
> @@ -996,6 +1022,7 @@ static __always_inline void rcu_read_lock_dont_migrate(void)
> }
>
> static inline void rcu_read_unlock_migrate(void)
> + __releases_shared(RCU)
> {
> rcu_read_unlock();
> if (IS_ENABLED(CONFIG_PREEMPT_RCU))
> @@ -1041,10 +1068,10 @@ static inline void rcu_read_unlock_migrate(void)
> * ordering guarantees for either the CPU or the compiler.
> */
> #define RCU_INIT_POINTER(p, v) \
> - do { \
> + context_unsafe( \
> rcu_check_sparse(p, __rcu); \
> WRITE_ONCE(p, RCU_INITIALIZER(v)); \
> - } while (0)
> + )
>
> /**
> * RCU_POINTER_INITIALIZER() - statically initialize an RCU protected pointer
> @@ -1206,4 +1233,6 @@ DEFINE_LOCK_GUARD_0(rcu,
> } while (0),
> rcu_read_unlock())
>
> +DECLARE_LOCK_GUARD_0_ATTRS(rcu, __acquires_shared(RCU), __releases_shared(RCU))
> +
> #endif /* __LINUX_RCUPDATE_H */
> diff --git a/lib/test_context-analysis.c b/lib/test_context-analysis.c
> index 77e599a9281b..f18b7252646d 100644
> --- a/lib/test_context-analysis.c
> +++ b/lib/test_context-analysis.c
> @@ -7,6 +7,7 @@
> #include <linux/bit_spinlock.h>
> #include <linux/build_bug.h>
> #include <linux/mutex.h>
> +#include <linux/rcupdate.h>
> #include <linux/seqlock.h>
> #include <linux/spinlock.h>
>
> @@ -277,3 +278,87 @@ static void __used test_bit_spin_lock(struct test_bit_spinlock_data *d)
> bit_spin_unlock(3, &d->bits);
> }
> }
> +
> +/*
> + * Test that we can mark a variable guarded by RCU, and we can dereference and
> + * write to the pointer with RCU's primitives.
> + */
> +struct test_rcu_data {
> + long __rcu_guarded *data;
> +};
> +
> +static void __used test_rcu_guarded_reader(struct test_rcu_data *d)
> +{
> + rcu_read_lock();
> + (void)rcu_dereference(d->data);
> + rcu_read_unlock();
> +
> + rcu_read_lock_bh();
> + (void)rcu_dereference(d->data);
> + rcu_read_unlock_bh();
> +
> + rcu_read_lock_sched();
> + (void)rcu_dereference(d->data);
> + rcu_read_unlock_sched();
> +}
> +
> +static void __used test_rcu_guard(struct test_rcu_data *d)
> +{
> + guard(rcu)();
> + (void)rcu_dereference(d->data);
> +}
> +
> +static void __used test_rcu_guarded_updater(struct test_rcu_data *d)
> +{
> + rcu_assign_pointer(d->data, NULL);
> + RCU_INIT_POINTER(d->data, NULL);
> + (void)unrcu_pointer(d->data);
> +}
> +
> +static void wants_rcu_held(void) __must_hold_shared(RCU) { }
> +static void wants_rcu_held_bh(void) __must_hold_shared(RCU_BH) { }
> +static void wants_rcu_held_sched(void) __must_hold_shared(RCU_SCHED) { }
> +
> +static void __used test_rcu_lock_variants(void)
> +{
> + rcu_read_lock();
> + wants_rcu_held();
> + rcu_read_unlock();
> +
> + rcu_read_lock_bh();
> + wants_rcu_held_bh();
> + rcu_read_unlock_bh();
> +
> + rcu_read_lock_sched();
> + wants_rcu_held_sched();
> + rcu_read_unlock_sched();
> +}
> +
> +static void __used test_rcu_lock_reentrant(void)
> +{
> + rcu_read_lock();
> + rcu_read_lock();
> + rcu_read_lock_bh();
> + rcu_read_lock_bh();
> + rcu_read_lock_sched();
> + rcu_read_lock_sched();
> +
> + rcu_read_unlock_sched();
> + rcu_read_unlock_sched();
> + rcu_read_unlock_bh();
> + rcu_read_unlock_bh();
> + rcu_read_unlock();
> + rcu_read_unlock();
> +}
> +
> +static void __used test_rcu_assert_variants(void)
> +{
> + lockdep_assert_in_rcu_read_lock();
> + wants_rcu_held();
> +
> + lockdep_assert_in_rcu_read_lock_bh();
> + wants_rcu_held_bh();
> +
> + lockdep_assert_in_rcu_read_lock_sched();
> + wants_rcu_held_sched();
> +}
> --
> 2.52.0.rc1.455.g30608eb744-goog
>
^ permalink raw reply
* Re: [PATCH 1/1] IMA event log trimming
From: Mimi Zohar @ 2025-12-10 19:16 UTC (permalink / raw)
To: Roberto Sassu, James Bottomley, steven chen, linux-integrity
Cc: roberto.sassu, dmitry.kasatkin, eric.snowberg, paul, jmorris,
serge, linux-security-module, anirudhve, gregorylumen, nramas,
sushring
In-Reply-To: <736e21826c6a283d74d592393c392abbff56a409.camel@huaweicloud.com>
On Tue, 2025-12-09 at 10:36 +0100, Roberto Sassu wrote:
> On Tue, 2025-12-09 at 07:17 +0900, James Bottomley wrote:
> > On Mon, 2025-12-08 at 10:40 +0100, Roberto Sassu wrote:
> > > I have the impression that none the functionality you cited has to be
> > > implemented in the kernel, because the only component one can trust
> > > to verify the integrity of the IMA measurements list is the TPM.
> > > Whether either the kernel or user space retain the measurements is
> > > irrelevant.
> >
> > That's correct, I'm not advocating moving quoting into the kernel. Co-
> > ordinating the trim with where the quote gets you to is phenomenally
> > useful. While you could theoretically store any mismatch in userspace,
> > having two locations for the log makes it more error prone.
> >
> > > I believe that the only role of the kernel is to get rid of the
> > > measurements entries as fast as possible (the kernel would act more
> > > like a buffer).
> >
> > I wouldn't say that, I'd say to get rid of measurements that the user
> > has indicated are of no further use\
I really hope I'm wrong, but I don't like the sound of "No further use". The
complete IMA measurement needs to be saved somewhere to support multiple
verifiers.
>
> Different users could have different and conflicting requirements, and
> we would spend time trying to conciliate those. We can avoid that by
> doing it the same for everyone, and the additional cost of handling it
> I believe it is fair.
>
> I could accept staging N entries since I already agreed with Gregory
> and Steven, and since it requires only an extra iteration in the linked
> list. The other desired functionality should be implemented in user
> space.
>
> > > This was actually the intent of my original proposal in
> > > https://github.com/linux-integrity/linux/issues/1 . The idea of
> > > staging (was snapshotting, but Mimi thinks the term is not accurate)
> > > is simply to detach the entire IMA measurement list as fast as
> > > possible. Further read and delete after staging is done without
> > > interfering with new measurements (sure, the detaching of the hash
> > > table is not yet as efficient as I hoped).
> >
> > From the application point of view, offloading the log and random
> > points is a bit more work because now the log collector has to be
> > modified to look in multiple locations and we'd also need an agreement
> > of where those locations are and how the log is sequenced in a naming
> > scheme so it's the same for every distribution. If the application is
> > in charge of trimming the log at a particular point, collection remains
> > the same (it can simply be the existing in-kernel location), so we
> > don't need a cross distro agreement, and the trim can simply be added
> > as an extra function.
>
> It could be a single location, the user space program would be
> responsible to present the IMA measurement list as if it was never
> trimmed.
Even if it is a single, new location or user space program/daemon, it needs to
be documented. The user space program/daemon would be responsible for returning
the IMA measurement list.
Keylime, for example, supports incremental verification[1], meaning initially
the verification would be from the beginning of the IMA measurement list, but
subsequently the verifier could do an incremental verification. For incremental
verification, state information (e.g. number of records verified, pcr value)
needs to be preserved by the verifier, not the kernel.
[1] Keylime commit 7870f45f909f ("ima: Remember the number of lines successfully
processed and last IMA PCR value(s)")
--
thanks,
Mimi
^ permalink raw reply
* Re: [RFC][PATCH] ima: Add support for staging measurements for deletion
From: Gregory Lumen @ 2025-12-10 19:12 UTC (permalink / raw)
To: Roberto Sassu
Cc: corbet, zohar, dmitry.kasatkin, eric.snowberg, paul, jmorris,
serge, linux-doc, linux-kernel, linux-integrity,
linux-security-module, chenste, nramas, Roberto Sassu
In-Reply-To: <20251209101725.3680225-1-roberto.sassu@huaweicloud.com>
Roberto,
The proposed approach appears to be workable. However, if our primary goal
here is to enable UM to free kernel memory consumed by the IMA log with an
absolute minimum of kernel functionality/change, then I would argue that
the proposed Stage-then-delete approach still represents unnecessary
complexity when compared to a trim-to-N solution. Specifically:
- Any functional benefit offered through the introduction of a staged
measurement list could be equally achieved in UM with a trim-to-N solution
coupled with the proposed ima_measure_users counter for access locking.
- There exists a potential UM measurement-loss race condition introduced
by the staging functionality that would not exist with a trim-to-N
approach. (Occurs if a kexec call occurs after a UM agent has staged
measurements for deletion, but has not completed copying them to
userspace). This could be avoided by persisting staged measurements across
kexec calls at the cost of making the proposed change larger.
Thanks,
-Gregory Lumen
^ permalink raw reply
* [PATCH v4 8/8] tpm: In tpm_get_random() replace 'retries' with a zero check
From: Jarkko Sakkinen @ 2025-12-10 17:20 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe, David Howells,
Paul Moore, James Morris, Serge E. Hallyn, open list,
open list:KEYS/KEYRINGS, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251210172027.109938-1-jarkko@kernel.org>
Check for zero byte read instead of having retries counter in order to make
wait flag properly enforcing. Progress is still guaranteed given the zero
check and iterations are capped up to TPM_MAX_RNG_DATA iterations at most
(as per theoretical limit).
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
drivers/char/tpm/tpm-interface.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
index 5cc2bbabd57a..594ad095a90b 100644
--- a/drivers/char/tpm/tpm-interface.c
+++ b/drivers/char/tpm/tpm-interface.c
@@ -620,7 +620,6 @@ int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max, bool wait)
{
u32 num_bytes = max;
u8 *out_ptr = out;
- int retries = 5;
int total = 0;
int rc;
@@ -646,8 +645,12 @@ int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max, bool wait)
else
rc = tpm1_get_random(chip, out_ptr, num_bytes);
- if (rc < 0)
+ if (rc <= 0) {
+ if (!rc)
+ rc = -EIO;
+
goto err;
+ }
if (!wait) {
total = rc;
@@ -657,7 +660,7 @@ int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max, bool wait)
out_ptr += rc;
total += rc;
num_bytes -= rc;
- } while (retries-- && total < max);
+ } while (total < max);
tpm_put_ops(chip);
return total ? total : -EIO;
--
2.39.5
^ permalink raw reply related
* [PATCH v4 7/8] tpm: Send only one at most TPM2_GetRandom command
From: Jarkko Sakkinen @ 2025-12-10 17:20 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, David S . Miller, Herbert Xu, Peter Huewe,
Jason Gunthorpe, David Howells, Paul Moore, James Morris,
Serge E. Hallyn, James Bottomley, Mimi Zohar, open list,
open list:KEYS/KEYRINGS, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251210172027.109938-1-jarkko@kernel.org>
hwrng framework does not have a requirement that the all bytes requested
need to be provided. By enforcing such a requirement internally, TPM driver
can cause unpredictability in latency, as a single tpm_get_random() call
can result multiple TPM commands.
Especially, when TCG_TPM2_HMAC is enabled, extra roundtrips could have
significant effect to the system latency.
Add a wait-parameter to enforce the old behavior and set it to true only at
the call sites for TPM 1.2 keys. At the call sites of hwrng, set @wait to
false.
Cc: David S. Miller <davem@davemloft.net>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
v4:
- Fixed grammar mistakes.
---
drivers/char/tpm/tpm-chip.c | 2 +-
drivers/char/tpm/tpm-interface.c | 11 +++++++++--
include/linux/tpm.h | 2 +-
security/keys/trusted-keys/trusted_tpm1.c | 8 ++++----
4 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
index 082b910ddf0d..8fca4373e2df 100644
--- a/drivers/char/tpm/tpm-chip.c
+++ b/drivers/char/tpm/tpm-chip.c
@@ -494,7 +494,7 @@ static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
{
struct tpm_chip *chip = container_of(rng, struct tpm_chip, hwrng);
- return tpm_get_random(chip, data, max);
+ return tpm_get_random(chip, data, max, false);
}
static bool tpm_is_hwrng_enabled(struct tpm_chip *chip)
diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
index ab52a1cb0a78..5cc2bbabd57a 100644
--- a/drivers/char/tpm/tpm-interface.c
+++ b/drivers/char/tpm/tpm-interface.c
@@ -604,9 +604,11 @@ static int tpm2_get_random(struct tpm_chip *chip, u8 *out, size_t max)
* @chip: A &tpm_chip instance. Whenset to %NULL, the default chip is used.
* @out: Destination buffer for the acquired random bytes.
* @max: The maximum number of bytes to write to @out.
+ * @wait: Set to true when all of the @max bytes need to be acquired.
*
* Iterates pulling more bytes from TPM up until all of the @max bytes have been
- * received.
+ * received, when @wait it sets true. Otherwise, the queries for @max bytes from
+ * TPM exactly once, and returns the bytes that were received.
*
* Returns the number of random bytes read on success.
* Returns -EINVAL when @out is NULL, or @max is not between zero and
@@ -614,7 +616,7 @@ static int tpm2_get_random(struct tpm_chip *chip, u8 *out, size_t max)
* Returns tpm_transmit_cmd() error codes when the TPM command results an
* error.
*/
-int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max)
+int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max, bool wait)
{
u32 num_bytes = max;
u8 *out_ptr = out;
@@ -647,6 +649,11 @@ int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max)
if (rc < 0)
goto err;
+ if (!wait) {
+ total = rc;
+ break;
+ }
+
out_ptr += rc;
total += rc;
num_bytes -= rc;
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index e68995df8796..177833d6b965 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -485,7 +485,7 @@ extern int tpm_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digest);
extern int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digests);
-extern int tpm_get_random(struct tpm_chip *chip, u8 *data, size_t max);
+int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max, bool wait);
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);
diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
index 759c1ecb0435..f36f6a0b533f 100644
--- a/security/keys/trusted-keys/trusted_tpm1.c
+++ b/security/keys/trusted-keys/trusted_tpm1.c
@@ -361,7 +361,7 @@ static int osap(struct tpm_buf *tb, struct osapsess *s,
unsigned char ononce[TPM_NONCE_SIZE];
int ret;
- ret = tpm_get_random(chip, ononce, TPM_NONCE_SIZE);
+ ret = tpm_get_random(chip, ononce, TPM_NONCE_SIZE, true);
if (ret < 0)
return ret;
@@ -454,7 +454,7 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype,
memcpy(td->xorwork + SHA1_DIGEST_SIZE, sess.enonce, SHA1_DIGEST_SIZE);
sha1(td->xorwork, SHA1_DIGEST_SIZE * 2, td->xorhash);
- ret = tpm_get_random(chip, td->nonceodd, TPM_NONCE_SIZE);
+ ret = tpm_get_random(chip, td->nonceodd, TPM_NONCE_SIZE, true);
if (ret < 0)
goto out;
@@ -565,7 +565,7 @@ static int tpm_unseal(struct tpm_buf *tb,
}
ordinal = htonl(TPM_ORD_UNSEAL);
- ret = tpm_get_random(chip, nonceodd, TPM_NONCE_SIZE);
+ ret = tpm_get_random(chip, nonceodd, TPM_NONCE_SIZE, true);
if (ret < 0)
return ret;
@@ -938,7 +938,7 @@ static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob)
static int trusted_tpm_get_random(unsigned char *key, size_t key_len)
{
- return tpm_get_random(chip, key, key_len);
+ return tpm_get_random(chip, key, key_len, true);
}
static int __init init_digests(void)
--
2.39.5
^ permalink raw reply related
* [PATCH v4 6/8] tpm: Orchestrate TPM commands in tpm_get_random()
From: Jarkko Sakkinen @ 2025-12-10 17:20 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe, David Howells,
Paul Moore, James Morris, Serge E. Hallyn, open list,
open list:KEYS/KEYRINGS, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251210172027.109938-1-jarkko@kernel.org>
tpm1_get_random() and tpm2_get_random() contain duplicate orchestration
code. Consolidate orchestration to tpm_get_random().
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
drivers/char/tpm/tpm-interface.c | 165 ++++++++++++++++++++++++++++---
drivers/char/tpm/tpm.h | 2 -
drivers/char/tpm/tpm1-cmd.c | 62 ------------
drivers/char/tpm/tpm2-cmd.c | 95 ------------------
4 files changed, 154 insertions(+), 170 deletions(-)
diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
index f745a098908b..ab52a1cb0a78 100644
--- a/drivers/char/tpm/tpm-interface.c
+++ b/drivers/char/tpm/tpm-interface.c
@@ -26,7 +26,7 @@
#include <linux/suspend.h>
#include <linux/freezer.h>
#include <linux/tpm_eventlog.h>
-
+#include <linux/tpm_command.h>
#include "tpm.h"
/*
@@ -486,19 +486,143 @@ int tpm_pm_resume(struct device *dev)
}
EXPORT_SYMBOL_GPL(tpm_pm_resume);
+struct tpm1_get_random_out {
+ __be32 rng_data_len;
+ u8 rng_data[TPM_MAX_RNG_DATA];
+} __packed;
+
+static int tpm1_get_random(struct tpm_chip *chip, u8 *out, size_t max)
+{
+ struct tpm1_get_random_out *resp;
+ u32 recd;
+ int rc;
+
+ if (!out || !max || max > TPM_MAX_RNG_DATA)
+ return -EINVAL;
+
+ 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_GETRANDOM);
+ tpm_buf_append_u32(buf, max);
+
+ rc = tpm_transmit_cmd(chip, buf, sizeof(resp->rng_data_len), "TPM_GetRandom");
+ if (rc) {
+ if (rc > 0)
+ rc = -EIO;
+ return rc;
+ }
+
+ resp = (struct tpm1_get_random_out *)&buf->data[TPM_HEADER_SIZE];
+
+ recd = be32_to_cpu(resp->rng_data_len);
+ if (recd > max)
+ return -EIO;
+
+ if (buf->length < TPM_HEADER_SIZE + sizeof(resp->rng_data_len) + recd)
+ return -EIO;
+
+ memcpy(out, resp->rng_data, recd);
+ return recd;
+}
+
+struct tpm2_get_random_out {
+ __be16 size;
+ u8 buffer[TPM_MAX_RNG_DATA];
+} __packed;
+
+static int tpm2_get_random(struct tpm_chip *chip, u8 *out, size_t max)
+{
+ struct tpm2_get_random_out *resp;
+ struct tpm_header *head;
+ off_t offset;
+ u32 recd;
+ int ret;
+
+ if (!out || !max || max > TPM_MAX_RNG_DATA)
+ return -EINVAL;
+
+ 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_SESSIONS, TPM2_CC_GET_RANDOM);
+ if (tpm2_chip_auth(chip)) {
+ tpm_buf_append_hmac_session(chip, buf,
+ TPM2_SA_ENCRYPT | TPM2_SA_CONTINUE_SESSION,
+ NULL, 0);
+ } else {
+ head = (struct tpm_header *)buf->data;
+ head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
+ }
+ tpm_buf_append_u16(buf, max);
+
+ ret = tpm_buf_fill_hmac_session(chip, buf);
+ if (ret)
+ return ret;
+
+ ret = tpm_transmit_cmd(chip, buf, offsetof(struct tpm2_get_random_out, buffer),
+ "TPM2_GetRandom");
+
+ ret = tpm_buf_check_hmac_response(chip, buf, ret);
+ if (ret) {
+ if (ret > 0)
+ ret = -EIO;
+
+ goto out;
+ }
+
+ 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;
+
+ resp = (struct tpm2_get_random_out *)&buf->data[offset];
+ recd = min_t(u32, be16_to_cpu(resp->size), max);
+
+ if (tpm_buf_length(buf) <
+ TPM_HEADER_SIZE + offsetof(struct tpm2_get_random_out, buffer) + recd) {
+ ret = -EIO;
+ goto out;
+ }
+
+ memcpy(out, resp->buffer, recd);
+ return recd;
+
+out:
+ tpm2_end_auth_session(chip);
+ return ret;
+}
+
/**
- * tpm_get_random() - get random bytes from the TPM's RNG
- * @chip: a &struct tpm_chip instance, %NULL for the default chip
- * @out: destination buffer for the random bytes
- * @max: the max number of bytes to write to @out
+ * tpm_get_random() - Get random bytes from the TPM's RNG
+ * @chip: A &tpm_chip instance. Whenset to %NULL, the default chip is used.
+ * @out: Destination buffer for the acquired random bytes.
+ * @max: The maximum number of bytes to write to @out.
*
- * Return: number of random bytes read or a negative error value.
+ * Iterates pulling more bytes from TPM up until all of the @max bytes have been
+ * received.
+ *
+ * Returns the number of random bytes read on success.
+ * Returns -EINVAL when @out is NULL, or @max is not between zero and
+ * %TPM_MAX_RNG_DATA.
+ * Returns tpm_transmit_cmd() error codes when the TPM command results an
+ * error.
*/
int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max)
{
+ u32 num_bytes = max;
+ u8 *out_ptr = out;
+ int retries = 5;
+ int total = 0;
int rc;
- if (!out || max > TPM_MAX_RNG_DATA)
+ if (!out || !max || max > TPM_MAX_RNG_DATA)
return -EINVAL;
if (!chip)
@@ -508,11 +632,30 @@ int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max)
if (rc)
return rc;
- if (chip->flags & TPM_CHIP_FLAG_TPM2)
- rc = tpm2_get_random(chip, out, max);
- else
- rc = tpm1_get_random(chip, out, max);
+ if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+ rc = tpm2_start_auth_session(chip);
+ if (rc)
+ return rc;
+ }
+
+ do {
+ if (chip->flags & TPM_CHIP_FLAG_TPM2)
+ rc = tpm2_get_random(chip, out_ptr, num_bytes);
+ else
+ rc = tpm1_get_random(chip, out_ptr, num_bytes);
+
+ if (rc < 0)
+ goto err;
+
+ out_ptr += rc;
+ total += rc;
+ num_bytes -= rc;
+ } while (retries-- && total < max);
+
+ tpm_put_ops(chip);
+ return total ? total : -EIO;
+err:
tpm_put_ops(chip);
return rc;
}
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index 5395927c62fc..c4bbd8f04605 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -250,7 +250,6 @@ int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf);
ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
const char *desc, size_t min_cap_length);
-int tpm1_get_random(struct tpm_chip *chip, u8 *out, size_t max);
int tpm1_get_pcr_allocation(struct tpm_chip *chip);
unsigned long tpm_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
int tpm_pm_suspend(struct device *dev);
@@ -290,7 +289,6 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digest, u16 *digest_size_ptr);
int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digests);
-int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max);
ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id,
u32 *value, const char *desc);
diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
index 11090053ef54..d8f16e66666b 100644
--- a/drivers/char/tpm/tpm1-cmd.c
+++ b/drivers/char/tpm/tpm1-cmd.c
@@ -502,68 +502,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
}
EXPORT_SYMBOL_GPL(tpm1_getcap);
-#define TPM_ORD_GET_RANDOM 70
-struct tpm1_get_random_out {
- __be32 rng_data_len;
- u8 rng_data[TPM_MAX_RNG_DATA];
-} __packed;
-
-/**
- * tpm1_get_random() - get random bytes from the TPM's RNG
- * @chip: a &struct tpm_chip instance
- * @dest: destination buffer for the random bytes
- * @max: the maximum number of bytes to write to @dest
- *
- * Return:
- * * number of bytes read
- * * -errno (positive TPM return codes are masked to -EIO)
- */
-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);
- u32 total = 0;
- int retries = 5;
- u32 recd;
- int 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_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), "TPM_GetRandom");
- if (rc) {
- if (rc > 0)
- rc = -EIO;
- return rc;
- }
-
- out = (struct tpm1_get_random_out *)&buf->data[TPM_HEADER_SIZE];
-
- recd = be32_to_cpu(out->rng_data_len);
- if (recd > num_bytes)
- return -EFAULT;
-
- if (buf->length < TPM_HEADER_SIZE +
- sizeof(out->rng_data_len) + recd)
- return -EFAULT;
-
- memcpy(dest, out->rng_data, recd);
-
- dest += recd;
- total += recd;
- num_bytes -= recd;
- } while (retries-- && total < max);
-
- rc = total ? (int)total : -EIO;
- return rc;
-}
-
#define TPM_ORD_PCRREAD 21
int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
{
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index bc50d6b734cf..f066efb54a2c 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -233,101 +233,6 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
return rc;
}
-struct tpm2_get_random_out {
- __be16 size;
- u8 buffer[TPM_MAX_RNG_DATA];
-} __packed;
-
-/**
- * tpm2_get_random() - get random bytes from the TPM RNG
- *
- * @chip: a &tpm_chip instance
- * @dest: destination buffer
- * @max: the max number of random bytes to pull
- *
- * Return:
- * size of the buffer on success,
- * -errno otherwise (positive TPM return codes are masked to -EIO)
- */
-int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
-{
- struct tpm2_get_random_out *out;
- struct tpm_header *head;
- u32 recd;
- u32 num_bytes = max;
- int err;
- int total = 0;
- int retries = 5;
- u8 *dest_ptr = dest;
- off_t offset;
-
- 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;
-
- tpm_buf_init(buf, TPM_BUFSIZE);
- do {
- tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_GET_RANDOM);
- if (tpm2_chip_auth(chip)) {
- tpm_buf_append_hmac_session(chip, buf,
- TPM2_SA_ENCRYPT |
- TPM2_SA_CONTINUE_SESSION,
- NULL, 0);
- } else {
- head = (struct tpm_header *)buf->data;
- 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)
- return err;
-
- err = tpm_transmit_cmd(chip, buf,
- offsetof(struct tpm2_get_random_out,
- buffer),
- "TPM2_GetRandom");
- err = tpm_buf_check_hmac_response(chip, buf, err);
- if (err) {
- if (err > 0)
- err = -EIO;
- goto out;
- }
-
- 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];
- recd = min_t(u32, be16_to_cpu(out->size), num_bytes);
- if (tpm_buf_length(buf) <
- TPM_HEADER_SIZE +
- offsetof(struct tpm2_get_random_out, buffer) +
- recd) {
- err = -EFAULT;
- goto out;
- }
- memcpy(dest_ptr, out->buffer, recd);
-
- dest_ptr += recd;
- total += recd;
- num_bytes -= recd;
- } while (retries-- && total < max);
-
- return total ? total : -EIO;
-out:
- tpm2_end_auth_session(chip);
- return err;
-}
-
/**
* tpm2_flush_context() - execute a TPM2_FlushContext command
* @chip: TPM chip to use
--
2.39.5
^ permalink raw reply related
* [PATCH v4 5/8] tpm-buf: Remove tpm_buf_append_handle
From: Jarkko Sakkinen @ 2025-12-10 17:20 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe, David Howells,
Paul Moore, James Morris, Serge E. Hallyn, open list,
open list:KEYS/KEYRINGS, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251210172027.109938-1-jarkko@kernel.org>
Since the number of handles is fixed to a single handle, eliminate all uses
of buf->handles and deduce values during compile-time.
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
v2:
- Streamline the code change and remove dead code.
---
drivers/char/tpm/tpm-buf.c | 25 -------------------------
drivers/char/tpm/tpm2-cmd.c | 6 ++----
drivers/char/tpm/tpm2-sessions.c | 14 ++------------
include/linux/tpm.h | 2 --
4 files changed, 4 insertions(+), 43 deletions(-)
diff --git a/drivers/char/tpm/tpm-buf.c b/drivers/char/tpm/tpm-buf.c
index 73be8a87b472..752c69b8a4f5 100644
--- a/drivers/char/tpm/tpm-buf.c
+++ b/drivers/char/tpm/tpm-buf.c
@@ -40,7 +40,6 @@ static void __tpm_buf_reset(struct tpm_buf *buf, u16 buf_size, u16 tag, u32 ordi
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);
@@ -56,7 +55,6 @@ static void __tpm_buf_reset_sized(struct tpm_buf *buf, u16 buf_size)
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;
}
@@ -177,29 +175,6 @@ void tpm_buf_append_u32(struct tpm_buf *buf, const u32 value)
}
EXPORT_SYMBOL_GPL(tpm_buf_append_u32);
-/**
- * tpm_buf_append_handle() - Add a handle
- * @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_buf *buf, u32 handle)
-{
- if (buf->flags & TPM_BUF_INVALID)
- return;
-
- if (buf->flags & TPM_BUF_TPM2B) {
- WARN(1, "tpm-buf: invalid type: TPM2B\n");
- buf->flags |= TPM_BUF_INVALID;
- return;
- }
-
- tpm_buf_append_u32(buf, handle);
- buf->handles++;
-}
-
/**
* tpm_buf_read() - Read from a TPM buffer
* @buf: &tpm_buf instance
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index 436ee82620e4..bc50d6b734cf 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -205,7 +205,7 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
return rc;
tpm_buf_append_hmac_session(chip, buf, 0, NULL, 0);
} else {
- tpm_buf_append_handle(buf, pcr_idx);
+ tpm_buf_append_u32(buf, pcr_idx);
tpm_buf_append_auth(chip, buf, NULL, 0);
}
@@ -281,10 +281,8 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
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)
- head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
+ head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
}
tpm_buf_append_u16(buf, num_bytes);
err = tpm_buf_fill_hmac_session(chip, buf);
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
index 62a200ae72d7..f2b8ca893e15 100644
--- a/drivers/char/tpm/tpm2-sessions.c
+++ b/drivers/char/tpm/tpm2-sessions.c
@@ -261,7 +261,7 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
}
if (!tpm2_chip_auth(chip)) {
- tpm_buf_append_handle(buf, handle);
+ tpm_buf_append_u32(buf, handle);
return 0;
}
@@ -288,17 +288,7 @@ EXPORT_SYMBOL_GPL(tpm_buf_append_name);
void tpm_buf_append_auth(struct tpm_chip *chip, struct tpm_buf *buf,
u8 *passphrase, int passphrase_len)
{
- /* offset tells us where the sessions area begins */
- int offset = buf->handles * 4 + TPM_HEADER_SIZE;
- u32 len = 9 + passphrase_len;
-
- if (tpm_buf_length(buf) != offset) {
- /* not the first session so update the existing length */
- len += get_unaligned_be32(&buf->data[offset]);
- put_unaligned_be32(len, &buf->data[offset]);
- } else {
- tpm_buf_append_u32(buf, len);
- }
+ tpm_buf_append_u32(buf, 9 + passphrase_len);
/* auth handle */
tpm_buf_append_u32(buf, TPM2_RS_PW);
/* nonce */
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 9f684fc7ae04..e68995df8796 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -395,7 +395,6 @@ enum tpm_buf_flags {
*/
struct tpm_buf {
u8 flags;
- u8 handles;
u16 length;
u16 capacity;
u8 data[];
@@ -441,7 +440,6 @@ 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_buf *buf, u32 handle);
/*
* Check if TPM device is in the firmware upgrade mode.
--
2.39.5
^ permalink raw reply related
* [PATCH v4 4/8] tpm2-sessions: Remove AUTH_MAX_NAMES
From: Jarkko Sakkinen @ 2025-12-10 17:20 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe, David Howells,
Paul Moore, James Morris, Serge E. Hallyn, open list,
open list:KEYS/KEYRINGS, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251210172027.109938-1-jarkko@kernel.org>
In all of the call sites only one session is ever append. Thus, reduce
AUTH_MAX_NAMES, which leads into removing constant completely.
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
drivers/char/tpm/tpm2-sessions.c | 31 +++++++++++--------------------
1 file changed, 11 insertions(+), 20 deletions(-)
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
index 0816a91134fc..62a200ae72d7 100644
--- a/drivers/char/tpm/tpm2-sessions.c
+++ b/drivers/char/tpm/tpm2-sessions.c
@@ -72,9 +72,6 @@
#include <crypto/sha2.h>
#include <crypto/utils.h>
-/* maximum number of names the TPM must remember for authorization */
-#define AUTH_MAX_NAMES 3
-
#define AES_KEY_BYTES AES_KEYSIZE_128
#define AES_KEY_BITS (AES_KEY_BYTES*8)
@@ -136,8 +133,8 @@ struct tpm2_auth {
* handle, but they are part of the session by name, which
* we must compute and remember
*/
- u8 name[AUTH_MAX_NAMES][TPM2_MAX_NAME_SIZE];
- u16 name_size_tbl[AUTH_MAX_NAMES];
+ u8 name[TPM2_MAX_NAME_SIZE];
+ u16 name_size;
};
#ifdef CONFIG_TCG_TPM2_HMAC
@@ -254,11 +251,14 @@ EXPORT_SYMBOL_GPL(tpm2_read_public);
int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
u32 handle, u8 *name, u16 name_size)
{
-#ifdef CONFIG_TCG_TPM2_HMAC
struct tpm2_auth *auth;
- int slot;
int ret;
-#endif
+
+ if (tpm_buf_length(buf) != TPM_HEADER_SIZE) {
+ dev_err(&chip->dev, "too many handles\n");
+ ret = -EIO;
+ goto err;
+ }
if (!tpm2_chip_auth(chip)) {
tpm_buf_append_handle(buf, handle);
@@ -266,12 +266,6 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
}
#ifdef CONFIG_TCG_TPM2_HMAC
- slot = (tpm_buf_length(buf) - TPM_HEADER_SIZE) / 4;
- if (slot >= AUTH_MAX_NAMES) {
- dev_err(&chip->dev, "too many handles\n");
- ret = -EIO;
- goto err;
- }
auth = chip->auth;
if (auth->session != tpm_buf_length(buf)) {
dev_err(&chip->dev, "session state malformed");
@@ -280,16 +274,14 @@ int tpm_buf_append_name(struct tpm_chip *chip, struct tpm_buf *buf,
}
tpm_buf_append_u32(buf, handle);
auth->session += 4;
- memcpy(auth->name[slot], name, name_size);
- auth->name_size_tbl[slot] = name_size;
+ memcpy(auth->name, name, name_size);
+ auth->name_size = name_size;
#endif
return 0;
-#ifdef CONFIG_TCG_TPM2_HMAC
err:
tpm2_end_auth_session(chip);
return ret;
-#endif
}
EXPORT_SYMBOL_GPL(tpm_buf_append_name);
@@ -658,8 +650,7 @@ 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++)
- sha256_update(&sctx, auth->name[i], auth->name_size_tbl[i]);
+ sha256_update(&sctx, auth->name, auth->name_size);
if (offset_s != tpm_buf_length(buf))
sha256_update(&sctx, &buf->data[offset_s],
tpm_buf_length(buf) - offset_s);
--
2.39.5
^ permalink raw reply related
* [PATCH v4 3/8] KEYS: trusted: Re-orchestrate tpm2_read_public() calls
From: Jarkko Sakkinen @ 2025-12-10 17:20 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe, David Howells,
Paul Moore, James Morris, Serge E. Hallyn, James Bottomley,
Mimi Zohar, open list, open list:KEYS/KEYRINGS,
open list:SECURITY SUBSYSTEM
In-Reply-To: <20251210172027.109938-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>
---
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 543c1c62c938..436ee82620e4 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -199,7 +199,8 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
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);
+ rc = tpm_buf_append_name(chip, buf, pcr_idx, (u8 *)&pcr_idx,
+ sizeof(u32));
if (rc)
return rc;
tpm_buf_append_hmac_session(chip, buf, 0, NULL, 0);
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
index a0c88fb1804c..0816a91134fc 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][TPM2_MAX_NAME_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;
@@ -212,14 +222,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
@@ -236,15 +248,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
@@ -269,36 +280,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);
@@ -606,22 +596,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;
@@ -682,23 +658,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 922a43ef23b5..9f684fc7ae04 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -543,7 +543,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);
@@ -557,6 +557,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>
@@ -580,6 +581,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 27424e1a4a63..63539b344ffb 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -203,7 +203,9 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
struct trusted_key_payload *payload,
struct trusted_key_options *options)
{
+ u8 parent_name[TPM2_MAX_NAME_SIZE];
off_t offset = TPM_HEADER_SIZE;
+ u16 parent_name_size;
int blob_len = 0;
int hash;
u32 flags;
@@ -220,6 +222,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;
@@ -234,7 +242,8 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
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, parent_name,
+ parent_name_size);
if (rc)
goto out_put;
@@ -326,48 +335,38 @@ 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;
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);
@@ -406,7 +405,8 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
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);
+ rc = tpm_buf_append_name(chip, buf, options->keyhandle, parent_name,
+ parent_name_size);
if (rc)
return rc;
@@ -434,20 +434,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)
{
u16 data_len;
@@ -465,7 +468,8 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
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, parent_name,
+ parent_name_size);
if (rc)
return rc;
@@ -526,30 +530,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[TPM2_MAX_NAME_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 v4 2/8] tpm2-sessions: Define TPM2_NAME_MAX_SIZE
From: Jarkko Sakkinen @ 2025-12-10 17:20 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe, David Howells,
Paul Moore, James Morris, Serge E. Hallyn, open list,
open list:KEYS/KEYRINGS, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251210172027.109938-1-jarkko@kernel.org>
This is somewhat cosmetic change but it does serve a purpose on tracking
the value set for the maximum length of TPM names, and to clearly states
what components it is composed of. It also anchors the value so that when
buffers are declared for this particular purpose, the same value is used
for the capacity.
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
v2:
- Rename TPM2_NAME_SIZE as TPM2_NULL_NAME_SIZE.
---
drivers/char/tpm/tpm-sysfs.c | 2 +-
drivers/char/tpm/tpm2-sessions.c | 2 +-
include/linux/tpm.h | 37 +++++++++++++++++++++-----------
3 files changed, 27 insertions(+), 14 deletions(-)
diff --git a/drivers/char/tpm/tpm-sysfs.c b/drivers/char/tpm/tpm-sysfs.c
index f5dcadb1ab3c..0f321c307bc6 100644
--- a/drivers/char/tpm/tpm-sysfs.c
+++ b/drivers/char/tpm/tpm-sysfs.c
@@ -313,7 +313,7 @@ static ssize_t null_name_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct tpm_chip *chip = to_tpm_chip(dev);
- int size = TPM2_NAME_SIZE;
+ int size = TPM2_NULL_NAME_SIZE;
bin2hex(buf, chip->null_key_name, size);
size *= 2;
diff --git a/drivers/char/tpm/tpm2-sessions.c b/drivers/char/tpm/tpm2-sessions.c
index 79f27a46bd7f..a0c88fb1804c 100644
--- a/drivers/char/tpm/tpm2-sessions.c
+++ b/drivers/char/tpm/tpm2-sessions.c
@@ -137,7 +137,7 @@ struct tpm2_auth {
* we must compute and remember
*/
u32 name_h[AUTH_MAX_NAMES];
- u8 name[AUTH_MAX_NAMES][2 + SHA512_DIGEST_SIZE];
+ u8 name[AUTH_MAX_NAMES][TPM2_MAX_NAME_SIZE];
};
#ifdef CONFIG_TCG_TPM2_HMAC
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 42e2a091f43d..922a43ef23b5 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -28,9 +28,33 @@
#define TPM_DIGEST_SIZE 20 /* Max TPM v1.2 PCR size */
#define TPM_BUFSIZE 4096
+/*
+ * SHA-512 is, as of today, the largest digest in the TCG algorithm repository.
+ */
#define TPM2_MAX_DIGEST_SIZE SHA512_DIGEST_SIZE
+
+/*
+ * A TPM name digest i.e., TPMT_HA, is a concatenation of TPM_ALG_ID of the
+ * name algorithm and hash of TPMT_PUBLIC.
+ */
+#define TPM2_MAX_NAME_SIZE (TPM2_MAX_DIGEST_SIZE + 2)
+
+/*
+ * The maximum number of PCR banks.
+ */
#define TPM2_MAX_PCR_BANKS 8
+/*
+ * fixed define for the size of a name. This is actually HASHALG size
+ * plus 2, so 32 for SHA256
+ */
+#define TPM2_NULL_NAME_SIZE 34
+
+/*
+ * The maximum size for an object context
+ */
+#define TPM2_MAX_CONTEXT_SIZE 4096
+
struct tpm_chip;
struct trusted_key_payload;
struct trusted_key_options;
@@ -140,17 +164,6 @@ struct tpm_chip_seqops {
/* fixed define for the curve we use which is NIST_P256 */
#define EC_PT_SZ 32
-/*
- * fixed define for the size of a name. This is actually HASHALG size
- * plus 2, so 32 for SHA256
- */
-#define TPM2_NAME_SIZE 34
-
-/*
- * The maximum size for an object context
- */
-#define TPM2_MAX_CONTEXT_SIZE 4096
-
struct tpm_chip {
struct device dev;
struct device devs;
@@ -212,7 +225,7 @@ struct tpm_chip {
/* saved context for NULL seed */
u8 null_key_context[TPM2_MAX_CONTEXT_SIZE];
/* name of NULL seed */
- u8 null_key_name[TPM2_NAME_SIZE];
+ u8 null_key_name[TPM2_NULL_NAME_SIZE];
u8 null_ec_key_x[EC_PT_SZ];
u8 null_ec_key_y[EC_PT_SZ];
struct tpm2_auth *auth;
--
2.39.5
^ permalink raw reply related
* [PATCH v4 1/8] KEYS: trusted: Remove dead branch from tpm2_unseal_cmd
From: Jarkko Sakkinen @ 2025-12-10 17:20 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, David Howells, Paul Moore, James Morris,
Serge E. Hallyn, James Bottomley, Mimi Zohar,
open list:KEYS/KEYRINGS, open list:SECURITY SUBSYSTEM, open list
In-Reply-To: <20251210172027.109938-1-jarkko@kernel.org>
TPM2_Unseal requires TPM2_ST_SESSIONS, and tpm2_unseal_cmd() always does
set up either password or HMAC session.
Remove the branch in tpm2_unseal_cmd() conditionally setting
TPM2_ST_NO_SESSIONS. It is faulty but luckily it is never exercised at
run-time, and thus does not cause regressions.
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
security/keys/trusted-keys/trusted_tpm2.c | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index 9074ae1a5896..27424e1a4a63 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -450,9 +450,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
struct trusted_key_options *options,
u32 blob_handle)
{
- struct tpm_header *head;
u16 data_len;
- int offset;
u8 *data;
int rc;
@@ -488,14 +486,8 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
tpm_buf_append_u16(buf, options->blobauth_len);
tpm_buf_append(buf, options->blobauth, options->blobauth_len);
- if (tpm2_chip_auth(chip)) {
+ if (tpm2_chip_auth(chip))
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)
- head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
- }
}
rc = tpm_buf_fill_hmac_session(chip, buf);
--
2.39.5
^ permalink raw reply related
* [PATCH v4 0/8] Streamline TPM2 HMAC sessions
From: Jarkko Sakkinen @ 2025-12-10 17:20 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, David Howells, Paul Moore, James Morris,
Serge E. Hallyn, open list:KEYS/KEYRINGS,
open list:SECURITY SUBSYSTEM, open list
Since we cannot at this point cache names of the keys given limitations
of the ASN.1 file format, I'll start a fresh patch set. Let's fixup what
we can right now.
This patch set addresses two major issues in the feature:
1. Dynamic resolution without gain. All kernel sites have at most single
handle to authorize. Even if this changes some day this is how it is
as of today and we definitely do not want to dictate the future but
instead downscale code to the metrics that we have as of today.
2. Eliminate at least one unnnecessary tpm2_read_public() call.
Jarkko Sakkinen (8):
KEYS: trusted: Remove dead branch from tpm2_unseal_cmd
tpm2-sessions: Define TPM2_NAME_MAX_SIZE
KEYS: trusted: Re-orchestrate tpm2_read_public() calls
tpm2-sessions: Remove AUTH_MAX_NAMES
tpm-buf: Remove tpm_buf_append_handle
tpm: Orchestrate TPM commands in tpm_get_random()
tpm: Send only one at most TPM2_GetRandom command
tpm: In tpm_get_random() replace 'retries' with a zero check
drivers/char/tpm/tpm-buf.c | 25 ---
drivers/char/tpm/tpm-chip.c | 2 +-
drivers/char/tpm/tpm-interface.c | 177 ++++++++++++++++++++--
drivers/char/tpm/tpm-sysfs.c | 2 +-
drivers/char/tpm/tpm.h | 2 -
drivers/char/tpm/tpm1-cmd.c | 62 --------
drivers/char/tpm/tpm2-cmd.c | 102 +------------
drivers/char/tpm/tpm2-sessions.c | 130 +++++-----------
include/linux/tpm.h | 51 +++++--
security/keys/trusted-keys/trusted_tpm1.c | 8 +-
security/keys/trusted-keys/trusted_tpm2.c | 134 +++++++++-------
11 files changed, 325 insertions(+), 370 deletions(-)
--
2.39.5
^ permalink raw reply
* Re: [PATCH v4 00/35] Compiler-Based Context- and Locking-Analysis
From: Peter Zijlstra @ 2025-12-10 16:37 UTC (permalink / raw)
To: Marco Elver
Cc: Boqun Feng, Ingo Molnar, Will Deacon, Linus Torvalds,
David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, Jonathan Corbet,
Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda,
Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda,
Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers,
Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf,
Uladzislau Rezki, Waiman Long, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <aTmdSMuP0LUAdfO_@elver.google.com>
On Wed, Dec 10, 2025 at 05:18:16PM +0100, Marco Elver wrote:
> All,
>
> On Thu, Nov 20, 2025 at 03:49PM +0100, Marco Elver wrote:
> > Context Analysis is a language extension, which enables statically
> > checking that required contexts are active (or inactive) by acquiring
> > and releasing user-definable "context guards". An obvious application is
> > lock-safety checking for the kernel's various synchronization primitives
> > (each of which represents a "context guard"), and checking that locking
> > rules are not violated.
> [...]
> > A Clang version that supports `-Wthread-safety-pointer` and the new
> > alias-analysis of context-guard pointers is required (from this version
> > onwards):
> >
> > https://github.com/llvm/llvm-project/commit/7ccb5c08f0685d4787f12c3224a72f0650c5865e
> >
> > The minimum required release version will be Clang 22.
> >
> > This series is also available at this Git tree:
> >
> > https://git.kernel.org/pub/scm/linux/kernel/git/melver/linux.git/log/?h=ctx-analysis/dev
> [...]
>
> I realize that I sent this series at the end of the last release cycle,
> and now we're in the merge window, along with LPC going on -- so it
> wasn't the best timing (however, it might be something to discuss at
> LPC, too :-) .. I'm attending virtually, however :-/).
>
> How to proceed?
Ah, I knew I was forgetting something :/ I'll try and have a peek at
this series this week.
^ permalink raw reply
* Re: [PATCH v4 00/35] Compiler-Based Context- and Locking-Analysis
From: Marco Elver @ 2025-12-10 16:18 UTC (permalink / raw)
To: Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon,
Linus Torvalds
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, Jonathan Corbet,
Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda,
Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda,
Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers,
Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf,
Uladzislau Rezki, Waiman Long, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120145835.3833031-2-elver@google.com>
All,
On Thu, Nov 20, 2025 at 03:49PM +0100, Marco Elver wrote:
> Context Analysis is a language extension, which enables statically
> checking that required contexts are active (or inactive) by acquiring
> and releasing user-definable "context guards". An obvious application is
> lock-safety checking for the kernel's various synchronization primitives
> (each of which represents a "context guard"), and checking that locking
> rules are not violated.
[...]
> A Clang version that supports `-Wthread-safety-pointer` and the new
> alias-analysis of context-guard pointers is required (from this version
> onwards):
>
> https://github.com/llvm/llvm-project/commit/7ccb5c08f0685d4787f12c3224a72f0650c5865e
>
> The minimum required release version will be Clang 22.
>
> This series is also available at this Git tree:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/melver/linux.git/log/?h=ctx-analysis/dev
[...]
I realize that I sent this series at the end of the last release cycle,
and now we're in the merge window, along with LPC going on -- so it
wasn't the best timing (however, it might be something to discuss at
LPC, too :-) .. I'm attending virtually, however :-/).
How to proceed?
I'll be preparing a rebased and retested version of all this when
v6.19-rc1 is out. One outstanding recommendation from Linus was to
investigate compile-times, but as-is, it's unclear there's any notable
overhead per brief investigation: https://lore.kernel.org/all/aR-plHrWDMqRRlcI@elver.google.com/
From what I can tell most of this has to go through the locking tree,
given the potential for conflict there. However, it is possible to split
this up as follows:
Batch 1:
> compiler_types: Move lock checking attributes to
> compiler-context-analysis.h
> compiler-context-analysis: Add infrastructure for Context Analysis
> with Clang
> compiler-context-analysis: Add test stub
> Documentation: Add documentation for Compiler-Based Context Analysis
> checkpatch: Warn about context_unsafe() without comment
> cleanup: Basic compatibility with context analysis
> lockdep: Annotate lockdep assertions for context analysis
> locking/rwlock, spinlock: Support Clang's context analysis
> compiler-context-analysis: Change __cond_acquires to take return value
> locking/mutex: Support Clang's context analysis
> locking/seqlock: Support Clang's context analysis
> bit_spinlock: Include missing <asm/processor.h>
> bit_spinlock: Support Clang's context analysis
> rcu: Support Clang's context analysis
> srcu: Support Clang's context analysis
> kref: Add context-analysis annotations
> locking/rwsem: Support Clang's context analysis
> locking/local_lock: Include missing headers
> locking/local_lock: Support Clang's context analysis
> locking/ww_mutex: Support Clang's context analysis
> debugfs: Make debugfs_cancellation a context guard struct
> compiler-context-analysis: Remove Sparse support
> compiler-context-analysis: Remove __cond_lock() function-like helper
> compiler-context-analysis: Introduce header suppressions
> compiler: Let data_race() imply disabled context analysis
> MAINTAINERS: Add entry for Context Analysis
Batch 2: Everything below this can wait for the initial support in
mainline, at which point subsystem maintainers can pick them up if
deemed appropriate.
> kfence: Enable context analysis
> kcov: Enable context analysis
> kcsan: Enable context analysis
> stackdepot: Enable context analysis
> rhashtable: Enable context analysis
> printk: Move locking annotation to printk.c
> security/tomoyo: Enable context analysis
> crypto: Enable context analysis
> sched: Enable context analysis for core.c and fair.c
Thanks,
-- Marco
^ permalink raw reply
* [PATCH v3 7/7] tpm: Send only one TPM command per hwrng request
From: Jarkko Sakkinen @ 2025-12-10 14:39 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, David S. Miller, Herbert Xu, Peter Huewe,
Jason Gunthorpe, David Howells, Paul Moore, James Morris,
Serge E. Hallyn, James Bottomley, Mimi Zohar, open list,
open list:KEYS/KEYRINGS, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251210143940.264191-1-jarkko@kernel.org>
hwrng framework does not have a enforce that all bytes requested need to be
read. By enforcing such a requirement internally, TPM driver can cause
unpredicatability in latency, as a single tpm_get_random() call can result
multiple TPM commands.
Especially, when TCG_TPM2_HMAC is enabled, roundtrips with the TPM should
be capped to exactly one.
Add a @wait parameter to enforce this behavior and set it to true at the
call sites TPM 1.2 keys. At the call sites of hwrng, set @wait to false.
Cc: David S. Miller <davem@davemloft.net>
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
drivers/char/tpm/tpm-chip.c | 2 +-
drivers/char/tpm/tpm-interface.c | 11 +++++++++--
include/linux/tpm.h | 2 +-
security/keys/trusted-keys/trusted_tpm1.c | 8 ++++----
4 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/drivers/char/tpm/tpm-chip.c b/drivers/char/tpm/tpm-chip.c
index 082b910ddf0d..8fca4373e2df 100644
--- a/drivers/char/tpm/tpm-chip.c
+++ b/drivers/char/tpm/tpm-chip.c
@@ -494,7 +494,7 @@ static int tpm_hwrng_read(struct hwrng *rng, void *data, size_t max, bool wait)
{
struct tpm_chip *chip = container_of(rng, struct tpm_chip, hwrng);
- return tpm_get_random(chip, data, max);
+ return tpm_get_random(chip, data, max, false);
}
static bool tpm_is_hwrng_enabled(struct tpm_chip *chip)
diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
index ab52a1cb0a78..5cc2bbabd57a 100644
--- a/drivers/char/tpm/tpm-interface.c
+++ b/drivers/char/tpm/tpm-interface.c
@@ -604,9 +604,11 @@ static int tpm2_get_random(struct tpm_chip *chip, u8 *out, size_t max)
* @chip: A &tpm_chip instance. Whenset to %NULL, the default chip is used.
* @out: Destination buffer for the acquired random bytes.
* @max: The maximum number of bytes to write to @out.
+ * @wait: Set to true when all of the @max bytes need to be acquired.
*
* Iterates pulling more bytes from TPM up until all of the @max bytes have been
- * received.
+ * received, when @wait it sets true. Otherwise, the queries for @max bytes from
+ * TPM exactly once, and returns the bytes that were received.
*
* Returns the number of random bytes read on success.
* Returns -EINVAL when @out is NULL, or @max is not between zero and
@@ -614,7 +616,7 @@ static int tpm2_get_random(struct tpm_chip *chip, u8 *out, size_t max)
* Returns tpm_transmit_cmd() error codes when the TPM command results an
* error.
*/
-int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max)
+int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max, bool wait)
{
u32 num_bytes = max;
u8 *out_ptr = out;
@@ -647,6 +649,11 @@ int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max)
if (rc < 0)
goto err;
+ if (!wait) {
+ total = rc;
+ break;
+ }
+
out_ptr += rc;
total += rc;
num_bytes -= rc;
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index e68995df8796..177833d6b965 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -485,7 +485,7 @@ extern int tpm_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digest);
extern int tpm_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digests);
-extern int tpm_get_random(struct tpm_chip *chip, u8 *data, size_t max);
+int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max, bool wait);
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);
diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
index 759c1ecb0435..f36f6a0b533f 100644
--- a/security/keys/trusted-keys/trusted_tpm1.c
+++ b/security/keys/trusted-keys/trusted_tpm1.c
@@ -361,7 +361,7 @@ static int osap(struct tpm_buf *tb, struct osapsess *s,
unsigned char ononce[TPM_NONCE_SIZE];
int ret;
- ret = tpm_get_random(chip, ononce, TPM_NONCE_SIZE);
+ ret = tpm_get_random(chip, ononce, TPM_NONCE_SIZE, true);
if (ret < 0)
return ret;
@@ -454,7 +454,7 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype,
memcpy(td->xorwork + SHA1_DIGEST_SIZE, sess.enonce, SHA1_DIGEST_SIZE);
sha1(td->xorwork, SHA1_DIGEST_SIZE * 2, td->xorhash);
- ret = tpm_get_random(chip, td->nonceodd, TPM_NONCE_SIZE);
+ ret = tpm_get_random(chip, td->nonceodd, TPM_NONCE_SIZE, true);
if (ret < 0)
goto out;
@@ -565,7 +565,7 @@ static int tpm_unseal(struct tpm_buf *tb,
}
ordinal = htonl(TPM_ORD_UNSEAL);
- ret = tpm_get_random(chip, nonceodd, TPM_NONCE_SIZE);
+ ret = tpm_get_random(chip, nonceodd, TPM_NONCE_SIZE, true);
if (ret < 0)
return ret;
@@ -938,7 +938,7 @@ static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob)
static int trusted_tpm_get_random(unsigned char *key, size_t key_len)
{
- return tpm_get_random(chip, key, key_len);
+ return tpm_get_random(chip, key, key_len, true);
}
static int __init init_digests(void)
--
2.52.0
^ permalink raw reply related
* [PATCH v3 6/7] tpm: Orchestrate TPM commands in tpm_get_random()
From: Jarkko Sakkinen @ 2025-12-10 14:39 UTC (permalink / raw)
To: linux-integrity
Cc: Jarkko Sakkinen, Peter Huewe, Jason Gunthorpe, David Howells,
Paul Moore, James Morris, Serge E. Hallyn, open list,
open list:KEYS/KEYRINGS, open list:SECURITY SUBSYSTEM
In-Reply-To: <20251210143940.264191-1-jarkko@kernel.org>
tpm1_get_random() and tpm2_get_random() contain duplicate orchestration
code. Consolidate orchestration to tpm_get_random().
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
drivers/char/tpm/tpm-interface.c | 165 ++++++++++++++++++++++++++++---
drivers/char/tpm/tpm.h | 2 -
drivers/char/tpm/tpm1-cmd.c | 62 ------------
drivers/char/tpm/tpm2-cmd.c | 95 ------------------
4 files changed, 154 insertions(+), 170 deletions(-)
diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c
index f745a098908b..ab52a1cb0a78 100644
--- a/drivers/char/tpm/tpm-interface.c
+++ b/drivers/char/tpm/tpm-interface.c
@@ -26,7 +26,7 @@
#include <linux/suspend.h>
#include <linux/freezer.h>
#include <linux/tpm_eventlog.h>
-
+#include <linux/tpm_command.h>
#include "tpm.h"
/*
@@ -486,19 +486,143 @@ int tpm_pm_resume(struct device *dev)
}
EXPORT_SYMBOL_GPL(tpm_pm_resume);
+struct tpm1_get_random_out {
+ __be32 rng_data_len;
+ u8 rng_data[TPM_MAX_RNG_DATA];
+} __packed;
+
+static int tpm1_get_random(struct tpm_chip *chip, u8 *out, size_t max)
+{
+ struct tpm1_get_random_out *resp;
+ u32 recd;
+ int rc;
+
+ if (!out || !max || max > TPM_MAX_RNG_DATA)
+ return -EINVAL;
+
+ 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_GETRANDOM);
+ tpm_buf_append_u32(buf, max);
+
+ rc = tpm_transmit_cmd(chip, buf, sizeof(resp->rng_data_len), "TPM_GetRandom");
+ if (rc) {
+ if (rc > 0)
+ rc = -EIO;
+ return rc;
+ }
+
+ resp = (struct tpm1_get_random_out *)&buf->data[TPM_HEADER_SIZE];
+
+ recd = be32_to_cpu(resp->rng_data_len);
+ if (recd > max)
+ return -EIO;
+
+ if (buf->length < TPM_HEADER_SIZE + sizeof(resp->rng_data_len) + recd)
+ return -EIO;
+
+ memcpy(out, resp->rng_data, recd);
+ return recd;
+}
+
+struct tpm2_get_random_out {
+ __be16 size;
+ u8 buffer[TPM_MAX_RNG_DATA];
+} __packed;
+
+static int tpm2_get_random(struct tpm_chip *chip, u8 *out, size_t max)
+{
+ struct tpm2_get_random_out *resp;
+ struct tpm_header *head;
+ off_t offset;
+ u32 recd;
+ int ret;
+
+ if (!out || !max || max > TPM_MAX_RNG_DATA)
+ return -EINVAL;
+
+ 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_SESSIONS, TPM2_CC_GET_RANDOM);
+ if (tpm2_chip_auth(chip)) {
+ tpm_buf_append_hmac_session(chip, buf,
+ TPM2_SA_ENCRYPT | TPM2_SA_CONTINUE_SESSION,
+ NULL, 0);
+ } else {
+ head = (struct tpm_header *)buf->data;
+ head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
+ }
+ tpm_buf_append_u16(buf, max);
+
+ ret = tpm_buf_fill_hmac_session(chip, buf);
+ if (ret)
+ return ret;
+
+ ret = tpm_transmit_cmd(chip, buf, offsetof(struct tpm2_get_random_out, buffer),
+ "TPM2_GetRandom");
+
+ ret = tpm_buf_check_hmac_response(chip, buf, ret);
+ if (ret) {
+ if (ret > 0)
+ ret = -EIO;
+
+ goto out;
+ }
+
+ 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;
+
+ resp = (struct tpm2_get_random_out *)&buf->data[offset];
+ recd = min_t(u32, be16_to_cpu(resp->size), max);
+
+ if (tpm_buf_length(buf) <
+ TPM_HEADER_SIZE + offsetof(struct tpm2_get_random_out, buffer) + recd) {
+ ret = -EIO;
+ goto out;
+ }
+
+ memcpy(out, resp->buffer, recd);
+ return recd;
+
+out:
+ tpm2_end_auth_session(chip);
+ return ret;
+}
+
/**
- * tpm_get_random() - get random bytes from the TPM's RNG
- * @chip: a &struct tpm_chip instance, %NULL for the default chip
- * @out: destination buffer for the random bytes
- * @max: the max number of bytes to write to @out
+ * tpm_get_random() - Get random bytes from the TPM's RNG
+ * @chip: A &tpm_chip instance. Whenset to %NULL, the default chip is used.
+ * @out: Destination buffer for the acquired random bytes.
+ * @max: The maximum number of bytes to write to @out.
*
- * Return: number of random bytes read or a negative error value.
+ * Iterates pulling more bytes from TPM up until all of the @max bytes have been
+ * received.
+ *
+ * Returns the number of random bytes read on success.
+ * Returns -EINVAL when @out is NULL, or @max is not between zero and
+ * %TPM_MAX_RNG_DATA.
+ * Returns tpm_transmit_cmd() error codes when the TPM command results an
+ * error.
*/
int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max)
{
+ u32 num_bytes = max;
+ u8 *out_ptr = out;
+ int retries = 5;
+ int total = 0;
int rc;
- if (!out || max > TPM_MAX_RNG_DATA)
+ if (!out || !max || max > TPM_MAX_RNG_DATA)
return -EINVAL;
if (!chip)
@@ -508,11 +632,30 @@ int tpm_get_random(struct tpm_chip *chip, u8 *out, size_t max)
if (rc)
return rc;
- if (chip->flags & TPM_CHIP_FLAG_TPM2)
- rc = tpm2_get_random(chip, out, max);
- else
- rc = tpm1_get_random(chip, out, max);
+ if (chip->flags & TPM_CHIP_FLAG_TPM2) {
+ rc = tpm2_start_auth_session(chip);
+ if (rc)
+ return rc;
+ }
+
+ do {
+ if (chip->flags & TPM_CHIP_FLAG_TPM2)
+ rc = tpm2_get_random(chip, out_ptr, num_bytes);
+ else
+ rc = tpm1_get_random(chip, out_ptr, num_bytes);
+
+ if (rc < 0)
+ goto err;
+
+ out_ptr += rc;
+ total += rc;
+ num_bytes -= rc;
+ } while (retries-- && total < max);
+
+ tpm_put_ops(chip);
+ return total ? total : -EIO;
+err:
tpm_put_ops(chip);
return rc;
}
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index 5395927c62fc..c4bbd8f04605 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -250,7 +250,6 @@ int tpm1_pcr_extend(struct tpm_chip *chip, u32 pcr_idx, const u8 *hash,
int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf);
ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
const char *desc, size_t min_cap_length);
-int tpm1_get_random(struct tpm_chip *chip, u8 *out, size_t max);
int tpm1_get_pcr_allocation(struct tpm_chip *chip);
unsigned long tpm_calc_ordinal_duration(struct tpm_chip *chip, u32 ordinal);
int tpm_pm_suspend(struct device *dev);
@@ -290,7 +289,6 @@ int tpm2_pcr_read(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digest, u16 *digest_size_ptr);
int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
struct tpm_digest *digests);
-int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max);
ssize_t tpm2_get_tpm_pt(struct tpm_chip *chip, u32 property_id,
u32 *value, const char *desc);
diff --git a/drivers/char/tpm/tpm1-cmd.c b/drivers/char/tpm/tpm1-cmd.c
index 11090053ef54..d8f16e66666b 100644
--- a/drivers/char/tpm/tpm1-cmd.c
+++ b/drivers/char/tpm/tpm1-cmd.c
@@ -502,68 +502,6 @@ ssize_t tpm1_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap,
}
EXPORT_SYMBOL_GPL(tpm1_getcap);
-#define TPM_ORD_GET_RANDOM 70
-struct tpm1_get_random_out {
- __be32 rng_data_len;
- u8 rng_data[TPM_MAX_RNG_DATA];
-} __packed;
-
-/**
- * tpm1_get_random() - get random bytes from the TPM's RNG
- * @chip: a &struct tpm_chip instance
- * @dest: destination buffer for the random bytes
- * @max: the maximum number of bytes to write to @dest
- *
- * Return:
- * * number of bytes read
- * * -errno (positive TPM return codes are masked to -EIO)
- */
-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);
- u32 total = 0;
- int retries = 5;
- u32 recd;
- int 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_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), "TPM_GetRandom");
- if (rc) {
- if (rc > 0)
- rc = -EIO;
- return rc;
- }
-
- out = (struct tpm1_get_random_out *)&buf->data[TPM_HEADER_SIZE];
-
- recd = be32_to_cpu(out->rng_data_len);
- if (recd > num_bytes)
- return -EFAULT;
-
- if (buf->length < TPM_HEADER_SIZE +
- sizeof(out->rng_data_len) + recd)
- return -EFAULT;
-
- memcpy(dest, out->rng_data, recd);
-
- dest += recd;
- total += recd;
- num_bytes -= recd;
- } while (retries-- && total < max);
-
- rc = total ? (int)total : -EIO;
- return rc;
-}
-
#define TPM_ORD_PCRREAD 21
int tpm1_pcr_read(struct tpm_chip *chip, u32 pcr_idx, u8 *res_buf)
{
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index bc50d6b734cf..f066efb54a2c 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -233,101 +233,6 @@ int tpm2_pcr_extend(struct tpm_chip *chip, u32 pcr_idx,
return rc;
}
-struct tpm2_get_random_out {
- __be16 size;
- u8 buffer[TPM_MAX_RNG_DATA];
-} __packed;
-
-/**
- * tpm2_get_random() - get random bytes from the TPM RNG
- *
- * @chip: a &tpm_chip instance
- * @dest: destination buffer
- * @max: the max number of random bytes to pull
- *
- * Return:
- * size of the buffer on success,
- * -errno otherwise (positive TPM return codes are masked to -EIO)
- */
-int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
-{
- struct tpm2_get_random_out *out;
- struct tpm_header *head;
- u32 recd;
- u32 num_bytes = max;
- int err;
- int total = 0;
- int retries = 5;
- u8 *dest_ptr = dest;
- off_t offset;
-
- 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;
-
- tpm_buf_init(buf, TPM_BUFSIZE);
- do {
- tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_GET_RANDOM);
- if (tpm2_chip_auth(chip)) {
- tpm_buf_append_hmac_session(chip, buf,
- TPM2_SA_ENCRYPT |
- TPM2_SA_CONTINUE_SESSION,
- NULL, 0);
- } else {
- head = (struct tpm_header *)buf->data;
- 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)
- return err;
-
- err = tpm_transmit_cmd(chip, buf,
- offsetof(struct tpm2_get_random_out,
- buffer),
- "TPM2_GetRandom");
- err = tpm_buf_check_hmac_response(chip, buf, err);
- if (err) {
- if (err > 0)
- err = -EIO;
- goto out;
- }
-
- 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];
- recd = min_t(u32, be16_to_cpu(out->size), num_bytes);
- if (tpm_buf_length(buf) <
- TPM_HEADER_SIZE +
- offsetof(struct tpm2_get_random_out, buffer) +
- recd) {
- err = -EFAULT;
- goto out;
- }
- memcpy(dest_ptr, out->buffer, recd);
-
- dest_ptr += recd;
- total += recd;
- num_bytes -= recd;
- } while (retries-- && total < max);
-
- return total ? total : -EIO;
-out:
- tpm2_end_auth_session(chip);
- return err;
-}
-
/**
* tpm2_flush_context() - execute a TPM2_FlushContext command
* @chip: TPM chip to use
--
2.52.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox