--- ./include/linux/audit.h~ 2005-06-01 10:45:42.000000000 +0100 +++ ./include/linux/audit.h 2005-06-01 17:11:31.000000000 +0100 @@ -215,32 +215,28 @@ struct watch_transport { #ifdef __KERNEL__ /* Structure associated with inode->i_audit */ -struct audit_inode_data { - struct audit_inode_data *next_hash; - struct inode *inode; - struct audit_wentry *wentry; - struct hlist_head watchlist; - rwlock_t lock; -}; - struct audit_watch { - dev_t dev; /* Superblock device */ - __u32 perms; /* Permissions filtering */ - char *name; /* Watch point beneath parent */ - char *path; /* Insertion path */ - char *filterkey; /* An arbitrary filtering key */ -}; - -struct audit_wentry { - struct rcu_head w_rcu; - struct hlist_node w_node; - struct hlist_node w_master; - struct audit_watch *w_watch; - struct dentry *w_dentry; atomic_t w_count; + struct hlist_node w_node; /* per-directory list */ + struct hlist_node w_master; /* Master watch list */ + struct dentry *w_dentry; /* Watched inode */ + dev_t w_dev; /* Superblock device */ + __u32 w_perms; /* Permissions filtering */ + char *w_name; /* Watch point beneath parent */ + char *w_path; /* Insertion path */ + char *w_filterkey; /* An arbitrary filtering key */ +}; +struct audit_inode_data { + atomic_t count; + struct audit_inode_data *next_hash; /* Watch data hash table */ + struct inode *inode; /* Inode to which it belongs */ + struct audit_watch *watch; /* Watch for this inode */ + struct hlist_head watchlist; /* Watches for children */ + rwlock_t lock; }; + struct audit_sig_info { uid_t uid; pid_t pid; @@ -306,11 +302,11 @@ extern int audit_receive_watch(int type, extern int audit_inode_alloc(struct inode *inode); extern void audit_inode_free(struct inode *inode); extern void audit_update_watch(struct dentry *dentry, int remove); -extern void audit_wentry_put(struct audit_wentry *wentry); +extern void audit_watch_put(struct audit_watch *watch); extern void audit_dentry_unpin(struct dentry *dentry); -extern struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry); +extern struct audit_watch *audit_watch_get(struct audit_watch *watch); extern int audit_notify_watch(struct inode *inode, int mask); -extern int auditfs_attach_wdata(struct inode *inode, struct audit_wentry *wentry, +extern int auditfs_attach_wdata(struct inode *inode, struct audit_watch *watch, int mask); #else #define audit_filesystem_init() ({ 0; }) @@ -319,8 +315,8 @@ extern int auditfs_attach_wdata(struct i #define audit_inode_alloc(i) ({ 0; }) #define audit_inode_free(i) do { ; } while(0) #define audit_update_watch(d,r) do { ; } while (0) -#define audit_wentry_put(w) do { ; } while(0) -#define audit_wentry_get(w) ({ 0; }) +#define audit_watch_put(w) do { ; } while(0) +#define audit_watch_get(w) ({ 0; }) #define audit_notify_watch(i,m) ({ 0; }) #endif --- ./kernel/auditsc.c~ 2005-06-01 11:05:38.000000000 +0100 +++ ./kernel/auditsc.c 2005-06-01 11:08:06.000000000 +0100 @@ -131,7 +131,7 @@ struct audit_aux_data_path { struct audit_aux_data_watched { struct audit_aux_data link; - struct audit_wentry *wentry; + struct audit_watch *watch; unsigned long ino; int mask; uid_t uid; @@ -586,7 +586,7 @@ static inline void audit_free_aux(struct break; } case AUDIT_FS_WATCH: { struct audit_aux_data_watched *axi = (void *)aux; - audit_wentry_put(axi->wentry); + audit_watch_put(axi->watch); break; } } @@ -769,13 +769,13 @@ static void audit_log_exit(struct audit_ case AUDIT_FS_WATCH: { struct audit_aux_data_watched *axi = (void *)aux; audit_log_format(ab, " watch="); - audit_log_untrustedstring(ab, axi->wentry->w_watch->name); + audit_log_untrustedstring(ab, axi->watch->w_name); audit_log_format(ab, " filterkey=%s perm=%u perm_mask=%d" " inode=%lu inode_uid=%u inode_gid=%u" " inode_dev=%02x:%02x inode_rdev=%02x:%02x", - axi->wentry->w_watch->filterkey, - axi->wentry->w_watch->perms, + axi->watch->w_filterkey, + axi->watch->w_perms, axi->mask, axi->ino, axi->uid, axi->gid, MAJOR(axi->dev), MINOR(axi->dev), MAJOR(axi->rdev), MINOR(axi->rdev)); @@ -1198,7 +1198,7 @@ void audit_signal_info(int sig, struct t #ifdef CONFIG_AUDITFILESYSTEM /* This has to be here instead of in auditfs.c, because it needs to see the audit context */ -int auditfs_attach_wdata(struct inode *inode, struct audit_wentry *wentry, +int auditfs_attach_wdata(struct inode *inode, struct audit_watch *watch, int mask) { struct audit_context *context = current->audit_context; @@ -1211,7 +1211,7 @@ int auditfs_attach_wdata(struct inode *i if (context->in_syscall && !context->auditable) context->auditable = 1; - ax->wentry = wentry; + ax->watch = watch; ax->mask = mask; ax->ino = inode->i_ino; ax->uid = inode->i_uid; --- ./kernel/auditfs.c~ 2005-06-01 11:05:38.000000000 +0100 +++ ./kernel/auditfs.c 2005-06-01 17:32:53.000000000 +0100 @@ -40,19 +40,21 @@ extern int audit_enabled; static kmem_cache_t *audit_watch_cache; -static kmem_cache_t *audit_wentry_cache; /* Read-heavy list */ -HLIST_HEAD(master_watchlist); +static HLIST_HEAD(master_watchlist); static spinlock_t master_watchlist_lock = SPIN_LOCK_UNLOCKED; struct audit_skb_list { - struct hlist_node list; - void *memblk; - size_t size; + struct hlist_node list; + void *memblk; + size_t size; }; +static atomic_t audit_nr_watches = ATOMIC_INIT(0); +static atomic_t audit_pool_size = ATOMIC_INIT(0); +static struct audit_inode_data *audit_data_pool; static struct audit_inode_data **auditfs_hash_table; static spinlock_t auditfs_hash_lock = SPIN_LOCK_UNLOCKED; static int auditfs_hash_bits; @@ -60,11 +62,32 @@ static int auditfs_cache_buckets = 16384 module_param(auditfs_cache_buckets, int, 0); MODULE_PARM_DESC(auditfs_cache_buckets, "Number of auditfs cache entries to allocate (default 16384)\n"); -struct audit_inode_data *inode_audit_data(struct inode *inode) +static int audit_data_pool_grow(void) +{ + struct audit_inode_data *new; + + new = kmalloc(sizeof(*new), GFP_KERNEL); + if (!new) + return -ENOMEM; + new->next_hash = kmalloc(sizeof(*new), GFP_KERNEL); + if (!new->next_hash) { + kfree(new); + return -ENOMEM; + } + + spin_lock(&auditfs_hash_lock); + new->next_hash->next_hash = audit_data_pool; + audit_data_pool = new; + atomic_add(2, &audit_pool_size); + spin_unlock(&auditfs_hash_lock); + return 0; +} + +static struct audit_inode_data *audit_data_get(struct inode *inode, int allocate) { struct audit_inode_data **list; - int h = hash_ptr(inode, auditfs_hash_bits); struct audit_inode_data *ret = NULL; + int h = hash_ptr(inode, auditfs_hash_bits); list = &auditfs_hash_table[h]; spin_lock(&auditfs_hash_lock); @@ -75,62 +98,68 @@ struct audit_inode_data *inode_audit_dat if (*list && (*list)->inode == inode) ret = *list; - if (!ret) { - /* Hm. At the moment, we should never see an inode which doesn't have audit data */ - printk("Hm. No audit data for inode %p. Hash bucket for hash %d follows...\n", inode, h); - list = &auditfs_hash_table[h]; - while (*list) { - printk("ino=%p data=%p\n", (*list)->inode, *list); - list = &(*list)->next_hash; - } + if (ret) { + atomic_inc(&ret->count); + } else if (allocate) { + ret = audit_data_pool; + audit_data_pool = ret->next_hash; + atomic_dec(&audit_pool_size); + + INIT_HLIST_HEAD(&ret->watchlist); + ret->watch = NULL; + ret->lock = RW_LOCK_UNLOCKED; + ret->inode = inode; + ret->next_hash = *list; + atomic_set(&ret->count, 2); + *list = ret; } - spin_unlock(&auditfs_hash_lock); + return ret; } /* Private Interface */ -/* Unpin the dentry stored at wentry->w_dentry. */ -static inline void audit_unpin(struct audit_wentry *wentry) +/* Unpin the dentry stored at watch->w_dentry. */ +static inline void audit_unpin(struct audit_watch *watch) { - if (wentry && wentry->w_dentry) { - dput(wentry->w_dentry); - wentry->w_dentry = NULL; + if (watch && watch->w_dentry) { + dput(watch->w_dentry); + watch->w_dentry = NULL; } } -/* Pin the dentry and store it at wentry->w_dentry. */ -static inline void audit_pin(struct audit_wentry *wentry, +/* Pin the dentry and store it at watch->w_dentry. */ +static inline void audit_pin(struct audit_watch *watch, struct dentry *dentry) { - if (wentry && !wentry->w_dentry) - wentry->w_dentry = dget(dentry); + if (watch && !watch->w_dentry) + watch->w_dentry = dget(dentry); } -static inline struct audit_wentry *audit_wentry_fetch(const char *name, +static inline struct audit_watch *audit_watch_fetch(const char *name, struct audit_inode_data *data) { - struct audit_wentry *wentry, *ret = NULL; + struct audit_watch *watch, *ret = NULL; struct hlist_node *pos; - hlist_for_each_entry(wentry, pos, &data->watchlist, w_node) - if(!strcmp(wentry->w_watch->name, name)) { - ret = audit_wentry_get(wentry); + hlist_for_each_entry(watch, pos, &data->watchlist, w_node) + if(!strcmp(watch->w_name, name)) { + ret = audit_watch_get(watch); break; } return ret; } -static inline struct audit_wentry *audit_wentry_fetch_lock(const char *name, +static inline struct audit_watch *audit_watch_fetch_lock(const char *name, struct audit_inode_data *data) { - struct audit_wentry *ret = NULL; + struct audit_watch *ret = NULL; if (name && data) { read_lock(&data->lock); - ret = audit_wentry_fetch(name, data); + ret = audit_watch_fetch(name, data); read_unlock(&data->lock); } @@ -143,25 +172,32 @@ static inline struct audit_watch *audit_ watch = kmem_cache_alloc(audit_watch_cache, GFP_KERNEL); if (watch) { - watch->name = NULL; - watch->path = NULL; - watch->filterkey = NULL; - watch->perms = 0; + memset(watch, 0, sizeof(*watch)); + atomic_set(&watch->w_count, 1); } return watch; } +/* + * If a watch has been removed from a watchlist, we promptly unpin the + * dentry that was being watched, as it is no longer important to us. + */ static inline void audit_watch_free(struct audit_watch *watch) { if (watch) { - kfree(watch->name); - kfree(watch->path); - kfree(watch->filterkey); + audit_unpin(watch); + kfree(watch->w_name); + kfree(watch->w_path); + kfree(watch->w_filterkey); + BUG_ON(watch->w_dentry); + BUG_ON(!hlist_unhashed(&watch->w_node)); + BUG_ON(!hlist_unhashed(&watch->w_master)); kmem_cache_free(audit_watch_cache, watch); } } + /* Convert a watch_transport structure into a kernel audit_watch structure. */ static inline struct audit_watch *audit_to_watch(void *memblk) { @@ -173,23 +209,23 @@ static inline struct audit_watch *audit_ if (!watch) goto audit_to_watch_exit; - t = (struct watch_transport *)memblk; + t = memblk; - watch->perms = t->perms; + watch->w_perms = t->perms; offset = sizeof(struct watch_transport); - watch->filterkey = kmalloc(t->fklen+1, GFP_KERNEL); - if (!watch->filterkey) + watch->w_filterkey = kmalloc(t->fklen+1, GFP_KERNEL); + if (!watch->w_filterkey) goto audit_to_watch_fail; - watch->filterkey[t->fklen] = 0; - memcpy(watch->filterkey, memblk + offset, t->fklen); + watch->w_filterkey[t->fklen] = 0; + memcpy(watch->w_filterkey, memblk + offset, t->fklen); offset += t->fklen; - watch->path = kmalloc(t->pathlen+1, GFP_KERNEL); - if (!watch->path) + watch->w_path = kmalloc(t->pathlen+1, GFP_KERNEL); + if (!watch->w_path) goto audit_to_watch_fail; - watch->path[t->pathlen] = 0; - memcpy(watch->path, memblk + offset, t->pathlen); + watch->w_path[t->pathlen] = 0; + memcpy(watch->w_path, memblk + offset, t->pathlen); goto audit_to_watch_exit; @@ -206,99 +242,31 @@ audit_to_watch_exit: */ static inline void *audit_to_transport(struct audit_watch *watch, size_t size) { - unsigned int offset; - struct watch_transport t; - void *memblk; + struct watch_transport *t; + char *p; - memblk = kmalloc(size, GFP_ATOMIC); - if (!memblk) + t = kmalloc(size, GFP_KERNEL); + if (!t) goto audit_to_transport_exit; - memset(&t, 0, sizeof(t)); + memset(t, 0, sizeof(*t)); - t.dev_major = MAJOR(watch->dev); - t.dev_minor = MINOR(watch->dev); - t.perms = watch->perms; - t.pathlen = strlen(watch->path) + 1; - if (watch->filterkey) - t.fklen = strlen(watch->filterkey) + 1; - - memcpy(memblk, &t, sizeof(t)); - offset = sizeof(t); - memcpy(memblk + offset, watch->filterkey, t.fklen); - offset += t.fklen; - memcpy(memblk + offset, watch->path, t.pathlen); + t->dev_major = MAJOR(watch->w_dev); + t->dev_minor = MINOR(watch->w_dev); + t->perms = watch->w_perms; + t->pathlen = strlen(watch->w_path) + 1; -audit_to_transport_exit: - return memblk; -} + p = (char *)&t[1]; -static inline struct audit_watch *audit_create_watch(const char *path, - const char *name, - const char *filterkey, - __u32 perms, dev_t dev) -{ - struct audit_watch *err = NULL; - struct audit_watch *watch = NULL; - - err = ERR_PTR(-ENOMEM); - watch = audit_watch_alloc(); - if (watch) { - watch->path = kmalloc(strlen(path)+1, GFP_KERNEL); - if (!watch->path) - goto audit_create_watch_fail; - strcpy(watch->path, path); - - watch->name = kmalloc(strlen(name)+1, GFP_KERNEL); - if (!watch->name) - goto audit_create_watch_fail; - strcpy(watch->name, name); - - if (filterkey) { - watch->filterkey = kmalloc(strlen(filterkey)+1, - GFP_KERNEL); - if (!watch->filterkey) - goto audit_create_watch_fail; - strcpy(watch->filterkey, filterkey); - } - - watch->dev = dev; - watch->perms = perms; - - goto audit_create_watch_exit; + if (watch->w_filterkey) { + t->fklen = strlen(watch->w_filterkey) + 1; + memcpy(p, watch->w_filterkey, t->fklen); + p += t->fklen; } + memcpy(p, watch->w_path, t->pathlen); -audit_create_watch_fail: - audit_watch_free(watch); - watch = err; -audit_create_watch_exit: - return watch; -} - -static inline struct audit_wentry *audit_wentry_alloc(void) -{ - struct audit_wentry *wentry; - - wentry = kmem_cache_alloc(audit_wentry_cache, GFP_KERNEL); - if (wentry) { - atomic_set(&wentry->w_count, 1); - wentry->w_watch = NULL; - wentry->w_dentry = NULL; - } - - return wentry; -} -/* - * If a watch has been removed from a watchlist, we promptly unpin the - * dentry that was being watched, as it is no longer important to us. - */ -static inline void audit_wentry_free(struct audit_wentry *wentry) -{ - if (wentry) { - audit_unpin(wentry); - audit_watch_free(wentry->w_watch); - kmem_cache_free(audit_wentry_cache, wentry); - } +audit_to_transport_exit: + return t; } /* @@ -307,62 +275,66 @@ static inline void audit_wentry_free(str * watchlist and the local watchlist to ensure that insertions and removals of * watches are seralized. */ -static inline int audit_create_wentry(const char *path, - const char *name, - const char *filterkey, - __u32 perms, dev_t dev, - struct audit_inode_data *data) +static inline int audit_create_watch(const char *path, + const char *name, + const char *filterkey, + __u32 perms, dev_t dev, + struct audit_inode_data *data) { int ret; - struct audit_wentry *wentry = NULL; - struct audit_wentry *new = NULL; struct audit_watch *watch; ret = -EEXIST; - wentry = audit_wentry_fetch_lock(name, data); - if (wentry) { - audit_wentry_put(wentry); - goto audit_create_wentry_exit; + watch = audit_watch_fetch_lock(name, data); + if (watch) { + audit_watch_put(watch); + goto audit_create_watch_exit; } ret = -ENOMEM; - new = audit_wentry_alloc(); - if (!new) - goto audit_create_wentry_exit; + watch = audit_watch_alloc(); + if (!watch) + goto audit_create_watch_exit; - watch = audit_create_watch(path, name, filterkey, perms, dev); - if (IS_ERR(watch)) { - ret = PTR_ERR(watch); - audit_wentry_put(new); - goto audit_create_wentry_exit; + watch->w_path = kmalloc(strlen(path)+1, GFP_KERNEL); + if (!watch->w_path) + goto audit_create_watch_fail; + strcpy(watch->w_path, path); + + watch->w_name = kmalloc(strlen(name)+1, GFP_KERNEL); + if (!watch->w_name) + goto audit_create_watch_fail; + strcpy(watch->w_name, name); + + if (filterkey) { + watch->w_filterkey = kmalloc(strlen(filterkey)+1, GFP_KERNEL); + if (!watch->w_filterkey) + goto audit_create_watch_fail; + strcpy(watch->w_filterkey, filterkey); } - ret = 0; + watch->w_dev = dev; + watch->w_perms = perms; - new->w_watch = watch; - audit_wentry_get(new); + audit_watch_get(watch); write_lock(&data->lock); - hlist_add_head(&new->w_node, &data->watchlist); - spin_lock(&master_watchlist_lock); - hlist_add_head_rcu(&new->w_master, &master_watchlist); - spin_unlock(&master_watchlist_lock); + hlist_add_head(&watch->w_node, &data->watchlist); + spin_lock(&master_watchlist_lock); + hlist_add_head(&watch->w_master, &master_watchlist); + spin_unlock(&master_watchlist_lock); write_unlock(&data->lock); + return 0; -audit_create_wentry_exit: - return ret; -} - -static inline void audit_wentry_rcu_put(struct rcu_head *head) -{ - struct audit_wentry *wentry; + audit_create_watch_fail: + audit_watch_put(watch); - wentry = container_of(head, struct audit_wentry, w_rcu); - audit_wentry_put(wentry); + audit_create_watch_exit: + return ret; } /* - * There's three ways we can arrive at audit_destroy_wentry. Two of the ways + * There's three ways we can arrive at audit_destroy_watch. Two of the ways * may cause contention with one another, while the third may not. They are: * * 1) An administrator has explicitly requested a watch removal @@ -374,18 +346,18 @@ static inline void audit_wentry_rcu_put( * consecutive entries concurrently in the master watchlist. * * If way 3) occurs there will no longer be any reachable references to the - * inode, thus there is only one way to remove this wentry, and no need to + * inode, thus there is only one way to remove this watch, and no need to * hold the data->lock. */ -static inline void audit_destroy_wentry(struct audit_wentry *wentry) +static inline void audit_destroy_watch(struct audit_watch *watch) { - if (wentry) { - hlist_del_init(&wentry->w_node); - audit_wentry_put(wentry); - spin_lock(&master_watchlist_lock); - hlist_del_rcu(&wentry->w_master); - call_rcu(&wentry->w_rcu, audit_wentry_rcu_put); - spin_unlock(&master_watchlist_lock); + if (watch) { + hlist_del_init(&watch->w_node); + spin_lock(&master_watchlist_lock); + hlist_del_init(&watch->w_master); + spin_unlock(&master_watchlist_lock); + audit_watch_put(watch); + audit_watch_put(watch); } } @@ -400,29 +372,66 @@ static inline void audit_destroy_wentry( */ static inline void audit_drain_watchlist(struct audit_inode_data *data) { - struct audit_wentry *wentry; + struct audit_watch *watch; struct hlist_node *pos, *tmp; - hlist_for_each_entry_safe(wentry, pos, tmp, &data->watchlist, w_node) - audit_destroy_wentry(wentry); + hlist_for_each_entry_safe(watch, pos, tmp, &data->watchlist, w_node) + audit_destroy_watch(watch); } -static inline void audit_data_free(struct audit_inode_data *data) +static inline void audit_data_put(struct audit_inode_data *data) { - if (data) { - audit_drain_watchlist(data); - audit_wentry_put(data->wentry); - kfree(data); + if (!data) + return; + + if (data->next_hash && atomic_read(&data->count) == 2 && !data->watch) { + spin_lock(&auditfs_hash_lock); + + /* We are last user. Remove it from the hash, give it back + if data->next_hash && audit + if (data-> + if (data && atomic_dec_and_test(&data->count)) { + /* If there are fewer items in the pool than there are watches, + allowing for the one extra item which the pool might need if + audit_insert_watch is currently running, then we should stick + this one back in the pool instead of freeing it */ + nr_watches = atomic_read(&audit_nr_watches); + if (nr_watches + 1 > atomic_read(&audit_pool_size)) { + spin_lock(&auditfs_hash_lock); + data->next_hash = audit_data_pool; + audit_data_pool = data; + atomic_inc(&audit_pool_size); + } else { + kfree(data); + } } } + +static inline void audit_data_free(struct audit_inode_data *data) +{ + int nr_watches; + + audit_drain_watchlist(data); + audit_watch_put(data->watch); + audit_data_put(data); +} + static inline int audit_insert_watch(struct audit_watch *watch, uid_t loginuid) { int ret; struct nameidata nd; + struct audit_inode_data *pdata; + + atomic_inc(&audit_nr_watches); + + /* Grow the pool by two -- one for the watch itself, and + one for the parent directory */ + if (audit_data_pool_grow()) + return -ENOMEM; - ret = path_lookup(watch->path, LOOKUP_PARENT, &nd); + ret = path_lookup(watch->w_path, LOOKUP_PARENT, &nd); if (ret < 0) goto audit_insert_watch_exit; @@ -430,12 +439,18 @@ static inline int audit_insert_watch(str if (nd.last_type != LAST_NORM || !nd.last.name) goto audit_insert_watch_release; - ret = audit_create_wentry(watch->path, - nd.last.name, - watch->filterkey, - watch->perms, - nd.dentry->d_inode->i_sb->s_dev, - inode_audit_data(nd.dentry->d_inode)); + + ret = -ENOMEM; + pdata = audit_data_get(nd.dentry->d_inode, 1); + if (!pdata) + goto audit_insert_watch_release; + + ret = audit_create_watch(watch->w_path, + nd.last.name, + watch->w_filterkey, + watch->w_perms, + nd.dentry->d_inode->i_sb->s_dev, + pdata); if (ret < 0) goto audit_insert_watch_release; @@ -444,8 +459,11 @@ static inline int audit_insert_watch(str dput(d_lookup(nd.dentry, &nd.last)); audit_insert_watch_release: + audit_data_put(pdata); path_release(&nd); audit_insert_watch_exit: + if (ret) + atomic_dec(&audit_nr_watches); return ret; } @@ -460,9 +478,9 @@ static inline int audit_remove_watch(str int ret; struct nameidata nd; struct audit_inode_data *data; - struct audit_wentry *wentry; + struct audit_watch *real; - ret = path_lookup(watch->path, LOOKUP_PARENT, &nd); + ret = path_lookup(watch->w_path, LOOKUP_PARENT, &nd); if (ret < 0) goto audit_remove_watch_exit; @@ -470,19 +488,21 @@ static inline int audit_remove_watch(str if (nd.last_type != LAST_NORM || !nd.last.name) goto audit_remove_watch_release; - data = inode_audit_data(nd.dentry->d_inode); + data = audit_data_get(nd.dentry->d_inode, 0); + if (!data) + goto audit_remove_watch_release; write_lock(&data->lock); - wentry = audit_wentry_fetch(nd.last.name, data); - if (!wentry) { + real = audit_watch_fetch(nd.last.name, data); + if (!real) { write_unlock(&data->lock); goto audit_remove_watch_release; } - audit_destroy_wentry(wentry); - audit_wentry_put(wentry); + audit_destroy_watch(real); + audit_watch_put(real); audit_log(NULL, AUDIT_CONFIG_CHANGE, "auid=%u removed watch\n", loginuid); write_unlock(&data->lock); - + audit_data_put(data); ret = 0; audit_remove_watch_release: @@ -491,20 +511,20 @@ audit_remove_watch_exit: return ret; } -struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry) +struct audit_watch *audit_watch_get(struct audit_watch *watch) { - if (wentry) { - BUG_ON(!atomic_read(&wentry->w_count)); - atomic_inc(&wentry->w_count); + if (watch) { + BUG_ON(!atomic_read(&watch->w_count)); + atomic_inc(&watch->w_count); } - return wentry; + return watch; } -void audit_wentry_put(struct audit_wentry *wentry) +void audit_watch_put(struct audit_watch *watch) { - if (wentry && atomic_dec_and_test(&wentry->w_count)) - audit_wentry_free(wentry); + if (watch && atomic_dec_and_test(&watch->w_count)) + audit_watch_free(watch); } /* @@ -522,7 +542,7 @@ void audit_wentry_put(struct audit_wentr */ void audit_update_watch(struct dentry *dentry, int remove) { - struct audit_wentry *wentry; + struct audit_watch *watch = NULL; struct audit_inode_data *data, *parent; if (likely(!audit_enabled)) @@ -534,37 +554,56 @@ void audit_update_watch(struct dentry *d if (!dentry->d_parent || !dentry->d_parent->d_inode) return; - data = inode_audit_data(dentry->d_inode); - if (!data) { - printk(KERN_WARNING "Hmmm. No audit data for inode #%lu at %p. name %s\n", - dentry->d_inode->i_ino, dentry->d_inode, dentry->d_name.name); + /* If there's no audit data on the parent inode, then there can + be no watches to add or remove */ + parent = audit_data_get(dentry->d_parent->d_inode, 0); + if (!parent) return; - } - parent = inode_audit_data(dentry->d_parent->d_inode); - wentry = audit_wentry_fetch_lock(dentry->d_name.name, parent); + watch = audit_watch_fetch_lock(dentry->d_name.name, parent); + + /* Fetch audit data, using the preallocated one from the watch if + there is actually a relevant watch and the inode didn't already + have any audit data */ + data = audit_data_get(dentry->d_inode, !!watch); + + /* If there's no data, then there wasn't a watch either. + Nothing to see here; move along */ + if (!data) + goto put_watch; write_lock(&data->lock); /* FIXME: long watchlist == too much spinning? */ - if (remove) { + if (remove == 1) { + /* Inode actually being removed. If it was a directory, drain + its watchlist too. */ audit_drain_watchlist(data); - if ((wentry && data->wentry)) - if (!strcmp(wentry->w_watch->name, - data->wentry->w_watch->name)) { - audit_wentry_put(data->wentry); - data->wentry = NULL; + } + if (remove) { + /* If the inode was being watched for _this_ pathname, clear the watch */ + /* FIXME: If there was _another_ path to the same inode which was + supposed to be watched, we ought to continue watching. */ + if (watch && data->watch && + !strcmp(watch->w_name, data->watch->w_name)) { + audit_watch_put(data->watch); + data->watch = NULL; } - } else if (!data->wentry) { - data->wentry = audit_wentry_get(wentry); - audit_pin(data->wentry, dentry); - } else if (hlist_unhashed(&data->wentry->w_node)) { - audit_wentry_put(data->wentry); - data->wentry = audit_wentry_get(wentry); - audit_pin(data->wentry, dentry); + } else if (!data->watch) { + /* Inode wasn't watched before. Maybe it is now */ + data->watch = audit_watch_get(watch); + audit_pin(data->watch, dentry); + } else if (hlist_unhashed(&data->watch->w_node)) { + /* Old watch is dead now. Drop it and add new one .*/ + audit_watch_put(data->watch); + data->watch = audit_watch_get(watch); + audit_pin(data->watch, dentry); } + /* FIXME: If inode's i_audit is no longer used, clear it. */ write_unlock(&data->lock); - - audit_wentry_put(wentry); + audit_data_put(data); + put_watch: + audit_watch_put(watch); + audit_data_put(parent); } /* Convert a watch to a audit_skb_list */ @@ -575,15 +614,15 @@ struct audit_skb_list *audit_to_skb(stru struct audit_skb_list *entry; /* We must include space for both "\0" */ - size = sizeof(struct watch_transport) + strlen(watch->path) + - strlen(watch->filterkey) + 2; + size = sizeof(struct watch_transport) + strlen(watch->w_path) + + strlen(watch->w_filterkey) + 2; entry = ERR_PTR(-ENOMEM); memblk = audit_to_transport(watch, size); if (!memblk) goto audit_queue_watch_exit; - entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + entry = kmalloc(sizeof(*entry), GFP_KERNEL); if (!entry) { entry = ERR_PTR(-ENOMEM); goto audit_queue_watch_exit; @@ -601,7 +640,7 @@ audit_queue_watch_exit: * file system and send it to user space. There will never be concurrent * readers of this list. * - * The reference to wentry will not be put back during a read upon a + * The reference to watch will not be put back during a read upon a * watch removal, until after we're done reading. So, the potential * for the rug being pulled out from under us is NIL. * @@ -613,21 +652,42 @@ int audit_list_watches(int pid, int seq) struct hlist_head skb_list; struct hlist_node *tmp, *pos; struct audit_skb_list *entry; - struct audit_wentry *wentry; + struct audit_watch *watch; + restart: INIT_HLIST_HEAD(&skb_list); + spin_lock(&master_watchlist_lock); - rcu_read_lock(); - hlist_for_each_entry_rcu(wentry, pos, &master_watchlist, w_master) { - entry = audit_to_skb(wentry->w_watch); + hlist_for_each_entry(watch, pos, &master_watchlist, w_master) { + audit_watch_get(watch); + spin_unlock(&master_watchlist_lock); + entry = audit_to_skb(watch); if (IS_ERR(entry)) { ret = PTR_ERR(entry); - rcu_read_unlock(); + spin_unlock(&master_watchlist_lock); goto audit_list_watches_fail; } hlist_add_head(&entry->list, &skb_list); + spin_lock(&master_watchlist_lock); + if (hlist_unhashed(&watch->w_master)) { + /* This watch was removed from the list while we + pondered it. We could play tricks to find how far + we'd got, but we might as well just start again + from scratch. There's no real chance of livelock, + as the number of watches in the system has + decreased, and the netlink sem prevents new watches + from being added while we're looping */ + audit_watch_put(watch); + hlist_for_each_entry_safe(entry, pos, tmp, &skb_list, list) { + hlist_del(&entry->list); + kfree(entry->memblk); + kfree(entry); + } + goto restart; + } + audit_watch_put(watch); } - rcu_read_unlock(); + spin_unlock(&master_watchlist_lock); hlist_for_each_entry_safe(entry, pos, tmp, &skb_list, list) { audit_send_reply(pid, seq, AUDIT_WATCH_LIST, 0, 1, @@ -655,6 +715,7 @@ int audit_receive_watch(int type, int pi { int ret = 0; struct audit_watch *watch = NULL; + char *payload = (char *)&req[1]; ret = -EINVAL; if (req->pathlen > PATH_MAX || req->pathlen == 0) @@ -663,6 +724,9 @@ int audit_receive_watch(int type, int pi if (req->fklen > AUDIT_FILTERKEY_MAX) goto audit_receive_watch_exit; + if ( payload[req->fklen] != '/') + goto audit_receive_watch_exit; + if (req->perms > (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND)) goto audit_receive_watch_exit; @@ -687,36 +751,6 @@ audit_receive_watch_exit: return ret; } -int audit_inode_alloc(struct inode *inode) -{ - struct audit_inode_data *data; - struct audit_inode_data **list; - int h = hash_ptr(inode, auditfs_hash_bits); - - data = kmalloc(sizeof(struct audit_inode_data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - INIT_HLIST_HEAD(&data->watchlist); - data->wentry = NULL; - data->lock = RW_LOCK_UNLOCKED; - data->inode = inode; - data->next_hash = NULL; - - /* Add it to the hash table */ - list = &auditfs_hash_table[h]; - spin_lock(&auditfs_hash_lock); - - while (*list && (unsigned long)((*list)->inode) < (unsigned long)inode) - list = &(*list)->next_hash; - - data->next_hash = *list; - *list = data; - - spin_unlock(&auditfs_hash_lock); - return 0; -} - void audit_inode_free(struct inode *inode) { struct audit_inode_data *data = NULL; @@ -751,15 +785,17 @@ void audit_inode_free(struct inode *inod void audit_dentry_unpin(struct dentry *dentry) { struct audit_inode_data *parent; - struct audit_wentry *wentry; + struct audit_watch *watch; - parent = inode_audit_data(dentry->d_parent->d_inode); - wentry = audit_wentry_fetch_lock(dentry->d_name.name, parent); - if (wentry) { - struct audit_inode_data *data = inode_audit_data(dentry->d_inode); + parent = audit_data_get(dentry->d_parent->d_inode, 0); + watch = audit_watch_fetch_lock(dentry->d_name.name, parent); + if (watch) { + struct audit_inode_data *data = audit_data_get(dentry->d_inode, 0); - audit_unpin(data->wentry); - audit_wentry_put(wentry); + if (data) + audit_unpin(data->watch); + audit_watch_put(watch); + audit_data_put(data); } } @@ -774,12 +810,6 @@ int audit_filesystem_init(void) if (!audit_watch_cache) goto audit_filesystem_init_fail; - audit_wentry_cache = - kmem_cache_create("audit_wentry_cache", - sizeof(struct audit_wentry), 0, 0, NULL, NULL); - if (!audit_wentry_cache) - goto audit_filesystem_init_fail; - /* Set up hash table for inode objects */ auditfs_hash_bits = long_log2(auditfs_cache_buckets); if (auditfs_cache_buckets != (1 << auditfs_hash_bits)) { @@ -804,7 +834,6 @@ int audit_filesystem_init(void) audit_filesystem_init_fail: kmem_cache_destroy(audit_watch_cache); - kmem_cache_destroy(audit_wentry_cache); audit_filesystem_init_exit: return ret; } @@ -814,7 +843,7 @@ int audit_notify_watch(struct inode *ino { int ret = 0; struct audit_inode_data *data; - struct audit_wentry *wentry = NULL; + struct audit_watch *watch = NULL; if (likely(!audit_enabled)) return 0; @@ -822,29 +851,29 @@ int audit_notify_watch(struct inode *ino if (!inode || !current->audit_context) return 0; - data = inode_audit_data(inode); + data = audit_data_get(inode, 0); if (!data) return 0; /* - * Won't be able to drop i_audit->wentry reference + * Won't be able to drop i_audit->watch reference * before we obtain our own reference */ read_lock(&data->lock); - wentry = audit_wentry_get(data->wentry); + watch = audit_watch_get(data->watch); read_unlock(&data->lock); - if (!wentry) + if (!watch) return 0; - if (mask && (wentry->w_watch->perms && !(wentry->w_watch->perms&mask))) + if (mask && (watch->w_perms && !(watch->w_perms & mask))) goto audit_notify_watch_fail; - ret = auditfs_attach_wdata(inode, wentry, mask); - if (!ret) - return 0; - - audit_notify_watch_fail: - audit_wentry_put(wentry); + ret = auditfs_attach_wdata(inode, watch, mask); + if (ret) { + audit_notify_watch_fail: + audit_watch_put(watch); + } + audit_data_put(data); return ret; }