* Re: [PATCH v5 09/13] ima: Add support for staging measurements with prompt [not found] ` <20260429160319.4162918-10-roberto.sassu@huaweicloud.com> @ 2026-05-04 12:51 ` Roberto Sassu 0 siblings, 0 replies; 4+ messages in thread From: Roberto Sassu @ 2026-05-04 12:51 UTC (permalink / raw) To: corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul, jmorris, serge Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module, gregorylumen, chenste, nramas, Roberto Sassu On Wed, 2026-04-29 at 18:03 +0200, Roberto Sassu wrote: > From: Roberto Sassu <roberto.sassu@huawei.com> > > Introduce the ability of staging the IMA measurement list and deleting them > with a prompt. > > Staging means moving the current content of the measurement list to a > separate location, and allowing users to read and delete it. This causes > the measurement list to be atomically truncated before new measurements can > be added. Staging can be done only once at a time. In the event of kexec(), > staging is reverted and staged entries will be carried over to the new > kernel. > > Introduce ascii_runtime_measurements_<algo>_staged and > binary_runtime_measurements_<algo>_staged interfaces to access and delete > the measurements. Also, add write permission to the original measurement > interfaces. > > Use 'echo A > <IMA original interface>' and > 'echo D > <IMA _staged interface>' to respectively stage and delete the > entire measurements list. Locking of these interfaces is also mediated with > a call to _ima_measurements_open() and with ima_measurements_release(). While doing the staging in the original interface looks more intuitive, since it is interface the user operates on, it causes loss of transaction atomicity. An agent opening the original interface has to close it, open the staged interface to read and delete the staged measurement. Other agents can open the staged interface first and do operations the original agent didn't intend to do. Will restore the previous behavior of staging/reading/deleting on the staged interface. Will keep deleting N entries on the original interface, since there is no risk of races. Roberto > 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. > > Finally, introduce the BINARY_STAGED and BINARY_FULL binary measurements > list types, to maintain the counters and the binary size of staged > measurements and the full measurements list (including entries that were > staged). BINARY still represents the current binary measurements list. > > Use the binary size for the BINARY + BINARY_STAGED types in > ima_add_kexec_buffer(), since both measurements list types are copied to > the secondary kernel during kexec. Use BINARY_FULL in > ima_measure_kexec_event(), to generate a critical data record. > > It should be noted that the BINARY_FULL counter is not passed through > kexec. Thus, the number of entries included in the kexec critical data > records refers to the entries since the previous kexec records. > > Note: This code derives from the Alt-IMA Huawei project, whose license is > GPL-2.0 OR MIT. > > Link: https://github.com/linux-integrity/linux/issues/1 > Suggested-by: Gregory Lumen <gregorylumen@linux.microsoft.com> (staging revert) > Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com> > --- > security/integrity/ima/Kconfig | 13 +++ > security/integrity/ima/ima.h | 8 +- > security/integrity/ima/ima_fs.c | 181 ++++++++++++++++++++++++++--- > security/integrity/ima/ima_kexec.c | 24 +++- > security/integrity/ima/ima_queue.c | 97 +++++++++++++++- > 5 files changed, 302 insertions(+), 21 deletions(-) > > diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig > index 862fbee2b174..48c906793efb 100644 > --- a/security/integrity/ima/Kconfig > +++ b/security/integrity/ima/Kconfig > @@ -332,4 +332,17 @@ config IMA_KEXEC_EXTRA_MEMORY_KB > If set to the default value of 0, an extra half page of memory for those > additional measurements will be allocated. > > +config IMA_STAGING > + bool "Support for staging the measurements list" > + default y > + help > + Add support for staging the measurements list. > + > + It allows user space to stage the measurements list for deletion and > + to delete the staged measurements after confirmation. > + > + On kexec, staging is reverted and staged measurements are prepended > + to the current measurements list when measurements are copied to the > + secondary kernel. > + > endif > diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h > index f8ab6b604c0d..ca8fa43ec72b 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); > @@ -335,6 +340,7 @@ extern spinlock_t ima_queue_lock; > extern atomic_long_t ima_num_entries[BINARY__LAST]; > extern atomic_long_t ima_num_violations; > extern struct hlist_head __rcu *ima_htable; > +extern struct mutex ima_extend_list_mutex; > > static inline unsigned int ima_hash_key(u8 *digest) > { > diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c > index 7709a4576322..088d5a69aa92 100644 > --- a/security/integrity/ima/ima_fs.c > +++ b/security/integrity/ima/ima_fs.c > @@ -24,6 +24,13 @@ > > #include "ima.h" > > +/* > + * Requests: > + * 'A\n': stage the entire measurements list > + * 'D\n': delete all staged measurements > + */ > +#define STAGED_REQ_LENGTH 21 > + > static DEFINE_MUTEX(ima_write_mutex); > static DEFINE_MUTEX(ima_measure_mutex); > static long ima_measure_users; > @@ -97,6 +104,11 @@ static void *ima_measurements_start(struct seq_file *m, loff_t *pos) > return _ima_measurements_start(m, pos, &ima_measurements); > } > > +static void *ima_measurements_staged_start(struct seq_file *m, loff_t *pos) > +{ > + return _ima_measurements_start(m, pos, &ima_measurements_staged); > +} > + > static void *_ima_measurements_next(struct seq_file *m, void *v, loff_t *pos, > struct list_head *head) > { > @@ -118,6 +130,12 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos) > return _ima_measurements_next(m, v, pos, &ima_measurements); > } > > +static void *ima_measurements_staged_next(struct seq_file *m, void *v, > + loff_t *pos) > +{ > + return _ima_measurements_next(m, v, pos, &ima_measurements_staged); > +} > + > static void ima_measurements_stop(struct seq_file *m, void *v) > { > } > @@ -211,6 +229,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); > @@ -276,9 +301,78 @@ 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 (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH) > + return -EINVAL; > + > + if (copy_from_user(req, buf, datalen) != 0) > + return -EFAULT; > + > + if (req[datalen - 1] != '\n') > + return -EINVAL; > + > + req[datalen - 1] = '\0'; > + > + switch (req[0]) { > + case 'A': > + if (datalen != 2 || 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_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) > +{ > + return _ima_measurements_write(file, buf, datalen, ppos, true); > +} > + > 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, > +}; > + > +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, > }; > @@ -352,6 +446,29 @@ 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, > +}; > + > +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, > }; > @@ -459,10 +576,20 @@ static const struct seq_operations ima_policy_seqops = { > }; > #endif > > -static int __init create_securityfs_measurement_lists(void) > +static int __init create_securityfs_measurement_lists(bool staging) > { > + const struct file_operations *ascii_ops = &ima_ascii_measurements_ops; > + const struct file_operations *binary_ops = &ima_measurements_ops; > + mode_t permissions = (S_IRUSR | S_IRGRP | 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; > + file_suffix = "_staged"; > + } > + > if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip)) > count++; > > @@ -473,29 +600,32 @@ static int __init create_securityfs_measurement_lists(void) > > if (algo == HASH_ALGO__LAST) > snprintf(file_name, sizeof(file_name), > - "ascii_runtime_measurements_tpm_alg_%x", > - ima_tpm_chip->allocated_banks[i].alg_id); > + "ascii_runtime_measurements_tpm_alg_%x%s", > + ima_tpm_chip->allocated_banks[i].alg_id, > + file_suffix); > else > snprintf(file_name, sizeof(file_name), > - "ascii_runtime_measurements_%s", > - hash_algo_name[algo]); > - dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP, > + "ascii_runtime_measurements_%s%s", > + hash_algo_name[algo], file_suffix); > + dentry = securityfs_create_file(file_name, permissions, > ima_dir, (void *)(uintptr_t)i, > - &ima_ascii_measurements_ops); > + ascii_ops); > if (IS_ERR(dentry)) > return PTR_ERR(dentry); > > if (algo == HASH_ALGO__LAST) > snprintf(file_name, sizeof(file_name), > - "binary_runtime_measurements_tpm_alg_%x", > - ima_tpm_chip->allocated_banks[i].alg_id); > + "binary_runtime_measurements_tpm_alg_%x%s", > + ima_tpm_chip->allocated_banks[i].alg_id, > + file_suffix); > else > snprintf(file_name, sizeof(file_name), > - "binary_runtime_measurements_%s", > - hash_algo_name[algo]); > - dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP, > + "binary_runtime_measurements_%s%s", > + hash_algo_name[algo], file_suffix); > + > + dentry = securityfs_create_file(file_name, permissions, > ima_dir, (void *)(uintptr_t)i, > - &ima_measurements_ops); > + binary_ops); > if (IS_ERR(dentry)) > return PTR_ERR(dentry); > } > @@ -503,6 +633,23 @@ static int __init create_securityfs_measurement_lists(void) > return 0; > } > > +static int __init create_securityfs_staging_links(void) > +{ > + struct dentry *dentry; > + > + dentry = securityfs_create_symlink("binary_runtime_measurements_staged", > + ima_dir, "binary_runtime_measurements_sha1_staged", NULL); > + if (IS_ERR(dentry)) > + return PTR_ERR(dentry); > + > + dentry = securityfs_create_symlink("ascii_runtime_measurements_staged", > + ima_dir, "ascii_runtime_measurements_sha1_staged", NULL); > + if (IS_ERR(dentry)) > + return PTR_ERR(dentry); > + > + return 0; > +} > + > /* > * ima_open_policy: sequentialize access to the policy file > */ > @@ -595,7 +742,13 @@ int __init ima_fs_init(void) > goto out; > } > > - ret = create_securityfs_measurement_lists(); > + ret = create_securityfs_measurement_lists(false); > + if (ret == 0 && IS_ENABLED(CONFIG_IMA_STAGING)) { > + ret = create_securityfs_measurement_lists(true); > + if (ret == 0) > + ret = create_securityfs_staging_links(); > + } > + > if (ret != 0) > goto out; > > diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c > index d7d0fb639d99..064cfce0c318 100644 > --- a/security/integrity/ima/ima_kexec.c > +++ b/security/integrity/ima/ima_kexec.c > @@ -42,8 +42,8 @@ void ima_measure_kexec_event(const char *event_name) > long len; > int n; > > - buf_size = ima_get_binary_runtime_size(BINARY); > - len = atomic_long_read(&ima_num_entries[BINARY]); > + buf_size = ima_get_binary_runtime_size(BINARY_FULL); > + len = atomic_long_read(&ima_num_entries[BINARY_FULL]); > > n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN, > "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;" > @@ -106,13 +106,28 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer, > > memset(&khdr, 0, sizeof(khdr)); > khdr.version = 1; > - /* This is an append-only list, no need to hold the RCU read lock */ > - list_for_each_entry_rcu(qe, &ima_measurements, later, true) { > + /* > + * It can race with ima_queue_stage() and ima_queue_staged_delete_all(). > + */ > + mutex_lock(&ima_extend_list_mutex); > + > + list_for_each_entry_rcu(qe, &ima_measurements_staged, later, > + lockdep_is_held(&ima_extend_list_mutex)) { > ret = ima_dump_measurement(&khdr, qe); > if (ret < 0) > break; > } > > + list_for_each_entry_rcu(qe, &ima_measurements, later, > + lockdep_is_held(&ima_extend_list_mutex)) { > + if (!ret) > + ret = ima_dump_measurement(&khdr, qe); > + if (ret < 0) > + break; > + } > + > + mutex_unlock(&ima_extend_list_mutex); > + > /* > * fill in reserved space with some buffer details > * (eg. version, buffer size, number of measurements) > @@ -167,6 +182,7 @@ void ima_add_kexec_buffer(struct kimage *image) > extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024; > > binary_runtime_size = ima_get_binary_runtime_size(BINARY) + > + ima_get_binary_runtime_size(BINARY_STAGED) + > extra_memory; > > if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE) > diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c > index b6d10dceb669..50519ed837d4 100644 > --- a/security/integrity/ima/ima_queue.c > +++ b/security/integrity/ima/ima_queue.c > @@ -26,6 +26,7 @@ > static struct tpm_digest *digests; > > LIST_HEAD(ima_measurements); /* list of all measurements */ > +LIST_HEAD(ima_measurements_staged); /* list of staged measurements */ > #ifdef CONFIG_IMA_KEXEC > static unsigned long binary_runtime_size[BINARY__LAST]; > #else > @@ -45,11 +46,11 @@ atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0); > /* key: inode (before secure-hashing a file) */ > struct hlist_head __rcu *ima_htable; > > -/* mutex protects atomicity of extending measurement list > +/* mutex protects atomicity of extending and staging measurement list > * and extending the TPM PCR aggregate. Since tpm_extend can take > * long (and the tpm driver uses a mutex), we can't use the spinlock. > */ > -static DEFINE_MUTEX(ima_extend_list_mutex); > +DEFINE_MUTEX(ima_extend_list_mutex); > > /* > * Used internally by the kernel to suspend measurements. > @@ -174,12 +175,16 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, > lockdep_is_held(&ima_extend_list_mutex)); > > atomic_long_inc(&ima_num_entries[BINARY]); > + atomic_long_inc(&ima_num_entries[BINARY_FULL]); > + > if (update_htable) { > key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest); > hlist_add_head_rcu(&qe->hnext, &htable[key]); > } > > ima_update_binary_runtime_size(entry, BINARY); > + ima_update_binary_runtime_size(entry, BINARY_FULL); > + > return 0; > } > > @@ -280,6 +285,94 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation, > return result; > } > > +int ima_queue_stage(void) > +{ > + int ret = 0; > + > + mutex_lock(&ima_extend_list_mutex); > + if (!list_empty(&ima_measurements_staged)) { > + ret = -EEXIST; > + goto out_unlock; > + } > + > + if (list_empty(&ima_measurements)) { > + ret = -ENOENT; > + goto out_unlock; > + } > + > + list_replace(&ima_measurements, &ima_measurements_staged); > + INIT_LIST_HEAD(&ima_measurements); > + > + atomic_long_set(&ima_num_entries[BINARY_STAGED], > + atomic_long_read(&ima_num_entries[BINARY])); > + atomic_long_set(&ima_num_entries[BINARY], 0); > + > + if (IS_ENABLED(CONFIG_IMA_KEXEC)) { > + binary_runtime_size[BINARY_STAGED] = > + binary_runtime_size[BINARY]; > + binary_runtime_size[BINARY] = 0; > + } > +out_unlock: > + mutex_unlock(&ima_extend_list_mutex); > + return ret; > +} > + > +static void ima_queue_delete(struct list_head *head); > + > +int ima_queue_staged_delete_all(void) > +{ > + LIST_HEAD(ima_measurements_trim); > + > + mutex_lock(&ima_extend_list_mutex); > + if (list_empty(&ima_measurements_staged)) { > + mutex_unlock(&ima_extend_list_mutex); > + return -ENOENT; > + } > + > + list_replace(&ima_measurements_staged, &ima_measurements_trim); > + INIT_LIST_HEAD(&ima_measurements_staged); > + > + atomic_long_set(&ima_num_entries[BINARY_STAGED], 0); > + > + if (IS_ENABLED(CONFIG_IMA_KEXEC)) > + binary_runtime_size[BINARY_STAGED] = 0; > + > + mutex_unlock(&ima_extend_list_mutex); > + > + ima_queue_delete(&ima_measurements_trim); > + return 0; > +} > + > +static void ima_queue_delete(struct list_head *head) > +{ > + struct ima_queue_entry *qe, *qe_tmp; > + unsigned int i; > + > + list_for_each_entry_safe(qe, qe_tmp, head, later) { > + /* > + * Safe to free template_data here without synchronize_rcu() > + * because the only htable reader, ima_lookup_digest_entry(), > + * accesses only entry->digests, not template_data. If new > + * htable readers are added that access template_data, a > + * synchronize_rcu() is required here. > + */ > + for (i = 0; i < qe->entry->template_desc->num_fields; i++) { > + kfree(qe->entry->template_data[i].data); > + qe->entry->template_data[i].data = NULL; > + qe->entry->template_data[i].len = 0; > + } > + > + list_del(&qe->later); > + > + /* No leak if condition is false, referenced by ima_htable. */ > + if (IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE)) { > + kfree(qe->entry->digests); > + kfree(qe->entry); > + kfree(qe); > + } > + } > +} > + > int ima_restore_measurement_entry(struct ima_template_entry *entry) > { > int result = 0; ^ permalink raw reply [flat|nested] 4+ messages in thread
[parent not found: <20260429160319.4162918-12-roberto.sassu@huaweicloud.com>]
* Re: [PATCH v5 11/13] ima: Support staging and deleting N measurements entries [not found] ` <20260429160319.4162918-12-roberto.sassu@huaweicloud.com> @ 2026-05-05 18:43 ` steven chen 0 siblings, 0 replies; 4+ messages in thread From: steven chen @ 2026-05-05 18:43 UTC (permalink / raw) To: Roberto Sassu, corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul, jmorris, serge Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module, gregorylumen, nramas, Roberto Sassu, steven chen On 4/29/2026 9:03 AM, Roberto Sassu wrote: > From: Roberto Sassu <roberto.sassu@huawei.com> > > Add support for sending a value N between 1 and ULONG_MAX to the 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 entries 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 entries after releasing the > lock. > > Flushing the hash table is not supported for N entries, since it would > require removing the N entries one by one from the hash table under the > ima_extend_list_mutex lock, which would increase the locking time. > > The ima_extend_list_mutex lock is necessary in ima_dump_measurement_list() > because ima_queue_delete_partial() uses __list_cut_position() to modify > ima_measurements, for which no RCU-safe variant exists. For the staging > with prompt flavor alone, list_replace_rcu() could have been used instead, > but since both flavors share the same kexec serialization path, the mutex > is required regardless. This submit provides two ways for trimming logs: Patch 9: stage and delete This patch 11: stage and delete N Both are doing the same thing in different ways I think the best way is just keep the patch 11 for following reasons: Kernel list lock time is minimum Kernel code change will be much simpler (almost half gone) User space processing for log trimming is much simpler no need to maintain two lists (old and staged) in user space No two lists seen from user space (same as before) no staged list shown Steven > Link: https://github.com/linux-integrity/linux/issues/1 > Suggested-by: Steven Chen <chenste@linux.microsoft.com> > Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com> > --- > security/integrity/ima/Kconfig | 3 +++ > security/integrity/ima/ima.h | 1 + > security/integrity/ima/ima_fs.c | 21 ++++++++++++++- > security/integrity/ima/ima_kexec.c | 3 ++- > security/integrity/ima/ima_queue.c | 43 ++++++++++++++++++++++++++++++ > 5 files changed, 69 insertions(+), 2 deletions(-) > > diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig > index 48c906793efb..4f4373859a4f 100644 > --- a/security/integrity/ima/Kconfig > +++ b/security/integrity/ima/Kconfig > @@ -341,6 +341,9 @@ config IMA_STAGING > It allows user space to stage the measurements list for deletion and > to delete the staged measurements after confirmation. > > + Or, alternatively, it allows user space to specify N measurements > + entries to stage internally, so that they can be immediately deleted. > + > On kexec, staging is reverted and staged measurements are prepended > to the current measurements list when measurements are copied to the > secondary kernel. > diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h > index 4af66c1de4dc..9a741b33d524 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); > diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c > index 088d5a69aa92..6843dc203b54 100644 > --- a/security/integrity/ima/ima_fs.c > +++ b/security/integrity/ima/ima_fs.c > @@ -28,6 +28,7 @@ > * Requests: > * 'A\n': stage the entire measurements list > * 'D\n': delete all staged measurements > + * '[1, ULONG_MAX]\n' delete N measurements entries > */ > #define STAGED_REQ_LENGTH 21 > > @@ -312,6 +313,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 (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH) > @@ -339,7 +341,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) > diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c > index 064cfce0c318..e7bde3d917b2 100644 > --- a/security/integrity/ima/ima_kexec.c > +++ b/security/integrity/ima/ima_kexec.c > @@ -107,7 +107,8 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer, > memset(&khdr, 0, sizeof(khdr)); > khdr.version = 1; > /* > - * It can race with ima_queue_stage() and ima_queue_staged_delete_all(). > + * It can race with ima_queue_stage(), ima_queue_staged_delete_all() > + * and ima_queue_delete_partial(). > */ > mutex_lock(&ima_extend_list_mutex); > > diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c > index f5c18acfbc43..64c4fe73dd5f 100644 > --- a/security/integrity/ima/ima_queue.c > +++ b/security/integrity/ima/ima_queue.c > @@ -371,6 +371,49 @@ int ima_queue_staged_delete_all(void) > return 0; > } > > +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; > + > + /* > + * Safe to walk without rcu_read_lock(): single-writer > + * exclusion in ima_fs.c prevents any concurrent modification > + * to ima_measurements during this walk. > + */ > + 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 entries to delete. */ > + if (req_value_copy > 0) > + return -ENOENT; > + > + mutex_lock(&ima_extend_list_mutex); > + /* > + * 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_entries[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; > +} > + > static void ima_queue_delete(struct list_head *head, bool flush_htable) > { > struct ima_queue_entry *qe, *qe_tmp; ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v5 00/13] ima: Introduce staging mechanism [not found] <20260429160319.4162918-1-roberto.sassu@huaweicloud.com> [not found] ` <20260429160319.4162918-10-roberto.sassu@huaweicloud.com> [not found] ` <20260429160319.4162918-12-roberto.sassu@huaweicloud.com> @ 2026-05-07 16:47 ` steven chen 2026-05-11 17:29 ` Lakshmi Ramasubramanian 2 siblings, 1 reply; 4+ messages in thread From: steven chen @ 2026-05-07 16:47 UTC (permalink / raw) To: Roberto Sassu, corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul, jmorris, serge Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module, gregorylumen, nramas, Roberto Sassu, steven chen On 4/29/2026 9:03 AM, Roberto Sassu wrote: > From: Roberto Sassu <roberto.sassu@huawei.com> > > Introduction > ============ > > The IMA measurements list is currently stored in the kernel memory. > Memory occupation grows linearly with the number of entries, 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 components. > > Storing the IMA measurements list 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 allow user space > to remove the desired portion of the measurements list from the kernel. > > > Usage > ===== > > The IMA staging mechanism can be enabled from the kernel configuration > with the CONFIG_IMA_STAGING option. > > If it is enabled, IMA duplicates the current 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, and > staged measurements are deleted upon confirmation. > > This staging process is achieved with the following steps. > > 1. echo A > <original 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 <original interface>: the user reads the current measurements > list and determines what the value N for staging should be; > 2. echo N > <original interface>: the user requests IMA to delete N > measurements from the current measurements list. This submission proposes two ways for log trimming: *Favour 1:* Staging with prompt *Favour 2:* stage and delete N Functionally, both approaches address the same problem, but *Favour 2 *is the stronger design and should be preferred. There is no good reason to keep *Favour 1.* From a kernel implementation perspective, *Favour 2 *is more efficient because it minimizes the time spent holding the list lock (can’t be shorter). It also substantially reduces the amount of kernel-side logic, removing nearly half of the code required by the alternative approach. From a user-space perspective, *Favour 2 *results in a much cleaner model. It avoids the need to track and reconcile both old and staged lists in user space as well as two lists (cur and staged) in the kernel space, which simplifies log trimming logic and reduces maintenance overhead. In addition, it preserves the existing external behavior by not exposing any staged list to user space. Overall, *Favour 2 *provides the same functional result with lower kernel complexity, shorter kernel list lock hold time, and a simpler user-space interface. For those reasons, it is the preferable approach and *Favour 1* does not appear to offer sufficient justification to keep both implementations. Steven > > Management of Staged Measurements > ================================= > > Since with the staging mechanism measurement entries are removed from > the kernel, the user needs to save the staged ones in a storage and > concatenate them together, so that it can present them to remote > attestation agents as if staging was never done. > > > Patch set content > ================= > > Patches 1-8 are preparatory patches to quickly replace the hash table, > maintain separate counters for the different measurements list types, > mediate access to the measurements list interface, and simplify the staging > patches. > > Patch 9 introduces the staging with prompt flavor. Patch 10 makes it > possible to flush the hash table when deleting all the staged measurements. > Patch 11 introduces the staging and deleting flavor. Patch 12 avoids > measurements entries to be stored twice if there is contention between the > measurements interfaces and kexec. Patch 13 adds the documentation of the > staging mechanism. > > > Changelog > ========= > > v4: > - Add write permission to the original measurement interface, and move > the A and N staging commands to that interface > - Explain better the two staging flavors and highlight that the staging > and delete only stages measurements internally > - Rename ima_queue_staged_delete_partial() to ima_queue_delete_partial() > - Replace ima_staged_measurements_prepended with per measurements list > flag to avoid copying staged and active list measurements twice > - Optimize the staging and deleting flavor by locklessly determining the > cut position in the active list, and immediately deleting entries > without explicit staging and splicing (suggested by Steven Chen) > > v3: > - Add Kconfig option to enable the staging mechanism (suggested by Mimi) > - Change the meaning of BINARY_STAGED to be just the staged measurements > - Separate the two staging flavors in two different functions: > ima_queue_staged_delete_all() for staging with prompt, > ima_queue_staged_delete_partial() for staging and deleting > - Delete N entries without staging first (suggested by Mimi) > - Avoid duplicate staged entries if there is contention between the > measurements list interfaces and kexec > > v2: > - New patch to move measurements and violation counters outside the > ima_h_table structure > - New patch to quickly replace the hash table > - Forbid partial deletion when flushing hash table (suggested by Mimi) > - Ignore ima_flush_htable if CONFIG_IMA_DISABLE_HTABLE is enabled > - BINARY_SIZE_* renamed to BINARY_* for better clarity > - Removed ima_measurements_staged_exist and testing list empty instead > - ima_queue_stage_trim() and ima_queue_delete_staged_trimmed() renamed to > ima_queue_stage() and ima_queue_delete_staged() > - New delete interval [1, ULONG_MAX - 1] > - Rename ima_measure_lock to ima_measure_mutex > - Move seq_open() and seq_release() outside the ima_measure_mutex lock > - Drop ima_measurements_staged_read() and use seq_read() instead > - Optimize create_securityfs_measurement_lists() changes > - New file name format with _staged suffix at the end of the file name > - Use _rcu list variant in ima_dump_measurement_list() > - Remove support for direct trimming and splice the remaining entries to > the active list (suggested by Mimi) > - Hot swap the hash table if flushing is requested > > v1: > - Support for direct trimming without staging > - Support unstaging on kexec (requested by Gregory Lumen) > > Roberto Sassu (13): > ima: Remove ima_h_table structure > ima: Replace static htable queue with dynamically allocated array > ima: Introduce per binary measurements list type ima_num_entries > counter > ima: Introduce per binary measurements list type binary_runtime_size > value > ima: Introduce _ima_measurements_start() and _ima_measurements_next() > ima: Mediate open/release method of the measurements list > ima: Use snprintf() in create_securityfs_measurement_lists > ima: Introduce ima_dump_measurement() > ima: Add support for staging measurements with prompt > ima: Add support for flushing the hash table when staging measurements > ima: Support staging and deleting N measurements entries > ima: Return error on deleting measurements already copied during kexec > doc: security: Add documentation of the IMA staging mechanism > > .../admin-guide/kernel-parameters.txt | 4 + > Documentation/security/IMA-staging.rst | 163 +++++++++ > Documentation/security/index.rst | 1 + > MAINTAINERS | 2 + > security/integrity/ima/Kconfig | 16 + > security/integrity/ima/ima.h | 32 +- > security/integrity/ima/ima_api.c | 2 +- > security/integrity/ima/ima_fs.c | 315 ++++++++++++++++-- > security/integrity/ima/ima_init.c | 5 + > security/integrity/ima/ima_kexec.c | 53 ++- > security/integrity/ima/ima_queue.c | 283 ++++++++++++++-- > 11 files changed, 803 insertions(+), 73 deletions(-) > create mode 100644 Documentation/security/IMA-staging.rst > ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v5 00/13] ima: Introduce staging mechanism 2026-05-07 16:47 ` [PATCH v5 00/13] ima: Introduce staging mechanism steven chen @ 2026-05-11 17:29 ` Lakshmi Ramasubramanian 0 siblings, 0 replies; 4+ messages in thread From: Lakshmi Ramasubramanian @ 2026-05-11 17:29 UTC (permalink / raw) To: steven chen, Roberto Sassu, corbet, skhan, zohar, dmitry.kasatkin, eric.snowberg, paul, jmorris, serge Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module, gregorylumen, Roberto Sassu On 5/7/2026 9:47 AM, steven chen wrote: >> >> Usage >> ===== >> >> The IMA staging mechanism can be enabled from the kernel configuration >> with the CONFIG_IMA_STAGING option. >> >> If it is enabled, IMA duplicates the current 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, and >> staged measurements are deleted upon confirmation. >> >> This staging process is achieved with the following steps. >> >> 1. echo A > <original 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 <original interface>: the user reads the current measurements >> list and determines what the value N for staging should be; >> 2. echo N > <original interface>: the user requests IMA to delete N >> measurements from the current measurements list. > > This submission proposes two ways for log trimming: > > *Flavor 1:* Staging with prompt > *Flavor 2:* stage and delete N > > Functionally, both approaches address the same problem, but *Favour 2 > *is the > stronger design and should be preferred. There is no good reason to keep > *Flavor 1.* > > From a kernel implementation perspective, *Flavor 2 *is more efficient > because it > minimizes the time spent holding the list lock (can’t be shorter). It > also substantially > reduces the amount of kernel-side logic, removing nearly half of the > code required > by the alternative approach. > > From a user-space perspective, *Flavor 2 *results in a much cleaner > model. It avoids > the need to track and reconcile both old and staged lists in user space > as well as > two lists (cur and staged) in the kernel space, which simplifies log > trimming logic > and reduces maintenance overhead. In addition, it preserves the existing > external > behavior by not exposing any staged list to user space. > > Overall, *Flavor 2 *provides the same functional result with lower > kernel complexity, > shorter kernel list lock hold time, and a simpler user-space interface. > For those > reasons, it is the preferable approach and *Favour 1* does not appear to > offer sufficient > justification to keep both implementations. > > Steven Roberto, Mimi: I want to add on to the point Steven has brought up. With "Stage and Delete N" approach, we have the following sequence of tasks for trimming the IMA log: 1. User mode locks the IMA measurement list through the "write interface". a. While this prevents any other user mode process from updating the IMA log, kernel can still add new IMA events to the measurement log 2. User mode reads the TPM Quote and the IMA measurement events and sends it to the remote attestation service 3. Once the remote service has successfully processed the IMA events, the user mode determines the number of IMA events "N" to be removed from the measurement list maintained in the kernel 4. User mode provides the value "N" to the kernel 5. Kernel now determines the point at which to snap the IMA measurement list using "N" - without holding a lock 6. Then, the kernel lock is held and the list is snapped at the point determined in the previous step thus keeping the kernel lock time to the minimum. 7. Now, user mode removes the "write" lock on the IMA measurement list With the above, we believe "Stage and Delete N" alone is sufficient to trim IMA log. -lakshmi >> .../admin-guide/kernel-parameters.txt | 4 + >> Documentation/security/IMA-staging.rst | 163 +++++++++ >> Documentation/security/index.rst | 1 + >> MAINTAINERS | 2 + >> security/integrity/ima/Kconfig | 16 + >> security/integrity/ima/ima.h | 32 +- >> security/integrity/ima/ima_api.c | 2 +- >> security/integrity/ima/ima_fs.c | 315 ++++++++++++++++-- >> security/integrity/ima/ima_init.c | 5 + >> security/integrity/ima/ima_kexec.c | 53 ++- >> security/integrity/ima/ima_queue.c | 283 ++++++++++++++-- >> 11 files changed, 803 insertions(+), 73 deletions(-) >> create mode 100644 Documentation/security/IMA-staging.rst >> ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-05-11 17:29 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260429160319.4162918-1-roberto.sassu@huaweicloud.com>
[not found] ` <20260429160319.4162918-10-roberto.sassu@huaweicloud.com>
2026-05-04 12:51 ` [PATCH v5 09/13] ima: Add support for staging measurements with prompt Roberto Sassu
[not found] ` <20260429160319.4162918-12-roberto.sassu@huaweicloud.com>
2026-05-05 18:43 ` [PATCH v5 11/13] ima: Support staging and deleting N measurements entries steven chen
2026-05-07 16:47 ` [PATCH v5 00/13] ima: Introduce staging mechanism steven chen
2026-05-11 17:29 ` Lakshmi Ramasubramanian
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox