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]> 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 -- 2.43.0

