diff options
Diffstat (limited to 'security')
| -rw-r--r-- | security/integrity/ima/Kconfig | 12 | ||||
| -rw-r--r-- | security/integrity/ima/ima.h | 7 | ||||
| -rw-r--r-- | security/integrity/ima/ima_fs.c | 174 | ||||
| -rw-r--r-- | security/integrity/ima/ima_kexec.c | 20 | ||||
| -rw-r--r-- | security/integrity/ima/ima_queue.c | 140 |
5 files changed, 333 insertions, 20 deletions
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 862fbee2b174..02436670f746 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -332,4 +332,16 @@ 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 n + 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 aborted and any staged measurement records are + copied to the secondary kernel. + endif diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index c00c133a140f..3892d2a6c2e2 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); diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index f6ecee2d7699..96d7503a605b 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; @@ -99,6 +106,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) { @@ -120,6 +132,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) { } @@ -213,6 +231,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); @@ -307,6 +332,60 @@ 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 (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_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, @@ -314,6 +393,14 @@ static const struct file_operations ima_measurements_ops = { .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, +}; + void ima_print_digest(struct seq_file *m, u8 *digest, u32 size) { u32 i; @@ -387,6 +474,28 @@ static const struct file_operations ima_ascii_measurements_ops = { .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, +}; + static ssize_t ima_read_policy(char *path) { void *data = NULL; @@ -490,10 +599,21 @@ 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; + umode_t permissions = (S_IRUSR | S_IRGRP); + 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"; + } + if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip)) count++; @@ -504,29 +624,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); } @@ -534,6 +657,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 */ @@ -626,7 +766,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 26d41974429e..0d845693a1f7 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_records[BINARY]); + buf_size = ima_get_binary_runtime_size(BINARY_FULL); + len = atomic_long_read(&ima_num_records[BINARY_FULL]); n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN, "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;" @@ -106,13 +106,24 @@ 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) { + /* + * Lockless walks possible due to strict ordering of the reboot + * notifiers, suspending measurement before dump, and forbidding + * staging/deleting (list mutations) after suspend. + */ + list_for_each_entry(qe, &ima_measurements_staged, later) { ret = ima_dump_measurement(&khdr, qe); if (ret < 0) break; } + list_for_each_entry(qe, &ima_measurements, later) { + if (!ret) + ret = ima_dump_measurement(&khdr, qe); + if (ret < 0) + break; + } + /* * fill in reserved space with some buffer details * (eg. version, buffer size, number of measurements) @@ -167,6 +178,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 618694d5c082..cdc21e1b929b 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 @@ -42,7 +43,7 @@ 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. */ @@ -171,12 +172,16 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, lockdep_is_held(&ima_extend_list_mutex)); atomic_long_inc(&ima_num_records[BINARY]); + atomic_long_inc(&ima_num_records[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; } @@ -277,6 +282,139 @@ out: return result; } +/** + * ima_queue_stage - Stage all measurements + * + * If the staged measurements list is empty, the current measurements list is + * not empty, and measurement is not suspended, move the measurements from the + * current list to the staged one, and update the number of records and binary + * run-time size accordingly. + * + * Do not allow staging after measurement is suspended, so that dumping + * measurements can be done in a lockless way. + * + * Return: Zero on success, a negative value otherwise. + */ +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; + } + + if (ima_measurements_suspended) { + ret = -EACCES; + goto out_unlock; + } + + list_replace(&ima_measurements, &ima_measurements_staged); + INIT_LIST_HEAD(&ima_measurements); + + atomic_long_set(&ima_num_records[BINARY_STAGED], + atomic_long_read(&ima_num_records[BINARY])); + atomic_long_set(&ima_num_records[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); + +/** + * ima_queue_staged_delete_all - Delete staged measurements + * + * Move staged measurements to a temporary list, ima_measurements_trim, update + * the number of records and the binary run-time size accordingly. Finally, + * delete measurements in the temporary list. + * + * Refuse to delete staged measurements if measurement is suspended, so that + * dump can be done in a lockless way and user space is notified about staged + * 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_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; + } + + if (ima_measurements_suspended) { + mutex_unlock(&ima_extend_list_mutex); + return -ESTALE; + } + + list_replace(&ima_measurements_staged, &ima_measurements_trim); + INIT_LIST_HEAD(&ima_measurements_staged); + + atomic_long_set(&ima_num_records[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; +} + +/** + * ima_queue_delete - Delete measurements + * @head: List head measurements are deleted from + * + * Delete the measurements from the passed list head completely if the + * hash table is not enabled, or partially (only the template data), if the + * hash table is used. + */ +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; |
