* Re: [PATCH v2] KEYS: trusted: Debugging as a feature
From: Nayna Jain @ 2026-04-07 2:42 UTC (permalink / raw)
To: Jarkko Sakkinen, linux-integrity
Cc: keyrings, Srish Srinivasan, James Bottomley, Mimi Zohar,
David Howells, Paul Moore, James Morris, Serge E. Hallyn,
Ahmad Fatoum, Pengutronix Kernel Team, open list,
open list:SECURITY SUBSYSTEM
In-Reply-To: <20260324110043.67248-1-jarkko@kernel.org>
On 3/24/26 7:00 AM, Jarkko Sakkinen wrote:
> TPM_DEBUG, and other similar flags, are a non-standard way to specify a
> feature in Linux kernel. Introduce CONFIG_TRUSTED_KEYS_DEBUG for
> trusted keys, and use it to replace these ad-hoc feature flags.
>
> Given that trusted keys debug dumps can contain sensitive data, harden
> the feature as follows:
>
> 1. In the Kconfig description postulate that pr_debug() statements must be
> used.
> 2. Use pr_debug() statements in TPM 1.x driver to print the protocol dump.
>
> Traces, when actually needed, can be easily enabled by providing
> trusted.dyndbg='+p' in the kernel command-line.
>
> Cc: Srish Srinivasan <ssrish@linux.ibm.com>
> Reported-by: Nayna Jain <nayna@linux.ibm.com>
> Closes: https://lore.kernel.org/all/7f8b8478-5cd8-4d97-bfd0-341fd5cf10f9@linux.ibm.com/
> Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
> ---
> v2:
> - Implement for all trusted keys backends.
> - Add HAVE_TRUSTED_KEYS_DEBUG as it is a good practice despite full
> coverage.
> ---
> include/keys/trusted-type.h | 18 +++++-------
> security/keys/trusted-keys/Kconfig | 19 ++++++++++++
> security/keys/trusted-keys/trusted_caam.c | 4 +--
> security/keys/trusted-keys/trusted_tpm1.c | 36 +++++++++++------------
> 4 files changed, 46 insertions(+), 31 deletions(-)
>
> diff --git a/include/keys/trusted-type.h b/include/keys/trusted-type.h
> index 03527162613f..620a1f890b6b 100644
> --- a/include/keys/trusted-type.h
> +++ b/include/keys/trusted-type.h
> @@ -83,18 +83,16 @@ struct trusted_key_source {
>
> extern struct key_type key_type_trusted;
>
> -#define TRUSTED_DEBUG 0
> -
> -#if TRUSTED_DEBUG
> +#ifdef CONFIG_TRUSTED_KEYS_DEBUG
> static inline void dump_payload(struct trusted_key_payload *p)
> {
> - pr_info("key_len %d\n", p->key_len);
> - print_hex_dump(KERN_INFO, "key ", DUMP_PREFIX_NONE,
> - 16, 1, p->key, p->key_len, 0);
> - pr_info("bloblen %d\n", p->blob_len);
> - print_hex_dump(KERN_INFO, "blob ", DUMP_PREFIX_NONE,
> - 16, 1, p->blob, p->blob_len, 0);
> - pr_info("migratable %d\n", p->migratable);
> + pr_debug("key_len %d\n", p->key_len);
> + print_hex_dump_debug("key ", DUMP_PREFIX_NONE,
> + 16, 1, p->key, p->key_len, 0);
> + pr_debug("bloblen %d\n", p->blob_len);
> + print_hex_dump_debug("blob ", DUMP_PREFIX_NONE,
> + 16, 1, p->blob, p->blob_len, 0);
> + pr_debug("migratable %d\n", p->migratable);
> }
> #else
> static inline void dump_payload(struct trusted_key_payload *p)
> diff --git a/security/keys/trusted-keys/Kconfig b/security/keys/trusted-keys/Kconfig
> index 9e00482d886a..2ad9ba0e03f1 100644
> --- a/security/keys/trusted-keys/Kconfig
> +++ b/security/keys/trusted-keys/Kconfig
> @@ -1,10 +1,25 @@
> config HAVE_TRUSTED_KEYS
> bool
>
> +config HAVE_TRUSTED_KEYS_DEBUG
> + bool
> +
> +config TRUSTED_KEYS_DEBUG
> + bool "Debug trusted keys"
> + depends on HAVE_TRUSTED_KEYS_DEBUG
> + default n
> + help
> + Trusted keys backends and core code that support debug dumps
> + can opt-in that feature here. Dumps must only use DEBUG
> + level output, as sensitive data may pass by. In the
> + kernel-command line traces can be enabled via
> + trusted.dyndbg='+p'.
Would it be good idea to add an explicit note/warning:
NOTE: This option is intended for debugging purposes only. Do not enable
on production systems as debug output may expose sensitive cryptographic
material.
If you are unsure, say N.
Apart from this, looks good to me.
Reviewed-by: Nayna Jain <nayna@linux.ibm.com>
^ permalink raw reply
* Re: [PATCH 1/3] crypto: public_key: Remove check for valid hash_algo for ML-DSA keys
From: Eric Biggers @ 2026-04-06 16:53 UTC (permalink / raw)
To: Stefan Berger
Cc: linux-integrity, linux-security-module, linux-kernel, zohar,
roberto.sassu, David Howells, Lukas Wunner, Ignat Korchagin,
keyrings, linux-crypto
In-Reply-To: <20260405231224.4008298-2-stefanb@linux.ibm.com>
On Sun, Apr 05, 2026 at 07:12:22PM -0400, Stefan Berger wrote:
> Remove the check for the hash_algo since ML-DSA is only used in pure mode
> and there is no relevance of a hash_algo for the input data.
>
> Cc: David Howells <dhowells@redhat.com>
> Cc: Lukas Wunner <lukas@wunner.de>
> Cc: Ignat Korchagin <ignat@linux.win>
> Cc: keyrings@vger.kernel.org
> Cc: linux-crypto@vger.kernel.org
> Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
> ---
> crypto/asymmetric_keys/public_key.c | 5 -----
> 1 file changed, 5 deletions(-)
>
> diff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c
> index 09a0b83d5d77..df6918a77ab8 100644
> --- a/crypto/asymmetric_keys/public_key.c
> +++ b/crypto/asymmetric_keys/public_key.c
> @@ -147,11 +147,6 @@ software_key_determine_akcipher(const struct public_key *pkey,
> strcmp(pkey->pkey_algo, "mldsa87") == 0) {
> if (strcmp(encoding, "raw") != 0)
> return -EINVAL;
> - if (!hash_algo)
> - return -EINVAL;
> - if (strcmp(hash_algo, "none") != 0 &&
> - strcmp(hash_algo, "sha512") != 0)
> - return -EINVAL;
Does this broaden which hash algorithms are accepted for CMS signatures
that use ML-DSA and contain signed attributes?
- Eric
^ permalink raw reply
* [ima-evm-utils PATCH 4/5] examples: Implement script to create ML-DSA-65 CA and signing keys
From: Stefan Berger @ 2026-04-06 0:08 UTC (permalink / raw)
To: linux-integrity; +Cc: zohar, roberto.sassu, ebiggers, coxu, Stefan Berger
In-Reply-To: <20260406000810.4013201-1-stefanb@linux.ibm.com>
ima-gen-local-ca-mldsa65.sh creates a CA with an ML-DSA-65 key and
ima-genkey-mldsa65.sh creates an ML-DSA-65 IMA file signing key along with
its certificate.
Also add a script for creating an ML-DSA-87 IMA file signing key. This key
type is good for local testing with the largest possible signature.
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
examples/ima-gen-local-ca-mldsa65.sh | 29 ++++++++++++++++++++++++
examples/ima-genkey-mldsa65.sh | 34 ++++++++++++++++++++++++++++
examples/ima-genkey-mldsa87.sh | 34 ++++++++++++++++++++++++++++
3 files changed, 97 insertions(+)
create mode 100755 examples/ima-gen-local-ca-mldsa65.sh
create mode 100755 examples/ima-genkey-mldsa65.sh
create mode 100755 examples/ima-genkey-mldsa87.sh
diff --git a/examples/ima-gen-local-ca-mldsa65.sh b/examples/ima-gen-local-ca-mldsa65.sh
new file mode 100755
index 0000000..e2b54cd
--- /dev/null
+++ b/examples/ima-gen-local-ca-mldsa65.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+GENKEY=ima-local-ca.genkey
+
+cat << __EOF__ >$GENKEY
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+string_mask = utf8only
+x509_extensions = v3_ca
+
+[ req_distinguished_name ]
+O = IMA-CA
+CN = IMA/EVM certificate signing key
+emailAddress = ca@ima-ca
+
+[ v3_ca ]
+basicConstraints=CA:TRUE
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer
+keyUsage = cRLSign, keyCertSign
+__EOF__
+
+openssl req -new -x509 -utf8 -sha256 -days 3650 -batch -config $GENKEY \
+ -outform DER -out ima-local-ca.x509 -keyout ima-local-ca.priv \
+ -newkey mldsa65
+
+openssl x509 -inform DER -in ima-local-ca.x509 -out ima-local-ca.pem
diff --git a/examples/ima-genkey-mldsa65.sh b/examples/ima-genkey-mldsa65.sh
new file mode 100755
index 0000000..b1aaf41
--- /dev/null
+++ b/examples/ima-genkey-mldsa65.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+GENKEY=ima.genkey
+
+cat << __EOF__ >$GENKEY
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+string_mask = utf8only
+x509_extensions = v3_usr
+
+[ req_distinguished_name ]
+O = `hostname`
+CN = `whoami` signing key
+emailAddress = `whoami`@`hostname`
+
+[ v3_usr ]
+basicConstraints=critical,CA:FALSE
+#basicConstraints=CA:FALSE
+keyUsage=digitalSignature
+#keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+extendedKeyUsage=critical,codeSigning
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid
+#authorityKeyIdentifier=keyid,issuer
+__EOF__
+
+openssl req -new -nodes -utf8 -sha256 -days 365 -batch -config $GENKEY \
+ -out csr_ima.pem -keyout privkey_ima.pem \
+ -newkey mldsa65
+openssl x509 -req -in csr_ima.pem -days 365 -extfile $GENKEY -extensions v3_usr \
+ -CA ima-local-ca.pem -CAkey ima-local-ca.priv -CAcreateserial \
+ -outform DER -out x509_ima.der
diff --git a/examples/ima-genkey-mldsa87.sh b/examples/ima-genkey-mldsa87.sh
new file mode 100755
index 0000000..347ff91
--- /dev/null
+++ b/examples/ima-genkey-mldsa87.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+GENKEY=ima.genkey
+
+cat << __EOF__ >$GENKEY
+[ req ]
+distinguished_name = req_distinguished_name
+prompt = no
+string_mask = utf8only
+x509_extensions = v3_usr
+
+[ req_distinguished_name ]
+O = `hostname`
+CN = `whoami` signing key
+emailAddress = `whoami`@`hostname`
+
+[ v3_usr ]
+basicConstraints=critical,CA:FALSE
+#basicConstraints=CA:FALSE
+keyUsage=digitalSignature
+#keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+extendedKeyUsage=critical,codeSigning
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid
+#authorityKeyIdentifier=keyid,issuer
+__EOF__
+
+openssl req -new -nodes -utf8 -sha256 -days 365 -batch -config $GENKEY \
+ -out csr_ima.pem -keyout privkey_ima.pem \
+ -newkey mldsa87
+openssl x509 -req -in csr_ima.pem -days 365 -extfile $GENKEY -extensions v3_usr \
+ -CA ima-local-ca.pem -CAkey ima-local-ca.priv -CAcreateserial \
+ -outform DER -out x509_ima.der
--
2.53.0
^ permalink raw reply related
* [ima-evm-utils PATCH 3/5] Support signing with ML-DSA keys when OpenSSL >=3.5 is available
From: Stefan Berger @ 2026-04-06 0:08 UTC (permalink / raw)
To: linux-integrity; +Cc: zohar, roberto.sassu, ebiggers, coxu, Stefan Berger
In-Reply-To: <20260406000810.4013201-1-stefanb@linux.ibm.com>
OpenSSL >= v3.5.0 supports signing with ML-DSA-44/65/87. Add support for
it to the imaevm_create_sigv3 library function. Since the ML-DSA signatures
require a lot more space for the signature now, increase the size of the
array where the signatures are stored. The following are the sizes of
ML-DSA signatures by key type:
- ML-DSA-44: 2420
- ML-DSA-65: 3309
- ML-DSA-87: 4627
Prevent signature V2 from being created with any other key types than
'RSA', 'EC', 'GOST' (ECRDSA), or 'SM2'.
In the functions that created a v2 signature, only RSA, ECDSA, and ECRDSA
signatures are created and they can easily work with the old buffer size of
less than 1024 bytes.
The size available for extended attributes may be smaller than what is
required by the ML-DSA signature size, and therefore may not be possible
to store for example ML-DSA-87 signatures (depends on type of filesystem).
Nevertheless, extend the MAX_SIGNATURE_SIZE to the required size of
ML-DSA-87 and display an error if writing the signature of a size larger
than 4k did not work.
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
README | 3 +-
src/evmctl.c | 4 +
src/imaevm.h | 5 +-
src/libimaevm.c | 258 ++++++++++++++++++++++++++++++++++++++++++++++--
4 files changed, 258 insertions(+), 12 deletions(-)
diff --git a/README b/README
index 34dfddf..1b0be4c 100644
--- a/README
+++ b/README
@@ -153,7 +153,8 @@ in the kernel (since kernel 3.9). CONFIG_INTEGRITY_ASYMMETRIC_KEYS must be enabl
For v2 and v3 signatures x509 certificate (containing the public key) could be appended to the
private key (they both are in PEM format) to automatically extract keyid from its Subject
-Key Identifier (SKID).
+Key Identifier (SKID). v3 signatures can be created with the --v3 option. This signature format
+is required for signing with ML-DSA keys.
Integrity keyrings
----------------
diff --git a/src/evmctl.c b/src/evmctl.c
index c8da495..44b52f6 100644
--- a/src/evmctl.c
+++ b/src/evmctl.c
@@ -617,6 +617,10 @@ static int sign_evm(const char *file, char *hash_algo, const char *key)
if (err < 0) {
log_errno_reset(LOG_ERR, "Setting EVM xattr failed: %s",
file);
+ if (len >= 4096)
+ log_err("The signature with %zu bytes is likely too large for the file "
+ "extended attribute. Consider using a different key type.\n",
+ len);
return err;
}
}
diff --git a/src/imaevm.h b/src/imaevm.h
index 5a8441b..a098ee3 100644
--- a/src/imaevm.h
+++ b/src/imaevm.h
@@ -74,8 +74,11 @@ typedef struct ossl_provider_st OSSL_PROVIDER;
#define DATA_SIZE 4096
#define SHA1_HASH_LEN 20
+#define ML_DSA_87_SIGNATURE_SIZE 4627
+
#define MAX_DIGEST_SIZE 64
-#define MAX_SIGNATURE_SIZE 1024
+#define MAX_SIGNATURE_SIZE (1 + sizeof(struct signature_v2_hdr) + \
+ ML_DSA_87_SIGNATURE_SIZE)
/*
* The maximum template data size is dependent on the template format. For
diff --git a/src/libimaevm.c b/src/libimaevm.c
index 49bfb62..d26074e 100644
--- a/src/libimaevm.c
+++ b/src/libimaevm.c
@@ -37,6 +37,9 @@
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/err.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000
+#include <openssl/core_names.h>
+#endif
#if CONFIG_IMA_EVM_ENGINE
#include <openssl/engine.h>
@@ -81,8 +84,25 @@ struct libimaevm_params imaevm_params = {
.hash_algo = DEFAULT_HASH_ALGO,
};
+#define HASH_MAX_DIGESTSIZE 64 /* kernel HASH_MAX_DIGESTSIZE is 64 bytes */
+
+struct ima_file_id {
+ __u8 hash_type; /* xattr type [enum evm_ima_xattr_type] */
+ __u8 hash_algorithm; /* Digest algorithm [enum hash_algo] */
+ __u8 hash[HASH_MAX_DIGESTSIZE];
+} __packed;
+
static void __attribute__ ((constructor)) libinit(void);
+/* RSA, ECDSA, ECDRSA, and SM2 all use hashes for signing */
+static inline bool keytype_uses_hash_for_signing(const char *keytype)
+{
+ return strcmp("RSA", keytype) == 0 ||
+ strcmp("EC", keytype) == 0 ||
+ strncmp("GOST2012_", keytype, 9) == 0 ||
+ strcmp("SM2", keytype) == 0;
+}
+
void imaevm_do_hexdump(FILE *fp, const void *ptr, int len, bool newline)
{
int i;
@@ -460,6 +480,77 @@ void init_public_keys(const char *keyfiles)
imaevm_init_public_keys(keyfiles, &g_public_keys);
}
+#if OPENSSL_VERSION_NUMBER >= 0x30500000
+static int verify_hashless(EVP_PKEY *pkey,
+ const char *algo,
+ const unsigned char *hash, int hash_size,
+ unsigned char *sig, int siglen,
+ enum evm_ima_xattr_type type,
+ const char *file)
+{
+ struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
+ struct ima_file_id file_id = { .hash_type = type, };
+ const char *keytype = EVP_PKEY_get0_type_name(pkey);
+ uint8_t *data = (uint8_t *)&file_id;
+ EVP_SIGNATURE *sig_alg = NULL;
+ unsigned int unused;
+ EVP_PKEY_CTX *ctx;
+ int hash_algo;
+ const char *st;
+ int ret = -1;
+
+ if (!algo) {
+ log_err("Hash algorithm unspecified\n");
+ return -EINVAL;
+ }
+
+ hash_algo = imaevm_get_hash_algo(algo);
+ if (hash_algo < 0) {
+ log_err("Hash algorithm %s not supported\n", algo);
+ return -EINVAL;
+ }
+ file_id.hash_algorithm = hash_algo;
+
+ memcpy(file_id.hash, hash, hash_size);
+ unused = HASH_MAX_DIGESTSIZE - hash_size;
+
+ st = "EVP_PKEY_CTX_new";
+ ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx)
+ goto err;
+ st = "EVP_SIGNATURE_fetch";
+ sig_alg = EVP_SIGNATURE_fetch(NULL, keytype, NULL);
+ if (!sig_alg)
+ goto err;
+ st = "EVP_PKEY_verify_message_init";
+ if (!EVP_PKEY_verify_message_init(ctx, sig_alg, NULL))
+ goto err;
+ st = "EVP_PKEY_verify";
+ ret = EVP_PKEY_verify(ctx, sig + sizeof(*hdr),
+ siglen - sizeof(*hdr),
+ data, sizeof(file_id) - unused);
+ if (ret == 1) {
+ ret = 0;
+ } else if (ret == 0) {
+ log_err("%s: verification failed: %d (%s)\n",
+ file, ret, ERR_reason_error_string(ERR_get_error()));
+ output_openssl_errors();
+ ret = 1;
+ }
+err:
+ if (ret < 0 || ret > 1) {
+ log_err("%s: verification failed: %d (%s) in %s\n",
+ file, ret, ERR_reason_error_string(ERR_peek_error()),
+ st);
+ output_openssl_errors();
+ ret = -1;
+ }
+ EVP_SIGNATURE_free(sig_alg);
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+}
+#endif
+
/*
* Verify a signature, prefixed with the signature_v2_hdr, either based
* directly or indirectly on the file data hash.
@@ -577,6 +668,28 @@ static int verify_hash_v3(struct public_key_entry *public_keys,
{
unsigned char sigv3_hash[MAX_DIGEST_SIZE];
int ret;
+#if OPENSSL_VERSION_NUMBER >= 0x30500000
+ struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)(sig + 1);
+ const char *keytype;
+ EVP_PKEY *pkey; // do not free here
+
+ pkey = find_keyid(public_keys, hdr->keyid);
+ if (!pkey) {
+ uint32_t keyid = hdr->keyid;
+
+ if (imaevm_params.verbose > LOG_INFO)
+ log_info("%s: verification failed: unknown keyid %x\n",
+ file, __be32_to_cpup(&keyid));
+ return -1;
+ }
+
+ keytype = EVP_PKEY_get0_type_name(pkey);
+
+ if (keytype && !keytype_uses_hash_for_signing(keytype))
+ return verify_hashless(pkey, hash_algo,
+ hash, size, sig + 1, siglen - 1,
+ sig[0], file);
+#endif
ret = calc_hash_sigv3(sig[0], hash_algo, hash, sigv3_hash);
if (ret < 0)
@@ -587,14 +700,6 @@ static int verify_hash_v3(struct public_key_entry *public_keys,
size, sig + 1, siglen - 1);
}
-#define HASH_MAX_DIGESTSIZE 64 /* kernel HASH_MAX_DIGESTSIZE is 64 bytes */
-
-struct ima_file_id {
- __u8 hash_type; /* xattr type [enum evm_ima_xattr_type] */
- __u8 hash_algorithm; /* Digest algorithm [enum hash_algo] */
- __u8 hash[HASH_MAX_DIGESTSIZE];
-} __packed;
-
/*
* Calculate the signature format version 3 hash based on the portion
* of the ima_file_id structure used, not the entire structure.
@@ -1292,7 +1397,7 @@ out:
#endif /* CONFIG_SIGV1 */
/*
- * @sig is assumed to be of (MAX_SIGNATURE_SIZE - 1) size
+ * @sig is assumed to be of at least (1024 - 1) size
* Return: -1 signing error, >0 length of signature
*/
static int sign_hash_v2(const char *algo, const unsigned char *hash,
@@ -1306,6 +1411,7 @@ static int sign_hash_v2(const char *algo, const unsigned char *hash,
EVP_PKEY *pkey;
char name[20];
EVP_PKEY_CTX *ctx = NULL;
+ const char *keytype;
const EVP_MD *md;
size_t sigsize;
const char *st;
@@ -1337,6 +1443,15 @@ static int sign_hash_v2(const char *algo, const unsigned char *hash,
if (!pkey)
return -1;
+#if OPENSSL_VERSION_NUMBER >= 0x30000000
+ keytype = EVP_PKEY_get0_type_name(pkey);
+ /* if it's neither an RSA, EC(R)DSA, nor SM2 key then it cannot be used here */
+ if (keytype && !keytype_uses_hash_for_signing(keytype)) {
+ log_err("sign_hash_v2: Cannot use '%s' type of key\n", keytype);
+ return -1;
+ }
+#endif
+
hdr = (struct signature_v2_hdr *)sig;
hdr->version = (uint8_t) DIGSIG_VERSION_2;
@@ -1379,7 +1494,7 @@ static int sign_hash_v2(const char *algo, const unsigned char *hash,
if (!EVP_PKEY_CTX_set_signature_md(ctx, md))
goto err;
st = "EVP_PKEY_sign";
- sigsize = MAX_SIGNATURE_SIZE - sizeof(struct signature_v2_hdr) - 1;
+ sigsize = 1024 - sizeof(struct signature_v2_hdr) - 1;
if (!EVP_PKEY_sign(ctx, hdr->sig, &sigsize, hash, size))
goto err;
len = (int)sigsize;
@@ -1451,6 +1566,106 @@ int imaevm_signhash(const char *hashalgo, const unsigned char *hash, int size,
access_info, keyid);
}
+#if OPENSSL_VERSION_NUMBER >= 0x30500000
+static int create_sigv3_hashless(EVP_PKEY *pkey, const char *algo,
+ const unsigned char *hash, int hash_size,
+ unsigned char **sig, size_t siglen,
+ enum evm_ima_xattr_type type,
+ const struct imaevm_ossl_access *access_info,
+ uint32_t keyid)
+{
+ const char *keytype = EVP_PKEY_get0_type_name(pkey);
+ struct ima_file_id file_id = { .hash_type = type, };
+ uint8_t *data = (uint8_t *)&file_id;
+ EVP_SIGNATURE *sig_alg = NULL;
+ struct signature_v2_hdr *hdr;
+ EVP_PKEY_CTX *ctx = NULL;
+ bool allocated = false;
+ unsigned int unused;
+ size_t slen = -1;
+ const char *st;
+ int hash_algo;
+ char name[20];
+
+ hash_algo = imaevm_get_hash_algo(algo);
+ if (hash_algo < 0) {
+ log_err("Hash algorithm %s not supported\n", algo);
+ return -EINVAL;
+ }
+ file_id.hash_algorithm = hash_algo;
+ memcpy(file_id.hash, hash, hash_size);
+ unused = HASH_MAX_DIGESTSIZE - hash_size;
+
+ st = "EVP_PKEY_CTX_new";
+ ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx)
+ goto err;
+ st = "EVP_SIGNATURE_fetch";
+ sig_alg = EVP_SIGNATURE_fetch(NULL, keytype, NULL);
+ if (!sig_alg)
+ goto err;
+ st = "EVP_PKEY_sign_init";
+ if (!EVP_PKEY_sign_message_init(ctx, sig_alg, NULL))
+ goto err;
+ st = "EVP_PKEY_sign";
+ /* query for size of signature */
+ if (!EVP_PKEY_sign(ctx, NULL, &slen,
+ data, sizeof(file_id) - unused)) {
+ slen = -1;
+ goto err;
+ }
+
+ if (*sig) {
+ if (siglen < (1 + sizeof(*hdr) + slen)) {
+ siglen = -1;
+ goto err;
+ }
+ } else {
+ *sig = malloc(1 + sizeof(*hdr) + slen);
+ if (!*sig) {
+ siglen = -1;
+ goto err;
+ }
+ allocated = true;
+ }
+ hdr = (struct signature_v2_hdr *)(*sig + 1);
+
+ if (!EVP_PKEY_sign(ctx, hdr->sig, &slen,
+ data, sizeof(file_id) - unused)) {
+ slen = -1;
+ goto err;
+ }
+
+ (*sig)[0] = type;
+ hdr->version = DIGSIG_VERSION_3;
+ hdr->hash_algo = hash_algo;
+ if (keyid)
+ keyid = htonl(keyid);
+ else
+ calc_keyid_v2(&keyid, name, pkey);
+ hdr->keyid = keyid;
+ hdr->sig_size = __cpu_to_be16(slen);
+
+ siglen = 1 + sizeof(*hdr) + slen;
+ log_info("evm/ima signature: %zu bytes\n", siglen);
+
+err:
+ EVP_SIGNATURE_free(sig_alg);
+ EVP_PKEY_CTX_free(ctx);
+ if (slen == -1) {
+ if (allocated) {
+ free(*sig);
+ *sig = NULL;
+ }
+ log_err("create_sigv3_mldsa signing failed: (%s) in %s\n",
+ ERR_reason_error_string(ERR_peek_error()), st);
+ output_openssl_errors();
+ return -1;
+ }
+ return siglen;
+}
+#endif
+
/*
* Create a v3 signature given a file hash
*
@@ -1482,6 +1697,29 @@ int imaevm_create_sigv3(const char *hash_algo, const unsigned char *hash, int si
/* buffer capable of holding (more than) RSA-4096 signature; */
unsigned char sigbuf[1024];
int len, slen, err;
+#if OPENSSL_VERSION_NUMBER >= 0x30500000
+ const char *keytype;
+ EVP_PKEY *pkey;
+
+ if (access_info) {
+ err = check_ossl_access(access_info);
+ if (err)
+ return err;
+ }
+ pkey = read_priv_pkey(keyfile, keypass, access_info, keyid);
+ if (!pkey)
+ return -1;
+
+ keytype = EVP_PKEY_get0_type_name(pkey);
+ if (keytype && strncmp("ML-DSA-", keytype, 7) == 0) {
+ slen = create_sigv3_hashless(pkey, hash_algo, hash, size,
+ sig, siglen,
+ xattr_type, access_info, keyid);
+ EVP_PKEY_free(pkey);
+ return slen;
+ }
+ EVP_PKEY_free(pkey);
+#endif
len = calc_hash_sigv3(xattr_type, hash_algo, hash, sigv3_hash);
if (len < 0 || len == 1) {
--
2.53.0
^ permalink raw reply related
* [ima-evm-utils PATCH 5/5] test: Add tests for signing and verifying with ML-DSA keys
From: Stefan Berger @ 2026-04-06 0:08 UTC (permalink / raw)
To: linux-integrity; +Cc: zohar, roberto.sassu, ebiggers, coxu, Stefan Berger
In-Reply-To: <20260406000810.4013201-1-stefanb@linux.ibm.com>
Create ML-DSA-44 & ML-DSA-65 keys if ML-DSA-44 can be created with the
installed version of OpenSSL. Add test cases for signing and verifying with
these types of keys.
Do not test with ML-DSA-87 keys since the signatures they create may be
too large for some filesystems' xattrs. On Btrfs for example it would be
possible to store the large signatures.
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
tests/gen-keys.sh | 22 ++++++++++++++++++++++
tests/sign_verify.test | 40 ++++++++++++++++++++++++++++++++--------
2 files changed, 54 insertions(+), 8 deletions(-)
diff --git a/tests/gen-keys.sh b/tests/gen-keys.sh
index db0189a..13e6d77 100755
--- a/tests/gen-keys.sh
+++ b/tests/gen-keys.sh
@@ -148,6 +148,28 @@ if [ -x /opt/openssl3/bin/openssl ]; then
done)
fi
+# If creating mldsa44 key works, create all ML-DSA sizes
+if openssl genpkey -algorithm mldsa44 &>/dev/null; then
+ for mldsa in mldsa44 mldsa65; do
+ if [ "$1" = clean ] || [ "$1" = force ]; then
+ rm -f test-$mldsa.cer test-$mldsa.key test-$mldsa.pub
+ fi
+ if [ "$1" = clean ]; then
+ continue
+ fi
+ if [ ! -e test-$mldsa.key ]; then
+ log openssl req -verbose -new -nodes -utf8 -sha256 -days 10000 -batch -x509 \
+ -config test-ca.conf \
+ -newkey "$mldsa" \
+ -out test-$mldsa.cer -outform DER \
+ -keyout test-$mldsa.key
+ if [ -s test-$mldsa.key ]; then
+ log openssl pkey -in test-$mldsa.key -out test-$mldsa.pub -pubout
+ fi
+ fi
+ done
+fi
+
# This script leaves test-ca.conf, *.cer, *.pub, *.key files for sing/verify tests.
# They are never deleted except by `make distclean'.
diff --git a/tests/sign_verify.test b/tests/sign_verify.test
index 9319123..2b94b91 100755
--- a/tests/sign_verify.test
+++ b/tests/sign_verify.test
@@ -166,8 +166,15 @@ check_sign() {
fi
# Can openssl sign with this digest and key?
- cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -sign $key -hex $FILE"
- echo - "$cmd"
+ case "${KEY:0:10}" in
+ "test-mldsa")
+ cmd="openssl pkeyutl -sign -inkey $key -in $FILE"
+ echo >> "$FILE" # need at least 1 byte in the file for signing to work
+ ;;
+ *)
+ cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -sign $key -hex $FILE"
+ ;;
+ esac
if ! $cmd >/dev/null; then
echo "${CYAN}$ALG ($key) test is skipped (openssl is unable to sign)$NORM"
return "$SKIP"
@@ -216,11 +223,21 @@ check_sign() {
if [[ "$OPTS" =~ "--v3" ]]; then
# In case of v3 signatures we need to create ima_file_id now.
# All data for it can be found in PREFIX and by hashing $FILE.
- echo -en "\x${PREFIX:2:2}\x${PREFIX:6:2}" > "$FILE.tmp"
+ echo -en "\x${PREFIX:2:2}\x${PREFIX:6:2}" > "$FILE.ima_file_id"
# shellcheck disable=SC2086
- openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -"$ALG" -binary "$FILE" >> "$FILE.tmp"
- cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -verify ${verifykey} \
- -signature $FILE.sig2 $FILE.tmp"
+ openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -"$ALG" -binary "$FILE" \
+ >> "$FILE.ima_file_id"
+
+ case "${KEY:0:10}" in
+ "test-mldsa")
+ # ML-DSA does not accept a hash algorithm on command line
+ cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -verify ${verifykey} \
+ -signature $FILE.sig2 $FILE.ima_file_id"
+ ;;
+ *)
+ cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -verify ${verifykey} \
+ -signature $FILE.sig2 $FILE.ima_file_id"
+ esac
sigver=3
else
cmd="openssl dgst $OPENSSL_ENGINE $OPENSSL_KEYFORM -$ALG -verify ${verifykey} \
@@ -232,11 +249,11 @@ check_sign() {
color_red_on_failure
echo "Signature v${sigver} verification with openssl is failed."
color_restore
- rm "$FILE.sig2" "$FILE.tmp"
+ rm "$FILE.sig2" "$FILE.ima_file_id"
return "$FAIL"
fi
- rm "$FILE.sig2" "$FILE.tmp"
+ rm "$FILE.sig2" "$FILE.ima_file_id"
return "$OK"
}
@@ -424,6 +441,13 @@ sign_verify prime256v1 sha256 0x030304:K:004[345678] --v3
sign_verify prime256v1 sha384 0x030305:K:004[345678] --v3
sign_verify prime256v1 sha512 0x030306:K:004[345678] --v3
+sign_verify mldsa44 sha256 0x030304:K:0974 --v3
+sign_verify mldsa44 sha384 0x030305:K:0974 --v3
+sign_verify mldsa44 sha512 0x030306:K:0974 --v3
+sign_verify mldsa65 sha256 0x030304:K:0ced --v3
+sign_verify mldsa65 sha384 0x030305:K:0ced --v3
+sign_verify mldsa65 sha512 0x030306:K:0ced --v3
+
# If openssl 3.0 is installed, test the SM2/3 algorithm combination
ssl_major_version=$(openssl version | sed -n 's/^OpenSSL \([^\.]\).*/\1/p')
if [ "${ssl_major_version}" = 3 ]; then
--
2.53.0
^ permalink raw reply related
* [ima-evm-utils PATCH 1/5] checkpatch: Remove warning when function name is found in output string
From: Stefan Berger @ 2026-04-06 0:08 UTC (permalink / raw)
To: linux-integrity; +Cc: zohar, roberto.sassu, ebiggers, coxu, Stefan Berger
In-Reply-To: <20260406000810.4013201-1-stefanb@linux.ibm.com>
Remove the following type of warning:
WARNING: Prefer using '"%s...", __func__' to using 'create_sigv3_mldsa', \
this function's name, in a string
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
scripts/checkpatch.pl | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 3d22bf8..f9553be 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -6285,13 +6285,13 @@ sub process {
# This does not work very well for -f --file checking as it depends on patch
# context providing the function name or a single line form for in-file
# function declarations
- if ($line =~ /^\+.*$String/ &&
- defined($context_function) &&
- get_quoted_string($line, $rawline) =~ /\b$context_function\b/ &&
- length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) {
- WARN("EMBEDDED_FUNCTION_NAME",
- "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr);
- }
+# if ($line =~ /^\+.*$String/ &&
+# defined($context_function) &&
+# get_quoted_string($line, $rawline) =~ /\b$context_function\b/ &&
+# length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) {
+# WARN("EMBEDDED_FUNCTION_NAME",
+# "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr);
+# }
# check for unnecessary function tracing like uses
# This does not use $logFunctions because there are many instances like
--
2.53.0
^ permalink raw reply related
* [ima-evm-utils PATCH 0/5] Add support for ML-DSA signing and verification
From: Stefan Berger @ 2026-04-06 0:08 UTC (permalink / raw)
To: linux-integrity; +Cc: zohar, roberto.sassu, ebiggers, coxu, Stefan Berger
This series adds support for ML-DSA signing and verification for EVM and
IMA using ML-DSA in pure mode and IMA's sigv3 signature format. Evmctl
will prevent users from trying to use sigv2 signatures with ML-DSA and
therefore ML-DSA requires the usage of --v3. Test cases for IMA and EVM
sigv3 are added using ML-DSA-44 (2420 bytes) and ML-DSA-65 (3309 bytes)
type of keys since those are more likely to fit into xattrs on most
filesystems than the even larger ML-DSA-87 signatures (4627 bytes).
Stefan
Stefan Berger (5):
checkpatch: Remove warning when function name is found in output
string
Set size of xattr_value to MAX_SIGNATURE_SIZE
Support signing with ML-DSA keys when OpenSSL >=3.5 is available
examples: Implement script to create ML-DSA-65 CA and signing keys
test: Add tests for signing and verifying with ML-DSA keys
README | 3 +-
examples/ima-gen-local-ca-mldsa65.sh | 29 +++
examples/ima-genkey-mldsa65.sh | 34 ++++
examples/ima-genkey-mldsa87.sh | 34 ++++
scripts/checkpatch.pl | 14 +-
src/evmctl.c | 6 +-
src/imaevm.h | 5 +-
src/libimaevm.c | 258 +++++++++++++++++++++++++--
tests/gen-keys.sh | 22 +++
tests/sign_verify.test | 40 ++++-
10 files changed, 417 insertions(+), 28 deletions(-)
create mode 100755 examples/ima-gen-local-ca-mldsa65.sh
create mode 100755 examples/ima-genkey-mldsa65.sh
create mode 100755 examples/ima-genkey-mldsa87.sh
--
2.53.0
^ permalink raw reply
* [ima-evm-utils PATCH 2/5] Set size of xattr_value to MAX_SIGNATURE_SIZE
From: Stefan Berger @ 2026-04-06 0:08 UTC (permalink / raw)
To: linux-integrity; +Cc: zohar, roberto.sassu, ebiggers, coxu, Stefan Berger
In-Reply-To: <20260406000810.4013201-1-stefanb@linux.ibm.com>
Set the size of an xattr_value that can be read to MAX_SIGNATURE_SIZE
so that ML-DSA keys can also be read once enabled (and MAX_SIGNATURE_SIZE
gets a larger value).
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
src/evmctl.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/evmctl.c b/src/evmctl.c
index de67178..c8da495 100644
--- a/src/evmctl.c
+++ b/src/evmctl.c
@@ -347,7 +347,7 @@ static int calc_evm_hash(const char *file, const char *hash_algo,
EVP_MD_CTX *pctx;
unsigned int mdlen;
char **xattrname;
- char xattr_value[1024];
+ char xattr_value[MAX_SIGNATURE_SIZE];
char list[1024];
ssize_t list_size;
char uuid[16];
--
2.53.0
^ permalink raw reply related
* [PATCH 1/3] crypto: public_key: Remove check for valid hash_algo for ML-DSA keys
From: Stefan Berger @ 2026-04-05 23:12 UTC (permalink / raw)
To: linux-integrity, linux-security-module
Cc: linux-kernel, zohar, roberto.sassu, ebiggers, Stefan Berger,
David Howells, Lukas Wunner, Ignat Korchagin, keyrings,
linux-crypto
In-Reply-To: <20260405231224.4008298-1-stefanb@linux.ibm.com>
Remove the check for the hash_algo since ML-DSA is only used in pure mode
and there is no relevance of a hash_algo for the input data.
Cc: David Howells <dhowells@redhat.com>
Cc: Lukas Wunner <lukas@wunner.de>
Cc: Ignat Korchagin <ignat@linux.win>
Cc: keyrings@vger.kernel.org
Cc: linux-crypto@vger.kernel.org
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
crypto/asymmetric_keys/public_key.c | 5 -----
1 file changed, 5 deletions(-)
diff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c
index 09a0b83d5d77..df6918a77ab8 100644
--- a/crypto/asymmetric_keys/public_key.c
+++ b/crypto/asymmetric_keys/public_key.c
@@ -147,11 +147,6 @@ software_key_determine_akcipher(const struct public_key *pkey,
strcmp(pkey->pkey_algo, "mldsa87") == 0) {
if (strcmp(encoding, "raw") != 0)
return -EINVAL;
- if (!hash_algo)
- return -EINVAL;
- if (strcmp(hash_algo, "none") != 0 &&
- strcmp(hash_algo, "sha512") != 0)
- return -EINVAL;
} else {
/* Unknown public key algorithm */
return -ENOPKG;
--
2.53.0
^ permalink raw reply related
* [PATCH 3/3] integrity: Add support for sigv3 verification using ML-DSA keys
From: Stefan Berger @ 2026-04-05 23:12 UTC (permalink / raw)
To: linux-integrity, linux-security-module
Cc: linux-kernel, zohar, roberto.sassu, ebiggers, Stefan Berger
In-Reply-To: <20260405231224.4008298-1-stefanb@linux.ibm.com>
Add support for sigv3 signature verification using ML-DSA in pure mode.
When a sigv3 signature is verified, first check whether the key to use
for verification is an ML-DSA key and therefore uses a hashless signature
verification scheme. The hashless signature verification method uses the
ima_file_id structure directly for signature verification rather than
its digest.
Suggested-by: Eric Biggers <ebiggers@kernel.org>
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
security/integrity/digsig_asymmetric.c | 84 ++++++++++++++++++++++++--
1 file changed, 79 insertions(+), 5 deletions(-)
diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c
index e29ed73f15cd..e25534117c16 100644
--- a/security/integrity/digsig_asymmetric.c
+++ b/security/integrity/digsig_asymmetric.c
@@ -190,17 +190,91 @@ static int calc_file_id_hash(enum evm_ima_xattr_type type,
return rc;
}
+/*
+ * asymmetric_verify_v3_hashless - Use hashless signature verification on sigv3
+ * @key: The key to use for signature verification
+ * @pk: The associated public key
+ * @encoding: The encoding the key type uses
+ * @sig: The signature
+ * @siglen: The length of the xattr signature
+ * @algo: The hash algorithm
+ * @digest: The file digest
+ *
+ * Create an ima_file_id structure and use it for signature verification
+ * directly. This can be used for ML-DSA in pure mode for example.
+ */
+static int asymmetric_verify_v3_hashless(struct key *key,
+ const struct public_key *pk,
+ const char *encoding,
+ const char *sig, int siglen,
+ u8 algo,
+ const u8 *digest)
+{
+ struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
+ struct ima_file_id file_id = {
+ .hash_type = hdr->type,
+ .hash_algorithm = algo,
+ };
+ size_t digest_size = hash_digest_size[algo];
+ struct public_key_signature pks = {
+ .m = (u8 *)&file_id,
+ .m_size = sizeof(file_id) - (HASH_MAX_DIGESTSIZE - digest_size),
+ .s = hdr->sig,
+ .s_size = siglen - sizeof(*hdr),
+ .pkey_algo = pk->pkey_algo,
+ .hash_algo = hash_algo_name[hdr->hash_algo],
+ .encoding = encoding,
+ };
+ int ret;
+
+ if (hdr->type != IMA_VERITY_DIGSIG &&
+ hdr->type != EVM_IMA_XATTR_DIGSIG &&
+ hdr->type != EVM_XATTR_PORTABLE_DIGSIG)
+ return -EINVAL;
+
+ if (pks.s_size != be16_to_cpu(hdr->sig_size))
+ return -EBADMSG;
+
+ memcpy(file_id.hash, digest, digest_size);
+
+ ret = verify_signature(key, &pks);
+ pr_debug("%s() = %d\n", __func__, ret);
+ return ret;
+}
+
int asymmetric_verify_v3(struct key *keyring, const char *sig, int siglen,
const char *data, int datalen, u8 algo)
{
struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
struct ima_max_digest_data hash;
+ const struct public_key *pk;
+ struct key *key;
int rc;
- rc = calc_file_id_hash(hdr->type, algo, data, &hash);
- if (rc)
- return -EINVAL;
+ if (siglen <= sizeof(*hdr))
+ return -EBADMSG;
+
+ key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid));
+ if (IS_ERR(key))
+ return PTR_ERR(key);
- return asymmetric_verify(keyring, sig, siglen, hash.digest,
- hash.hdr.length);
+ pk = asymmetric_key_public_key(key);
+ if (!strncmp(pk->pkey_algo, "mldsa", 5)) {
+ rc = asymmetric_verify_v3_hashless(key, pk, "raw",
+ sig, siglen, algo, data);
+ } else {
+ rc = calc_file_id_hash(hdr->type, algo, data, &hash);
+ if (rc) {
+ rc = -EINVAL;
+ goto err_exit;
+ }
+
+ rc = asymmetric_verify_common(key, pk, sig, siglen, hash.digest,
+ hash.hdr.length);
+ }
+
+err_exit:
+ key_put(key);
+
+ return rc;
}
--
2.53.0
^ permalink raw reply related
* [PATCH 2/3] integrity: Refactor asymmetric_verify for reusability
From: Stefan Berger @ 2026-04-05 23:12 UTC (permalink / raw)
To: linux-integrity, linux-security-module
Cc: linux-kernel, zohar, roberto.sassu, ebiggers, Stefan Berger
In-Reply-To: <20260405231224.4008298-1-stefanb@linux.ibm.com>
Refactor asymmetric_verify for reusability. Have it call
asymmetric_verify_common with the signature verification key and the
public_key structure as parameters. sigv3 support for ML-DSA will need to
check the public key type first to decide how to do the signature
verification and therefore will have these parameters available for
calling asymmetric_verify_common.
Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
security/integrity/digsig_asymmetric.c | 42 +++++++++++++++++---------
1 file changed, 28 insertions(+), 14 deletions(-)
diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c
index 6e68ec3becbd..e29ed73f15cd 100644
--- a/security/integrity/digsig_asymmetric.c
+++ b/security/integrity/digsig_asymmetric.c
@@ -79,18 +79,15 @@ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid)
return key;
}
-int asymmetric_verify(struct key *keyring, const char *sig,
- int siglen, const char *data, int datalen)
+static int asymmetric_verify_common(const struct key *key,
+ const struct public_key *pk,
+ const char *sig, int siglen,
+ const char *data, int datalen)
{
- struct public_key_signature pks;
struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
- const struct public_key *pk;
- struct key *key;
+ struct public_key_signature pks;
int ret;
- if (siglen <= sizeof(*hdr))
- return -EBADMSG;
-
siglen -= sizeof(*hdr);
if (siglen != be16_to_cpu(hdr->sig_size))
@@ -99,15 +96,10 @@ int asymmetric_verify(struct key *keyring, const char *sig,
if (hdr->hash_algo >= HASH_ALGO__LAST)
return -ENOPKG;
- key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid));
- if (IS_ERR(key))
- return PTR_ERR(key);
-
memset(&pks, 0, sizeof(pks));
pks.hash_algo = hash_algo_name[hdr->hash_algo];
- pk = asymmetric_key_public_key(key);
pks.pkey_algo = pk->pkey_algo;
if (!strcmp(pk->pkey_algo, "rsa")) {
pks.encoding = "pkcs1";
@@ -127,11 +119,33 @@ int asymmetric_verify(struct key *keyring, const char *sig,
pks.s_size = siglen;
ret = verify_signature(key, &pks);
out:
- key_put(key);
pr_debug("%s() = %d\n", __func__, ret);
return ret;
}
+int asymmetric_verify(struct key *keyring, const char *sig,
+ int siglen, const char *data, int datalen)
+{
+ struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig;
+ const struct public_key *pk;
+ struct key *key;
+ int ret;
+
+ if (siglen <= sizeof(*hdr))
+ return -EBADMSG;
+
+ key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid));
+ if (IS_ERR(key))
+ return PTR_ERR(key);
+ pk = asymmetric_key_public_key(key);
+
+ ret = asymmetric_verify_common(key, pk, sig, siglen, data, datalen);
+
+ key_put(key);
+
+ return ret;
+}
+
/*
* calc_file_id_hash - calculate the hash of the ima_file_id struct data
* @type: xattr type [enum evm_ima_xattr_type]
--
2.53.0
^ permalink raw reply related
* [PATCH 0/3] Add support for ML-DSA signature for EVM and IMA
From: Stefan Berger @ 2026-04-05 23:12 UTC (permalink / raw)
To: linux-integrity, linux-security-module
Cc: linux-kernel, zohar, roberto.sassu, ebiggers, Stefan Berger
Based on IMA sigv3 type of signatures, add support for ML-DSA signature
for EVM and IMA. Use the existing ML-DSA hashless signing mode (pure mode).
Stefan
Stefan Berger (3):
crypto: public_key: Remove check for valid hash_algo for ML-DSA keys
integrity: Refactor asymmetric_verify for reusability
integrity: Add support for sigv3 verification using ML-DSA keys
crypto/asymmetric_keys/public_key.c | 5 -
security/integrity/digsig_asymmetric.c | 126 +++++++++++++++++++++----
2 files changed, 107 insertions(+), 24 deletions(-)
base-commit: 82bbd447199ff1441031d2eaf9afe041550cf525
--
2.53.0
^ permalink raw reply
* Re: [PATCH 1/3] ima: Define asymmetric_verify_v3() to verify IMA sigv3 signatures
From: Mimi Zohar @ 2026-04-05 9:46 UTC (permalink / raw)
To: Eric Biggers; +Cc: linux-integrity, Stefan Berger
In-Reply-To: <20260330201336.GE4303@sol>
On Mon, 2026-03-30 at 13:13 -0700, Eric Biggers wrote:
> On Tue, Mar 24, 2026 at 04:39:27PM -0400, Mimi Zohar wrote:
> > + * IMA signature version 3 disambiguates the data that is signed by
> > + * indirectly signing the hash of the ima_file_id structure data.
>
> The right way to think about it is that it's the ima_file_id itself that
> is being signed and verified, and taking the hash of it is only a
> workaround for legacy algorithms that can only sign and verify hashes.
> With modern algorithms like Ed25519 and ML-DSA that accept
> arbitrary-length messages, that workaround won't be needed.
I'll keep that in mind. As previously discussed, the hashes are being
calculated for other purposes, like inclusion in the IMA measurement list and
the audit log. Providing the potentially large, variable sized data so that the
crypto signing/verifying algorithm can recalculate the hash is superfluous.
Your recommendation of signing the ima_file_id works nicely.
thanks!
Mimi
^ permalink raw reply
* [PATCH] tpm: Fix auth session leak in tpm2_get_random() error path
From: Gunnar Kudrjavets @ 2026-04-02 18:11 UTC (permalink / raw)
To: peterhuewe, jarkko
Cc: jgg, noodles, linux-integrity, linux-kernel, Justinien Bouron
When tpm_buf_fill_hmac_session() fails inside the do-while loop in
tpm2_get_random(), the function returns directly after destroying the
buffer, without ending the auth session via tpm2_end_auth_session().
This leaks the TPM auth session resource. All other error paths within
the loop correctly reach the 'out' label which calls both
tpm_buf_destroy() and tpm2_end_auth_session().
Fix this by replacing the early return with a goto to the existing 'out'
label, which already handles both cleanup operations. The redundant
tpm_buf_destroy() call is removed since 'out' takes care of it.
Fixes: 6e9722e9a7bf ("tpm2-sessions: Fix out of range indexing in name_size")
Signed-off-by: Gunnar Kudrjavets <gunnarku@amazon.com>
Reviewed-by: Justinien Bouron <jbouron@amazon.com>
---
drivers/char/tpm/tpm2-cmd.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c
index e00f668f8c84..b11e6fa8b740 100644
--- a/drivers/char/tpm/tpm2-cmd.c
+++ b/drivers/char/tpm/tpm2-cmd.c
@@ -295,10 +295,8 @@ int tpm2_get_random(struct tpm_chip *chip, u8 *dest, size_t max)
}
tpm_buf_append_u16(&buf, num_bytes);
err = tpm_buf_fill_hmac_session(chip, &buf);
- if (err) {
- tpm_buf_destroy(&buf);
- return err;
- }
+ if (err)
+ goto out;
err = tpm_transmit_cmd(chip, &buf,
offsetof(struct tpm2_get_random_out,
base-commit: 7f2a32c0e87814f0e7852b17fa9f10321f882c36
--
2.47.3
^ permalink raw reply related
* Re: [PATCH v4 09/13] ima: Add support for staging measurements with prompt
From: steven chen @ 2026-04-01 17:52 UTC (permalink / raw)
To: Roberto Sassu, corbet, skhan, 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: <19a1815a1222bd78f6bfde30f60b60ebfacb65aa.camel@huaweicloud.com>
On 3/27/2026 9:45 AM, Roberto Sassu wrote:
> On Thu, 2026-03-26 at 15:44 -0700, steven chen wrote:
>> On 3/26/2026 10:30 AM, Roberto Sassu wrote:
>>> From: Roberto Sassu <roberto.sassu@huawei.com>
>>>
>>> Introduce the ability of staging the IMA measurement list and deleting them
>>> with a prompt.
>>>
>>> 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. In the event of kexec(),
>>> staging is reverted and staged entries will be carried over to the new
>>> kernel.
>>>
>>> Introduce ascii_runtime_measurements_<algo>_staged and
>>> binary_runtime_measurements_<algo>_staged interfaces to stage and delete
>>> the measurements. Use 'echo A > <IMA interface>' and
>>> 'echo D > <IMA interface>' to respectively stage and delete the entire
>>> measurements list. Locking of these interfaces is also mediated with a call
>>> to _ima_measurements_open() and with ima_measurements_release().
>>>
>>> Implement the staging functionality by introducing the new global
>>> measurements list ima_measurements_staged, and ima_queue_stage() and
>>> ima_queue_delete_staged_all() to respectively move measurements from the
>>> current measurements list to the staged one, and to move staged
>>> measurements to the ima_measurements_trim list for deletion. Introduce
>>> ima_queue_delete() to delete the measurements.
>>>
>>> Finally, introduce the BINARY_STAGED AND BINARY_FULL binary measurements
>>> list types, to maintain the counters and the binary size of staged
>>> measurements and the full measurements list (including entries that were
>>> staged). BINARY still represents the current binary measurements list.
>>>
>>> Use the binary size for the BINARY + BINARY_STAGED types in
>>> ima_add_kexec_buffer(), since both measurements list types are copied to
>>> the secondary kernel during kexec. Use BINARY_FULL in
>>> ima_measure_kexec_event(), to generate a critical data record.
>>>
>>> It should be noted that the BINARY_FULL counter is not passed through
>>> kexec. Thus, the number of entries included in the kexec critical data
>>> records refers to the entries since the previous kexec records.
>>>
>>> Note: This code derives from the Alt-IMA Huawei project, whose license is
>>> GPL-2.0 OR MIT.
>>>
>>> Link: https://github.com/linux-integrity/linux/issues/1
>>> Suggested-by: Gregory Lumen <gregorylumen@linux.microsoft.com> (staging revert)
>>> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
>>> ---
>>> security/integrity/ima/Kconfig | 13 +++
>>> security/integrity/ima/ima.h | 8 +-
>>> security/integrity/ima/ima_fs.c | 167 ++++++++++++++++++++++++++---
>>> security/integrity/ima/ima_kexec.c | 22 +++-
>>> security/integrity/ima/ima_queue.c | 97 ++++++++++++++++-
>>> 5 files changed, 286 insertions(+), 21 deletions(-)
>>>
>>> diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
>>> index 976e75f9b9ba..e714726f3384 100644
>>> --- a/security/integrity/ima/Kconfig
>>> +++ b/security/integrity/ima/Kconfig
>>> @@ -332,4 +332,17 @@ config IMA_KEXEC_EXTRA_MEMORY_KB
>>> If set to the default value of 0, an extra half page of memory for those
>>> additional measurements will be allocated.
>>>
>>> +config IMA_STAGING
>>> + bool "Support for staging the measurements list"
>>> + default y
>>> + help
>>> + Add support for staging the measurements list.
>>> +
>>> + It allows user space to stage the measurements list for deletion and
>>> + to delete the staged measurements after confirmation.
>>> +
>>> + On kexec, staging is reverted and staged measurements are prepended
>>> + to the current measurements list when measurements are copied to the
>>> + secondary kernel.
>>> +
>>> endif
>>> diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
>>> index 97b7d6024b5d..65db152a0a24 100644
>>> --- a/security/integrity/ima/ima.h
>>> +++ b/security/integrity/ima/ima.h
>>> @@ -30,9 +30,11 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 };
>>>
>>> /*
>>> * BINARY: current binary measurements list
>>> + * BINARY_STAGED: staged binary measurements list
>>> + * BINARY_FULL: binary measurements list since IMA init (lost after kexec)
>>> */
>>> enum binary_lists {
>>> - BINARY, BINARY__LAST
>>> + BINARY, BINARY_STAGED, BINARY_FULL, BINARY__LAST
>>> };
>>>
>>> /* digest size for IMA, fits SHA1 or MD5 */
>>> @@ -125,6 +127,7 @@ 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. */
>>>
>>> /* Some details preceding the binary serialized measurement list */
>>> struct ima_kexec_hdr {
>>> @@ -314,6 +317,8 @@ 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(void);
>>> +int ima_queue_staged_delete_all(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);
>>> @@ -334,6 +339,7 @@ extern spinlock_t ima_queue_lock;
>>> extern atomic_long_t ima_num_entries[BINARY__LAST];
>>> extern atomic_long_t ima_num_violations;
>>> extern struct hlist_head __rcu *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 7709a4576322..39d9128e9f22 100644
>>> --- a/security/integrity/ima/ima_fs.c
>>> +++ b/security/integrity/ima/ima_fs.c
>>> @@ -24,6 +24,13 @@
>>>
>>> #include "ima.h"
>>>
>>> +/*
>>> + * Requests:
>>> + * 'A\n': stage the entire measurements list
>>> + * 'D\n': delete all staged measurements
>>> + */
>>> +#define STAGED_REQ_LENGTH 21
>>> +
>>> static DEFINE_MUTEX(ima_write_mutex);
>>> static DEFINE_MUTEX(ima_measure_mutex);
>>> static long ima_measure_users;
>>> @@ -97,6 +104,11 @@ 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)
>>> {
>>> @@ -118,6 +130,12 @@ 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)
>>> {
>>> }
>>> @@ -283,6 +301,68 @@ static const struct file_operations ima_measurements_ops = {
>>> .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_write(struct file *file,
>>> + const char __user *buf,
>>> + size_t datalen, loff_t *ppos)
>>> +{
>>> + char req[STAGED_REQ_LENGTH];
>>> + int ret;
>>> +
>>> + if (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH)
>>> + return -EINVAL;
>>> +
>>> + if (copy_from_user(req, buf, datalen) != 0)
>>> + return -EFAULT;
>>> +
>>> + if (req[datalen - 1] != '\n')
>>> + return -EINVAL;
>>> +
>>> + req[datalen - 1] = '\0';
>>> +
>>> + switch (req[0]) {
>>> + case 'A':
>>> + if (datalen != 2)
>>> + return -EINVAL;
>>> +
>>> + ret = ima_queue_stage();
>>> + break;
>>> + case 'D':
>>> + if (datalen != 2)
>>> + return -EINVAL;
>>> +
>>> + ret = ima_queue_staged_delete_all();
>>> + break;
>> I think the following two steps may not work because of race condition:
>>
>> step1: ret = ima_queue_stage(); //this will put all logs in active list into staged list;
>> step2: ret = ima_queue_staged_delete_all(); //this will delete all logs in staged list;
>>
>> The following is the step of race condition:
>> 1. current active log list LA1;
>> 2. user agent read the TPM quote QA1 match list LA1;
>> 3. new event NewLog is added into active log list LA1+NewLog
>> 4. user agent call ima_queue_stage() and generated staged list
>> including LA1+NewLog.
>> 5. user agent call ima_queue_staged_delete_all();
>> The new log NewLog in step 3 is also deleted
> Please refer to the documentation patch which explains the intended
> workflow of this approach (Remote Attestation Agent Workflow).
>
> Roberto
So the user agent needs to deeply involve measurement list management:
in user space, user agent need to maintain two lists
user agent is more complicated in this case
Steven
>> Next time the attestation will fail if using the active log list in the
>> kernel.
>>
>> Thanks,
>>
>> Steven
>>
>>> + default:
>>> + ret = -EINVAL;
>>> + }
>>> +
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + return datalen;
>>> +}
>>> +
>>> +static const struct file_operations ima_measurements_staged_ops = {
>>> + .open = ima_measurements_staged_open,
>>> + .read = seq_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)
>>> {
>>> u32 i;
>>> @@ -356,6 +436,28 @@ static const struct file_operations ima_ascii_measurements_ops = {
>>> .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 = seq_read,
>>> + .write = ima_measurements_staged_write,
>>> + .llseek = seq_lseek,
>>> + .release = ima_measurements_release,
>>> +};
>>> +
>>> static ssize_t ima_read_policy(char *path)
>>> {
>>> void *data = NULL;
>>> @@ -459,10 +561,21 @@ static const struct seq_operations ima_policy_seqops = {
>>> };
>>> #endif
>>>
>>> -static int __init create_securityfs_measurement_lists(void)
>>> +static int __init create_securityfs_measurement_lists(bool staging)
>>> {
>>> + const struct file_operations *ascii_ops = &ima_ascii_measurements_ops;
>>> + const struct file_operations *binary_ops = &ima_measurements_ops;
>>> + mode_t permissions = S_IRUSR | S_IRGRP;
>>> + const char *file_suffix = "";
>>> int count = NR_BANKS(ima_tpm_chip);
>>>
>>> + if (staging) {
>>> + ascii_ops = &ima_ascii_measurements_staged_ops;
>>> + binary_ops = &ima_measurements_staged_ops;
>>> + file_suffix = "_staged";
>>> + permissions |= (S_IWUSR | S_IWGRP);
>>> + }
>>> +
>>> if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip))
>>> count++;
>>>
>>> @@ -473,29 +586,32 @@ static int __init create_securityfs_measurement_lists(void)
>>>
>>> if (algo == HASH_ALGO__LAST)
>>> snprintf(file_name, sizeof(file_name),
>>> - "ascii_runtime_measurements_tpm_alg_%x",
>>> - ima_tpm_chip->allocated_banks[i].alg_id);
>>> + "ascii_runtime_measurements_tpm_alg_%x%s",
>>> + ima_tpm_chip->allocated_banks[i].alg_id,
>>> + file_suffix);
>>> else
>>> snprintf(file_name, sizeof(file_name),
>>> - "ascii_runtime_measurements_%s",
>>> - hash_algo_name[algo]);
>>> - dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
>>> + "ascii_runtime_measurements_%s%s",
>>> + hash_algo_name[algo], file_suffix);
>>> + dentry = securityfs_create_file(file_name, permissions,
>>> ima_dir, (void *)(uintptr_t)i,
>>> - &ima_ascii_measurements_ops);
>>> + ascii_ops);
>>> if (IS_ERR(dentry))
>>> return PTR_ERR(dentry);
>>>
>>> if (algo == HASH_ALGO__LAST)
>>> snprintf(file_name, sizeof(file_name),
>>> - "binary_runtime_measurements_tpm_alg_%x",
>>> - ima_tpm_chip->allocated_banks[i].alg_id);
>>> + "binary_runtime_measurements_tpm_alg_%x%s",
>>> + ima_tpm_chip->allocated_banks[i].alg_id,
>>> + file_suffix);
>>> else
>>> snprintf(file_name, sizeof(file_name),
>>> - "binary_runtime_measurements_%s",
>>> - hash_algo_name[algo]);
>>> - dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
>>> + "binary_runtime_measurements_%s%s",
>>> + hash_algo_name[algo], file_suffix);
>>> +
>>> + dentry = securityfs_create_file(file_name, permissions,
>>> ima_dir, (void *)(uintptr_t)i,
>>> - &ima_measurements_ops);
>>> + binary_ops);
>>> if (IS_ERR(dentry))
>>> return PTR_ERR(dentry);
>>> }
>>> @@ -503,6 +619,23 @@ static int __init create_securityfs_measurement_lists(void)
>>> return 0;
>>> }
>>>
>>> +static int __init create_securityfs_staging_links(void)
>>> +{
>>> + struct dentry *dentry;
>>> +
>>> + dentry = securityfs_create_symlink("binary_runtime_measurements_staged",
>>> + ima_dir, "binary_runtime_measurements_sha1_staged", NULL);
>>> + if (IS_ERR(dentry))
>>> + return PTR_ERR(dentry);
>>> +
>>> + dentry = securityfs_create_symlink("ascii_runtime_measurements_staged",
>>> + ima_dir, "ascii_runtime_measurements_sha1_staged", NULL);
>>> + if (IS_ERR(dentry))
>>> + return PTR_ERR(dentry);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> /*
>>> * ima_open_policy: sequentialize access to the policy file
>>> */
>>> @@ -595,7 +728,13 @@ int __init ima_fs_init(void)
>>> goto out;
>>> }
>>>
>>> - ret = create_securityfs_measurement_lists();
>>> + ret = create_securityfs_measurement_lists(false);
>>> + if (ret == 0 && IS_ENABLED(CONFIG_IMA_STAGING)) {
>>> + ret = create_securityfs_measurement_lists(true);
>>> + if (ret == 0)
>>> + ret = create_securityfs_staging_links();
>>> + }
>>> +
>>> if (ret != 0)
>>> goto out;
>>>
>>> diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
>>> index d7d0fb639d99..d5503dd5cc9b 100644
>>> --- a/security/integrity/ima/ima_kexec.c
>>> +++ b/security/integrity/ima/ima_kexec.c
>>> @@ -42,8 +42,8 @@ void ima_measure_kexec_event(const char *event_name)
>>> long len;
>>> int n;
>>>
>>> - buf_size = ima_get_binary_runtime_size(BINARY);
>>> - len = atomic_long_read(&ima_num_entries[BINARY]);
>>> + buf_size = ima_get_binary_runtime_size(BINARY_FULL);
>>> + len = atomic_long_read(&ima_num_entries[BINARY_FULL]);
>>>
>>> n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
>>> "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
>>> @@ -106,13 +106,26 @@ 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() and ima_queue_delete_staged(). */
>>> + mutex_lock(&ima_extend_list_mutex);
>>> +
>>> + list_for_each_entry_rcu(qe, &ima_measurements_staged, later,
>>> + lockdep_is_held(&ima_extend_list_mutex)) {
>>> ret = ima_dump_measurement(&khdr, qe);
>>> if (ret < 0)
>>> break;
>>> }
>>>
>>> + list_for_each_entry_rcu(qe, &ima_measurements, later,
>>> + lockdep_is_held(&ima_extend_list_mutex)) {
>>> + if (!ret)
>>> + ret = ima_dump_measurement(&khdr, qe);
>>> + if (ret < 0)
>>> + break;
>>> + }
>>> +
>>> + mutex_unlock(&ima_extend_list_mutex);
>>> +
>>> /*
>>> * fill in reserved space with some buffer details
>>> * (eg. version, buffer size, number of measurements)
>>> @@ -167,6 +180,7 @@ void ima_add_kexec_buffer(struct kimage *image)
>>> extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024;
>>>
>>> binary_runtime_size = ima_get_binary_runtime_size(BINARY) +
>>> + ima_get_binary_runtime_size(BINARY_STAGED) +
>>> extra_memory;
>>>
>>> if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE)
>>> diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
>>> index b6d10dceb669..50519ed837d4 100644
>>> --- a/security/integrity/ima/ima_queue.c
>>> +++ b/security/integrity/ima/ima_queue.c
>>> @@ -26,6 +26,7 @@
>>> static struct tpm_digest *digests;
>>>
>>> LIST_HEAD(ima_measurements); /* list of all measurements */
>>> +LIST_HEAD(ima_measurements_staged); /* list of staged measurements */
>>> #ifdef CONFIG_IMA_KEXEC
>>> static unsigned long binary_runtime_size[BINARY__LAST];
>>> #else
>>> @@ -45,11 +46,11 @@ atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0);
>>> /* key: inode (before secure-hashing a file) */
>>> struct hlist_head __rcu *ima_htable;
>>>
>>> -/* mutex protects atomicity of extending measurement list
>>> +/* mutex protects atomicity of extending and staging measurement list
>>> * 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.
>>> @@ -174,12 +175,16 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
>>> lockdep_is_held(&ima_extend_list_mutex));
>>>
>>> atomic_long_inc(&ima_num_entries[BINARY]);
>>> + atomic_long_inc(&ima_num_entries[BINARY_FULL]);
>>> +
>>> if (update_htable) {
>>> key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
>>> hlist_add_head_rcu(&qe->hnext, &htable[key]);
>>> }
>>>
>>> ima_update_binary_runtime_size(entry, BINARY);
>>> + ima_update_binary_runtime_size(entry, BINARY_FULL);
>>> +
>>> return 0;
>>> }
>>>
>>> @@ -280,6 +285,94 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
>>> return result;
>>> }
>>>
>>> +int ima_queue_stage(void)
>>> +{
>>> + int ret = 0;
>>> +
>>> + mutex_lock(&ima_extend_list_mutex);
>>> + if (!list_empty(&ima_measurements_staged)) {
>>> + ret = -EEXIST;
>>> + goto out_unlock;
>>> + }
>>> +
>>> + if (list_empty(&ima_measurements)) {
>>> + ret = -ENOENT;
>>> + goto out_unlock;
>>> + }
>>> +
>>> + list_replace(&ima_measurements, &ima_measurements_staged);
>>> + INIT_LIST_HEAD(&ima_measurements);
>>> +
>>> + atomic_long_set(&ima_num_entries[BINARY_STAGED],
>>> + atomic_long_read(&ima_num_entries[BINARY]));
>>> + atomic_long_set(&ima_num_entries[BINARY], 0);
>>> +
>>> + if (IS_ENABLED(CONFIG_IMA_KEXEC)) {
>>> + binary_runtime_size[BINARY_STAGED] =
>>> + binary_runtime_size[BINARY];
>>> + binary_runtime_size[BINARY] = 0;
>>> + }
>>> +out_unlock:
>>> + mutex_unlock(&ima_extend_list_mutex);
>>> + return ret;
>>> +}
>>> +
>>> +static void ima_queue_delete(struct list_head *head);
>>> +
>>> +int ima_queue_staged_delete_all(void)
>>> +{
>>> + LIST_HEAD(ima_measurements_trim);
>>> +
>>> + mutex_lock(&ima_extend_list_mutex);
>>> + if (list_empty(&ima_measurements_staged)) {
>>> + mutex_unlock(&ima_extend_list_mutex);
>>> + return -ENOENT;
>>> + }
>>> +
>>> + list_replace(&ima_measurements_staged, &ima_measurements_trim);
>>> + INIT_LIST_HEAD(&ima_measurements_staged);
>>> +
>>> + atomic_long_set(&ima_num_entries[BINARY_STAGED], 0);
>>> +
>>> + if (IS_ENABLED(CONFIG_IMA_KEXEC))
>>> + binary_runtime_size[BINARY_STAGED] = 0;
>>> +
>>> + mutex_unlock(&ima_extend_list_mutex);
>>> +
>>> + ima_queue_delete(&ima_measurements_trim);
>>> + return 0;
>>> +}
>>> +
>>> +static void ima_queue_delete(struct list_head *head)
>>> +{
>>> + struct ima_queue_entry *qe, *qe_tmp;
>>> + unsigned int i;
>>> +
>>> + list_for_each_entry_safe(qe, qe_tmp, head, later) {
>>> + /*
>>> + * Safe to free template_data here without synchronize_rcu()
>>> + * because the only htable reader, ima_lookup_digest_entry(),
>>> + * accesses only entry->digests, not template_data. If new
>>> + * htable readers are added that access template_data, a
>>> + * synchronize_rcu() is required here.
>>> + */
>>> + 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);
>>> +
>>> + /* No leak if condition is false, referenced by ima_htable. */
>>> + if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) {
>>> + kfree(qe->entry->digests);
>>> + kfree(qe->entry);
>>> + kfree(qe);
>>> + }
>>> + }
>>> +}
>>> +
>>> int ima_restore_measurement_entry(struct ima_template_entry *entry)
>>> {
>>> int result = 0;
^ permalink raw reply
* Re: [PATCH v4 11/13] ima: Support staging and deleting N measurements entries
From: steven chen @ 2026-04-01 17:48 UTC (permalink / raw)
To: Roberto Sassu, corbet, skhan, 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: <af6aa732b85af36e07e4a82b29170e80b13dc7c4.camel@huaweicloud.com>
On 3/27/2026 10:02 AM, Roberto Sassu wrote:
> On Thu, 2026-03-26 at 16:19 -0700, steven chen wrote:
>> On 3/26/2026 10:30 AM, Roberto Sassu wrote:
>>> From: Roberto Sassu <roberto.sassu@huawei.com>
>>>
>>> Add support for sending a value N between 1 and ULONG_MAX to the staging
>>> interface. This value represents the number of measurements that should be
>>> deleted from the current measurements list.
>>>
>>> This staging method allows the remote attestation agents to easily separate
>>> the measurements that were verified (staged and deleted) from those that
>>> weren't due to the race between taking a TPM quote and reading the
>>> measurements list.
>>>
>>> In order to minimize the locking time of ima_extend_list_mutex, deleting
>>> N entries is realized by staging the entire current measurements list
>>> (with the lock), by determining the N-th staged entry (without the lock),
>>> and by splicing the entries in excess back to the current measurements list
>>> (with the lock). Finally, the N entries are deleted (without the lock).
>>>
>>> Flushing the hash table is not supported for N entries, since it would
>>> require removing the N entries one by one from the hash table under the
>>> ima_extend_list_mutex lock, which would increase the locking time.
>>>
>>> The ima_extend_list_mutex lock is necessary in ima_dump_measurement_list()
>>> because ima_queue_staged_delete_partial() uses __list_cut_position() to
>>> modify ima_measurements_staged, for which no RCU-safe variant exists. For
>>> the staging with prompt flavor alone, list_replace_rcu() could have been
>>> used instead, but since both flavors share the same kexec serialization
>>> path, the mutex is required regardless.
>>>
>>> Link: https://github.com/linux-integrity/linux/issues/1
>>> Suggested-by: Steven Chen <chenste@linux.microsoft.com>
>>> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
>>> ---
>>> security/integrity/ima/Kconfig | 3 ++
>>> security/integrity/ima/ima.h | 1 +
>>> security/integrity/ima/ima_fs.c | 22 +++++++++-
>>> security/integrity/ima/ima_queue.c | 70 ++++++++++++++++++++++++++++++
>>> 4 files changed, 95 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
>>> index e714726f3384..6ddb4e77bff5 100644
>>> --- a/security/integrity/ima/Kconfig
>>> +++ b/security/integrity/ima/Kconfig
>>> @@ -341,6 +341,9 @@ config IMA_STAGING
>>> It allows user space to stage the measurements list for deletion and
>>> to delete the staged measurements after confirmation.
>>>
>>> + Or, alternatively, it allows user space to specify N measurements
>>> + entries to be deleted.
>>> +
>>> On kexec, staging is reverted and staged measurements are prepended
>>> to the current measurements list when measurements are copied to the
>>> secondary kernel.
>>> diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
>>> index 699b735dec7d..de0693fce53c 100644
>>> --- a/security/integrity/ima/ima.h
>>> +++ b/security/integrity/ima/ima.h
>>> @@ -319,6 +319,7 @@ 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(void);
>>> int ima_queue_staged_delete_all(void);
>>> +int ima_queue_staged_delete_partial(unsigned long req_value);
>>> 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);
>>> diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
>>> index 39d9128e9f22..eb3f343c1138 100644
>>> --- a/security/integrity/ima/ima_fs.c
>>> +++ b/security/integrity/ima/ima_fs.c
>>> @@ -28,6 +28,7 @@
>>> * Requests:
>>> * 'A\n': stage the entire measurements list
>>> * 'D\n': delete all staged measurements
>>> + * '[1, ULONG_MAX]\n' delete N measurements entries
>>> */
>>> #define STAGED_REQ_LENGTH 21
>>>
>>> @@ -319,6 +320,7 @@ static ssize_t ima_measurements_staged_write(struct file *file,
>>> size_t datalen, loff_t *ppos)
>>> {
>>> char req[STAGED_REQ_LENGTH];
>>> + unsigned long req_value;
>>> int ret;
>>>
>>> if (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH)
>>> @@ -346,7 +348,25 @@ static ssize_t ima_measurements_staged_write(struct file *file,
>>> ret = ima_queue_staged_delete_all();
>>> break;
>>> default:
>>> - ret = -EINVAL;
>>> + if (ima_flush_htable) {
>>> + pr_debug("Deleting staged N measurements not supported when flushing the hash table is requested\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + ret = kstrtoul(req, 10, &req_value);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + if (req_value == 0) {
>>> + pr_debug("Must delete at least one entry\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + ret = ima_queue_stage();
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + ret = ima_queue_staged_delete_partial(req_value);
>> The default processing is "Trim N" idea plus performance improvement.
>>
>> Here do everything in one time. And this is what I said in v3.
>>
>> [PATCH v3 1/3] ima: Remove ima_h_table structure
>> <https://lore.kernel.org/linux-integrity/c61aeaa79929a98cb3a6d30835972891fac3570f.camel@linux.ibm.com/T/#t>
> In your approach you do:
>
> lock ima_extend_measure_list_mutex
> scan entries until N
> cut list staged -> trim
> unlock ima_extend_measure_list_mutex
>
>
> In my approach I do:
> lock ima_extend_measure_list_mutex
> list replace active -> staged
> unlock ima_extend_measure_list_mutex
>
> scan entries until N
>
> lock ima_extend_measure_list_mutex
> cut list staged -> trim
> splice staged ->active
> unlock ima_extend_measure_list_mutex
>
> So, I guess if you refer to less user space locking time, you mean one
> lock/unlock and one list replace + list splice less in your solution.
>
> In exchange, you propose to hold the lock in the kernel while scanning
> N. I think it is a significant increase of kernel locking time vs a
> negligible increase of user space locking time (in the kernel, all
> processes need to wait for the ima_extend_measure_list_mutex to be
> released, in user space it is just the agent waiting).
>
> Roberto
Please the version 5:
[PATCH v5 0/3] Trim N entries of IMA event logs
<https://lore.kernel.org/linux-integrity/20260401172956.4581-1-chenste@linux.microsoft.com/T/#t>
Scanning N is moved out of the lock period.
"Trim N" proposal has less lock time than "Staging and deleting" proposal.
"Trim N" proposal has much less code than "Staging and deleting" proposal.
"Trim N" proposal user space operation is more simple than "Staging and
deleting".
Steven
>> The important two parts of trimming is "trim N" and performance improvement.
>>
>> The performance improvement include two parts:
>> hash table staging
>> active log list staging
>>
>> And I think "Trim N" plus performance improvement is the right direction
>> to go.
>> Lots of code for two steps "stage and trim" "stage" part can be removed.
>>
>> Also race condition may happen if not holding the list all time in user
>> space
>> during attestation period: from stage, read list, attestation and trimming.
>>
>> So in order to improve the above user space lock time, "Trim T:N" can be
>> used
>> not to hold list long in user space during attestation.
>>
>> For Trim T:N, T represent total log trimmed since system boot up. Please
>> refer to
>> https://lore.kernel.org/linux-integrity/20260205235849.7086-1-chenste@linux.microsoft.com/T/#t
>>
>> Thanks,
>>
>> Steven
>>> }
>>>
>>> if (ret < 0)
>>> diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
>>> index f5c18acfbc43..4fb557d61a88 100644
>>> --- a/security/integrity/ima/ima_queue.c
>>> +++ b/security/integrity/ima/ima_queue.c
>>> @@ -371,6 +371,76 @@ int ima_queue_staged_delete_all(void)
>>> return 0;
>>> }
>>>
>>> +int ima_queue_staged_delete_partial(unsigned long req_value)
>>> +{
>>> + unsigned long req_value_copy = req_value;
>>> + unsigned long size_to_remove = 0, num_to_remove = 0;
>>> + struct list_head *cut_pos = NULL;
>>> + LIST_HEAD(ima_measurements_trim);
>>> + struct ima_queue_entry *qe;
>>> + int ret = 0;
>>> +
>>> + /*
>>> + * Safe walk (no concurrent write), not under ima_extend_list_mutex
>>> + * for performance reasons.
>>> + */
>>> + list_for_each_entry(qe, &ima_measurements_staged, later) {
>>> + size_to_remove += get_binary_runtime_size(qe->entry);
>>> + num_to_remove++;
>>> +
>>> + if (--req_value_copy == 0) {
>>> + /* qe->later always points to a valid list entry. */
>>> + cut_pos = &qe->later;
>>> + break;
>>> + }
>>> + }
>>> +
>>> + /* Nothing to remove, undoing staging. */
>>> + if (req_value_copy > 0) {
>>> + size_to_remove = 0;
>>> + num_to_remove = 0;
>>> + ret = -ENOENT;
>>> + }
>>> +
>>> + mutex_lock(&ima_extend_list_mutex);
>>> + if (list_empty(&ima_measurements_staged)) {
>>> + mutex_unlock(&ima_extend_list_mutex);
>>> + return -ENOENT;
>>> + }
>>> +
>>> + if (cut_pos != NULL)
>>> + /*
>>> + * ima_dump_measurement_list() does not modify the list,
>>> + * cut_pos remains the same even if it was computed before
>>> + * the lock.
>>> + */
>>> + __list_cut_position(&ima_measurements_trim,
>>> + &ima_measurements_staged, cut_pos);
>>> +
>>> + atomic_long_sub(num_to_remove, &ima_num_entries[BINARY_STAGED]);
>>> + atomic_long_add(atomic_long_read(&ima_num_entries[BINARY_STAGED]),
>>> + &ima_num_entries[BINARY]);
>>> + atomic_long_set(&ima_num_entries[BINARY_STAGED], 0);
>>> +
>>> + if (IS_ENABLED(CONFIG_IMA_KEXEC)) {
>>> + binary_runtime_size[BINARY_STAGED] -= size_to_remove;
>>> + binary_runtime_size[BINARY] +=
>>> + binary_runtime_size[BINARY_STAGED];
>>> + binary_runtime_size[BINARY_STAGED] = 0;
>>> + }
>>> +
>>> + /*
>>> + * Splice (prepend) any remaining non-deleted staged entries to the
>>> + * active list (RCU not needed, there cannot be concurrent readers).
>>> + */
>>> + list_splice(&ima_measurements_staged, &ima_measurements);
>>> + INIT_LIST_HEAD(&ima_measurements_staged);
>>> + mutex_unlock(&ima_extend_list_mutex);
>>> +
>>> + ima_queue_delete(&ima_measurements_trim, false);
>>> + return ret;
>>> +}
>>> +
>>> static void ima_queue_delete(struct list_head *head, bool flush_htable)
>>> {
>>> struct ima_queue_entry *qe, *qe_tmp;
^ permalink raw reply
* [PATCH v5 3/3] ima: add new critical data record to measure log trim
From: steven chen @ 2026-04-01 17:29 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: <20260401172956.4581-1-chenste@linux.microsoft.com>
Add a new critical data record to measure the trimming event when
ima event records are deleted since system boot up.
If all IMA event logs are saved in the userspace, use this log to get total
numbers of records deleted since system boot up at that point.
Signed-off-by: steven chen <chenste@linux.microsoft.com>
---
security/integrity/ima/ima_fs.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 8e26e0f34311..38d0a49b587f 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -43,6 +43,7 @@ static int valid_policy = 1;
#define IMA_LOG_TRIM_REQ_NUM_LENGTH 15
#define IMA_LOG_TRIM_REQ_TOTAL_LENGTH 32
+#define IMA_LOG_TRIM_EVENT_LEN 256
atomic_long_t ima_number_entries = ATOMIC_LONG_INIT(0);
static long trimcount;
/* mutex protects atomicity of trimming measurement list
@@ -52,6 +53,22 @@ static long trimcount;
static DEFINE_MUTEX(ima_measure_lock);
static long ima_measure_users;
+static void ima_measure_trim_event(void)
+{
+ 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; number= %lu;", time_ns, trimcount);
+
+ ima_measure_critical_data("ima_log_trim", "trim ima event logs",
+ ima_log_trim_event, n, false, NULL, 0);
+}
+
static ssize_t ima_show_htable_value(char __user *buf, size_t count,
loff_t *ppos, atomic_long_t *val)
{
@@ -436,6 +453,9 @@ static ssize_t ima_log_trim_write(struct file *file,
if (ret < 0)
goto out;
+ if (ret > 0)
+ ima_measure_trim_event();
+
trimcount += ret;
ret = datalen;
--
2.43.0
^ permalink raw reply related
* [PATCH v5 2/3] ima: trim N IMA event log records
From: steven chen @ 2026-04-01 17:29 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: <20260401172956.4581-1-chenste@linux.microsoft.com>
Trim N entries of the IMA event logs. Do not clean the hash table.
The values saved in hash table were already used.
Provide a userspace interface ima_trim_log:
When read this interface, it returns total number T of entries trimmed
since system boot up.
When write to this interface need to provide two numbers T:N to let
kernel to trim N entries of IMA event logs.
Kernel measurement list lock time performance improvement by not
clean the hash table.
when kernel get log trim request T:N
- Get the T, compare with the total trimmed number
- if equal, then do trim N and change T to T+N
- else return error
Signed-off-by: steven chen <chenste@linux.microsoft.com>
---
.../admin-guide/kernel-parameters.txt | 4 +
security/integrity/ima/ima.h | 4 +-
security/integrity/ima/ima_fs.c | 198 +++++++++++++++++-
security/integrity/ima/ima_kexec.c | 2 +-
security/integrity/ima/ima_queue.c | 96 +++++++++
5 files changed, 296 insertions(+), 8 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..5cbee3a295a0 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -243,11 +243,13 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key,
const void *payload, size_t plen,
unsigned long flags, bool create);
#endif
-
+extern atomic_long_t ima_number_entries;
#ifdef CONFIG_IMA_KEXEC
void ima_measure_kexec_event(const char *event_name);
+long ima_delete_event_log(long req_val);
#else
static inline void ima_measure_kexec_event(const char *event_name) {}
+static inline long ima_delete_event_log(long req_val) { return 0; }
#endif
/*
diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index 87045b09f120..8e26e0f34311 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,17 @@ __setup("ima_canonical_fmt", default_canonical_fmt_setup);
static int valid_policy = 1;
+#define IMA_LOG_TRIM_REQ_NUM_LENGTH 15
+#define IMA_LOG_TRIM_REQ_TOTAL_LENGTH 32
+atomic_long_t ima_number_entries = ATOMIC_LONG_INIT(0);
+static long trimcount;
+/* mutex protects atomicity of trimming measurement list
+ * and also protects atomicity the measurement list read
+ * write operation.
+ */
+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)
{
@@ -64,8 +78,7 @@ static ssize_t ima_show_measurements_count(struct file *filp,
char __user *buf,
size_t count, loff_t *ppos)
{
- return ima_show_htable_value(buf, count, ppos, &ima_htable.len);
-
+ return ima_show_htable_value(buf, count, ppos, &ima_number_entries);
}
static const struct file_operations ima_measurements_count_ops = {
@@ -202,16 +215,77 @@ static const struct seq_operations ima_measurments_seqops = {
.show = ima_measurements_show
};
+/*
+ * _ima_measurements_open - open the IMA measurements file
+ * @inode: inode of the file being opened
+ * @file: file being opened
+ * @seq_ops: sequence operations for the file
+ *
+ * Returns 0 on success, or negative error code.
+ * Implements mutual exclusion between readers and writer
+ * of the measurements file. Multiple readers are allowed,
+ * but writer get exclusive access only no other readers/writers.
+ * Readers is not allowed when there is a writer.
+ */
+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 +353,114 @@ 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 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_NUM_LENGTH];
+ 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)
+{
+ char tmpbuf[IMA_LOG_TRIM_REQ_TOTAL_LENGTH];
+ char *p = tmpbuf;
+ long count, ret, val = 0, max = LONG_MAX;
+
+ if (*ppos > 0 || datalen > IMA_LOG_TRIM_REQ_TOTAL_LENGTH || datalen < 2) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (copy_from_user(tmpbuf, buf, datalen) != 0) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ p = tmpbuf;
+
+ while (*p && *p != ':') {
+ if (!isdigit((unsigned char)*p))
+ return -EINVAL;
+
+ /* digit value */
+ int d = *p - '0';
+
+ /* overflow check: val * 10 + d > max -> (val > (max - d) / 10) */
+ if (val > (max - d) / 10)
+ return -ERANGE;
+
+ val = val * 10 + d;
+ p++;
+ }
+
+ if (*p != ':')
+ return -EINVAL;
+
+ /* verify trim count matches */
+ if (val != trimcount)
+ return -EINVAL;
+
+ p++; /* skip ':' */
+ ret = kstrtoul(p, 0, &count);
+
+ if (ret < 0)
+ goto out;
+
+ ret = ima_delete_event_log(count);
+
+ if (ret < 0)
+ goto out;
+
+ trimcount += ret;
+
+ 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 +702,18 @@ int __init ima_fs_init(void)
goto out;
}
+ if (IS_ENABLED(CONFIG_IMA_LOG_TRIMMING)) {
+ 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_kexec.c b/security/integrity/ima/ima_kexec.c
index 7362f68f2d8b..bee997683e03 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -41,7 +41,7 @@ void ima_measure_kexec_event(const char *event_name)
int n;
buf_size = ima_get_binary_runtime_size();
- len = atomic_long_read(&ima_htable.len);
+ len = atomic_long_read(&ima_number_entries);
n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
"kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 590637e81ad1..07225e19b9b5 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;
@@ -114,6 +122,7 @@ 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_number_entries);
if (update_htable) {
key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]);
@@ -220,6 +229,93 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
return result;
}
+/**
+ * ima_delete_event_log - delete IMA event entry
+ * @num_records: number of records to delete
+ *
+ * delete num_records entries off the measurement list.
+ * Returns num_records, or negative error code.
+ */
+long ima_delete_event_log(long num_records)
+{
+ long len, cur = num_records, tmp_len = 0;
+ struct ima_queue_entry *qe, *qe_tmp;
+ LIST_HEAD(ima_measurements_to_delete);
+ struct list_head *list_ptr;
+
+ if (!IS_ENABLED(CONFIG_IMA_LOG_TRIMMING))
+ return -EOPNOTSUPP;
+
+ if (num_records <= 0)
+ return num_records;
+
+ list_ptr = &ima_measurements;
+
+ len = atomic_long_read(&ima_number_entries);
+
+ if (num_records <= len) {
+ list_for_each_entry(qe, list_ptr, later) {
+ if (cur > 0) {
+ tmp_len += get_binary_runtime_size(qe->entry);
+ --cur;
+ }
+ if (cur == 0) {
+ qe_tmp = qe;
+ break;
+ }
+ }
+ }
+ else {
+ return -ENOENT;
+ }
+
+
+ mutex_lock(&ima_extend_list_mutex);
+ len = atomic_long_read(&ima_number_entries);
+
+ if (num_records == len) {
+ list_replace(&ima_measurements, &ima_measurements_to_delete);
+ INIT_LIST_HEAD(&ima_measurements);
+ atomic_long_set(&ima_number_entries, 0);
+ list_ptr = &ima_measurements_to_delete;
+ }
+ else {
+ __list_cut_position(&ima_measurements_to_delete, &ima_measurements,
+ &qe_tmp->later);
+ atomic_long_sub(num_records, &ima_number_entries);
+ if (IS_ENABLED(CONFIG_IMA_KEXEC))
+ binary_runtime_size -= tmp_len;
+ }
+
+ mutex_unlock(&ima_extend_list_mutex);
+
+ if (ima_flush_htable)
+ synchronize_rcu();
+
+ list_for_each_entry_safe(qe, qe_tmp, &ima_measurements_to_delete, later) {
+ /*
+ * Ok because after list delete qe is only accessed by
+ * ima_lookup_digest_entry().
+ */
+ for (int 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);
+
+ /* No leak if !ima_flush_htable, referenced by ima_htable. */
+ if (ima_flush_htable) {
+ kfree(qe->entry->digests);
+ kfree(qe->entry);
+ kfree(qe);
+ }
+ }
+
+ return num_records;
+}
+
int ima_restore_measurement_entry(struct ima_template_entry *entry)
{
int result = 0;
--
2.43.0
^ permalink raw reply related
* [PATCH v5 1/3] ima: make ima event log trimming configurable
From: steven chen @ 2026-04-01 17:29 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: <20260401172956.4581-1-chenste@linux.microsoft.com>
Make ima event log trimming function configurable.
Suggested-by: Mimi Zohar <zohar@linux.ibm.com>
Signed-off-by: steven chen <chenste@linux.microsoft.com>
---
security/integrity/ima/Kconfig | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 976e75f9b9ba..322964ae4772 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -332,4 +332,16 @@ config IMA_KEXEC_EXTRA_MEMORY_KB
If set to the default value of 0, an extra half page of memory for those
additional measurements will be allocated.
+config IMA_LOG_TRIMMING
+ bool "IMA Event Log Trimming"
+ default n
+ help
+ Say Y here if you want support for IMA Event Log Trimming.
+ This creates the file /sys/kernel/security/integrity/ima/ima_trim_log.
+ Userspace
+ - writes to this file to trigger IMA event log trimming
+ - reads this file to get number of entried trimming last time
+
+ If unsure, say N.
+
endif
--
2.43.0
^ permalink raw reply related
* [PATCH v5 0/3] Trim N entries of IMA event logs
From: steven chen @ 2026-04-01 17:29 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. 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.
When this interface is read, it returns the total number T of entries
trimmed since system boot up. This value T need to be preserved across
kexec soft reboots. By writing two number T:N to this interface, userspace
can request the kernel to trim N entries from the IMA event logs.
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 stage/delete the measurements, the read permission
to read them. Write requires also the CAP_SYS_ADMIN capability (from
reference [2]). This ima_measure_users needs to be preserved across kexec
soft reboots
The total trimmed number T and the ima_measure_users both need to be
preserved across kexec soft reboot and new patch will be added for this
purpose in next version.
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.
A new critical data record "ima_log_trim" is added in this solution. This
record logs the trim event with number of entries deleted total T since
system start and time when this happened. User space can get the total
number T of entries trimmed by checking "ima_log_trim" event in the
measurement list.
The following are how user space to use the measurement list and
ima_log_trim interface
1. get the total numer trimmed T through "ima_log_trim" interface
2. get the PCR quote
3. read the measurement list file, close the file, send for verification
4. wait for response from verifier, until get the good response from
verifier with number N that matched the PCR quote got in step 2
5. get the number N from the above message
6. write the T:N to the ima_log_trim interface when no conflict
when kernel get log trim request T:N
Get the T, compare with the total trimmed number
if equal, then do trim N and change T to T+N
else return error
Using above way to trim the log, the time for user space to hold the list
will be trimming T:N operation itself at the step 6. User space agent
race condition is solved too in this way.
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
[3] [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
[4] [PATCH v3 0/3] Trim N entries of IMA event logs
https://lore.kernel.org/linux-integrity/20260106020713.3994-1-chenste@linux.microsoft.com/T/#t
[5] [PATCH v4 0/3] Trim N entries of IMA event logs
https://lore.kernel.org/linux-integrity/20260205235849.7086-1-chenste@linux.microsoft.com/T/#t
Change Log v5:
- lock time performance improvement
- Keep hash table unchanged because log already use the hash value
- Updated patch descriptions as necessary.
Change Log v4:
- Incorporated feedback from Roberto on v3 series.
- Update "ima_log_trim" interface definition
When read this interface, return total number of records trimmed T
need to write T:N to this interface to trim N records
- Update user space use case on how to trim IMA event logs
- Updated patch descriptions as necessary.
Change Log v3:
- Incorporated feedback from Mimi on v2 series.
- split patch into multiple patches
- lock time performance improvement
- Updated patch descriptions as necessary.
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 (3):
ima: make ima event log trimming configurable
ima: trim N IMA event log records
ima: add new critical data record to measure log trim
.../admin-guide/kernel-parameters.txt | 4 +
security/integrity/ima/Kconfig | 12 +
security/integrity/ima/ima.h | 4 +-
security/integrity/ima/ima_fs.c | 218 +++++++++++++++++-
security/integrity/ima/ima_kexec.c | 2 +-
security/integrity/ima/ima_queue.c | 96 ++++++++
6 files changed, 328 insertions(+), 8 deletions(-)
--
2.43.0
^ permalink raw reply
* Re: [PATCH 0/3] ima: add regular file data hash support for sigv3
From: Eric Biggers @ 2026-03-30 20:16 UTC (permalink / raw)
To: Mimi Zohar; +Cc: linux-integrity, Stefan Berger
In-Reply-To: <20260324203929.2475782-1-zohar@linux.ibm.com>
On Tue, Mar 24, 2026 at 04:39:26PM -0400, Mimi Zohar wrote:
> IMA signature version 3 (sigv3) support was introduced to avoid file
> signature ambiguity. Instead of directly signing a raw fs-verity hash,
> IMA signs the hash of ima_file_id structure, containing the type of
> signature, the hash algorithm, and the hash.
>
> Pure ML-DSA calculates and signs the hash directly rather than a
> pre-hashed digest. To avoid ML-DSA having to re-calculate the file data
> hash, Eric Biggers suggested signing the smaller ima_file_id structure.
>
> This patch set adds the sigv3 support for regular file data hashes. A
> subsequent patch set will add the ML-DSA support.
This explanation is a bit confusing, since this is actually needed
regardless of ML-DSA support.
Anyway, it's still the right thing to do.
Acked-by: Eric Biggers <ebiggers@kernel.org>
- Eric
^ permalink raw reply
* Re: [PATCH 1/3] ima: Define asymmetric_verify_v3() to verify IMA sigv3 signatures
From: Eric Biggers @ 2026-03-30 20:13 UTC (permalink / raw)
To: Mimi Zohar; +Cc: linux-integrity, Stefan Berger
In-Reply-To: <20260324203929.2475782-2-zohar@linux.ibm.com>
On Tue, Mar 24, 2026 at 04:39:27PM -0400, Mimi Zohar wrote:
> + * IMA signature version 3 disambiguates the data that is signed by
> + * indirectly signing the hash of the ima_file_id structure data.
The right way to think about it is that it's the ima_file_id itself that
is being signed and verified, and taking the hash of it is only a
workaround for legacy algorithms that can only sign and verify hashes.
With modern algorithms like Ed25519 and ML-DSA that accept
arbitrary-length messages, that workaround won't be needed.
- Eric
^ permalink raw reply
* Re: [PATCH v15 08/28] tpm/tpm_tis: Close all localities
From: Josh Snyder @ 2026-03-29 22:57 UTC (permalink / raw)
To: Ross Philipson
Cc: linux-kernel, x86, linux-integrity, linux-doc, linux-crypto,
kexec, linux-efi, iommu, dpsmith, tglx, mingo, bp, hpa,
dave.hansen, ardb, mjg59, James.Bottomley, peterhuewe, jarkko,
jgg, luto, nivedita, herbert, davem, corbet, ebiederm, dwmw2,
baolu.lu, kanth.ghatraju, andrew.cooper3, trenchboot-devel
In-Reply-To: <20251215233316.1076248-9-ross.philipson@oracle.com>
On Mon, Dec 15, 2025 at 03:32:56PM -0800, Ross Philipson wrote:
> From: "Daniel P. Smith" <dpsmith@apertussolutions.com>
> + if (check_locality(chip, i))
> + tpm_tis_relinquish_locality(chip, i);
When I applied this patch locally, tpm_chip's locality_count underflowed to -1
and no IO was performed. That is because tpm_tis_relinquish_locality is
implemented like so:
struct tpm_tis_data *priv = dev_get_drvdata(&chip->dev);
mutex_lock(&priv->locality_count_mutex);
priv->locality_count--;
if (priv->locality_count == 0)
__tpm_tis_relinquish_locality(priv, l);
I was able to work around the issue by calling __tpm_tis_relinquish_locality
instead.
Thanks,
Josh
^ permalink raw reply
* Re: [PATCH v4 11/13] ima: Support staging and deleting N measurements entries
From: Roberto Sassu @ 2026-03-27 17:02 UTC (permalink / raw)
To: steven chen, corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, nramas, Roberto Sassu
In-Reply-To: <0e186faf-8111-4fd9-a7df-bff30f7fb20a@linux.microsoft.com>
On Thu, 2026-03-26 at 16:19 -0700, steven chen wrote:
> On 3/26/2026 10:30 AM, Roberto Sassu wrote:
> > From: Roberto Sassu <roberto.sassu@huawei.com>
> >
> > Add support for sending a value N between 1 and ULONG_MAX to the staging
> > interface. This value represents the number of measurements that should be
> > deleted from the current measurements list.
> >
> > This staging method allows the remote attestation agents to easily separate
> > the measurements that were verified (staged and deleted) from those that
> > weren't due to the race between taking a TPM quote and reading the
> > measurements list.
> >
> > In order to minimize the locking time of ima_extend_list_mutex, deleting
> > N entries is realized by staging the entire current measurements list
> > (with the lock), by determining the N-th staged entry (without the lock),
> > and by splicing the entries in excess back to the current measurements list
> > (with the lock). Finally, the N entries are deleted (without the lock).
> >
> > Flushing the hash table is not supported for N entries, since it would
> > require removing the N entries one by one from the hash table under the
> > ima_extend_list_mutex lock, which would increase the locking time.
> >
> > The ima_extend_list_mutex lock is necessary in ima_dump_measurement_list()
> > because ima_queue_staged_delete_partial() uses __list_cut_position() to
> > modify ima_measurements_staged, for which no RCU-safe variant exists. For
> > the staging with prompt flavor alone, list_replace_rcu() could have been
> > used instead, but since both flavors share the same kexec serialization
> > path, the mutex is required regardless.
> >
> > Link: https://github.com/linux-integrity/linux/issues/1
> > Suggested-by: Steven Chen <chenste@linux.microsoft.com>
> > Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> > ---
> > security/integrity/ima/Kconfig | 3 ++
> > security/integrity/ima/ima.h | 1 +
> > security/integrity/ima/ima_fs.c | 22 +++++++++-
> > security/integrity/ima/ima_queue.c | 70 ++++++++++++++++++++++++++++++
> > 4 files changed, 95 insertions(+), 1 deletion(-)
> >
> > diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
> > index e714726f3384..6ddb4e77bff5 100644
> > --- a/security/integrity/ima/Kconfig
> > +++ b/security/integrity/ima/Kconfig
> > @@ -341,6 +341,9 @@ config IMA_STAGING
> > It allows user space to stage the measurements list for deletion and
> > to delete the staged measurements after confirmation.
> >
> > + Or, alternatively, it allows user space to specify N measurements
> > + entries to be deleted.
> > +
> > On kexec, staging is reverted and staged measurements are prepended
> > to the current measurements list when measurements are copied to the
> > secondary kernel.
> > diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> > index 699b735dec7d..de0693fce53c 100644
> > --- a/security/integrity/ima/ima.h
> > +++ b/security/integrity/ima/ima.h
> > @@ -319,6 +319,7 @@ 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(void);
> > int ima_queue_staged_delete_all(void);
> > +int ima_queue_staged_delete_partial(unsigned long req_value);
> > 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);
> > diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> > index 39d9128e9f22..eb3f343c1138 100644
> > --- a/security/integrity/ima/ima_fs.c
> > +++ b/security/integrity/ima/ima_fs.c
> > @@ -28,6 +28,7 @@
> > * Requests:
> > * 'A\n': stage the entire measurements list
> > * 'D\n': delete all staged measurements
> > + * '[1, ULONG_MAX]\n' delete N measurements entries
> > */
> > #define STAGED_REQ_LENGTH 21
> >
> > @@ -319,6 +320,7 @@ static ssize_t ima_measurements_staged_write(struct file *file,
> > size_t datalen, loff_t *ppos)
> > {
> > char req[STAGED_REQ_LENGTH];
> > + unsigned long req_value;
> > int ret;
> >
> > if (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH)
> > @@ -346,7 +348,25 @@ static ssize_t ima_measurements_staged_write(struct file *file,
> > ret = ima_queue_staged_delete_all();
> > break;
> > default:
> > - ret = -EINVAL;
> > + if (ima_flush_htable) {
> > + pr_debug("Deleting staged N measurements not supported when flushing the hash table is requested\n");
> > + return -EINVAL;
> > + }
> > +
> > + ret = kstrtoul(req, 10, &req_value);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (req_value == 0) {
> > + pr_debug("Must delete at least one entry\n");
> > + return -EINVAL;
> > + }
> > +
> > + ret = ima_queue_stage();
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = ima_queue_staged_delete_partial(req_value);
> The default processing is "Trim N" idea plus performance improvement.
>
> Here do everything in one time. And this is what I said in v3.
>
> [PATCH v3 1/3] ima: Remove ima_h_table structure
> <https://lore.kernel.org/linux-integrity/c61aeaa79929a98cb3a6d30835972891fac3570f.camel@linux.ibm.com/T/#t>
In your approach you do:
lock ima_extend_measure_list_mutex
scan entries until N
cut list staged -> trim
unlock ima_extend_measure_list_mutex
In my approach I do:
lock ima_extend_measure_list_mutex
list replace active -> staged
unlock ima_extend_measure_list_mutex
scan entries until N
lock ima_extend_measure_list_mutex
cut list staged -> trim
splice staged ->active
unlock ima_extend_measure_list_mutex
So, I guess if you refer to less user space locking time, you mean one
lock/unlock and one list replace + list splice less in your solution.
In exchange, you propose to hold the lock in the kernel while scanning
N. I think it is a significant increase of kernel locking time vs a
negligible increase of user space locking time (in the kernel, all
processes need to wait for the ima_extend_measure_list_mutex to be
released, in user space it is just the agent waiting).
Roberto
> The important two parts of trimming is "trim N" and performance improvement.
>
> The performance improvement include two parts:
> hash table staging
> active log list staging
>
> And I think "Trim N" plus performance improvement is the right direction
> to go.
> Lots of code for two steps "stage and trim" "stage" part can be removed.
>
> Also race condition may happen if not holding the list all time in user
> space
> during attestation period: from stage, read list, attestation and trimming.
>
> So in order to improve the above user space lock time, "Trim T:N" can be
> used
> not to hold list long in user space during attestation.
>
> For Trim T:N, T represent total log trimmed since system boot up. Please
> refer to
> https://lore.kernel.org/linux-integrity/20260205235849.7086-1-chenste@linux.microsoft.com/T/#t
>
> Thanks,
>
> Steven
> > }
> >
> > if (ret < 0)
> > diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
> > index f5c18acfbc43..4fb557d61a88 100644
> > --- a/security/integrity/ima/ima_queue.c
> > +++ b/security/integrity/ima/ima_queue.c
> > @@ -371,6 +371,76 @@ int ima_queue_staged_delete_all(void)
> > return 0;
> > }
> >
> > +int ima_queue_staged_delete_partial(unsigned long req_value)
> > +{
> > + unsigned long req_value_copy = req_value;
> > + unsigned long size_to_remove = 0, num_to_remove = 0;
> > + struct list_head *cut_pos = NULL;
> > + LIST_HEAD(ima_measurements_trim);
> > + struct ima_queue_entry *qe;
> > + int ret = 0;
> > +
> > + /*
> > + * Safe walk (no concurrent write), not under ima_extend_list_mutex
> > + * for performance reasons.
> > + */
> > + list_for_each_entry(qe, &ima_measurements_staged, later) {
> > + size_to_remove += get_binary_runtime_size(qe->entry);
> > + num_to_remove++;
> > +
> > + if (--req_value_copy == 0) {
> > + /* qe->later always points to a valid list entry. */
> > + cut_pos = &qe->later;
> > + break;
> > + }
> > + }
> > +
> > + /* Nothing to remove, undoing staging. */
> > + if (req_value_copy > 0) {
> > + size_to_remove = 0;
> > + num_to_remove = 0;
> > + ret = -ENOENT;
> > + }
> > +
> > + mutex_lock(&ima_extend_list_mutex);
> > + if (list_empty(&ima_measurements_staged)) {
> > + mutex_unlock(&ima_extend_list_mutex);
> > + return -ENOENT;
> > + }
> > +
> > + if (cut_pos != NULL)
> > + /*
> > + * ima_dump_measurement_list() does not modify the list,
> > + * cut_pos remains the same even if it was computed before
> > + * the lock.
> > + */
> > + __list_cut_position(&ima_measurements_trim,
> > + &ima_measurements_staged, cut_pos);
> > +
> > + atomic_long_sub(num_to_remove, &ima_num_entries[BINARY_STAGED]);
> > + atomic_long_add(atomic_long_read(&ima_num_entries[BINARY_STAGED]),
> > + &ima_num_entries[BINARY]);
> > + atomic_long_set(&ima_num_entries[BINARY_STAGED], 0);
> > +
> > + if (IS_ENABLED(CONFIG_IMA_KEXEC)) {
> > + binary_runtime_size[BINARY_STAGED] -= size_to_remove;
> > + binary_runtime_size[BINARY] +=
> > + binary_runtime_size[BINARY_STAGED];
> > + binary_runtime_size[BINARY_STAGED] = 0;
> > + }
> > +
> > + /*
> > + * Splice (prepend) any remaining non-deleted staged entries to the
> > + * active list (RCU not needed, there cannot be concurrent readers).
> > + */
> > + list_splice(&ima_measurements_staged, &ima_measurements);
> > + INIT_LIST_HEAD(&ima_measurements_staged);
> > + mutex_unlock(&ima_extend_list_mutex);
> > +
> > + ima_queue_delete(&ima_measurements_trim, false);
> > + return ret;
> > +}
> > +
> > static void ima_queue_delete(struct list_head *head, bool flush_htable)
> > {
> > struct ima_queue_entry *qe, *qe_tmp;
>
^ permalink raw reply
* Re: [PATCH v4 09/13] ima: Add support for staging measurements with prompt
From: Roberto Sassu @ 2026-03-27 16:45 UTC (permalink / raw)
To: steven chen, corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, nramas, Roberto Sassu
In-Reply-To: <ef9c296a-940a-4bb5-a0b9-184532cf4bb6@linux.microsoft.com>
On Thu, 2026-03-26 at 15:44 -0700, steven chen wrote:
> On 3/26/2026 10:30 AM, Roberto Sassu wrote:
> > From: Roberto Sassu <roberto.sassu@huawei.com>
> >
> > Introduce the ability of staging the IMA measurement list and deleting them
> > with a prompt.
> >
> > 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. In the event of kexec(),
> > staging is reverted and staged entries will be carried over to the new
> > kernel.
> >
> > Introduce ascii_runtime_measurements_<algo>_staged and
> > binary_runtime_measurements_<algo>_staged interfaces to stage and delete
> > the measurements. Use 'echo A > <IMA interface>' and
> > 'echo D > <IMA interface>' to respectively stage and delete the entire
> > measurements list. Locking of these interfaces is also mediated with a call
> > to _ima_measurements_open() and with ima_measurements_release().
> >
> > Implement the staging functionality by introducing the new global
> > measurements list ima_measurements_staged, and ima_queue_stage() and
> > ima_queue_delete_staged_all() to respectively move measurements from the
> > current measurements list to the staged one, and to move staged
> > measurements to the ima_measurements_trim list for deletion. Introduce
> > ima_queue_delete() to delete the measurements.
> >
> > Finally, introduce the BINARY_STAGED AND BINARY_FULL binary measurements
> > list types, to maintain the counters and the binary size of staged
> > measurements and the full measurements list (including entries that were
> > staged). BINARY still represents the current binary measurements list.
> >
> > Use the binary size for the BINARY + BINARY_STAGED types in
> > ima_add_kexec_buffer(), since both measurements list types are copied to
> > the secondary kernel during kexec. Use BINARY_FULL in
> > ima_measure_kexec_event(), to generate a critical data record.
> >
> > It should be noted that the BINARY_FULL counter is not passed through
> > kexec. Thus, the number of entries included in the kexec critical data
> > records refers to the entries since the previous kexec records.
> >
> > Note: This code derives from the Alt-IMA Huawei project, whose license is
> > GPL-2.0 OR MIT.
> >
> > Link: https://github.com/linux-integrity/linux/issues/1
> > Suggested-by: Gregory Lumen <gregorylumen@linux.microsoft.com> (staging revert)
> > Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> > ---
> > security/integrity/ima/Kconfig | 13 +++
> > security/integrity/ima/ima.h | 8 +-
> > security/integrity/ima/ima_fs.c | 167 ++++++++++++++++++++++++++---
> > security/integrity/ima/ima_kexec.c | 22 +++-
> > security/integrity/ima/ima_queue.c | 97 ++++++++++++++++-
> > 5 files changed, 286 insertions(+), 21 deletions(-)
> >
> > diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
> > index 976e75f9b9ba..e714726f3384 100644
> > --- a/security/integrity/ima/Kconfig
> > +++ b/security/integrity/ima/Kconfig
> > @@ -332,4 +332,17 @@ config IMA_KEXEC_EXTRA_MEMORY_KB
> > If set to the default value of 0, an extra half page of memory for those
> > additional measurements will be allocated.
> >
> > +config IMA_STAGING
> > + bool "Support for staging the measurements list"
> > + default y
> > + help
> > + Add support for staging the measurements list.
> > +
> > + It allows user space to stage the measurements list for deletion and
> > + to delete the staged measurements after confirmation.
> > +
> > + On kexec, staging is reverted and staged measurements are prepended
> > + to the current measurements list when measurements are copied to the
> > + secondary kernel.
> > +
> > endif
> > diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> > index 97b7d6024b5d..65db152a0a24 100644
> > --- a/security/integrity/ima/ima.h
> > +++ b/security/integrity/ima/ima.h
> > @@ -30,9 +30,11 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 };
> >
> > /*
> > * BINARY: current binary measurements list
> > + * BINARY_STAGED: staged binary measurements list
> > + * BINARY_FULL: binary measurements list since IMA init (lost after kexec)
> > */
> > enum binary_lists {
> > - BINARY, BINARY__LAST
> > + BINARY, BINARY_STAGED, BINARY_FULL, BINARY__LAST
> > };
> >
> > /* digest size for IMA, fits SHA1 or MD5 */
> > @@ -125,6 +127,7 @@ 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. */
> >
> > /* Some details preceding the binary serialized measurement list */
> > struct ima_kexec_hdr {
> > @@ -314,6 +317,8 @@ 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(void);
> > +int ima_queue_staged_delete_all(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);
> > @@ -334,6 +339,7 @@ extern spinlock_t ima_queue_lock;
> > extern atomic_long_t ima_num_entries[BINARY__LAST];
> > extern atomic_long_t ima_num_violations;
> > extern struct hlist_head __rcu *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 7709a4576322..39d9128e9f22 100644
> > --- a/security/integrity/ima/ima_fs.c
> > +++ b/security/integrity/ima/ima_fs.c
> > @@ -24,6 +24,13 @@
> >
> > #include "ima.h"
> >
> > +/*
> > + * Requests:
> > + * 'A\n': stage the entire measurements list
> > + * 'D\n': delete all staged measurements
> > + */
> > +#define STAGED_REQ_LENGTH 21
> > +
> > static DEFINE_MUTEX(ima_write_mutex);
> > static DEFINE_MUTEX(ima_measure_mutex);
> > static long ima_measure_users;
> > @@ -97,6 +104,11 @@ 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)
> > {
> > @@ -118,6 +130,12 @@ 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)
> > {
> > }
> > @@ -283,6 +301,68 @@ static const struct file_operations ima_measurements_ops = {
> > .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_write(struct file *file,
> > + const char __user *buf,
> > + size_t datalen, loff_t *ppos)
> > +{
> > + char req[STAGED_REQ_LENGTH];
> > + int ret;
> > +
> > + if (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH)
> > + return -EINVAL;
> > +
> > + if (copy_from_user(req, buf, datalen) != 0)
> > + return -EFAULT;
> > +
> > + if (req[datalen - 1] != '\n')
> > + return -EINVAL;
> > +
> > + req[datalen - 1] = '\0';
> > +
> > + switch (req[0]) {
> > + case 'A':
> > + if (datalen != 2)
> > + return -EINVAL;
> > +
> > + ret = ima_queue_stage();
> > + break;
> > + case 'D':
> > + if (datalen != 2)
> > + return -EINVAL;
> > +
> > + ret = ima_queue_staged_delete_all();
> > + break;
>
> I think the following two steps may not work because of race condition:
>
> step1: ret = ima_queue_stage(); //this will put all logs in active list into staged list;
> step2: ret = ima_queue_staged_delete_all(); //this will delete all logs in staged list;
>
> The following is the step of race condition:
> 1. current active log list LA1;
> 2. user agent read the TPM quote QA1 match list LA1;
> 3. new event NewLog is added into active log list LA1+NewLog
> 4. user agent call ima_queue_stage() and generated staged list
> including LA1+NewLog.
> 5. user agent call ima_queue_staged_delete_all();
> The new log NewLog in step 3 is also deleted
Please refer to the documentation patch which explains the intended
workflow of this approach (Remote Attestation Agent Workflow).
Roberto
> Next time the attestation will fail if using the active log list in the
> kernel.
>
> Thanks,
>
> Steven
>
> > + default:
> > + ret = -EINVAL;
> > + }
> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + return datalen;
> > +}
> > +
> > +static const struct file_operations ima_measurements_staged_ops = {
> > + .open = ima_measurements_staged_open,
> > + .read = seq_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)
> > {
> > u32 i;
> > @@ -356,6 +436,28 @@ static const struct file_operations ima_ascii_measurements_ops = {
> > .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 = seq_read,
> > + .write = ima_measurements_staged_write,
> > + .llseek = seq_lseek,
> > + .release = ima_measurements_release,
> > +};
> > +
> > static ssize_t ima_read_policy(char *path)
> > {
> > void *data = NULL;
> > @@ -459,10 +561,21 @@ static const struct seq_operations ima_policy_seqops = {
> > };
> > #endif
> >
> > -static int __init create_securityfs_measurement_lists(void)
> > +static int __init create_securityfs_measurement_lists(bool staging)
> > {
> > + const struct file_operations *ascii_ops = &ima_ascii_measurements_ops;
> > + const struct file_operations *binary_ops = &ima_measurements_ops;
> > + mode_t permissions = S_IRUSR | S_IRGRP;
> > + const char *file_suffix = "";
> > int count = NR_BANKS(ima_tpm_chip);
> >
> > + if (staging) {
> > + ascii_ops = &ima_ascii_measurements_staged_ops;
> > + binary_ops = &ima_measurements_staged_ops;
> > + file_suffix = "_staged";
> > + permissions |= (S_IWUSR | S_IWGRP);
> > + }
> > +
> > if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip))
> > count++;
> >
> > @@ -473,29 +586,32 @@ static int __init create_securityfs_measurement_lists(void)
> >
> > if (algo == HASH_ALGO__LAST)
> > snprintf(file_name, sizeof(file_name),
> > - "ascii_runtime_measurements_tpm_alg_%x",
> > - ima_tpm_chip->allocated_banks[i].alg_id);
> > + "ascii_runtime_measurements_tpm_alg_%x%s",
> > + ima_tpm_chip->allocated_banks[i].alg_id,
> > + file_suffix);
> > else
> > snprintf(file_name, sizeof(file_name),
> > - "ascii_runtime_measurements_%s",
> > - hash_algo_name[algo]);
> > - dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
> > + "ascii_runtime_measurements_%s%s",
> > + hash_algo_name[algo], file_suffix);
> > + dentry = securityfs_create_file(file_name, permissions,
> > ima_dir, (void *)(uintptr_t)i,
> > - &ima_ascii_measurements_ops);
> > + ascii_ops);
> > if (IS_ERR(dentry))
> > return PTR_ERR(dentry);
> >
> > if (algo == HASH_ALGO__LAST)
> > snprintf(file_name, sizeof(file_name),
> > - "binary_runtime_measurements_tpm_alg_%x",
> > - ima_tpm_chip->allocated_banks[i].alg_id);
> > + "binary_runtime_measurements_tpm_alg_%x%s",
> > + ima_tpm_chip->allocated_banks[i].alg_id,
> > + file_suffix);
> > else
> > snprintf(file_name, sizeof(file_name),
> > - "binary_runtime_measurements_%s",
> > - hash_algo_name[algo]);
> > - dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP,
> > + "binary_runtime_measurements_%s%s",
> > + hash_algo_name[algo], file_suffix);
> > +
> > + dentry = securityfs_create_file(file_name, permissions,
> > ima_dir, (void *)(uintptr_t)i,
> > - &ima_measurements_ops);
> > + binary_ops);
> > if (IS_ERR(dentry))
> > return PTR_ERR(dentry);
> > }
> > @@ -503,6 +619,23 @@ static int __init create_securityfs_measurement_lists(void)
> > return 0;
> > }
> >
> > +static int __init create_securityfs_staging_links(void)
> > +{
> > + struct dentry *dentry;
> > +
> > + dentry = securityfs_create_symlink("binary_runtime_measurements_staged",
> > + ima_dir, "binary_runtime_measurements_sha1_staged", NULL);
> > + if (IS_ERR(dentry))
> > + return PTR_ERR(dentry);
> > +
> > + dentry = securityfs_create_symlink("ascii_runtime_measurements_staged",
> > + ima_dir, "ascii_runtime_measurements_sha1_staged", NULL);
> > + if (IS_ERR(dentry))
> > + return PTR_ERR(dentry);
> > +
> > + return 0;
> > +}
> > +
> > /*
> > * ima_open_policy: sequentialize access to the policy file
> > */
> > @@ -595,7 +728,13 @@ int __init ima_fs_init(void)
> > goto out;
> > }
> >
> > - ret = create_securityfs_measurement_lists();
> > + ret = create_securityfs_measurement_lists(false);
> > + if (ret == 0 && IS_ENABLED(CONFIG_IMA_STAGING)) {
> > + ret = create_securityfs_measurement_lists(true);
> > + if (ret == 0)
> > + ret = create_securityfs_staging_links();
> > + }
> > +
> > if (ret != 0)
> > goto out;
> >
> > diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
> > index d7d0fb639d99..d5503dd5cc9b 100644
> > --- a/security/integrity/ima/ima_kexec.c
> > +++ b/security/integrity/ima/ima_kexec.c
> > @@ -42,8 +42,8 @@ void ima_measure_kexec_event(const char *event_name)
> > long len;
> > int n;
> >
> > - buf_size = ima_get_binary_runtime_size(BINARY);
> > - len = atomic_long_read(&ima_num_entries[BINARY]);
> > + buf_size = ima_get_binary_runtime_size(BINARY_FULL);
> > + len = atomic_long_read(&ima_num_entries[BINARY_FULL]);
> >
> > n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
> > "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
> > @@ -106,13 +106,26 @@ 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() and ima_queue_delete_staged(). */
> > + mutex_lock(&ima_extend_list_mutex);
> > +
> > + list_for_each_entry_rcu(qe, &ima_measurements_staged, later,
> > + lockdep_is_held(&ima_extend_list_mutex)) {
> > ret = ima_dump_measurement(&khdr, qe);
> > if (ret < 0)
> > break;
> > }
> >
> > + list_for_each_entry_rcu(qe, &ima_measurements, later,
> > + lockdep_is_held(&ima_extend_list_mutex)) {
> > + if (!ret)
> > + ret = ima_dump_measurement(&khdr, qe);
> > + if (ret < 0)
> > + break;
> > + }
> > +
> > + mutex_unlock(&ima_extend_list_mutex);
> > +
> > /*
> > * fill in reserved space with some buffer details
> > * (eg. version, buffer size, number of measurements)
> > @@ -167,6 +180,7 @@ void ima_add_kexec_buffer(struct kimage *image)
> > extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024;
> >
> > binary_runtime_size = ima_get_binary_runtime_size(BINARY) +
> > + ima_get_binary_runtime_size(BINARY_STAGED) +
> > extra_memory;
> >
> > if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE)
> > diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
> > index b6d10dceb669..50519ed837d4 100644
> > --- a/security/integrity/ima/ima_queue.c
> > +++ b/security/integrity/ima/ima_queue.c
> > @@ -26,6 +26,7 @@
> > static struct tpm_digest *digests;
> >
> > LIST_HEAD(ima_measurements); /* list of all measurements */
> > +LIST_HEAD(ima_measurements_staged); /* list of staged measurements */
> > #ifdef CONFIG_IMA_KEXEC
> > static unsigned long binary_runtime_size[BINARY__LAST];
> > #else
> > @@ -45,11 +46,11 @@ atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0);
> > /* key: inode (before secure-hashing a file) */
> > struct hlist_head __rcu *ima_htable;
> >
> > -/* mutex protects atomicity of extending measurement list
> > +/* mutex protects atomicity of extending and staging measurement list
> > * 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.
> > @@ -174,12 +175,16 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
> > lockdep_is_held(&ima_extend_list_mutex));
> >
> > atomic_long_inc(&ima_num_entries[BINARY]);
> > + atomic_long_inc(&ima_num_entries[BINARY_FULL]);
> > +
> > if (update_htable) {
> > key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
> > hlist_add_head_rcu(&qe->hnext, &htable[key]);
> > }
> >
> > ima_update_binary_runtime_size(entry, BINARY);
> > + ima_update_binary_runtime_size(entry, BINARY_FULL);
> > +
> > return 0;
> > }
> >
> > @@ -280,6 +285,94 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
> > return result;
> > }
> >
> > +int ima_queue_stage(void)
> > +{
> > + int ret = 0;
> > +
> > + mutex_lock(&ima_extend_list_mutex);
> > + if (!list_empty(&ima_measurements_staged)) {
> > + ret = -EEXIST;
> > + goto out_unlock;
> > + }
> > +
> > + if (list_empty(&ima_measurements)) {
> > + ret = -ENOENT;
> > + goto out_unlock;
> > + }
> > +
> > + list_replace(&ima_measurements, &ima_measurements_staged);
> > + INIT_LIST_HEAD(&ima_measurements);
> > +
> > + atomic_long_set(&ima_num_entries[BINARY_STAGED],
> > + atomic_long_read(&ima_num_entries[BINARY]));
> > + atomic_long_set(&ima_num_entries[BINARY], 0);
> > +
> > + if (IS_ENABLED(CONFIG_IMA_KEXEC)) {
> > + binary_runtime_size[BINARY_STAGED] =
> > + binary_runtime_size[BINARY];
> > + binary_runtime_size[BINARY] = 0;
> > + }
> > +out_unlock:
> > + mutex_unlock(&ima_extend_list_mutex);
> > + return ret;
> > +}
> > +
> > +static void ima_queue_delete(struct list_head *head);
> > +
> > +int ima_queue_staged_delete_all(void)
> > +{
> > + LIST_HEAD(ima_measurements_trim);
> > +
> > + mutex_lock(&ima_extend_list_mutex);
> > + if (list_empty(&ima_measurements_staged)) {
> > + mutex_unlock(&ima_extend_list_mutex);
> > + return -ENOENT;
> > + }
> > +
> > + list_replace(&ima_measurements_staged, &ima_measurements_trim);
> > + INIT_LIST_HEAD(&ima_measurements_staged);
> > +
> > + atomic_long_set(&ima_num_entries[BINARY_STAGED], 0);
> > +
> > + if (IS_ENABLED(CONFIG_IMA_KEXEC))
> > + binary_runtime_size[BINARY_STAGED] = 0;
> > +
> > + mutex_unlock(&ima_extend_list_mutex);
> > +
> > + ima_queue_delete(&ima_measurements_trim);
> > + return 0;
> > +}
> > +
> > +static void ima_queue_delete(struct list_head *head)
> > +{
> > + struct ima_queue_entry *qe, *qe_tmp;
> > + unsigned int i;
> > +
> > + list_for_each_entry_safe(qe, qe_tmp, head, later) {
> > + /*
> > + * Safe to free template_data here without synchronize_rcu()
> > + * because the only htable reader, ima_lookup_digest_entry(),
> > + * accesses only entry->digests, not template_data. If new
> > + * htable readers are added that access template_data, a
> > + * synchronize_rcu() is required here.
> > + */
> > + 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);
> > +
> > + /* No leak if condition is false, referenced by ima_htable. */
> > + if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) {
> > + kfree(qe->entry->digests);
> > + kfree(qe->entry);
> > + kfree(qe);
> > + }
> > + }
> > +}
> > +
> > int ima_restore_measurement_entry(struct ima_template_entry *entry)
> > {
> > int result = 0;
>
^ permalink raw reply
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