On Tue, 2026-06-02 at 13:14 +0200, Roberto Sassu wrote: > From: Roberto Sassu <[email protected]> > > 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 <[email protected]>
Hi Steven I did see your tags, but since I added a Co-developed-by, probably they are redundant. If you agree, please reply with your Signed-off-by, so that the tags are complete. Thanks Roberto > Co-developed-by: Roberto Sassu <[email protected]> > Signed-off-by: Roberto Sassu <[email protected]> > --- > security/integrity/ima/Kconfig | 3 ++ > security/integrity/ima/ima.h | 1 + > security/integrity/ima/ima_fs.c | 32 +++++++++++++-- > security/integrity/ima/ima_queue.c | 63 ++++++++++++++++++++++++++++++ > 4 files changed, 96 insertions(+), 3 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 d2e740c8ff75..7a1b2d6a8b59 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 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 af0502f27d57..718991ba8bcd 100644 > --- a/security/integrity/ima/ima_queue.c > +++ b/security/integrity/ima/ima_queue.c > @@ -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

