| /* |
| * Copyright (C) 2005,2006,2007,2008 IBM Corporation |
| * |
| * Authors: |
| * Kylene Hall <kjhall@us.ibm.com> |
| * Reiner Sailer <sailer@us.ibm.com> |
| * Mimi Zohar <zohar@us.ibm.com> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation, version 2 of the |
| * License. |
| * |
| * File: ima_fs.c |
| * implemenents security file system for reporting |
| * current measurement list and IMA statistics |
| */ |
| #include <linux/module.h> |
| #include <linux/seq_file.h> |
| #include <linux/rculist.h> |
| #include <linux/rcupdate.h> |
| #include <linux/parser.h> |
| |
| #include "ima.h" |
| |
| static int valid_policy = 1; |
| #define TMPBUFLEN 12 |
| static ssize_t ima_show_htable_value(char __user *buf, size_t count, |
| loff_t *ppos, atomic_long_t *val) |
| { |
| char tmpbuf[TMPBUFLEN]; |
| ssize_t len; |
| |
| len = scnprintf(tmpbuf, TMPBUFLEN, "%li\n", atomic_long_read(val)); |
| return simple_read_from_buffer(buf, count, ppos, tmpbuf, len); |
| } |
| |
| static ssize_t ima_show_htable_violations(struct file *filp, |
| char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| return ima_show_htable_value(buf, count, ppos, &ima_htable.violations); |
| } |
| |
| static struct file_operations ima_htable_violations_ops = { |
| .read = ima_show_htable_violations |
| }; |
| |
| static ssize_t ima_show_measurements_count(struct file *filp, |
| char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| return ima_show_htable_value(buf, count, ppos, &ima_htable.len); |
| |
| } |
| |
| static struct file_operations ima_measurements_count_ops = { |
| .read = ima_show_measurements_count |
| }; |
| |
| /* returns pointer to hlist_node */ |
| static void *ima_measurements_start(struct seq_file *m, loff_t *pos) |
| { |
| loff_t l = *pos; |
| struct ima_queue_entry *qe; |
| |
| /* we need a lock since pos could point beyond last element */ |
| rcu_read_lock(); |
| list_for_each_entry_rcu(qe, &ima_measurements, later) { |
| if (!l--) { |
| rcu_read_unlock(); |
| return qe; |
| } |
| } |
| rcu_read_unlock(); |
| return NULL; |
| } |
| |
| static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos) |
| { |
| struct ima_queue_entry *qe = v; |
| |
| /* lock protects when reading beyond last element |
| * against concurrent list-extension |
| */ |
| rcu_read_lock(); |
| qe = list_entry_rcu(qe->later.next, |
| struct ima_queue_entry, later); |
| rcu_read_unlock(); |
| (*pos)++; |
| |
| return (&qe->later == &ima_measurements) ? NULL : qe; |
| } |
| |
| static void ima_measurements_stop(struct seq_file *m, void *v) |
| { |
| } |
| |
| static void ima_putc(struct seq_file *m, void *data, int datalen) |
| { |
| while (datalen--) |
| seq_putc(m, *(char *)data++); |
| } |
| |
| /* print format: |
| * 32bit-le=pcr# |
| * char[20]=template digest |
| * 32bit-le=template name size |
| * char[n]=template name |
| * eventdata[n]=template specific data |
| */ |
| static int ima_measurements_show(struct seq_file *m, void *v) |
| { |
| /* the list never shrinks, so we don't need a lock here */ |
| struct ima_queue_entry *qe = v; |
| struct ima_template_entry *e; |
| int namelen; |
| u32 pcr = CONFIG_IMA_MEASURE_PCR_IDX; |
| |
| /* get entry */ |
| e = qe->entry; |
| if (e == NULL) |
| return -1; |
| |
| /* |
| * 1st: PCRIndex |
| * PCR used is always the same (config option) in |
| * little-endian format |
| */ |
| ima_putc(m, &pcr, sizeof pcr); |
| |
| /* 2nd: template digest */ |
| ima_putc(m, e->digest, IMA_DIGEST_SIZE); |
| |
| /* 3rd: template name size */ |
| namelen = strlen(e->template_name); |
| ima_putc(m, &namelen, sizeof namelen); |
| |
| /* 4th: template name */ |
| ima_putc(m, (void *)e->template_name, namelen); |
| |
| /* 5th: template specific data */ |
| ima_template_show(m, (struct ima_template_data *)&e->template, |
| IMA_SHOW_BINARY); |
| return 0; |
| } |
| |
| static struct seq_operations ima_measurments_seqops = { |
| .start = ima_measurements_start, |
| .next = ima_measurements_next, |
| .stop = ima_measurements_stop, |
| .show = ima_measurements_show |
| }; |
| |
| static int ima_measurements_open(struct inode *inode, struct file *file) |
| { |
| return seq_open(file, &ima_measurments_seqops); |
| } |
| |
| static struct file_operations ima_measurements_ops = { |
| .open = ima_measurements_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release, |
| }; |
| |
| static void ima_print_digest(struct seq_file *m, u8 *digest) |
| { |
| int i; |
| |
| for (i = 0; i < IMA_DIGEST_SIZE; i++) |
| seq_printf(m, "%02x", *(digest + i)); |
| } |
| |
| void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show) |
| { |
| struct ima_template_data *entry = e; |
| int namelen; |
| |
| switch (show) { |
| case IMA_SHOW_ASCII: |
| ima_print_digest(m, entry->digest); |
| seq_printf(m, " %s\n", entry->file_name); |
| break; |
| case IMA_SHOW_BINARY: |
| ima_putc(m, entry->digest, IMA_DIGEST_SIZE); |
| |
| namelen = strlen(entry->file_name); |
| ima_putc(m, &namelen, sizeof namelen); |
| ima_putc(m, entry->file_name, namelen); |
| default: |
| break; |
| } |
| } |
| |
| /* print in ascii */ |
| static int ima_ascii_measurements_show(struct seq_file *m, void *v) |
| { |
| /* the list never shrinks, so we don't need a lock here */ |
| struct ima_queue_entry *qe = v; |
| struct ima_template_entry *e; |
| |
| /* get entry */ |
| e = qe->entry; |
| if (e == NULL) |
| return -1; |
| |
| /* 1st: PCR used (config option) */ |
| seq_printf(m, "%2d ", CONFIG_IMA_MEASURE_PCR_IDX); |
| |
| /* 2nd: SHA1 template hash */ |
| ima_print_digest(m, e->digest); |
| |
| /* 3th: template name */ |
| seq_printf(m, " %s ", e->template_name); |
| |
| /* 4th: template specific data */ |
| ima_template_show(m, (struct ima_template_data *)&e->template, |
| IMA_SHOW_ASCII); |
| return 0; |
| } |
| |
| static struct seq_operations ima_ascii_measurements_seqops = { |
| .start = ima_measurements_start, |
| .next = ima_measurements_next, |
| .stop = ima_measurements_stop, |
| .show = ima_ascii_measurements_show |
| }; |
| |
| static int ima_ascii_measurements_open(struct inode *inode, struct file *file) |
| { |
| return seq_open(file, &ima_ascii_measurements_seqops); |
| } |
| |
| static struct file_operations ima_ascii_measurements_ops = { |
| .open = ima_ascii_measurements_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = seq_release, |
| }; |
| |
| static ssize_t ima_write_policy(struct file *file, const char __user *buf, |
| size_t datalen, loff_t *ppos) |
| { |
| char *data; |
| int rc; |
| |
| if (datalen >= PAGE_SIZE) |
| return -ENOMEM; |
| if (*ppos != 0) { |
| /* No partial writes. */ |
| return -EINVAL; |
| } |
| data = kmalloc(datalen + 1, GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| if (copy_from_user(data, buf, datalen)) { |
| kfree(data); |
| return -EFAULT; |
| } |
| *(data + datalen) = '\0'; |
| rc = ima_parse_add_rule(data); |
| if (rc < 0) { |
| datalen = -EINVAL; |
| valid_policy = 0; |
| } |
| |
| kfree(data); |
| return datalen; |
| } |
| |
| static struct dentry *ima_dir; |
| static struct dentry *binary_runtime_measurements; |
| static struct dentry *ascii_runtime_measurements; |
| static struct dentry *runtime_measurements_count; |
| static struct dentry *violations; |
| static struct dentry *ima_policy; |
| |
| static atomic_t policy_opencount = ATOMIC_INIT(1); |
| /* |
| * ima_open_policy: sequentialize access to the policy file |
| */ |
| int ima_open_policy(struct inode * inode, struct file * filp) |
| { |
| if (atomic_dec_and_test(&policy_opencount)) |
| return 0; |
| return -EBUSY; |
| } |
| |
| /* |
| * ima_release_policy - start using the new measure policy rules. |
| * |
| * Initially, ima_measure points to the default policy rules, now |
| * point to the new policy rules, and remove the securityfs policy file, |
| * assuming a valid policy. |
| */ |
| static int ima_release_policy(struct inode *inode, struct file *file) |
| { |
| if (!valid_policy) { |
| ima_delete_rules(); |
| valid_policy = 1; |
| atomic_set(&policy_opencount, 1); |
| return 0; |
| } |
| ima_update_policy(); |
| securityfs_remove(ima_policy); |
| ima_policy = NULL; |
| return 0; |
| } |
| |
| static struct file_operations ima_measure_policy_ops = { |
| .open = ima_open_policy, |
| .write = ima_write_policy, |
| .release = ima_release_policy |
| }; |
| |
| int ima_fs_init(void) |
| { |
| ima_dir = securityfs_create_dir("ima", NULL); |
| if (IS_ERR(ima_dir)) |
| return -1; |
| |
| binary_runtime_measurements = |
| securityfs_create_file("binary_runtime_measurements", |
| S_IRUSR | S_IRGRP, ima_dir, NULL, |
| &ima_measurements_ops); |
| if (IS_ERR(binary_runtime_measurements)) |
| goto out; |
| |
| ascii_runtime_measurements = |
| securityfs_create_file("ascii_runtime_measurements", |
| S_IRUSR | S_IRGRP, ima_dir, NULL, |
| &ima_ascii_measurements_ops); |
| if (IS_ERR(ascii_runtime_measurements)) |
| goto out; |
| |
| runtime_measurements_count = |
| securityfs_create_file("runtime_measurements_count", |
| S_IRUSR | S_IRGRP, ima_dir, NULL, |
| &ima_measurements_count_ops); |
| if (IS_ERR(runtime_measurements_count)) |
| goto out; |
| |
| violations = |
| securityfs_create_file("violations", S_IRUSR | S_IRGRP, |
| ima_dir, NULL, &ima_htable_violations_ops); |
| if (IS_ERR(violations)) |
| goto out; |
| |
| ima_policy = securityfs_create_file("policy", |
| S_IRUSR | S_IRGRP | S_IWUSR, |
| ima_dir, NULL, |
| &ima_measure_policy_ops); |
| if (IS_ERR(ima_policy)) |
| goto out; |
| |
| return 0; |
| out: |
| securityfs_remove(runtime_measurements_count); |
| securityfs_remove(ascii_runtime_measurements); |
| securityfs_remove(binary_runtime_measurements); |
| securityfs_remove(ima_dir); |
| securityfs_remove(ima_policy); |
| return -1; |
| } |
| |
| void __exit ima_fs_cleanup(void) |
| { |
| securityfs_remove(violations); |
| securityfs_remove(runtime_measurements_count); |
| securityfs_remove(ascii_runtime_measurements); |
| securityfs_remove(binary_runtime_measurements); |
| securityfs_remove(ima_dir); |
| securityfs_remove(ima_policy); |
| } |