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


Reply via email to