Some systems can end up carrying lots of entries in the ima measurement list. Since every entry is using a bit of kernel memory, allow the sysadmin to export the measurement list to the filesystem to free up some memory. Signed-off-by: Janne Karhunen <janne.karhunen@xxxxxxxxx> --- security/integrity/ima/ima.h | 7 +- security/integrity/ima/ima_fs.c | 171 +++++++++++++++++++++++++++++ security/integrity/ima/ima_queue.c | 2 +- 3 files changed, 175 insertions(+), 5 deletions(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 19769bf5f6ab..78f0b706848d 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -151,20 +151,19 @@ int template_desc_init_fields(const char *template_fmt, struct ima_template_desc *ima_template_desc_current(void); struct ima_template_desc *lookup_template_desc(const char *name); bool ima_template_has_modsig(const struct ima_template_desc *ima_template); +void ima_free_template_entry(struct ima_template_entry *entry); 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); unsigned long ima_get_binary_runtime_size(void); int ima_init_template(void); void ima_init_template_list(void); +int ima_export_list(const char *from); int __init ima_init_digests(void); int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event, void *lsm_data); -/* - * used to protect h_table and sha_table - */ -extern spinlock_t ima_queue_lock; +extern struct mutex ima_extend_list_mutex; struct ima_h_table { atomic_long_t len; /* number of stored measurements in the list */ diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 2000e8df0301..b60a241b0d8b 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -22,10 +22,17 @@ #include <linux/rcupdate.h> #include <linux/parser.h> #include <linux/vmalloc.h> +#include <linux/fs_struct.h> +#include <linux/syscalls.h> #include "ima.h" +#define secfs_mnt "/sys/kernel/security" +#define am_filename "/integrity/ima/ascii_runtime_measurements" + static DEFINE_MUTEX(ima_write_mutex); +static DEFINE_MUTEX(ima_list_mutex); +static char *ima_msmt_list_name; bool ima_canonical_fmt; static int __init default_canonical_fmt_setup(char *str) @@ -362,6 +369,7 @@ static struct dentry *ascii_runtime_measurements; static struct dentry *runtime_measurements_count; static struct dentry *violations; static struct dentry *ima_policy; +static struct dentry *ima_list_name; enum ima_fs_flags { IMA_FS_BUSY, @@ -449,6 +457,162 @@ static const struct file_operations ima_measure_policy_ops = { .llseek = generic_file_llseek, }; +static void ima_free_list(void) +{ + struct ima_queue_entry *qe, *e; + + list_for_each_entry_safe(qe, e, &ima_measurements, later) { + hlist_del_rcu(&qe->hnext); + atomic_long_dec(&ima_htable.len); + + list_del_rcu(&qe->later); + ima_free_template_entry(qe->entry); + kfree(qe); + } +} + +static int ima_unlink_file(const char *filename) +{ + struct filename *file; + + file = getname_kernel(filename); + if (IS_ERR(file)) + return -EINVAL; + + return do_unlinkat(AT_FDCWD, file); +} + +int ima_export_list(const char *from) +{ + static bool init_export = true; + + struct file *file_out = NULL; + struct file *file_in = NULL; + const char *to = ima_msmt_list_name; + ssize_t bytesin, bytesout; + mm_segment_t fs; + struct path root; + loff_t offin = 0, offout = 0; + char data[512]; + int err = 0; + + if (to == NULL) + goto out_err; + if (from == NULL) + from = secfs_mnt am_filename; + + pr_info("exporting msmt list to %s\n", to); + fs = get_fs(); + set_fs(KERNEL_DS); + + if (init_export) { + ima_unlink_file(to); + init_export = false; + } + /* + * Use the root of the init task.. + */ + task_lock(&init_task); + get_fs_root(init_task.fs, &root); + task_unlock(&init_task); + + file_out = file_open_root(root.dentry, root.mnt, to, + O_CREAT|O_WRONLY|O_APPEND|O_NOFOLLOW, + 0600); + if (IS_ERR(file_out)) { + err = PTR_ERR(file_out); + pr_err("failed to open %s, err %d\n", to, err); + file_out = NULL; + goto out_close; + } + file_in = file_open_root(root.dentry, root.mnt, from, O_RDONLY, 0); + if (IS_ERR(file_in)) { + err = PTR_ERR(file_in); + pr_err("failed to open %s, err %d\n", from, err); + file_in = NULL; + goto out_close; + } + mutex_lock(&ima_extend_list_mutex); + do { + bytesin = vfs_read(file_in, data, 512, &offin); + if (bytesin < 0) { + pr_err("read error at %lld\n", offin); + err = -EIO; + goto out_unlock; + } + bytesout = vfs_write(file_out, data, bytesin, &offout); + if (bytesin != bytesout) { + /* + * If we fail writing, don't free the list and allow + * a retry later on. + */ + pr_err("write error at %lld\n", offout); + err = -EIO; + goto out_unlock; + } + } while (bytesin == 512); + ima_free_list(); + +out_unlock: + mutex_unlock(&ima_extend_list_mutex); +out_close: + if (file_in) + filp_close(file_in, NULL); + if (file_out) + filp_close(file_out, NULL); + + path_put(&root); + set_fs(fs); +out_err: + return err; +} + +static ssize_t ima_write_list_name(struct file *filp, + const char __user *buf, + size_t count, loff_t *ppos) +{ + int err; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if ((count <= 1) || (count >= 255)) + return -EINVAL; + + if (*buf != '/') + return -EINVAL; + + mutex_lock(&ima_list_mutex); + kfree(ima_msmt_list_name); + + ima_msmt_list_name = kzalloc(count, GFP_KERNEL); + if (!ima_msmt_list_name) { + err = -ENOMEM; + goto out_unlock; + } + err = copy_from_user(ima_msmt_list_name, buf, count); + if (err) { + kfree(ima_msmt_list_name); + ima_msmt_list_name = NULL; + goto out_unlock; + } + if (ima_msmt_list_name[count-1] == '\n') + ima_msmt_list_name[count-1] = 0; + + err = ima_export_list(NULL); +out_unlock: + mutex_unlock(&ima_list_mutex); + if (err) { + pr_err("list export failed with %d\n", err); + return err; + } + return count; +} + +static const struct file_operations ima_list_export_ops = { + .write = ima_write_list_name, +}; + int __init ima_fs_init(void) { ima_dir = securityfs_create_dir("ima", integrity_dir); @@ -493,6 +657,11 @@ int __init ima_fs_init(void) if (IS_ERR(ima_policy)) goto out; + ima_list_name = securityfs_create_file("list_name", 0200, ima_dir, + NULL, &ima_list_export_ops); + if (IS_ERR(ima_list_name)) + goto out; + return 0; out: securityfs_remove(violations); @@ -502,5 +671,7 @@ int __init ima_fs_init(void) securityfs_remove(ima_symlink); securityfs_remove(ima_dir); securityfs_remove(ima_policy); + securityfs_remove(ima_list_name); + return -1; } diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index 1ce8b1701566..77c538ec8474 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -44,7 +44,7 @@ struct ima_h_table ima_htable = { * 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. */ -static DEFINE_MUTEX(ima_extend_list_mutex); +DEFINE_MUTEX(ima_extend_list_mutex); /* lookup up the digest value in the hash table, and return the entry */ static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value, -- 2.17.1