From 6afbc2061131558e03f01bf8fa3e2d027edb0d8f Mon Sep 17 00:00:00 2001 From: KAMEZAWA Hiroyuki Date: Tue, 26 Oct 2010 14:21:10 -0700 Subject: [PATCH] --- yaml --- r: 217423 b: refs/heads/master c: f8f72ad5396987e05a42cf7eff826fb2a15ff148 h: refs/heads/master i: 217421: 9e9d74c6936a6c858c2c9f8be2897624bacbcd31 217419: 03173089cef4a799af8b19d97b14d5565b8d1137 217415: 2d44184b2c8de1c43ddc5c2fe5a10f1b39bd1fc7 217407: aec49a43e08d34eedae1d8b4d87b8be05250212b v: v3 --- [refs] | 2 +- trunk/fs/inode.c | 1 - trunk/include/linux/fs.h | 6 - trunk/mm/memory_hotplug.c | 2 +- trunk/security/integrity/ima/ima.h | 18 ++- trunk/security/integrity/ima/ima_api.c | 2 +- trunk/security/integrity/ima/ima_iint.c | 158 +++++++++----------- trunk/security/integrity/ima/ima_main.c | 184 ++++++++++++------------ trunk/security/security.c | 10 +- 9 files changed, 179 insertions(+), 204 deletions(-) diff --git a/[refs] b/[refs] index f51956c1412e..f94d7de025fb 100644 --- a/[refs] +++ b/[refs] @@ -1,2 +1,2 @@ --- -refs/heads/master: f9ba5375a8aae4aeea6be15df77e24707a429812 +refs/heads/master: f8f72ad5396987e05a42cf7eff826fb2a15ff148 diff --git a/trunk/fs/inode.c b/trunk/fs/inode.c index 56d909d69bc8..86464332e590 100644 --- a/trunk/fs/inode.c +++ b/trunk/fs/inode.c @@ -24,7 +24,6 @@ #include #include #include -#include /* * This is needed for the following functions: diff --git a/trunk/include/linux/fs.h b/trunk/include/linux/fs.h index bb20373d0b46..4f34ff6e5558 100644 --- a/trunk/include/linux/fs.h +++ b/trunk/include/linux/fs.h @@ -231,7 +231,6 @@ struct inodes_stat_t { #define S_NOCMTIME 128 /* Do not update file c/mtime */ #define S_SWAPFILE 256 /* Do not truncate: swapon got its bmaps */ #define S_PRIVATE 512 /* Inode is fs-internal */ -#define S_IMA 1024 /* Inode has an associated IMA struct */ /* * Note that nosuid etc flags are inode-specific: setting some file-system @@ -266,7 +265,6 @@ struct inodes_stat_t { #define IS_NOCMTIME(inode) ((inode)->i_flags & S_NOCMTIME) #define IS_SWAPFILE(inode) ((inode)->i_flags & S_SWAPFILE) #define IS_PRIVATE(inode) ((inode)->i_flags & S_PRIVATE) -#define IS_IMA(inode) ((inode)->i_flags & S_IMA) /* the read-only stuff doesn't really belong here, but any other place is probably as bad and I don't want to create yet another include file. */ @@ -774,10 +772,6 @@ struct inode { unsigned int i_flags; -#ifdef CONFIG_IMA - /* protected by i_lock */ - unsigned int i_readcount; /* struct files open RO */ -#endif atomic_t i_writecount; #ifdef CONFIG_SECURITY void *i_security; diff --git a/trunk/mm/memory_hotplug.c b/trunk/mm/memory_hotplug.c index d4e940a26945..06662c5a3e86 100644 --- a/trunk/mm/memory_hotplug.c +++ b/trunk/mm/memory_hotplug.c @@ -659,7 +659,7 @@ static int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn) * Scanning pfn is much easier than scanning lru list. * Scan pfn from start to end and Find LRU page. */ -int scan_lru_pages(unsigned long start, unsigned long end) +unsigned long scan_lru_pages(unsigned long start, unsigned long end) { unsigned long pfn; struct page *page; diff --git a/trunk/security/integrity/ima/ima.h b/trunk/security/integrity/ima/ima.h index ac79032bdf23..3fbcd1dda0ef 100644 --- a/trunk/security/integrity/ima/ima.h +++ b/trunk/security/integrity/ima/ima.h @@ -70,7 +70,6 @@ int ima_init(void); void ima_cleanup(void); int ima_fs_init(void); void ima_fs_cleanup(void); -int ima_inode_alloc(struct inode *inode); int ima_add_template_entry(struct ima_template_entry *entry, int violation, const char *op, struct inode *inode); int ima_calc_hash(struct file *file, char *digest); @@ -97,16 +96,19 @@ static inline unsigned long ima_hash_key(u8 *digest) } /* iint cache flags */ -#define IMA_MEASURED 0x01 +#define IMA_MEASURED 1 /* integrity data associated with an inode */ struct ima_iint_cache { - struct rb_node rb_node; /* rooted in ima_iint_tree */ - struct inode *inode; /* back pointer to inode in question */ u64 version; /* track inode changes */ - unsigned char flags; + unsigned long flags; u8 digest[IMA_DIGEST_SIZE]; struct mutex mutex; /* protects: version, flags, digest */ + long readcount; /* measured files readcount */ + long writecount; /* measured files writecount */ + long opencount; /* opens reference count */ + struct kref refcount; /* ima_iint_cache reference count */ + struct rcu_head rcu; }; /* LIM API function definitions */ @@ -120,11 +122,13 @@ int ima_store_template(struct ima_template_entry *entry, int violation, void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show); -/* rbtree tree calls to lookup, insert, delete +/* radix tree calls to lookup, insert, delete * integrity data associated with an inode. */ struct ima_iint_cache *ima_iint_insert(struct inode *inode); -struct ima_iint_cache *ima_iint_find(struct inode *inode); +struct ima_iint_cache *ima_iint_find_get(struct inode *inode); +void iint_free(struct kref *kref); +void iint_rcu_free(struct rcu_head *rcu); /* IMA policy related functions */ enum ima_hooks { FILE_CHECK = 1, FILE_MMAP, BPRM_CHECK }; diff --git a/trunk/security/integrity/ima/ima_api.c b/trunk/security/integrity/ima/ima_api.c index d3963de6003d..52015d098fdf 100644 --- a/trunk/security/integrity/ima/ima_api.c +++ b/trunk/security/integrity/ima/ima_api.c @@ -116,7 +116,7 @@ int ima_must_measure(struct ima_iint_cache *iint, struct inode *inode, { int must_measure; - if (iint && iint->flags & IMA_MEASURED) + if (iint->flags & IMA_MEASURED) return 1; must_measure = ima_match_policy(inode, function, mask); diff --git a/trunk/security/integrity/ima/ima_iint.c b/trunk/security/integrity/ima/ima_iint.c index c442e47b6785..afba4aef812f 100644 --- a/trunk/security/integrity/ima/ima_iint.c +++ b/trunk/security/integrity/ima/ima_iint.c @@ -12,119 +12,98 @@ * File: ima_iint.c * - implements the IMA hooks: ima_inode_alloc, ima_inode_free * - cache integrity information associated with an inode - * using a rbtree tree. + * using a radix tree. */ #include #include #include -#include +#include #include "ima.h" -static struct rb_root ima_iint_tree = RB_ROOT; -static DEFINE_SPINLOCK(ima_iint_lock); +RADIX_TREE(ima_iint_store, GFP_ATOMIC); +DEFINE_SPINLOCK(ima_iint_lock); static struct kmem_cache *iint_cache __read_mostly; int iint_initialized = 0; -/* - * __ima_iint_find - return the iint associated with an inode +/* ima_iint_find_get - return the iint associated with an inode + * + * ima_iint_find_get gets a reference to the iint. Caller must + * remember to put the iint reference. */ -static struct ima_iint_cache *__ima_iint_find(struct inode *inode) +struct ima_iint_cache *ima_iint_find_get(struct inode *inode) { struct ima_iint_cache *iint; - struct rb_node *n = ima_iint_tree.rb_node; - - assert_spin_locked(&ima_iint_lock); - - while (n) { - iint = rb_entry(n, struct ima_iint_cache, rb_node); - - if (inode < iint->inode) - n = n->rb_left; - else if (inode > iint->inode) - n = n->rb_right; - else - break; - } - if (!n) - return NULL; + rcu_read_lock(); + iint = radix_tree_lookup(&ima_iint_store, (unsigned long)inode); + if (!iint) + goto out; + kref_get(&iint->refcount); +out: + rcu_read_unlock(); return iint; } -/* - * ima_iint_find - return the iint associated with an inode +/** + * ima_inode_alloc - allocate an iint associated with an inode + * @inode: pointer to the inode */ -struct ima_iint_cache *ima_iint_find(struct inode *inode) +int ima_inode_alloc(struct inode *inode) { - struct ima_iint_cache *iint; + struct ima_iint_cache *iint = NULL; + int rc = 0; + + iint = kmem_cache_alloc(iint_cache, GFP_NOFS); + if (!iint) + return -ENOMEM; - if (!IS_IMA(inode)) - return NULL; + rc = radix_tree_preload(GFP_NOFS); + if (rc < 0) + goto out; spin_lock(&ima_iint_lock); - iint = __ima_iint_find(inode); + rc = radix_tree_insert(&ima_iint_store, (unsigned long)inode, iint); spin_unlock(&ima_iint_lock); + radix_tree_preload_end(); +out: + if (rc < 0) + kmem_cache_free(iint_cache, iint); - return iint; + return rc; } -static void iint_free(struct ima_iint_cache *iint) +/* iint_free - called when the iint refcount goes to zero */ +void iint_free(struct kref *kref) { + struct ima_iint_cache *iint = container_of(kref, struct ima_iint_cache, + refcount); iint->version = 0; iint->flags = 0UL; + if (iint->readcount != 0) { + printk(KERN_INFO "%s: readcount: %ld\n", __func__, + iint->readcount); + iint->readcount = 0; + } + if (iint->writecount != 0) { + printk(KERN_INFO "%s: writecount: %ld\n", __func__, + iint->writecount); + iint->writecount = 0; + } + if (iint->opencount != 0) { + printk(KERN_INFO "%s: opencount: %ld\n", __func__, + iint->opencount); + iint->opencount = 0; + } + kref_init(&iint->refcount); kmem_cache_free(iint_cache, iint); } -/** - * ima_inode_alloc - allocate an iint associated with an inode - * @inode: pointer to the inode - */ -int ima_inode_alloc(struct inode *inode) +void iint_rcu_free(struct rcu_head *rcu_head) { - struct rb_node **p; - struct rb_node *new_node, *parent = NULL; - struct ima_iint_cache *new_iint, *test_iint; - int rc; - - new_iint = kmem_cache_alloc(iint_cache, GFP_NOFS); - if (!new_iint) - return -ENOMEM; - - new_iint->inode = inode; - new_node = &new_iint->rb_node; - - mutex_lock(&inode->i_mutex); /* i_flags */ - spin_lock(&ima_iint_lock); - - p = &ima_iint_tree.rb_node; - while (*p) { - parent = *p; - test_iint = rb_entry(parent, struct ima_iint_cache, rb_node); - - rc = -EEXIST; - if (inode < test_iint->inode) - p = &(*p)->rb_left; - else if (inode > test_iint->inode) - p = &(*p)->rb_right; - else - goto out_err; - } - - inode->i_flags |= S_IMA; - rb_link_node(new_node, parent, p); - rb_insert_color(new_node, &ima_iint_tree); - - spin_unlock(&ima_iint_lock); - mutex_unlock(&inode->i_mutex); /* i_flags */ - - return 0; -out_err: - spin_unlock(&ima_iint_lock); - mutex_unlock(&inode->i_mutex); /* i_flags */ - iint_free(new_iint); - - return rc; + struct ima_iint_cache *iint = container_of(rcu_head, + struct ima_iint_cache, rcu); + kref_put(&iint->refcount, iint_free); } /** @@ -137,20 +116,11 @@ void ima_inode_free(struct inode *inode) { struct ima_iint_cache *iint; - if (inode->i_readcount) - printk(KERN_INFO "%s: readcount: %u\n", __func__, inode->i_readcount); - - inode->i_readcount = 0; - - if (!IS_IMA(inode)) - return; - spin_lock(&ima_iint_lock); - iint = __ima_iint_find(inode); - rb_erase(&iint->rb_node, &ima_iint_tree); + iint = radix_tree_delete(&ima_iint_store, (unsigned long)inode); spin_unlock(&ima_iint_lock); - - iint_free(iint); + if (iint) + call_rcu(&iint->rcu, iint_rcu_free); } static void init_once(void *foo) @@ -161,6 +131,10 @@ static void init_once(void *foo) iint->version = 0; iint->flags = 0UL; mutex_init(&iint->mutex); + iint->readcount = 0; + iint->writecount = 0; + iint->opencount = 0; + kref_init(&iint->refcount); } static int __init ima_iintcache_init(void) diff --git a/trunk/security/integrity/ima/ima_main.c b/trunk/security/integrity/ima/ima_main.c index 203de979d305..e662b89d4079 100644 --- a/trunk/security/integrity/ima/ima_main.c +++ b/trunk/security/integrity/ima/ima_main.c @@ -85,6 +85,50 @@ static bool ima_limit_imbalance(struct file *file) return found; } +/* ima_read_write_check - reflect possible reading/writing errors in the PCR. + * + * When opening a file for read, if the file is already open for write, + * the file could change, resulting in a file measurement error. + * + * Opening a file for write, if the file is already open for read, results + * in a time of measure, time of use (ToMToU) error. + * + * In either case invalidate the PCR. + */ +enum iint_pcr_error { TOMTOU, OPEN_WRITERS }; +static void ima_read_write_check(enum iint_pcr_error error, + struct ima_iint_cache *iint, + struct inode *inode, + const unsigned char *filename) +{ + switch (error) { + case TOMTOU: + if (iint->readcount > 0) + ima_add_violation(inode, filename, "invalid_pcr", + "ToMToU"); + break; + case OPEN_WRITERS: + if (iint->writecount > 0) + ima_add_violation(inode, filename, "invalid_pcr", + "open_writers"); + break; + } +} + +/* + * Update the counts given an fmode_t + */ +static void ima_inc_counts(struct ima_iint_cache *iint, fmode_t mode) +{ + BUG_ON(!mutex_is_locked(&iint->mutex)); + + iint->opencount++; + if ((mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) + iint->readcount++; + if (mode & FMODE_WRITE) + iint->writecount++; +} + /* * ima_counts_get - increment file counts * @@ -101,101 +145,62 @@ void ima_counts_get(struct file *file) struct dentry *dentry = file->f_path.dentry; struct inode *inode = dentry->d_inode; fmode_t mode = file->f_mode; + struct ima_iint_cache *iint; int rc; - bool send_tomtou = false, send_writers = false; - if (!S_ISREG(inode->i_mode)) + if (!iint_initialized || !S_ISREG(inode->i_mode)) return; - - spin_lock(&inode->i_lock); - + iint = ima_iint_find_get(inode); + if (!iint) + return; + mutex_lock(&iint->mutex); if (!ima_initialized) goto out; + rc = ima_must_measure(iint, inode, MAY_READ, FILE_CHECK); + if (rc < 0) + goto out; if (mode & FMODE_WRITE) { - if (inode->i_readcount && IS_IMA(inode)) - send_tomtou = true; + ima_read_write_check(TOMTOU, iint, inode, dentry->d_name.name); goto out; } - - rc = ima_must_measure(NULL, inode, MAY_READ, FILE_CHECK); - if (rc < 0) - goto out; - - if (atomic_read(&inode->i_writecount) > 0) - send_writers = true; + ima_read_write_check(OPEN_WRITERS, iint, inode, dentry->d_name.name); out: - /* remember the vfs deals with i_writecount */ - if ((mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) - inode->i_readcount++; - - spin_unlock(&inode->i_lock); + ima_inc_counts(iint, file->f_mode); + mutex_unlock(&iint->mutex); - if (send_tomtou) - ima_add_violation(inode, dentry->d_name.name, "invalid_pcr", - "ToMToU"); - if (send_writers) - ima_add_violation(inode, dentry->d_name.name, "invalid_pcr", - "open_writers"); + kref_put(&iint->refcount, iint_free); } /* * Decrement ima counts */ -static void ima_dec_counts(struct inode *inode, struct file *file) +static void ima_dec_counts(struct ima_iint_cache *iint, struct inode *inode, + struct file *file) { mode_t mode = file->f_mode; + BUG_ON(!mutex_is_locked(&iint->mutex)); - assert_spin_locked(&inode->i_lock); - - if ((mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) { - if (unlikely(inode->i_readcount == 0)) { - if (!ima_limit_imbalance(file)) { - printk(KERN_INFO "%s: open/free imbalance (r:%u)\n", - __func__, inode->i_readcount); - dump_stack(); - } - return; + 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; } - inode->i_readcount--; } -} - -static void ima_check_last_writer(struct ima_iint_cache *iint, - struct inode *inode, - struct file *file) -{ - mode_t mode = file->f_mode; - - BUG_ON(!mutex_is_locked(&iint->mutex)); - assert_spin_locked(&inode->i_lock); - - if (mode & FMODE_WRITE && - atomic_read(&inode->i_writecount) == 1 && - iint->version != inode->i_version) - iint->flags &= ~IMA_MEASURED; -} - -static void ima_file_free_iint(struct ima_iint_cache *iint, struct inode *inode, - struct file *file) -{ - mutex_lock(&iint->mutex); - spin_lock(&inode->i_lock); - ima_dec_counts(inode, file); - ima_check_last_writer(iint, inode, file); - - spin_unlock(&inode->i_lock); - mutex_unlock(&iint->mutex); -} - -static void ima_file_free_noiint(struct inode *inode, struct file *file) -{ - spin_lock(&inode->i_lock); - - ima_dec_counts(inode, file); - - spin_unlock(&inode->i_lock); + 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", + __func__, iint->readcount, iint->writecount, + iint->opencount); + dump_stack(); + } } /** @@ -203,7 +208,7 @@ static void ima_file_free_noiint(struct inode *inode, struct file *file) * @file: pointer to file structure being freed * * Flag files that changed, based on i_version; - * and decrement the i_readcount. + * and decrement the iint readcount/writecount. */ void ima_file_free(struct file *file) { @@ -212,14 +217,14 @@ void ima_file_free(struct file *file) if (!iint_initialized || !S_ISREG(inode->i_mode)) return; + iint = ima_iint_find_get(inode); + if (!iint) + return; - iint = ima_iint_find(inode); - - if (iint) - ima_file_free_iint(iint, inode, file); - else - ima_file_free_noiint(inode, file); - + mutex_lock(&iint->mutex); + ima_dec_counts(iint, inode, file); + mutex_unlock(&iint->mutex); + kref_put(&iint->refcount, iint_free); } static int process_measurement(struct file *file, const unsigned char *filename, @@ -231,21 +236,11 @@ static int process_measurement(struct file *file, const unsigned char *filename, if (!ima_initialized || !S_ISREG(inode->i_mode)) return 0; - - rc = ima_must_measure(NULL, inode, mask, function); - if (rc != 0) - return rc; -retry: - iint = ima_iint_find(inode); - if (!iint) { - rc = ima_inode_alloc(inode); - if (!rc || rc == -EEXIST) - goto retry; - return rc; - } + iint = ima_iint_find_get(inode); + if (!iint) + return -ENOMEM; mutex_lock(&iint->mutex); - rc = ima_must_measure(iint, inode, mask, function); if (rc != 0) goto out; @@ -255,6 +250,7 @@ static int process_measurement(struct file *file, const unsigned char *filename, ima_store_measurement(iint, file, filename); out: mutex_unlock(&iint->mutex); + kref_put(&iint->refcount, iint_free); return rc; } diff --git a/trunk/security/security.c b/trunk/security/security.c index 3ef5e2a7a741..b50f472061a4 100644 --- a/trunk/security/security.c +++ b/trunk/security/security.c @@ -325,8 +325,16 @@ EXPORT_SYMBOL(security_sb_parse_opts_str); int security_inode_alloc(struct inode *inode) { + int ret; + inode->i_security = NULL; - return security_ops->inode_alloc_security(inode); + ret = security_ops->inode_alloc_security(inode); + if (ret) + return ret; + ret = ima_inode_alloc(inode); + if (ret) + security_inode_free(inode); + return ret; } void security_inode_free(struct inode *inode)