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