Commit 6ce317fd authored by Jan Kara's avatar Jan Kara Committed by Greg Kroah-Hartman

audit: Embed key into chunk

[ Upstream commit 8d20d6e9 ]

Currently chunk hash key (which is in fact pointer to the inode) is
derived as chunk->mark.conn->obj. It is tricky to make this dereference
reliable for hash table lookups only under RCU as mark can get detached
from the connector and connector gets freed independently of the
running lookup. Thus there is a possible use after free / NULL ptr
dereference issue:

CPU1					CPU2
					untag_chunk()
					  ...
audit_tree_lookup()
  list_for_each_entry_rcu(p, list, hash) {
					  list_del_rcu(&chunk->hash);
					  fsnotify_destroy_mark(entry);
					  fsnotify_put_mark(entry)
    chunk_to_key(p)
      if (!chunk->mark.connector)
					    ...
					    hlist_del_init_rcu(&mark->obj_list);
					    if (hlist_empty(&conn->list)) {
					      inode = fsnotify_detach_connector_from_object(conn);
					    mark->connector = NULL;
					    ...
					    frees connector from workqueue
      chunk->mark.connector->obj

This race is probably impossible to hit in practice as the race window
on CPU1 is very narrow and CPU2 has a lot of code to execute. Still it's
better to have this fixed. Since the inode the chunk is attached to is
constant during chunk's lifetime it is easy to cache the key in the
chunk itself and thus avoid these issues.
Reviewed-by: default avatarRichard Guy Briggs <rgb@redhat.com>
Signed-off-by: default avatarJan Kara <jack@suse.cz>
Signed-off-by: default avatarPaul Moore <paul@paul-moore.com>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent b8252ec6
......@@ -24,6 +24,7 @@ struct audit_tree {
struct audit_chunk {
struct list_head hash;
unsigned long key;
struct fsnotify_mark mark;
struct list_head trees; /* with root here */
int dead;
......@@ -172,21 +173,6 @@ static unsigned long inode_to_key(const struct inode *inode)
return (unsigned long)&inode->i_fsnotify_marks;
}
/*
* Function to return search key in our hash from chunk. Key 0 is special and
* should never be present in the hash.
*/
static unsigned long chunk_to_key(struct audit_chunk *chunk)
{
/*
* We have a reference to the mark so it should be attached to a
* connector.
*/
if (WARN_ON_ONCE(!chunk->mark.connector))
return 0;
return (unsigned long)chunk->mark.connector->obj;
}
static inline struct list_head *chunk_hash(unsigned long key)
{
unsigned long n = key / L1_CACHE_BYTES;
......@@ -196,12 +182,12 @@ static inline struct list_head *chunk_hash(unsigned long key)
/* hash_lock & entry->lock is held by caller */
static void insert_hash(struct audit_chunk *chunk)
{
unsigned long key = chunk_to_key(chunk);
struct list_head *list;
if (!(chunk->mark.flags & FSNOTIFY_MARK_FLAG_ATTACHED))
return;
list = chunk_hash(key);
WARN_ON_ONCE(!chunk->key);
list = chunk_hash(chunk->key);
list_add_rcu(&chunk->hash, list);
}
......@@ -213,7 +199,7 @@ struct audit_chunk *audit_tree_lookup(const struct inode *inode)
struct audit_chunk *p;
list_for_each_entry_rcu(p, list, hash) {
if (chunk_to_key(p) == key) {
if (p->key == key) {
atomic_long_inc(&p->refs);
return p;
}
......@@ -297,6 +283,7 @@ static void untag_chunk(struct node *p)
chunk->dead = 1;
spin_lock(&hash_lock);
new->key = chunk->key;
list_replace_init(&chunk->trees, &new->trees);
if (owner->root == chunk) {
list_del_init(&owner->same_root);
......@@ -378,6 +365,7 @@ static int create_chunk(struct inode *inode, struct audit_tree *tree)
tree->root = chunk;
list_add(&tree->same_root, &chunk->trees);
}
chunk->key = inode_to_key(inode);
insert_hash(chunk);
spin_unlock(&hash_lock);
spin_unlock(&entry->lock);
......@@ -462,6 +450,7 @@ static int tag_chunk(struct inode *inode, struct audit_tree *tree)
fsnotify_put_mark(old_entry);
return 0;
}
chunk->key = old->key;
list_replace_init(&old->trees, &chunk->trees);
for (n = 0, p = chunk->owners; n < old->count; n++, p++) {
struct audit_tree *s = old->owners[n].owner;
......@@ -661,7 +650,7 @@ void audit_trim_trees(void)
/* this could be NULL if the watch is dying else where... */
node->index |= 1U<<31;
if (iterate_mounts(compare_root,
(void *)chunk_to_key(chunk),
(void *)(chunk->key),
root_mnt))
node->index &= ~(1U<<31);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment