From 398ee113f15c1e8e62535e54f22fb4db340c7835 Mon Sep 17 00:00:00 2001 From: Kamlesh Kumar Date: Fri, 24 Apr 2026 17:09:46 +0530 Subject: [PATCH 01/19] ima: Fix sigv3 signature handling for EVM_IMA_XATTR_DIGSIG ima_get_hash_algo() only recognizes version 2 signatures when the xattr type is EVM_IMA_XATTR_DIGSIG. Since sigv3 signatures also use EVM_IMA_XATTR_DIGSIG as the xattr type, version 3 must be accepted as well to correctly determine the hash algorithm. Additionally, ima_validate_rule() does not include IMA_SIGV3_REQUIRED in the allowed flags bitmask for MODULE_CHECK, KEXEC_KERNEL_CHECK, and KEXEC_INITRAMFS_CHECK hook functions. As a result, policy rules with "appraise_type=sigv3" are rejected for these functions. Add version 3 to the accepted versions in ima_get_hash_algo() for EVM_IMA_XATTR_DIGSIG, and add IMA_SIGV3_REQUIRED to the allowed flags for MODULE_CHECK, KEXEC_KERNEL_CHECK, and KEXEC_INITRAMFS_CHECK in ima_validate_rule(). Signed-off-by: Kamlesh Kumar Tested-by: Stefan Berger Fixes: de4c44a7f559 ("ima: add support to require IMA sigv3 signatures") Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_appraise.c | 5 +++-- security/integrity/ima/ima_policy.c | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index de963b9f3634..2dd231567710 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -195,8 +195,9 @@ enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value, return sig->hash_algo; case EVM_IMA_XATTR_DIGSIG: sig = (typeof(sig))xattr_value; - if (sig->version != 2 || xattr_len <= sizeof(*sig) - || sig->hash_algo >= HASH_ALGO__LAST) + if ((sig->version != 2 && sig->version != 3) || + xattr_len <= sizeof(*sig) || + sig->hash_algo >= HASH_ALGO__LAST) return ima_hash_algo; return sig->hash_algo; case IMA_XATTR_DIGEST_NG: diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index f7f940a76922..b1c010e8eb13 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -1313,7 +1313,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) IMA_GID | IMA_EGID | IMA_FGROUP | IMA_DIGSIG_REQUIRED | IMA_PERMIT_DIRECTIO | IMA_MODSIG_ALLOWED | - IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS)) + IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS | + IMA_SIGV3_REQUIRED)) return false; break; From 69fc6474236d9edda6983623e4282f2bdfd8e3d8 Mon Sep 17 00:00:00 2001 From: Goldwyn Rodrigues Date: Wed, 22 Apr 2026 07:34:51 -0400 Subject: [PATCH 02/19] ima: return error early if file xattr cannot be changed During early boot, the filesystem is read-only and any changes to xattrs are not allowed. This fails in case of ext4 because changing xattr starts an ext4 transaction which fails with the following warning. WARNING: fs/ext4/ext4_jbd2.c:75 at ext4_journal_check_start+0x63/0xa0 [ext4], CPU#1: systemd-sysroot/561 CPU: 1 UID: 0 PID: 561 Comm: systemd-sysroot Not tainted 6.19.12-1-default #1 PREEMPT(voluntary) openSUSE Tumbleweed c2dfc3c9d9f6f1233251c5d4410574fe82a348ee Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS unknown 02/02/2022 RIP: 0010:ext4_journal_check_start+0x63/0xa0 [ext4] Call Trace: __ext4_journal_start_sb+0x3e/0x180 [ext4 6d025f3bc52c89a957b89a89d211fadf5e9434e1] ext4_xattr_set+0x104/0x150 [ext4 6d025f3bc52c89a957b89a89d211fadf5e9434e1] __vfs_setxattr+0x9a/0xd0 __vfs_setxattr_noperm+0x76/0x1f0 ima_appraise_measurement+0x23e/0xe40 ima_d_path+0x5a/0xd0 process_measurement+0xb29/0xc40 ? copy_from_kernel_nofault+0x21/0xe0 ? fscrypt_file_open+0xc0/0xe0 ? ext4_file_open+0x60/0x490 [ext4 6d025f3bc52c89a957b89a89d211fadf5e9434e1] ? bpf_prog_31efb7c56239148b_restrict_filesystems+0xab/0x126 ? __bpf_prog_exit+0x23/0xd0 ? __bpf_tramp_exit+0xd/0x50 ? bpf_trampoline_6442530367+0x9f/0xea ima_file_check+0x57/0x80 security_file_post_open+0x50/0xf0 path_openat+0x493/0x1650 do_filp_open+0xc7/0x170 Detect the state of the file early and return the error. Signed-off-by: Goldwyn Rodrigues Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_appraise.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 2dd231567710..18d0d9154317 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -90,6 +90,11 @@ static int ima_fix_xattr(struct dentry *dentry, struct ima_iint_cache *iint) int rc, offset; u8 algo = iint->ima_hash->algo; + if (IS_RDONLY(d_inode(dentry))) + return -EROFS; + if (IS_IMMUTABLE(d_inode(dentry))) + return -EPERM; + if (algo <= HASH_ALGO_SHA1) { offset = 1; iint->ima_hash->xattr.sha1.type = IMA_XATTR_DIGEST; From c93a5f038ccc11ed8558ce642f62d5ede701a348 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Thu, 16 Apr 2026 11:40:36 -0400 Subject: [PATCH 03/19] integrity: Check for NULL returned by asymmetric_key_public_key Check for a NULL pointer returned by asymmetric_key_public_key and return -ENOKEY in this case. Signed-off-by: Stefan Berger Tested-by: Kamlesh Kumar Signed-off-by: Mimi Zohar --- security/integrity/digsig_asymmetric.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c index 6e68ec3becbd..a791ad43b3fb 100644 --- a/security/integrity/digsig_asymmetric.c +++ b/security/integrity/digsig_asymmetric.c @@ -108,6 +108,10 @@ int asymmetric_verify(struct key *keyring, const char *sig, pks.hash_algo = hash_algo_name[hdr->hash_algo]; pk = asymmetric_key_public_key(key); + if (!pk) { + ret = -ENOKEY; + goto out; + } pks.pkey_algo = pk->pkey_algo; if (!strcmp(pk->pkey_algo, "rsa")) { pks.encoding = "pkcs1"; From 474c78c26744b6921549f0c679b7507a57cfcbb9 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Thu, 16 Apr 2026 11:40:37 -0400 Subject: [PATCH 04/19] integrity: Check that algo parameter is within valid range Check that the algo parameter passed to calc_file_id_hash is within valid range. Do this in asymmetric_verify_v3 since this value will also be passed to a hashless signature verification function from here. Signed-off-by: Stefan Berger Tested-by: Kamlesh Kumar Signed-off-by: Mimi Zohar --- security/integrity/digsig_asymmetric.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c index a791ad43b3fb..ed171a627d18 100644 --- a/security/integrity/digsig_asymmetric.c +++ b/security/integrity/digsig_asymmetric.c @@ -139,7 +139,7 @@ int asymmetric_verify(struct key *keyring, const char *sig, /* * calc_file_id_hash - calculate the hash of the ima_file_id struct data * @type: xattr type [enum evm_ima_xattr_type] - * @algo: hash algorithm [enum hash_algo] + * @algo: hash algorithm [enum hash_algo]; caller must ensure valid value * @digest: pointer to the digest to be hashed * @hash: (out) pointer to the hash * @@ -187,6 +187,9 @@ int asymmetric_verify_v3(struct key *keyring, const char *sig, int siglen, struct ima_max_digest_data hash; int rc; + if (algo >= HASH_ALGO__LAST) + return -ENOPKG; + rc = calc_file_id_hash(hdr->type, algo, data, &hash); if (rc) return -EINVAL; From 33aa0c8cf0657b9588f835645cbbcebe44a2a1ee Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Thu, 16 Apr 2026 11:40:38 -0400 Subject: [PATCH 05/19] integrity: Refactor asymmetric_verify for reusability 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 Tested-by: Kamlesh Kumar Signed-off-by: Mimi Zohar --- security/integrity/digsig_asymmetric.c | 62 ++++++++++++++++++-------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c index ed171a627d18..a4eb73bba6d2 100644 --- a/security/integrity/digsig_asymmetric.c +++ b/security/integrity/digsig_asymmetric.c @@ -79,18 +79,25 @@ 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) +/** + * asymmetric_verify_common -- sigv2 and sigv3 common verify function + * @key: The key to use for signature verification; caller must free it + * @pk: The associated public key; must not be NULL + * @sig: The xattr signature + * @siglen: The length of the xattr signature; must be at least + * sizeof(struct signature_v2_hdr) + * @data: The data to verify the signature on + * @datalen: Length of @data + */ +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,19 +106,9 @@ 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); - if (!pk) { - ret = -ENOKEY; - goto out; - } pks.pkey_algo = pk->pkey_algo; if (!strcmp(pk->pkey_algo, "rsa")) { pks.encoding = "pkcs1"; @@ -131,11 +128,38 @@ 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); + if (!pk) { + ret = -ENOKEY; + goto out; + } + + ret = asymmetric_verify_common(key, pk, sig, siglen, data, datalen); + +out: + 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] From 489d7e2e7e9a31faa38ca25be0e6cbe3eea2960f Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Thu, 16 Apr 2026 11:40:39 -0400 Subject: [PATCH 06/19] integrity: Add support for sigv3 verification using ML-DSA keys 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 Signed-off-by: Stefan Berger Tested-by: Kamlesh Kumar Signed-off-by: Mimi Zohar --- security/integrity/digsig_asymmetric.c | 89 ++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c index a4eb73bba6d2..b4c23a0ed68f 100644 --- a/security/integrity/digsig_asymmetric.c +++ b/security/integrity/digsig_asymmetric.c @@ -204,20 +204,99 @@ 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; caller must free it + * @pk: The associated public key; must not be NULL + * @encoding: The encoding the key type uses + * @sig: The xattr signature + * @siglen: The length of the xattr signature; must be at least + * sizeof(struct signature_v2_hdr) + * @algo: hash algorithm [enum hash_algo]; caller must ensure valid value + * @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 = "none", + .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; if (algo >= HASH_ALGO__LAST) return -ENOPKG; - rc = calc_file_id_hash(hdr->type, algo, data, &hash); - if (rc) - return -EINVAL; + if (siglen <= sizeof(*hdr)) + return -EBADMSG; - return asymmetric_verify(keyring, sig, siglen, hash.digest, - hash.hdr.length); + key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid)); + if (IS_ERR(key)) + return PTR_ERR(key); + + pk = asymmetric_key_public_key(key); + if (!pk) { + rc = -ENOKEY; + goto out; + } + 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 out; + } + + rc = asymmetric_verify_common(key, pk, sig, siglen, hash.digest, + hash.hdr.length); + } + +out: + key_put(key); + + return rc; } From 11143a19f5b8dc8f414deab87571134f9f447313 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Thu, 23 Apr 2026 23:30:00 +0800 Subject: [PATCH 07/19] evm: terminate and bound the evm_xattrs read buffer evm_read_xattrs() allocates size + 1 bytes, fills them from the list of enabled xattrs, and then passes strlen(temp) to simple_read_from_buffer(). When no configured xattrs are enabled, the fill loop stores nothing and temp[0] remains uninitialized, so strlen() reads beyond initialized memory. Explicitly terminate the buffer after allocation, use snprintf() for each formatted line, and pass the accumulated length, without risk of truncation, to simple_read_from_buffer(). Fixes: fa516b66a1bf ("EVM: Allow runtime modification of the set of verified xattrs") Signed-off-by: Pengpeng Hou Reviewed-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/evm/evm_secfs.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/security/integrity/evm/evm_secfs.c b/security/integrity/evm/evm_secfs.c index acd840461902..4baf5e23bc97 100644 --- a/security/integrity/evm/evm_secfs.c +++ b/security/integrity/evm/evm_secfs.c @@ -127,8 +127,8 @@ static ssize_t evm_read_xattrs(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { char *temp; - int offset = 0; - ssize_t rc, size = 0; + size_t offset = 0, size = 0; + ssize_t rc; struct xattr_list *xattr; if (*ppos != 0) @@ -151,16 +151,22 @@ static ssize_t evm_read_xattrs(struct file *filp, char __user *buf, return -ENOMEM; } + temp[size] = '\0'; + + /* + * No truncation possible: size is computed over the same enabled + * xattrs under xattr_list_mutex, so offset never exceeds size. + */ list_for_each_entry(xattr, &evm_config_xattrnames, list) { if (!xattr->enabled) continue; - sprintf(temp + offset, "%s\n", xattr->name); - offset += strlen(xattr->name) + 1; + offset += snprintf(temp + offset, size + 1 - offset, "%s\n", + xattr->name); } mutex_unlock(&xattr_list_mutex); - rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); + rc = simple_read_from_buffer(buf, count, ppos, temp, offset); kfree(temp); From a01183bd6fc66910e2837830aa21dd8da2b7795e Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:25 +0200 Subject: [PATCH 08/19] ima: Remove ima_h_table structure The ima_h_table structure is a collection of IMA measurement list metadata - number of records in the IMA measurement list, number of integrity violations, and a hash table containing the IMA template data hash, needed to prevent measurement list record duplication. Removing records from the measurement list needs to be reflected in the hash table. As a pre-req to removing records from the measurement list, separate those counters from the hash table, remove the ima_h_table structure, and just replace the hash table pointer. Finally, rename ima_show_htable_value(), ima_show_htable_violations() and ima_htable_violations_ops respectively to ima_show_counter(), ima_show_num_violations() and ima_num_violations_ops. Link: https://github.com/linux-integrity/linux/issues/1 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 11 +++++------ security/integrity/ima/ima_api.c | 2 +- security/integrity/ima/ima_fs.c | 20 +++++++++----------- security/integrity/ima/ima_kexec.c | 2 +- security/integrity/ima/ima_queue.c | 15 ++++++++------- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 69e9bf0b82c6..b3ad7eac6a1e 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -324,12 +324,11 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event, */ extern spinlock_t ima_queue_lock; -struct ima_h_table { - atomic_long_t len; /* number of stored measurements in the list */ - atomic_long_t violations; - struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE]; -}; -extern struct ima_h_table ima_htable; +/* Total number of measurement list records since hard boot. */ +extern atomic_long_t ima_num_records; +/* Total number of violations since hard boot. */ +extern atomic_long_t ima_num_violations; +extern struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE]; static inline unsigned int ima_hash_key(u8 *digest) { diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 0916f24f005f..122d127e108d 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -146,7 +146,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename, int result; /* can overflow, only indicator */ - atomic_long_inc(&ima_htable.violations); + atomic_long_inc(&ima_num_violations); result = ima_alloc_init_template(&event_data, &entry, NULL); if (result < 0) { diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index ca4931a95098..523d3e81f631 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -38,8 +38,8 @@ __setup("ima_canonical_fmt", default_canonical_fmt_setup); static int valid_policy = 1; -static ssize_t ima_show_htable_value(char __user *buf, size_t count, - loff_t *ppos, atomic_long_t *val) +static ssize_t ima_show_counter(char __user *buf, size_t count, loff_t *ppos, + atomic_long_t *val) { char tmpbuf[32]; /* greater than largest 'long' string value */ ssize_t len; @@ -48,15 +48,14 @@ static ssize_t ima_show_htable_value(char __user *buf, size_t count, return simple_read_from_buffer(buf, count, ppos, tmpbuf, len); } -static ssize_t ima_show_htable_violations(struct file *filp, - char __user *buf, - size_t count, loff_t *ppos) +static ssize_t ima_show_num_violations(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) { - return ima_show_htable_value(buf, count, ppos, &ima_htable.violations); + return ima_show_counter(buf, count, ppos, &ima_num_violations); } -static const struct file_operations ima_htable_violations_ops = { - .read = ima_show_htable_violations, +static const struct file_operations ima_num_violations_ops = { + .read = ima_show_num_violations, .llseek = generic_file_llseek, }; @@ -64,8 +63,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_counter(buf, count, ppos, &ima_num_records); } static const struct file_operations ima_measurements_count_ops = { @@ -545,7 +543,7 @@ int __init ima_fs_init(void) } dentry = securityfs_create_file("violations", S_IRUSR | S_IRGRP, - ima_dir, NULL, &ima_htable_violations_ops); + ima_dir, NULL, &ima_num_violations_ops); if (IS_ERR(dentry)) { ret = PTR_ERR(dentry); goto out; diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c index 36a34c54de58..77ad370dbc37 100644 --- a/security/integrity/ima/ima_kexec.c +++ b/security/integrity/ima/ima_kexec.c @@ -43,7 +43,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_num_records); 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 319522450854..6bdaefc790c3 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -32,11 +32,12 @@ static unsigned long binary_runtime_size; static unsigned long binary_runtime_size = ULONG_MAX; #endif +atomic_long_t ima_num_records = ATOMIC_LONG_INIT(0); +atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0); + /* key: inode (before secure-hashing a file) */ -struct ima_h_table ima_htable = { - .len = ATOMIC_LONG_INIT(0), - .violations = ATOMIC_LONG_INIT(0), - .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT +struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE] = { + [0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT }; /* mutex protects atomicity of extending measurement list @@ -61,7 +62,7 @@ static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value, key = ima_hash_key(digest_value); rcu_read_lock(); - hlist_for_each_entry_rcu(qe, &ima_htable.queue[key], hnext) { + hlist_for_each_entry_rcu(qe, &ima_htable[key], hnext) { rc = memcmp(qe->entry->digests[ima_hash_algo_idx].digest, digest_value, hash_digest_size[ima_hash_algo]); if ((rc == 0) && (qe->entry->pcr == pcr)) { @@ -113,10 +114,10 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, INIT_LIST_HEAD(&qe->later); list_add_tail_rcu(&qe->later, &ima_measurements); - atomic_long_inc(&ima_htable.len); + atomic_long_inc(&ima_num_records); if (update_htable) { key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest); - hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]); + hlist_add_head_rcu(&qe->hnext, &ima_htable[key]); } if (binary_runtime_size != ULONG_MAX) { From 7bc01800a7739972626e366766f54c3e76cc3e69 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:26 +0200 Subject: [PATCH 09/19] ima: Replace static htable queue with dynamically allocated array The IMA hash table is a fixed-size array of hlist_head buckets: struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE]; IMA_MEASURE_HTABLE_SIZE is (1 << IMA_HASH_BITS) = 1024 buckets, each a struct hlist_head (one pointer, 8 bytes on 64-bit). That is 8 KiB allocated in BSS for every kernel, regardless of whether IMA is ever used, and regardless of how many measurements are actually made. Replace the fixed-size array with a RCU-protected pointer to a dynamically allocated array that is initialized in ima_init_htable(), which is called from ima_init() during early boot. ima_init_htable() calls the static function ima_alloc_replace_htable() which, other than initializing the hash table the first time, can also hot-swap the existing hash table with a blank one. The allocation in ima_alloc_replace_htable() uses kcalloc() so the buckets are zero-initialised (equivalent to HLIST_HEAD_INIT { .first = NULL }). Callers of ima_alloc_replace_htable() must call synchronize_rcu() and free the returned hash table. Finally, access the hash table with rcu_dereference() in ima_lookup_digest_entry() (reader side) and with rcu_dereference_protected() in ima_add_digest_entry() (writer side). No functional change: bucket count, hash function, and all locking remain identical. Link: https://github.com/linux-integrity/linux/issues/1 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 3 +- security/integrity/ima/ima_init.c | 5 ++++ security/integrity/ima/ima_queue.c | 48 ++++++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index b3ad7eac6a1e..0e41c2113efd 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -311,6 +311,7 @@ bool ima_template_has_modsig(const struct ima_template_desc *ima_template); 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); +int __init ima_init_htable(void); unsigned long ima_get_binary_runtime_size(void); int ima_init_template(void); void ima_init_template_list(void); @@ -328,7 +329,7 @@ extern spinlock_t ima_queue_lock; extern atomic_long_t ima_num_records; /* Total number of violations since hard boot. */ extern atomic_long_t ima_num_violations; -extern struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE]; +extern struct hlist_head __rcu *ima_htable; static inline unsigned int ima_hash_key(u8 *digest) { diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index a2f34f2d8ad7..7e0aa09a12e6 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -140,6 +140,11 @@ int __init ima_init(void) rc = ima_init_digests(); if (rc != 0) return rc; + + rc = ima_init_htable(); + if (rc != 0) + return rc; + rc = ima_add_boot_aggregate(); /* boot aggregate must be first entry */ if (rc != 0) return rc; diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index 6bdaefc790c3..a31b75d9302b 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -36,9 +36,7 @@ atomic_long_t ima_num_records = ATOMIC_LONG_INIT(0); atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0); /* key: inode (before secure-hashing a file) */ -struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE] = { - [0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT -}; +struct hlist_head __rcu *ima_htable; /* mutex protects atomicity of extending measurement list * and extending the TPM PCR aggregate. Since tpm_extend can take @@ -52,17 +50,53 @@ static DEFINE_MUTEX(ima_extend_list_mutex); */ static bool ima_measurements_suspended; +/* Callers must call synchronize_rcu() and free the hash table. */ +static struct hlist_head *ima_alloc_replace_htable(void) +{ + struct hlist_head *old_htable, *new_htable; + + /* Initializing to zeros is equivalent to call HLIST_HEAD_INIT. */ + new_htable = kcalloc(IMA_MEASURE_HTABLE_SIZE, sizeof(struct hlist_head), + GFP_KERNEL); + if (!new_htable) + return ERR_PTR(-ENOMEM); + + old_htable = rcu_replace_pointer(ima_htable, new_htable, + lockdep_is_held(&ima_extend_list_mutex)); + + return old_htable; +} + +int __init ima_init_htable(void) +{ + struct hlist_head *old_htable; + + mutex_lock(&ima_extend_list_mutex); + old_htable = ima_alloc_replace_htable(); + mutex_unlock(&ima_extend_list_mutex); + + if (IS_ERR(old_htable)) + return PTR_ERR(old_htable); + + /* Synchronize_rcu() and kfree() not necessary, only for robustness. */ + synchronize_rcu(); + kfree(old_htable); + return 0; +} + /* lookup up the digest value in the hash table, and return the entry */ static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value, int pcr) { struct ima_queue_entry *qe, *ret = NULL; + struct hlist_head *htable; unsigned int key; int rc; key = ima_hash_key(digest_value); rcu_read_lock(); - hlist_for_each_entry_rcu(qe, &ima_htable[key], hnext) { + htable = rcu_dereference(ima_htable); + hlist_for_each_entry_rcu(qe, &htable[key], hnext) { rc = memcmp(qe->entry->digests[ima_hash_algo_idx].digest, digest_value, hash_digest_size[ima_hash_algo]); if ((rc == 0) && (qe->entry->pcr == pcr)) { @@ -102,6 +136,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, bool update_htable) { struct ima_queue_entry *qe; + struct hlist_head *htable; unsigned int key; qe = kmalloc_obj(*qe); @@ -114,10 +149,13 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, INIT_LIST_HEAD(&qe->later); list_add_tail_rcu(&qe->later, &ima_measurements); + htable = rcu_dereference_protected(ima_htable, + lockdep_is_held(&ima_extend_list_mutex)); + atomic_long_inc(&ima_num_records); if (update_htable) { key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest); - hlist_add_head_rcu(&qe->hnext, &ima_htable[key]); + hlist_add_head_rcu(&qe->hnext, &htable[key]); } if (binary_runtime_size != ULONG_MAX) { From 2fcebcd2aad24c13c27a6881a0866629f3ec57b2 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:27 +0200 Subject: [PATCH 10/19] ima: Introduce per binary measurements list type ima_num_records counter Make ima_num_records as an array, to have separate counters per binary measurements list type. Currently, define the BINARY type for the existing binary measurements list. No functional change: the BINARY type is equivalent to the value without the array. Link: https://github.com/linux-integrity/linux/issues/1 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 9 ++++++++- security/integrity/ima/ima_fs.c | 2 +- security/integrity/ima/ima_kexec.c | 2 +- security/integrity/ima/ima_queue.c | 6 ++++-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 0e41c2113efd..8f457f2c7b79 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -28,6 +28,13 @@ enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_BINARY_NO_FIELD_LEN, IMA_SHOW_BINARY_OLD_STRING_FMT, IMA_SHOW_ASCII }; enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 }; +/* + * BINARY: current binary measurements list + */ +enum binary_lists { + BINARY, BINARY__LAST +}; + /* digest size for IMA, fits SHA1 or MD5 */ #define IMA_DIGEST_SIZE SHA1_DIGEST_SIZE #define IMA_EVENT_NAME_LEN_MAX 255 @@ -326,7 +333,7 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event, extern spinlock_t ima_queue_lock; /* Total number of measurement list records since hard boot. */ -extern atomic_long_t ima_num_records; +extern atomic_long_t ima_num_records[BINARY__LAST]; /* Total number of violations since hard boot. */ extern atomic_long_t ima_num_violations; extern struct hlist_head __rcu *ima_htable; diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 523d3e81f631..fcfcf7b6eae2 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -63,7 +63,7 @@ static ssize_t ima_show_measurements_count(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { - return ima_show_counter(buf, count, ppos, &ima_num_records); + return ima_show_counter(buf, count, ppos, &ima_num_records[BINARY]); } static const struct file_operations ima_measurements_count_ops = { diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c index 77ad370dbc37..1a0211a12ea4 100644 --- a/security/integrity/ima/ima_kexec.c +++ b/security/integrity/ima/ima_kexec.c @@ -43,7 +43,7 @@ void ima_measure_kexec_event(const char *event_name) int n; buf_size = ima_get_binary_runtime_size(); - len = atomic_long_read(&ima_num_records); + len = atomic_long_read(&ima_num_records[BINARY]); 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 a31b75d9302b..012e725ed4fc 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -32,7 +32,9 @@ static unsigned long binary_runtime_size; static unsigned long binary_runtime_size = ULONG_MAX; #endif -atomic_long_t ima_num_records = ATOMIC_LONG_INIT(0); +atomic_long_t ima_num_records[BINARY__LAST] = { + [0 ... BINARY__LAST - 1] = ATOMIC_LONG_INIT(0) +}; atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0); /* key: inode (before secure-hashing a file) */ @@ -152,7 +154,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, htable = rcu_dereference_protected(ima_htable, lockdep_is_held(&ima_extend_list_mutex)); - atomic_long_inc(&ima_num_records); + atomic_long_inc(&ima_num_records[BINARY]); if (update_htable) { key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest); hlist_add_head_rcu(&qe->hnext, &htable[key]); From 8f19da70f794f380a4b5aacfec681315a0a325c5 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:28 +0200 Subject: [PATCH 11/19] ima: Introduce per binary measurements list type binary_runtime_size value Make binary_runtime_size as an array, to have separate counters per binary measurements list type. Currently, define the BINARY type for the existing binary measurements list. Introduce ima_update_binary_runtime_size() to facilitate updating a binary_runtime_size value with a given binary measurement list type. Also add the binary measurements list type parameter to ima_get_binary_runtime_size(), to retrieve the desired value. Retrieving the value is now done under the ima_extend_list_mutex, since there can be concurrent updates. No functional change (except for the mutex usage, that fixes the concurrency issue): the BINARY array element is equivalent to the old binary_runtime_size. Link: https://github.com/linux-integrity/linux/issues/1 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 2 +- security/integrity/ima/ima_kexec.c | 5 ++-- security/integrity/ima/ima_queue.c | 40 +++++++++++++++++++++--------- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 8f457f2c7b79..c00c133a140f 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -319,7 +319,7 @@ 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); int __init ima_init_htable(void); -unsigned long ima_get_binary_runtime_size(void); +unsigned long ima_get_binary_runtime_size(enum binary_lists binary_list); int ima_init_template(void); void ima_init_template_list(void); int __init ima_init_digests(void); diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c index 1a0211a12ea4..8dc9459622b3 100644 --- a/security/integrity/ima/ima_kexec.c +++ b/security/integrity/ima/ima_kexec.c @@ -42,7 +42,7 @@ void ima_measure_kexec_event(const char *event_name) long len; int n; - buf_size = ima_get_binary_runtime_size(); + buf_size = ima_get_binary_runtime_size(BINARY); len = atomic_long_read(&ima_num_records[BINARY]); n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN, @@ -159,7 +159,8 @@ void ima_add_kexec_buffer(struct kimage *image) else extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024; - binary_runtime_size = ima_get_binary_runtime_size() + extra_memory; + binary_runtime_size = ima_get_binary_runtime_size(BINARY) + + extra_memory; if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE) kexec_segment_size = ULONG_MAX; diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index 012e725ed4fc..618694d5c082 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -27,9 +27,11 @@ static struct tpm_digest *digests; LIST_HEAD(ima_measurements); /* list of all measurements */ #ifdef CONFIG_IMA_KEXEC -static unsigned long binary_runtime_size; +static unsigned long binary_runtime_size[BINARY__LAST]; #else -static unsigned long binary_runtime_size = ULONG_MAX; +static unsigned long binary_runtime_size[BINARY__LAST] = { + [0 ... BINARY__LAST - 1] = ULONG_MAX +}; #endif atomic_long_t ima_num_records[BINARY__LAST] = { @@ -128,6 +130,20 @@ static int get_binary_runtime_size(struct ima_template_entry *entry) return size; } +static void ima_update_binary_runtime_size(struct ima_template_entry *entry, + enum binary_lists binary_list) +{ + int size; + + if (binary_runtime_size[binary_list] == ULONG_MAX) + return; + + size = get_binary_runtime_size(entry); + binary_runtime_size[binary_list] = + (binary_runtime_size[binary_list] < ULONG_MAX - size) ? + binary_runtime_size[binary_list] + size : ULONG_MAX; +} + /* ima_add_template_entry helper function: * - Add template entry to the measurement list and hash table, for * all entries except those carried across kexec. @@ -160,13 +176,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, hlist_add_head_rcu(&qe->hnext, &htable[key]); } - if (binary_runtime_size != ULONG_MAX) { - int size; - - size = get_binary_runtime_size(entry); - binary_runtime_size = (binary_runtime_size < ULONG_MAX - size) ? - binary_runtime_size + size : ULONG_MAX; - } + ima_update_binary_runtime_size(entry, BINARY); return 0; } @@ -175,12 +185,18 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, * entire binary_runtime_measurement list, including the ima_kexec_hdr * structure. */ -unsigned long ima_get_binary_runtime_size(void) +unsigned long ima_get_binary_runtime_size(enum binary_lists binary_list) { - if (binary_runtime_size >= (ULONG_MAX - sizeof(struct ima_kexec_hdr))) + unsigned long val; + + mutex_lock(&ima_extend_list_mutex); + val = binary_runtime_size[binary_list]; + mutex_unlock(&ima_extend_list_mutex); + + if (val >= (ULONG_MAX - sizeof(struct ima_kexec_hdr))) return ULONG_MAX; else - return binary_runtime_size + sizeof(struct ima_kexec_hdr); + return val + sizeof(struct ima_kexec_hdr); } static int ima_pcr_extend(struct tpm_digest *digests_arg, int pcr) From cb431ff6a92fc62d91ba64f04c7af3bb54017a1d Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:29 +0200 Subject: [PATCH 12/19] ima: Introduce _ima_measurements_start() and _ima_measurements_next() Introduce _ima_measurements_start() and _ima_measurements_next(), renamed from ima_measurements_start() and ima_measurements_next(), to include the list head as an additional parameter, so that iteration on different lists can be implemented by calling those functions. No functional change: ima_measurements_start() and ima_measurements_next() pass the ima_measurements list head, used before. They become wrappers for the new functions. Link: https://github.com/linux-integrity/linux/issues/1 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_fs.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index fcfcf7b6eae2..dcdc4cb8fa0f 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -72,14 +72,15 @@ static const struct file_operations ima_measurements_count_ops = { }; /* returns pointer to hlist_node */ -static void *ima_measurements_start(struct seq_file *m, loff_t *pos) +static void *_ima_measurements_start(struct seq_file *m, loff_t *pos, + struct list_head *head) { loff_t l = *pos; struct ima_queue_entry *qe; /* we need a lock since pos could point beyond last element */ rcu_read_lock(); - list_for_each_entry_rcu(qe, &ima_measurements, later) { + list_for_each_entry_rcu(qe, head, later) { if (!l--) { rcu_read_unlock(); return qe; @@ -89,7 +90,13 @@ static void *ima_measurements_start(struct seq_file *m, loff_t *pos) return NULL; } -static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos) +static void *ima_measurements_start(struct seq_file *m, loff_t *pos) +{ + return _ima_measurements_start(m, pos, &ima_measurements); +} + +static void *_ima_measurements_next(struct seq_file *m, void *v, loff_t *pos, + struct list_head *head) { struct ima_queue_entry *qe = v; @@ -101,7 +108,12 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos) rcu_read_unlock(); (*pos)++; - return (&qe->later == &ima_measurements) ? NULL : qe; + return (&qe->later == head) ? NULL : qe; +} + +static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos) +{ + return _ima_measurements_next(m, v, pos, &ima_measurements); } static void ima_measurements_stop(struct seq_file *m, void *v) From 51bedcd803e0f140ee39e70a930d01223e1afb58 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:30 +0200 Subject: [PATCH 13/19] ima: Mediate open/release method of the measurements list Introduce the ima_measure_users counter, to implement a semaphore-like locking scheme where the binary and ASCII measurements list interfaces can be concurrently opened by multiple readers, or alternatively by a single writer. In addition, allow the same writer to open the other interfaces for write or read/write, so that it can see the same measurement state across all the interfaces. A semaphore cannot be used because the kernel cannot return to user space with a lock held. Introduce the ima_measure_lock() and ima_measure_unlock() primitives, to respectively lock/unlock the interfaces (safely with the ima_measure_users counter, without holding a lock). Finally, introduce _ima_measurements_open() to lock the interface before seq_open(), and call it from ima_measurements_open() and ima_ascii_measurements_open(). And, introduce ima_measurements_release(), to unlock the interface. Require CAP_SYS_ADMIN if the interface is opened for write (not possible for the current measurements interfaces, since they only have read permission). No functional changes: multiple readers are allowed as before. Link: https://github.com/linux-integrity/linux/issues/1 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_fs.c | 102 ++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index dcdc4cb8fa0f..91bd831d070f 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -25,6 +25,10 @@ #include "ima.h" static DEFINE_MUTEX(ima_write_mutex); +static DEFINE_MUTEX(ima_measure_mutex); +static long ima_measure_users; +static struct task_struct *measure_writer; +static long measure_writer_extra_writes; bool ima_canonical_fmt; static int __init default_canonical_fmt_setup(char *str) @@ -209,16 +213,105 @@ static const struct seq_operations ima_measurments_seqops = { .show = ima_measurements_show }; +static int ima_measure_lock(bool write) +{ + mutex_lock(&ima_measure_mutex); + /* Overflow check. */ + if (!write && ima_measure_users == LONG_MAX) { + mutex_unlock(&ima_measure_mutex); + return -ENFILE; + } + + /* Same writer can do additional writes or read/writes. */ + if (write && current == measure_writer) { + measure_writer_extra_writes++; + mutex_unlock(&ima_measure_mutex); + return 0; + } + + /* + * ima_measure_users: > 0 open readers + * ima_measure_users: == -1 open writer + */ + if ((write && ima_measure_users != 0) || + (!write && ima_measure_users < 0)) { + mutex_unlock(&ima_measure_mutex); + return -EBUSY; + } + + if (write) { + ima_measure_users--; + /* Pointer valid, no reuse while the file descriptor is open. */ + measure_writer = current; + } else { + ima_measure_users++; + } + mutex_unlock(&ima_measure_mutex); + return 0; +} + +static void ima_measure_unlock(bool write) +{ + mutex_lock(&ima_measure_mutex); + /* Decrement additional writes or read/writes. */ + if (write && current == measure_writer && + measure_writer_extra_writes != 0) { + measure_writer_extra_writes--; + mutex_unlock(&ima_measure_mutex); + return; + } + if (write) { + ima_measure_users++; + measure_writer = NULL; + } else { + ima_measure_users--; + } + mutex_unlock(&ima_measure_mutex); +} + +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; + + ret = ima_measure_lock(write); + if (ret < 0) + return ret; + + ret = seq_open(file, seq_ops); + if (ret < 0) + ima_measure_unlock(write); + + 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; + + /* seq_release() always returns zero. */ + ret = seq_release(inode, file); + + ima_measure_unlock(write); + + 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) @@ -283,14 +376,15 @@ 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 ssize_t ima_read_policy(char *path) From 56275ec7667adda1eea102911f76fb822dbfebc4 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:31 +0200 Subject: [PATCH 14/19] ima: Use snprintf() in create_securityfs_measurement_lists Use the more secure snprintf() function (accepting the buffer size) in create_securityfs_measurement_lists(). No functional change: sprintf() and snprintf() have the same behavior. Link: https://github.com/linux-integrity/linux/issues/1 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_fs.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 91bd831d070f..f6ecee2d7699 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -503,11 +503,13 @@ static int __init create_securityfs_measurement_lists(void) struct dentry *dentry; if (algo == HASH_ALGO__LAST) - sprintf(file_name, "ascii_runtime_measurements_tpm_alg_%x", - ima_tpm_chip->allocated_banks[i].alg_id); + snprintf(file_name, sizeof(file_name), + "ascii_runtime_measurements_tpm_alg_%x", + ima_tpm_chip->allocated_banks[i].alg_id); else - sprintf(file_name, "ascii_runtime_measurements_%s", - hash_algo_name[algo]); + snprintf(file_name, sizeof(file_name), + "ascii_runtime_measurements_%s", + hash_algo_name[algo]); dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP, ima_dir, (void *)(uintptr_t)i, &ima_ascii_measurements_ops); @@ -515,11 +517,13 @@ static int __init create_securityfs_measurement_lists(void) return PTR_ERR(dentry); if (algo == HASH_ALGO__LAST) - sprintf(file_name, "binary_runtime_measurements_tpm_alg_%x", - ima_tpm_chip->allocated_banks[i].alg_id); + snprintf(file_name, sizeof(file_name), + "binary_runtime_measurements_tpm_alg_%x", + ima_tpm_chip->allocated_banks[i].alg_id); else - sprintf(file_name, "binary_runtime_measurements_%s", - hash_algo_name[algo]); + snprintf(file_name, sizeof(file_name), + "binary_runtime_measurements_%s", + hash_algo_name[algo]); dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP, ima_dir, (void *)(uintptr_t)i, &ima_measurements_ops); From 292bc492f3d31ffd858600a331d599f1956bf612 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:32 +0200 Subject: [PATCH 15/19] ima: Introduce ima_dump_measurement() Introduce ima_dump_measurement() to simplify the code of ima_dump_measurement_list() and to avoid repeating the ima_dump_measurement() code block if iteration occurs on multiple lists. No functional change: only code moved to a separate function. Link: https://github.com/linux-integrity/linux/issues/1 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_kexec.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c index 8dc9459622b3..26d41974429e 100644 --- a/security/integrity/ima/ima_kexec.c +++ b/security/integrity/ima/ima_kexec.c @@ -80,6 +80,17 @@ static int ima_alloc_kexec_file_buf(size_t segment_size) return 0; } +static int ima_dump_measurement(struct ima_kexec_hdr *khdr, + struct ima_queue_entry *qe) +{ + if (ima_kexec_file.count >= ima_kexec_file.size) + return -EINVAL; + + khdr->count++; + ima_measurements_show(&ima_kexec_file, qe); + return 0; +} + static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer, unsigned long segment_size) { @@ -97,13 +108,9 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer, 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) { - if (ima_kexec_file.count < ima_kexec_file.size) { - khdr.count++; - ima_measurements_show(&ima_kexec_file, qe); - } else { - ret = -EINVAL; + ret = ima_dump_measurement(&khdr, qe); + if (ret < 0) break; - } } /* From e9b491e27bf6b9401e2e521955787a7a6e2bf808 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:33 +0200 Subject: [PATCH 16/19] ima: Add support for staging measurements with prompt Introduce the ability of staging the IMA measurement list and deleting them with a prompt. Staging means moving the current measurement list records to a separate location, and allowing users to read and delete it. This causes the current measurement list to be emptied (since records were moved) and new measurements to be added on the empty list. Staging can be done only once at a time. In the event of kexec(), staging is aborted and staged records will be carried over to the new kernel. Introduce ascii_runtime_measurements__staged and binary_runtime_measurements__staged interfaces to access and delete the measurements. Use 'echo A > ' and 'echo D > ' 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_staged_delete_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. Staging is forbidden after measurement is suspended, and between staging and deleting, so that walking the staged and current measurements list can be done locklessly in ima_dump_measurement_list(). Strict ordering of suspending and dumping is enforced by two reboot notifiers with different priority. Refusing to delete staged measurements also signals to user space that those measurements are already carried over to the secondary kernel, so that it does not save them twice. 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 records 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 records included in the kexec critical data records refers to the records since the critical data records generated from the previous kexec event. 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 (staging revert) Signed-off-by: Roberto Sassu Tested-by: Stefan Berger Signed-off-by: Mimi Zohar --- security/integrity/ima/Kconfig | 12 ++ security/integrity/ima/ima.h | 7 +- security/integrity/ima/ima_fs.c | 174 ++++++++++++++++++++++++++--- security/integrity/ima/ima_kexec.c | 20 +++- security/integrity/ima/ima_queue.c | 140 ++++++++++++++++++++++- 5 files changed, 333 insertions(+), 20 deletions(-) diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 862fbee2b174..02436670f746 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_STAGING + bool "Support for staging the measurements list" + default n + 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 aborted and any staged measurement records are + copied to the secondary kernel. + endif diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index c00c133a140f..3892d2a6c2e2 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 { @@ -315,6 +318,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); diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index f6ecee2d7699..96d7503a605b 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; @@ -99,6 +106,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) { @@ -120,6 +132,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) { } @@ -213,6 +231,13 @@ static const struct seq_operations ima_measurments_seqops = { .show = ima_measurements_show }; +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_measure_lock(bool write) { mutex_lock(&ima_measure_mutex); @@ -307,6 +332,60 @@ static int ima_measurements_release(struct inode *inode, struct file *file) return ret; } +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_write(struct file *file, + const char __user *buf, size_t datalen, + loff_t *ppos, bool staged_interface) +{ + char req[STAGED_REQ_LENGTH]; + int ret; + + if (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 || !staged_interface) + return -EINVAL; + + ret = ima_queue_stage(); + break; + case 'D': + if (datalen != 2 || !staged_interface) + return -EINVAL; + + ret = ima_queue_staged_delete_all(); + break; + default: + ret = -EINVAL; + } + + if (ret < 0) + return ret; + + return datalen; +} + +static ssize_t ima_measurements_staged_write(struct file *file, + const char __user *buf, + size_t datalen, loff_t *ppos) +{ + return _ima_measurements_write(file, buf, datalen, ppos, true); +} + static const struct file_operations ima_measurements_ops = { .open = ima_measurements_open, .read = seq_read, @@ -314,6 +393,14 @@ static const struct file_operations ima_measurements_ops = { .release = ima_measurements_release, }; +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; @@ -387,6 +474,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; @@ -490,10 +599,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; + umode_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; + permissions |= (S_IWUSR | S_IWGRP); + file_suffix = "_staged"; + } + if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip)) count++; @@ -504,29 +624,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); } @@ -534,6 +657,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 */ @@ -626,7 +766,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 26d41974429e..0d845693a1f7 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_records[BINARY]); + buf_size = ima_get_binary_runtime_size(BINARY_FULL); + len = atomic_long_read(&ima_num_records[BINARY_FULL]); n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN, "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;" @@ -106,13 +106,24 @@ 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) { + /* + * Lockless walks possible due to strict ordering of the reboot + * notifiers, suspending measurement before dump, and forbidding + * staging/deleting (list mutations) after suspend. + */ + list_for_each_entry(qe, &ima_measurements_staged, later) { ret = ima_dump_measurement(&khdr, qe); if (ret < 0) break; } + list_for_each_entry(qe, &ima_measurements, later) { + if (!ret) + ret = ima_dump_measurement(&khdr, qe); + if (ret < 0) + break; + } + /* * fill in reserved space with some buffer details * (eg. version, buffer size, number of measurements) @@ -167,6 +178,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 618694d5c082..cdc21e1b929b 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 @@ -42,7 +43,7 @@ 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. */ @@ -171,12 +172,16 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, lockdep_is_held(&ima_extend_list_mutex)); atomic_long_inc(&ima_num_records[BINARY]); + atomic_long_inc(&ima_num_records[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; } @@ -277,6 +282,139 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation, return result; } +/** + * ima_queue_stage - Stage all measurements + * + * If the staged measurements list is empty, the current measurements list is + * not empty, and measurement is not suspended, move the measurements from the + * current list to the staged one, and update the number of records and binary + * run-time size accordingly. + * + * Do not allow staging after measurement is suspended, so that dumping + * measurements can be done in a lockless way. + * + * Return: Zero on success, a negative value otherwise. + */ +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; + } + + if (ima_measurements_suspended) { + ret = -EACCES; + goto out_unlock; + } + + list_replace(&ima_measurements, &ima_measurements_staged); + INIT_LIST_HEAD(&ima_measurements); + + atomic_long_set(&ima_num_records[BINARY_STAGED], + atomic_long_read(&ima_num_records[BINARY])); + atomic_long_set(&ima_num_records[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); + +/** + * ima_queue_staged_delete_all - Delete staged measurements + * + * Move staged measurements to a temporary list, ima_measurements_trim, update + * the number of records and the binary run-time size accordingly. Finally, + * delete measurements in the temporary list. + * + * Refuse to delete staged measurements if measurement is suspended, so that + * dump can be done in a lockless way and user space is notified about staged + * measurements being carried over to the secondary kernel, so that it does not + * save them twice. + * + * Return: Zero on success, a negative value otherwise. + */ +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; + } + + if (ima_measurements_suspended) { + mutex_unlock(&ima_extend_list_mutex); + return -ESTALE; + } + + list_replace(&ima_measurements_staged, &ima_measurements_trim); + INIT_LIST_HEAD(&ima_measurements_staged); + + atomic_long_set(&ima_num_records[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; +} + +/** + * ima_queue_delete - Delete measurements + * @head: List head measurements are deleted from + * + * Delete the measurements from the passed list head completely if the + * hash table is not enabled, or partially (only the template data), if the + * hash table is used. + */ +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; From c26d9d9246cc66e3472a2bbd186152d0572d7aab Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:34 +0200 Subject: [PATCH 17/19] ima: Add support for flushing the hash table when staging measurements During staging and delete, measurements are not completely deallocated. Their entry digest portion is kept and is still reachable with the hash table to detect duplicate records. If the number of records is significant, this reduces the memory saving benefit of staging. Some users might be interested in achieving the best memory saving (the measurements are completely deallocated) at the cost of having duplicate records across the staged measurement lists. Duplicate records are still avoided within the current measurement list. Introduce the new kernel option ima_flush_htable to decide whether or not the digests of staged measurement records are flushed from the hash table, when they are deleted, to achieve the maximum memory saving. When the option is enabled, replace the old hash table with a new one, by calling ima_alloc_replace_htable(), and completely delete the measurements 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 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- .../admin-guide/kernel-parameters.txt | 6 +++ security/integrity/ima/ima_queue.c | 41 ++++++++++++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 4d0f545fb3ec..aad318803f82 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -2343,6 +2343,12 @@ Kernel parameters Use the canonical format for the binary runtime measurements, instead of host native format. + ima_flush_htable [IMA] + Flush the IMA hash table when deleting all the + staged measurement records, to achieve maximum + memory saving at the cost of having duplicate + records across the staged measurement lists. + ima_hash= [IMA] Format: { md5 | sha1 | rmd160 | sha256 | sha384 | sha512 | ... } diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index cdc21e1b929b..df1e81ea7a36 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -22,6 +22,20 @@ #define AUDIT_CAUSE_LEN_MAX 32 +static bool ima_flush_htable; + +static int __init ima_flush_htable_setup(char *str) +{ + if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) { + pr_warn("Hash table not enabled, ignoring request to flush\n"); + return 1; + } + + 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; @@ -332,7 +346,7 @@ int ima_queue_stage(void) return ret; } -static void ima_queue_delete(struct list_head *head); +static void ima_queue_delete(struct list_head *head, bool flush_htable); /** * ima_queue_staged_delete_all - Delete staged measurements @@ -350,6 +364,7 @@ static void ima_queue_delete(struct list_head *head); */ int ima_queue_staged_delete_all(void) { + struct hlist_head *old_queue = NULL; LIST_HEAD(ima_measurements_trim); mutex_lock(&ima_extend_list_mutex); @@ -371,21 +386,35 @@ int ima_queue_staged_delete_all(void) if (IS_ENABLED(CONFIG_IMA_KEXEC)) binary_runtime_size[BINARY_STAGED] = 0; + if (ima_flush_htable) { + old_queue = ima_alloc_replace_htable(); + if (IS_ERR(old_queue)) { + mutex_unlock(&ima_extend_list_mutex); + return PTR_ERR(old_queue); + } + } + mutex_unlock(&ima_extend_list_mutex); - ima_queue_delete(&ima_measurements_trim); + if (ima_flush_htable) { + synchronize_rcu(); + kfree(old_queue); + } + + ima_queue_delete(&ima_measurements_trim, ima_flush_htable); return 0; } /** * ima_queue_delete - Delete measurements * @head: List head measurements are deleted from + * @flush_htable: Whether or not the hash table is being flushed * * Delete the measurements from the passed list head completely if the - * hash table is not enabled, or partially (only the template data), if the - * hash table is used. + * hash table is not enabled or is being flushed, or partially (only the + * template data), if the hash table is used. */ -static void ima_queue_delete(struct list_head *head) +static void ima_queue_delete(struct list_head *head, bool flush_htable) { struct ima_queue_entry *qe, *qe_tmp; unsigned int i; @@ -407,7 +436,7 @@ static void ima_queue_delete(struct list_head *head) list_del(&qe->later); /* No leak if condition is false, referenced by ima_htable. */ - if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) { + if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE) || flush_htable) { kfree(qe->entry->digests); kfree(qe->entry); kfree(qe); From fcb0318a29696c13c9f8af0109855793a34371e6 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:35 +0200 Subject: [PATCH 18/19] ima: Support staging and deleting N measurements records Add support for sending a value N between 1 and ULONG_MAX to the IMA original measurement interface. This value represents the number of measurements that should be deleted from the current measurements list. In this case, measurements are staged in an internal non-user visible list, and immediately deleted. 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 records is realized by doing a lockless walk in the current measurements list to determine the N-th entry to cut, to cut the current measurements list under the lock, and by deleting the excess records after releasing the lock. Flushing the hash table is not supported for N records, since it would require removing the N records one by one from the hash table under the ima_extend_list_mutex lock, which would increase the locking time. Link: https://github.com/linux-integrity/linux/issues/1 Co-developed-by: Steven Chen Signed-off-by: Steven Chen Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/Kconfig | 3 ++ security/integrity/ima/ima.h | 2 + security/integrity/ima/ima_fs.c | 32 +++++++++++++-- security/integrity/ima/ima_queue.c | 65 +++++++++++++++++++++++++++++- 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 02436670f746..f4d25e045808 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 + records to stage internally, so that they can be immediately deleted. + On kexec, staging is aborted and any staged measurement records are copied to the secondary kernel. diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 3892d2a6c2e2..caaedd4b58fd 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -320,6 +320,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_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); @@ -342,6 +343,7 @@ extern atomic_long_t ima_num_records[BINARY__LAST]; /* Total number of violations since hard boot. */ extern atomic_long_t ima_num_violations; extern struct hlist_head __rcu *ima_htable; +extern bool ima_flush_htable; 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 96d7503a605b..174a94740da1 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 records */ #define STAGED_REQ_LENGTH 21 @@ -343,6 +344,7 @@ static ssize_t _ima_measurements_write(struct file *file, loff_t *ppos, bool staged_interface) { char req[STAGED_REQ_LENGTH]; + unsigned long req_value; int ret; if (datalen < 2 || datalen > STAGED_REQ_LENGTH) @@ -370,7 +372,24 @@ static ssize_t _ima_measurements_write(struct file *file, ret = ima_queue_staged_delete_all(); break; default: - ret = -EINVAL; + if (staged_interface) + return -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_delete_partial(req_value); } if (ret < 0) @@ -379,6 +398,12 @@ static ssize_t _ima_measurements_write(struct file *file, return datalen; } +static ssize_t ima_measurements_write(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + return _ima_measurements_write(file, buf, datalen, ppos, false); +} + static ssize_t ima_measurements_staged_write(struct file *file, const char __user *buf, size_t datalen, loff_t *ppos) @@ -389,6 +414,7 @@ static ssize_t ima_measurements_staged_write(struct file *file, static const struct file_operations ima_measurements_ops = { .open = ima_measurements_open, .read = seq_read, + .write = ima_measurements_write, .llseek = seq_lseek, .release = ima_measurements_release, }; @@ -470,6 +496,7 @@ static int ima_ascii_measurements_open(struct inode *inode, struct file *file) static const struct file_operations ima_ascii_measurements_ops = { .open = ima_ascii_measurements_open, .read = seq_read, + .write = ima_measurements_write, .llseek = seq_lseek, .release = ima_measurements_release, }; @@ -603,14 +630,13 @@ 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; - umode_t permissions = (S_IRUSR | S_IRGRP); + umode_t permissions = (S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP); 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; - permissions |= (S_IWUSR | S_IWGRP); file_suffix = "_staged"; } diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index df1e81ea7a36..f89f0ca3d4ed 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -22,7 +22,7 @@ #define AUDIT_CAUSE_LEN_MAX 32 -static bool ima_flush_htable; +bool ima_flush_htable; static int __init ima_flush_htable_setup(char *str) { @@ -405,6 +405,69 @@ int ima_queue_staged_delete_all(void) return 0; } +/** + * ima_queue_delete_partial - Delete current measurements + * @req_value: Number of measurements to delete + * + * Delete the requested number of measurements from the current measurements + * list, and update the number of records and the binary run-time size + * accordingly. + * + * Refuse to delete current measurements if measurement is suspended, so that + * dump can be done in a lockless way and user space is notified about current + * measurements being carried over to the secondary kernel, so that it does not + * save them twice. + * + * Return: Zero on success, a negative value otherwise. + */ +int ima_queue_delete_partial(unsigned long req_value) +{ + unsigned long req_value_copy = req_value; + unsigned long size_to_remove = 0, num_to_remove = 0; + LIST_HEAD(ima_measurements_trim); + struct ima_queue_entry *qe; + int ret = 0; + + /* + * list_for_each_entry_rcu() without rcu_read_lock() is fine because + * only list append can happen concurrently. No list replace due to the + * staging/delete writers mutual exclusion. + */ + list_for_each_entry_rcu(qe, &ima_measurements, later, true) { + size_to_remove += get_binary_runtime_size(qe->entry); + num_to_remove++; + + if (--req_value_copy == 0) + break; + } + + /* Not enough records to delete. */ + if (req_value_copy > 0) + return -ENOENT; + + mutex_lock(&ima_extend_list_mutex); + if (ima_measurements_suspended) { + mutex_unlock(&ima_extend_list_mutex); + return -ESTALE; + } + + /* + * qe remains valid because ima_fs.c enforces single-writer exclusion. + */ + __list_cut_position(&ima_measurements_trim, &ima_measurements, + &qe->later); + + atomic_long_sub(num_to_remove, &ima_num_records[BINARY]); + + if (IS_ENABLED(CONFIG_IMA_KEXEC)) + binary_runtime_size[BINARY] -= size_to_remove; + + mutex_unlock(&ima_extend_list_mutex); + + ima_queue_delete(&ima_measurements_trim, false); + return ret; +} + /** * ima_queue_delete - Delete measurements * @head: List head measurements are deleted from From 35d6f5e788dae0dcc4c42d1280360f19aef9ab52 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 5 Jun 2026 19:22:36 +0200 Subject: [PATCH 19/19] doc: security: Add documentation of exporting and deleting IMA measurements Add the documentation of exporting and deleting IMA measurements in Documentation/security/IMA-export-delete.rst. Also add the missing Documentation/security/IMA-templates.rst file in MAINTAINERS. Link: https://github.com/linux-integrity/linux/issues/1 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- Documentation/security/IMA-export-delete.rst | 203 +++++++++++++++++++ Documentation/security/index.rst | 1 + MAINTAINERS | 2 + 3 files changed, 206 insertions(+) create mode 100644 Documentation/security/IMA-export-delete.rst diff --git a/Documentation/security/IMA-export-delete.rst b/Documentation/security/IMA-export-delete.rst new file mode 100644 index 000000000000..1600ead03b03 --- /dev/null +++ b/Documentation/security/IMA-export-delete.rst @@ -0,0 +1,203 @@ +.. SPDX-License-Identifier: GPL-2.0 + +================================== +IMA Measurements Export and Delete +================================== + + +Introduction +============ + +The IMA measurements list is currently stored in the kernel memory. Memory +occupation grows linearly with the number of records, and can become a +problem especially in environments with reduced resources. + +While there is an advantage in keeping the IMA measurements list in kernel +memory, so that it is always available for reading from the securityfs +interfaces, storing it elsewhere would make it possible to free precious +memory for other kernel usage. + +The IMA measurements list needs to be retained and safely stored for new +attestation servers to validate it. Assuming the IMA measurements list is +properly saved, storing it outside the kernel does not introduce security +issues, since its integrity is anyway protected by the TPM. + +Hence, the new IMA staging mechanism is introduced to export IMA +measurements to user space and delete them from kernel space. + +Staging consists in atomically moving the current measurements list to a +temporary list, so that measurements can be deleted afterwards. The staging +operation locks the hot path (racing with addition of new measurements) for +a very short time, only for swapping the list pointers. Deletion of the +measurements instead is done locklessly, away from the hot path. + +There are two flavors of the staging mechanism. In the staging with prompt, +all current measurements are staged, read and deleted upon confirmation. In +the staging and deleting flavor, N measurements are staged from the +beginning of the current measurements list and immediately deleted without +confirmation. + + +Management of Staged Measurements +================================= + +Since with the staging mechanism measurement records are removed from the +kernel, the staged measurements need to be saved in a storage and +concatenated together, so that they can be presented during remote +attestation as if staging was never done. This task can be accomplished by +a remote attestation agent modified to support staging, or a system +service. + +Coordination is necessary in the case where there are multiple actors +requesting measurements to be staged. + +In the staging with prompt case, the measurement interfaces can be accessed +only by one actor (writer) at a time, so the others will get an error until +the former closes it. Since the actors don't care about N, when they gain +access to the interface, they will get all the staged measurements at the +time of their request. + +In the case of staging and deleting, coordination is more important, since +there is the risk that two actors unaware of each other compute the value N +on the current measurements list and request IMA to stage N twice. + + +Remote Attestation Agent Workflow +================================= + +Remote attestation agents can be configured to always present all the +measurements to the remote verifiers or, alternatively, to only provide the +measurements that have not been verified yet by the remote verifiers. + +In the latter case, determining which measurements need to be sent and +verified must solely depend on the remote verifier. The remote attestation +agent can proactively send partial measurements, at the condition that they +are the ones that the remote verifier needs. + +An agent can rely on one of the supported staging methods to proactively +send to a remote verifier the measurements since the previous request up +to the ones that verify the TPM quote obtained in the current request. +The workflow with each staging method is the following. + +With staging with prompt, the agent stages the current measurements list, +reads and stores the measurements in a storage and immediately requests +IMA to delete the staged measurements from kernel memory. Afterwards, it +calculates N by replaying the PCR extend on the stored measurements until +the calculated PCRs match the quoted PCRs. It then keeps the measurements +in excess for the next attestation request. + +At the next attestation request, the agent performs the same steps above, +and concatenates the new measurements to the ones in excess from the +previous request. Also in this case, the agent replays the PCR extend until +it matches the currently quoted PCRs, keeps the measurements in excess and +presents the new N measurement records to the remote attestation server. + +With the staging and deleting method, the agent reads the current +measurements list, calculates N and requests IMA to delete only those. The +measurements in excess are kept in the IMA measurements list and can be +retrieved at the next remote attestation request. + +While keeping only the excess measurements in the storage could be +sufficient to serve the requests of a remote verifier, it is advised to +keep all the obtained measurements locally, as they might be needed for the +attestation with a different remote verifier. + + +Usage +===== + +The IMA staging mechanism can be enabled from the kernel configuration with +the CONFIG_IMA_STAGING option. This option prevents inadvertently removing +the IMA measurement list on systems which do not properly save it. + +If the option is enabled, IMA duplicates the current securityfs +measurements interfaces (both binary and ASCII), by adding the ``_staged`` +file suffix. Both the original and the staging interfaces gain the write +permission for the root user and group, but require the process to have +CAP_SYS_ADMIN set. + +The staging mechanism supports two flavors. + + +Staging with prompt +~~~~~~~~~~~~~~~~~~~ + +The current measurements list is moved to a temporary staging area, +allowing it to be saved to external storage, before being deleted upon +confirmation. + +This staging process is achieved with the following steps. + + 1. ``echo A > <_staged interface>``: the user requests IMA to stage the + entire measurements list; + 2. ``cat <_staged interface>``: the user reads the staged measurements; + 3. ``echo D > <_staged interface>``: the user requests IMA to delete + staged measurements. + + +Staging and deleting +~~~~~~~~~~~~~~~~~~~~ + +N measurements are staged to a temporary staging area, and immediately +deleted without further confirmation. + +This staging process is achieved with the following steps. + + 1. ``cat ``: the user reads the current measurements + list and determines what the value N for staging should be; + 2. ``echo N > ``: the user requests IMA to delete N + measurements from the current measurements list. + + +Interface Access +================ + +In order to avoid the IMA measurements list being suddenly truncated by the +staging mechanism during a read, or having multiple concurrent staging, a +semaphore-like locking scheme has been implemented on all the measurements +list interfaces. + +Multiple readers can access concurrently the original and staged +interfaces, and they can be in mutual exclusion with one writer. In order +to see the same state across all the measurement interfaces, the same +writer is allowed to open multiple interfaces for write or read/write. + +If an illegal access occurs, the open to the measurements list interface is +denied. + + +Kexec +===== + +In the event a kexec() system call occurs between staging and deleting, the +staged measurement records are marshalled before the current measurements +list, so that they are both available when the secondary kernel starts. + +If measurement is suspended before requesting to delete staged or current +measurements, IMA returns an error to user space to let it know that +marshalling is already in progress, so that it does not save the +measurements twice. + +IMA also disallows staging when suspending measurement, to avoid the +situation where neither measurements are carried over to the secondary +kernel, nor they are saved by user space to the storage. + + +Hash table +========== + +By default, the template digest of staged measurement records are kept in +kernel memory (only template data are freed), to be able to detect +duplicate records independently of staging. + +The new kernel option ``ima_flush_htable`` has been introduced to +explicitly request a complete deletion of the staged measurements, for +maximum kernel memory saving. If the option has been specified, duplicate +records are still avoided on records of the current measurements list, +but there can be duplicates between different groups of staged +measurements. + +Flushing the hash table is supported only for the staging with prompt +flavor. For the staging and deleting flavor, it would have been necessary +to lock the hot path adding new measurements for the time needed to remove +each selected measurement individually. diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst index 3e0a7114a862..00650dcf38cb 100644 --- a/Documentation/security/index.rst +++ b/Documentation/security/index.rst @@ -8,6 +8,7 @@ Security Documentation credentials snp-tdx-threat-model IMA-templates + IMA-export-delete keys/index lsm lsm-development diff --git a/MAINTAINERS b/MAINTAINERS index 2fb1c75afd16..2bbfd7bbbab3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12740,6 +12740,8 @@ R: Eric Snowberg L: linux-integrity@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git +F: Documentation/security/IMA-export-delete.rst +F: Documentation/security/IMA-templates.rst F: include/linux/secure_boot.h F: security/integrity/ F: security/integrity/ima/