summaryrefslogtreecommitdiffstats
path: root/security/integrity/ima/ima_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/integrity/ima/ima_main.c')
-rw-r--r--security/integrity/ima/ima_main.c309
1 files changed, 139 insertions, 170 deletions
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index b85e61b..294b005 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -13,8 +13,8 @@
* License.
*
* File: ima_main.c
- * implements the IMA hooks: ima_bprm_check, ima_file_mmap,
- * and ima_path_check.
+ * implements the IMA hooks: ima_bprm_check, ima_file_mmap,
+ * and ima_file_check.
*/
#include <linux/module.h>
#include <linux/file.h>
@@ -35,50 +35,53 @@ static int __init hash_setup(char *str)
}
__setup("ima_hash=", hash_setup);
-/**
- * ima_file_free - called on __fput()
- * @file: pointer to file structure being freed
+struct ima_imbalance {
+ struct hlist_node node;
+ unsigned long fsmagic;
+};
+
+/*
+ * ima_limit_imbalance - emit one imbalance message per filesystem type
*
- * Flag files that changed, based on i_version;
- * and decrement the iint readcount/writecount.
+ * Maintain list of filesystem types that do not measure files properly.
+ * Return false if unknown, true if known.
*/
-void ima_file_free(struct file *file)
+static bool ima_limit_imbalance(struct file *file)
{
- struct inode *inode = file->f_dentry->d_inode;
- struct ima_iint_cache *iint;
-
- if (!ima_initialized || !S_ISREG(inode->i_mode))
- return;
- iint = ima_iint_find_get(inode);
- if (!iint)
- return;
-
- mutex_lock(&iint->mutex);
- if (iint->opencount <= 0) {
- printk(KERN_INFO
- "%s: %s open/free imbalance (r:%ld w:%ld o:%ld f:%ld)\n",
- __FUNCTION__, file->f_dentry->d_name.name,
- iint->readcount, iint->writecount,
- iint->opencount, atomic_long_read(&file->f_count));
- if (!(iint->flags & IMA_IINT_DUMP_STACK)) {
- dump_stack();
- iint->flags |= IMA_IINT_DUMP_STACK;
+ static DEFINE_SPINLOCK(ima_imbalance_lock);
+ static HLIST_HEAD(ima_imbalance_list);
+
+ struct super_block *sb = file->f_dentry->d_sb;
+ struct ima_imbalance *entry;
+ struct hlist_node *node;
+ bool found = false;
+
+ rcu_read_lock();
+ hlist_for_each_entry_rcu(entry, node, &ima_imbalance_list, node) {
+ if (entry->fsmagic == sb->s_magic) {
+ found = true;
+ break;
}
}
- iint->opencount--;
-
- if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
- iint->readcount--;
+ rcu_read_unlock();
+ if (found)
+ goto out;
- if (file->f_mode & FMODE_WRITE) {
- iint->writecount--;
- if (iint->writecount == 0) {
- if (iint->version != inode->i_version)
- iint->flags &= ~IMA_MEASURED;
- }
- }
- mutex_unlock(&iint->mutex);
- kref_put(&iint->refcount, iint_free);
+ entry = kmalloc(sizeof(*entry), GFP_NOFS);
+ if (!entry)
+ goto out;
+ entry->fsmagic = sb->s_magic;
+ spin_lock(&ima_imbalance_lock);
+ /*
+ * we could have raced and something else might have added this fs
+ * to the list, but we don't really care
+ */
+ hlist_add_head_rcu(&entry->node, &ima_imbalance_list);
+ spin_unlock(&ima_imbalance_lock);
+ printk(KERN_INFO "IMA: unmeasured files on fsmagic: %lX\n",
+ entry->fsmagic);
+out:
+ return found;
}
/* ima_read_write_check - reflect possible reading/writing errors in the PCR.
@@ -111,196 +114,142 @@ static void ima_read_write_check(enum iint_pcr_error error,
}
}
-static int get_path_measurement(struct ima_iint_cache *iint, struct file *file,
- const unsigned char *filename)
+/*
+ * Update the counts given an fmode_t
+ */
+static void ima_inc_counts(struct ima_iint_cache *iint, fmode_t mode)
{
- int rc = 0;
-
- iint->opencount++;
- iint->readcount++;
-
- rc = ima_collect_measurement(iint, file);
- if (!rc)
- ima_store_measurement(iint, file, filename);
- return rc;
-}
+ BUG_ON(!mutex_is_locked(&iint->mutex));
-static void ima_update_counts(struct ima_iint_cache *iint, int mask)
-{
iint->opencount++;
- if ((mask & MAY_WRITE) || (mask == 0))
- iint->writecount++;
- else if (mask & (MAY_READ | MAY_EXEC))
+ if ((mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
iint->readcount++;
+ if (mode & FMODE_WRITE)
+ iint->writecount++;
}
-/**
- * ima_path_check - based on policy, collect/store measurement.
- * @path: contains a pointer to the path to be measured
- * @mask: contains MAY_READ, MAY_WRITE or MAY_EXECUTE
- *
- * Measure the file being open for readonly, based on the
- * ima_must_measure() policy decision.
+/*
+ * ima_counts_get - increment file counts
*
- * Keep read/write counters for all files, but only
+ * Maintain read/write counters for all files, but only
* invalidate the PCR for measured files:
* - Opening a file for write when already open for read,
* results in a time of measure, time of use (ToMToU) error.
* - Opening a file for read when already open for write,
* could result in a file measurement error.
*
- * Always return 0 and audit dentry_open failures.
- * (Return code will be based upon measurement appraisal.)
*/
-int ima_path_check(struct path *path, int mask, int update_counts)
+void ima_counts_get(struct file *file)
{
- struct inode *inode = path->dentry->d_inode;
+ struct dentry *dentry = file->f_path.dentry;
+ struct inode *inode = dentry->d_inode;
+ fmode_t mode = file->f_mode;
struct ima_iint_cache *iint;
- struct file *file = NULL;
int rc;
if (!ima_initialized || !S_ISREG(inode->i_mode))
- return 0;
- iint = ima_iint_find_insert_get(inode);
+ return;
+ iint = ima_iint_find_get(inode);
if (!iint)
- return 0;
-
+ return;
mutex_lock(&iint->mutex);
- if (update_counts)
- ima_update_counts(iint, mask);
-
- rc = ima_must_measure(iint, inode, MAY_READ, PATH_CHECK);
+ rc = ima_must_measure(iint, inode, MAY_READ, FILE_CHECK);
if (rc < 0)
goto out;
- if ((mask & MAY_WRITE) || (mask == 0))
- ima_read_write_check(TOMTOU, iint, inode,
- path->dentry->d_name.name);
-
- if ((mask & (MAY_WRITE | MAY_READ | MAY_EXEC)) != MAY_READ)
+ if (mode & FMODE_WRITE) {
+ ima_read_write_check(TOMTOU, iint, inode, dentry->d_name.name);
goto out;
-
- ima_read_write_check(OPEN_WRITERS, iint, inode,
- path->dentry->d_name.name);
- if (!(iint->flags & IMA_MEASURED)) {
- struct dentry *dentry = dget(path->dentry);
- struct vfsmount *mnt = mntget(path->mnt);
-
- file = dentry_open(dentry, mnt, O_RDONLY | O_LARGEFILE,
- current_cred());
- if (IS_ERR(file)) {
- int audit_info = 0;
-
- integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode,
- dentry->d_name.name,
- "add_measurement",
- "dentry_open failed",
- 1, audit_info);
- file = NULL;
- goto out;
- }
- rc = get_path_measurement(iint, file, dentry->d_name.name);
}
+ ima_read_write_check(OPEN_WRITERS, iint, inode, dentry->d_name.name);
out:
+ ima_inc_counts(iint, file->f_mode);
mutex_unlock(&iint->mutex);
- if (file)
- fput(file);
+
kref_put(&iint->refcount, iint_free);
- return 0;
}
-EXPORT_SYMBOL_GPL(ima_path_check);
-static int process_measurement(struct file *file, const unsigned char *filename,
- int mask, int function)
+/*
+ * Decrement ima counts
+ */
+static void ima_dec_counts(struct ima_iint_cache *iint, struct inode *inode,
+ struct file *file)
{
- struct inode *inode = file->f_dentry->d_inode;
- struct ima_iint_cache *iint;
- int rc;
+ mode_t mode = file->f_mode;
+ BUG_ON(!mutex_is_locked(&iint->mutex));
- if (!ima_initialized || !S_ISREG(inode->i_mode))
- return 0;
- iint = ima_iint_find_insert_get(inode);
- if (!iint)
- return -ENOMEM;
-
- mutex_lock(&iint->mutex);
- rc = ima_must_measure(iint, inode, mask, function);
- if (rc != 0)
- goto out;
+ iint->opencount--;
+ if ((mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
+ iint->readcount--;
+ if (mode & FMODE_WRITE) {
+ iint->writecount--;
+ if (iint->writecount == 0) {
+ if (iint->version != inode->i_version)
+ iint->flags &= ~IMA_MEASURED;
+ }
+ }
- rc = ima_collect_measurement(iint, file);
- if (!rc)
- ima_store_measurement(iint, file, filename);
-out:
- mutex_unlock(&iint->mutex);
- kref_put(&iint->refcount, iint_free);
- return rc;
+ if (((iint->opencount < 0) ||
+ (iint->readcount < 0) ||
+ (iint->writecount < 0)) &&
+ !ima_limit_imbalance(file)) {
+ printk(KERN_INFO "%s: open/free imbalance (r:%ld w:%ld o:%ld)\n",
+ __FUNCTION__, iint->readcount, iint->writecount,
+ iint->opencount);
+ dump_stack();
+ }
}
-/*
- * ima_counts_put - decrement file counts
+/**
+ * ima_file_free - called on __fput()
+ * @file: pointer to file structure being freed
*
- * File counts are incremented in ima_path_check. On file open
- * error, such as ETXTBSY, decrement the counts to prevent
- * unnecessary imbalance messages.
+ * Flag files that changed, based on i_version;
+ * and decrement the iint readcount/writecount.
*/
-void ima_counts_put(struct path *path, int mask)
+void ima_file_free(struct file *file)
{
- struct inode *inode = path->dentry->d_inode;
+ struct inode *inode = file->f_dentry->d_inode;
struct ima_iint_cache *iint;
- /* The inode may already have been freed, freeing the iint
- * with it. Verify the inode is not NULL before dereferencing
- * it.
- */
- if (!ima_initialized || !inode || !S_ISREG(inode->i_mode))
+ if (!ima_initialized || !S_ISREG(inode->i_mode))
return;
- iint = ima_iint_find_insert_get(inode);
+ iint = ima_iint_find_get(inode);
if (!iint)
return;
mutex_lock(&iint->mutex);
- iint->opencount--;
- if ((mask & MAY_WRITE) || (mask == 0))
- iint->writecount--;
- else if (mask & (MAY_READ | MAY_EXEC))
- iint->readcount--;
+ ima_dec_counts(iint, inode, file);
mutex_unlock(&iint->mutex);
-
kref_put(&iint->refcount, iint_free);
}
-/*
- * ima_counts_get - increment file counts
- *
- * - for IPC shm and shmat file.
- * - for nfsd exported files.
- *
- * Increment the counts for these files to prevent unnecessary
- * imbalance messages.
- */
-void ima_counts_get(struct file *file)
+static int process_measurement(struct file *file, const unsigned char *filename,
+ int mask, int function)
{
struct inode *inode = file->f_dentry->d_inode;
struct ima_iint_cache *iint;
+ int rc;
if (!ima_initialized || !S_ISREG(inode->i_mode))
- return;
- iint = ima_iint_find_insert_get(inode);
+ return 0;
+ iint = ima_iint_find_get(inode);
if (!iint)
- return;
+ return -ENOMEM;
+
mutex_lock(&iint->mutex);
- iint->opencount++;
- if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
- iint->readcount++;
+ rc = ima_must_measure(iint, inode, mask, function);
+ if (rc != 0)
+ goto out;
- if (file->f_mode & FMODE_WRITE)
- iint->writecount++;
+ rc = ima_collect_measurement(iint, file);
+ if (!rc)
+ ima_store_measurement(iint, file, filename);
+out:
mutex_unlock(&iint->mutex);
-
kref_put(&iint->refcount, iint_free);
+ return rc;
}
-EXPORT_SYMBOL_GPL(ima_counts_get);
/**
* ima_file_mmap - based on policy, collect/store measurement.
@@ -347,11 +296,31 @@ int ima_bprm_check(struct linux_binprm *bprm)
return 0;
}
+/**
+ * ima_path_check - based on policy, collect/store measurement.
+ * @file: pointer to the file to be measured
+ * @mask: contains MAY_READ, MAY_WRITE or MAY_EXECUTE
+ *
+ * Measure files based on the ima_must_measure() policy decision.
+ *
+ * Always return 0 and audit dentry_open failures.
+ * (Return code will be based upon measurement appraisal.)
+ */
+int ima_file_check(struct file *file, int mask)
+{
+ int rc;
+
+ rc = process_measurement(file, file->f_dentry->d_name.name,
+ mask & (MAY_READ | MAY_WRITE | MAY_EXEC),
+ FILE_CHECK);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ima_file_check);
+
static int __init init_ima(void)
{
int error;
- ima_iintcache_init();
error = ima_init();
ima_initialized = 1;
return error;
OpenPOWER on IntegriCloud