Hi, Have in mind that below is the first trial draft that booted and seemingly accomplished the task once, it was not really tested at all yet. I will make a polished and tested version if people like the concept. Note that the code (almost) supports pushing and pulling of the entries. This variant is a simple pull given that the list size is above the defined limits. Pushing can be put in place if the recursion with the list extend_list_mutex is cleared, maybe this could be done via another patch later on when we have a workqueue for the export task? The workqueue might be the best context for the export job since clearing the list is a heavy operation (and it's not entirely correct here AFAIK, there is no rcu sync before the template free). -- Janne On Wed, Dec 18, 2019 at 2:53 PM Janne Karhunen <janne.karhunen@xxxxxxxxx> wrote: > > Some systems can end up carrying lots of entries in the ima > measurement list. Since every entry is using a bit of kernel > memory, add a new Kconfig variable to allow the sysadmin to > define the maximum measurement list size and the location > of the exported list. > > The list is written out in append mode, so the system will > keep writing new entries as long as it stays running or runs > out of space. File is also automatically truncated on startup. > > Signed-off-by: Janne Karhunen <janne.karhunen@xxxxxxxxx> > --- > security/integrity/ima/Kconfig | 10 ++ > security/integrity/ima/ima.h | 7 +- > security/integrity/ima/ima_fs.c | 178 +++++++++++++++++++++++++++++ > security/integrity/ima/ima_queue.c | 2 +- > 4 files changed, 192 insertions(+), 5 deletions(-) > > diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig > index 897bafc59a33..dbfb76c7b347 100644 > --- a/security/integrity/ima/Kconfig > +++ b/security/integrity/ima/Kconfig > @@ -310,3 +310,13 @@ config IMA_APPRAISE_SIGNED_INIT > default n > help > This option requires user-space init to be signed. > + > +config IMA_MEASUREMENT_LIST_SIZE > + int > + depends on IMA > + default 5000 > + help > + This option defines the maximum amount of entries in the > + measurement list. Once the limit is reached, the entire > + list is exported to a user defined file and the kernel > + list is freed. > diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h > index 19769bf5f6ab..cc9a8bbb723d 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(void); > 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..d938d3921c43 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[255]; > > 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,169 @@ 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; > + > + mutex_lock(&ima_extend_list_mutex); > + 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); > + } > + mutex_unlock(&ima_extend_list_mutex); > +} > + > +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(void) > +{ > + static bool export_ok = true; > + static bool init_export = true; > + > + struct file *file_out = NULL; > + struct file *file_in = NULL; > + struct path root; > + ssize_t bytesin, bytesout; > + mm_segment_t fs; > + loff_t offin = 0, offout = 0; > + char data[512]; > + long htable_len; > + int err = 0; > + > + mutex_lock(&ima_list_mutex); > + htable_len = atomic_long_read(&ima_htable.len); > + if (htable_len <= CONFIG_IMA_MEASUREMENT_LIST_SIZE) > + goto out_unlock; > + > + if (strlen(ima_msmt_list_name) == 0) { > + err = -ENOENT; > + goto out_unlock; > + } > + if (!export_ok) { > + err = -EINVAL; > + goto out_unlock; > + } > + > + fs = get_fs(); > + set_fs(KERNEL_DS); > + > + if (init_export) { > + pr_info("ima: list size (%ld/%ld) exceeded, exporting to %s\n", > + htable_len, (long)CONFIG_IMA_MEASUREMENT_LIST_SIZE, > + ima_msmt_list_name); > + > + ima_unlink_file(ima_msmt_list_name); > + 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, ima_msmt_list_name, > + O_CREAT|O_WRONLY|O_APPEND|O_NOFOLLOW|O_EXCL, > + 0400); > + if (IS_ERR(file_out)) { > + err = PTR_ERR(file_out); > + goto out; > + } > + file_in = file_open_root(root.dentry, root.mnt, secfs_mnt am_filename, > + O_RDONLY, 0); > + if (IS_ERR(file_in)) { > + err = PTR_ERR(file_in); > + goto out; > + } > + do { > + bytesin = vfs_read(file_in, data, 512, &offin); > + if (bytesin < 0) { > + pr_err("ima: read error at %lld\n", offin); > + err = -EIO; > + goto out; > + } > + bytesout = vfs_write(file_out, data, bytesin, &offout); > + if (bytesout < 0) { > + pr_err("ima: write error at %lld\n", offout); > + err = -EIO; > + goto out; > + } > + if (bytesin != bytesout) { > + /* > + * If we fail writing, the recovery is a job for the > + * admin. Keep piling things in the memory for now. > + */ > + export_ok = false; > + err = bytesout; > + goto out; > + } > + } while (bytesin == 512); > + ima_free_list(); > + > +out: > + filp_close(file_in, NULL); > + filp_close(file_out, NULL); > + > + path_put(&root); > + set_fs(fs); > + > +out_unlock: > + mutex_unlock(&ima_list_mutex); > + return err; > +} > + > +static ssize_t ima_write_list_name(struct file *filp, > + const char __user *buf, > + size_t count, loff_t *ppos) > +{ > + int err, len; > + > + if (!capable(CAP_SYS_ADMIN)) > + return -EPERM; > + > + if ((count <= 1) || (count >= 255)) > + return -EINVAL; > + > + if (*buf != '/') > + return -EINVAL; > + > + if (*ima_msmt_list_name == '/') > + goto try_export; > + > + err = copy_from_user(ima_msmt_list_name, buf, count); > + if (err) { > + memset(ima_msmt_list_name, 0, 255); > + return -EFAULT; > + } > + len = strnlen(ima_msmt_list_name, 255); > + if (ima_msmt_list_name[len-1] == '\n') > + ima_msmt_list_name[len-1] = 0; > + > +try_export: > + err = ima_export_list(); > + if (err) { > + pr_err("ima: list export failed with %d\n", err); > + return -1; > + } > + 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 +664,11 @@ int __init ima_fs_init(void) > if (IS_ERR(ima_policy)) > goto out; > > + ima_list_name = securityfs_create_file("list_name", 0600, ima_dir, > + NULL, &ima_list_export_ops); > + if (IS_ERR(ima_list_name)) > + goto out; > + > return 0; > out: > securityfs_remove(violations); > @@ -502,5 +678,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 >