Commit 2fb59d62 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'audit.b43' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/audit-current

* 'audit.b43' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/audit-current:
  [PATCH] audit: watching subtrees
  [PATCH] new helper - inotify_evict_watch()
  [PATCH] new helper - inotify_clone_watch()
  [PATCH] new helpers - collect_mounts() and release_collected_mounts()
  [PATCH] pass dentry to audit_inode()/audit_inode_child()
parents efdc3131 74c3cbe3
...@@ -38,7 +38,7 @@ int sysctl_vfs_cache_pressure __read_mostly = 100; ...@@ -38,7 +38,7 @@ int sysctl_vfs_cache_pressure __read_mostly = 100;
EXPORT_SYMBOL_GPL(sysctl_vfs_cache_pressure); EXPORT_SYMBOL_GPL(sysctl_vfs_cache_pressure);
__cacheline_aligned_in_smp DEFINE_SPINLOCK(dcache_lock); __cacheline_aligned_in_smp DEFINE_SPINLOCK(dcache_lock);
static __cacheline_aligned_in_smp DEFINE_SEQLOCK(rename_lock); __cacheline_aligned_in_smp DEFINE_SEQLOCK(rename_lock);
EXPORT_SYMBOL(dcache_lock); EXPORT_SYMBOL(dcache_lock);
......
...@@ -413,7 +413,7 @@ struct dentry *debugfs_rename(struct dentry *old_dir, struct dentry *old_dentry, ...@@ -413,7 +413,7 @@ struct dentry *debugfs_rename(struct dentry *old_dir, struct dentry *old_dentry,
d_move(old_dentry, dentry); d_move(old_dentry, dentry);
fsnotify_move(old_dir->d_inode, new_dir->d_inode, old_name, fsnotify_move(old_dir->d_inode, new_dir->d_inode, old_name,
old_dentry->d_name.name, S_ISDIR(old_dentry->d_inode->i_mode), old_dentry->d_name.name, S_ISDIR(old_dentry->d_inode->i_mode),
NULL, old_dentry->d_inode); NULL, old_dentry);
fsnotify_oldname_free(old_name); fsnotify_oldname_free(old_name);
unlock_rename(new_dir, old_dir); unlock_rename(new_dir, old_dir);
dput(dentry); dput(dentry);
......
...@@ -666,6 +666,49 @@ s32 inotify_add_watch(struct inotify_handle *ih, struct inotify_watch *watch, ...@@ -666,6 +666,49 @@ s32 inotify_add_watch(struct inotify_handle *ih, struct inotify_watch *watch,
} }
EXPORT_SYMBOL_GPL(inotify_add_watch); EXPORT_SYMBOL_GPL(inotify_add_watch);
/**
* inotify_clone_watch - put the watch next to existing one
* @old: already installed watch
* @new: new watch
*
* Caller must hold the inotify_mutex of inode we are dealing with;
* it is expected to remove the old watch before unlocking the inode.
*/
s32 inotify_clone_watch(struct inotify_watch *old, struct inotify_watch *new)
{
struct inotify_handle *ih = old->ih;
int ret = 0;
new->mask = old->mask;
new->ih = ih;
mutex_lock(&ih->mutex);
/* Initialize a new watch */
ret = inotify_handle_get_wd(ih, new);
if (unlikely(ret))
goto out;
ret = new->wd;
get_inotify_handle(ih);
new->inode = igrab(old->inode);
list_add(&new->h_list, &ih->watches);
list_add(&new->i_list, &old->inode->inotify_watches);
out:
mutex_unlock(&ih->mutex);
return ret;
}
void inotify_evict_watch(struct inotify_watch *watch)
{
get_inotify_watch(watch);
mutex_lock(&watch->ih->mutex);
inotify_remove_watch_locked(watch->ih, watch);
mutex_unlock(&watch->ih->mutex);
}
/** /**
* inotify_rm_wd - remove a watch from an inotify instance * inotify_rm_wd - remove a watch from an inotify instance
* @ih: inotify handle * @ih: inotify handle
......
...@@ -1174,7 +1174,7 @@ static int fastcall do_path_lookup(int dfd, const char *name, ...@@ -1174,7 +1174,7 @@ static int fastcall do_path_lookup(int dfd, const char *name,
out: out:
if (unlikely(!retval && !audit_dummy_context() && nd->dentry && if (unlikely(!retval && !audit_dummy_context() && nd->dentry &&
nd->dentry->d_inode)) nd->dentry->d_inode))
audit_inode(name, nd->dentry->d_inode); audit_inode(name, nd->dentry);
out_fail: out_fail:
return retval; return retval;
...@@ -1214,7 +1214,7 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt, ...@@ -1214,7 +1214,7 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
retval = path_walk(name, nd); retval = path_walk(name, nd);
if (unlikely(!retval && !audit_dummy_context() && nd->dentry && if (unlikely(!retval && !audit_dummy_context() && nd->dentry &&
nd->dentry->d_inode)) nd->dentry->d_inode))
audit_inode(name, nd->dentry->d_inode); audit_inode(name, nd->dentry);
return retval; return retval;
...@@ -1469,7 +1469,7 @@ static int may_delete(struct inode *dir,struct dentry *victim,int isdir) ...@@ -1469,7 +1469,7 @@ static int may_delete(struct inode *dir,struct dentry *victim,int isdir)
return -ENOENT; return -ENOENT;
BUG_ON(victim->d_parent->d_inode != dir); BUG_ON(victim->d_parent->d_inode != dir);
audit_inode_child(victim->d_name.name, victim->d_inode, dir); audit_inode_child(victim->d_name.name, victim, dir);
error = permission(dir,MAY_WRITE | MAY_EXEC, NULL); error = permission(dir,MAY_WRITE | MAY_EXEC, NULL);
if (error) if (error)
...@@ -1783,7 +1783,7 @@ int open_namei(int dfd, const char *pathname, int flag, ...@@ -1783,7 +1783,7 @@ int open_namei(int dfd, const char *pathname, int flag,
* It already exists. * It already exists.
*/ */
mutex_unlock(&dir->d_inode->i_mutex); mutex_unlock(&dir->d_inode->i_mutex);
audit_inode(pathname, path.dentry->d_inode); audit_inode(pathname, path.dentry);
error = -EEXIST; error = -EEXIST;
if (flag & O_EXCL) if (flag & O_EXCL)
...@@ -2562,7 +2562,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, ...@@ -2562,7 +2562,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
if (!error) { if (!error) {
const char *new_name = old_dentry->d_name.name; const char *new_name = old_dentry->d_name.name;
fsnotify_move(old_dir, new_dir, old_name, new_name, is_dir, fsnotify_move(old_dir, new_dir, old_name, new_name, is_dir,
new_dentry->d_inode, old_dentry->d_inode); new_dentry->d_inode, old_dentry);
} }
fsnotify_oldname_free(old_name); fsnotify_oldname_free(old_name);
......
...@@ -246,7 +246,7 @@ static struct vfsmount *clone_mnt(struct vfsmount *old, struct dentry *root, ...@@ -246,7 +246,7 @@ static struct vfsmount *clone_mnt(struct vfsmount *old, struct dentry *root,
list_add(&mnt->mnt_slave, &old->mnt_slave_list); list_add(&mnt->mnt_slave, &old->mnt_slave_list);
mnt->mnt_master = old; mnt->mnt_master = old;
CLEAR_MNT_SHARED(mnt); CLEAR_MNT_SHARED(mnt);
} else { } else if (!(flag & CL_PRIVATE)) {
if ((flag & CL_PROPAGATION) || IS_MNT_SHARED(old)) if ((flag & CL_PROPAGATION) || IS_MNT_SHARED(old))
list_add(&mnt->mnt_share, &old->mnt_share); list_add(&mnt->mnt_share, &old->mnt_share);
if (IS_MNT_SLAVE(old)) if (IS_MNT_SLAVE(old))
...@@ -746,6 +746,26 @@ struct vfsmount *copy_tree(struct vfsmount *mnt, struct dentry *dentry, ...@@ -746,6 +746,26 @@ struct vfsmount *copy_tree(struct vfsmount *mnt, struct dentry *dentry,
return NULL; return NULL;
} }
struct vfsmount *collect_mounts(struct vfsmount *mnt, struct dentry *dentry)
{
struct vfsmount *tree;
down_read(&namespace_sem);
tree = copy_tree(mnt, dentry, CL_COPY_ALL | CL_PRIVATE);
up_read(&namespace_sem);
return tree;
}
void drop_collected_mounts(struct vfsmount *mnt)
{
LIST_HEAD(umount_list);
down_read(&namespace_sem);
spin_lock(&vfsmount_lock);
umount_tree(mnt, 0, &umount_list);
spin_unlock(&vfsmount_lock);
up_read(&namespace_sem);
release_mounts(&umount_list);
}
/* /*
* @source_mnt : mount tree to be attached * @source_mnt : mount tree to be attached
* @nd : place the mount tree @source_mnt is attached * @nd : place the mount tree @source_mnt is attached
......
...@@ -569,7 +569,7 @@ asmlinkage long sys_fchmod(unsigned int fd, mode_t mode) ...@@ -569,7 +569,7 @@ asmlinkage long sys_fchmod(unsigned int fd, mode_t mode)
dentry = file->f_path.dentry; dentry = file->f_path.dentry;
inode = dentry->d_inode; inode = dentry->d_inode;
audit_inode(NULL, inode); audit_inode(NULL, dentry);
err = -EROFS; err = -EROFS;
if (IS_RDONLY(inode)) if (IS_RDONLY(inode))
...@@ -727,7 +727,7 @@ asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group) ...@@ -727,7 +727,7 @@ asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group)
goto out; goto out;
dentry = file->f_path.dentry; dentry = file->f_path.dentry;
audit_inode(NULL, dentry->d_inode); audit_inode(NULL, dentry);
error = chown_common(dentry, user, group); error = chown_common(dentry, user, group);
fput(file); fput(file);
out: out:
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#define CL_COPY_ALL 0x04 #define CL_COPY_ALL 0x04
#define CL_MAKE_SHARED 0x08 #define CL_MAKE_SHARED 0x08
#define CL_PROPAGATION 0x10 #define CL_PROPAGATION 0x10
#define CL_PRIVATE 0x20
static inline void set_mnt_shared(struct vfsmount *mnt) static inline void set_mnt_shared(struct vfsmount *mnt)
{ {
......
...@@ -267,7 +267,7 @@ sys_fsetxattr(int fd, char __user *name, void __user *value, ...@@ -267,7 +267,7 @@ sys_fsetxattr(int fd, char __user *name, void __user *value,
if (!f) if (!f)
return error; return error;
dentry = f->f_path.dentry; dentry = f->f_path.dentry;
audit_inode(NULL, dentry->d_inode); audit_inode(NULL, dentry);
error = setxattr(dentry, name, value, size, flags); error = setxattr(dentry, name, value, size, flags);
fput(f); fput(f);
return error; return error;
...@@ -349,7 +349,7 @@ sys_fgetxattr(int fd, char __user *name, void __user *value, size_t size) ...@@ -349,7 +349,7 @@ sys_fgetxattr(int fd, char __user *name, void __user *value, size_t size)
f = fget(fd); f = fget(fd);
if (!f) if (!f)
return error; return error;
audit_inode(NULL, f->f_path.dentry->d_inode); audit_inode(NULL, f->f_path.dentry);
error = getxattr(f->f_path.dentry, name, value, size); error = getxattr(f->f_path.dentry, name, value, size);
fput(f); fput(f);
return error; return error;
...@@ -422,7 +422,7 @@ sys_flistxattr(int fd, char __user *list, size_t size) ...@@ -422,7 +422,7 @@ sys_flistxattr(int fd, char __user *list, size_t size)
f = fget(fd); f = fget(fd);
if (!f) if (!f)
return error; return error;
audit_inode(NULL, f->f_path.dentry->d_inode); audit_inode(NULL, f->f_path.dentry);
error = listxattr(f->f_path.dentry, list, size); error = listxattr(f->f_path.dentry, list, size);
fput(f); fput(f);
return error; return error;
...@@ -485,7 +485,7 @@ sys_fremovexattr(int fd, char __user *name) ...@@ -485,7 +485,7 @@ sys_fremovexattr(int fd, char __user *name)
if (!f) if (!f)
return error; return error;
dentry = f->f_path.dentry; dentry = f->f_path.dentry;
audit_inode(NULL, dentry->d_inode); audit_inode(NULL, dentry);
error = removexattr(dentry, name); error = removexattr(dentry, name);
fput(f); fput(f);
return error; return error;
......
...@@ -63,6 +63,8 @@ ...@@ -63,6 +63,8 @@
#define AUDIT_ADD_RULE 1011 /* Add syscall filtering rule */ #define AUDIT_ADD_RULE 1011 /* Add syscall filtering rule */
#define AUDIT_DEL_RULE 1012 /* Delete syscall filtering rule */ #define AUDIT_DEL_RULE 1012 /* Delete syscall filtering rule */
#define AUDIT_LIST_RULES 1013 /* List syscall filtering rules */ #define AUDIT_LIST_RULES 1013 /* List syscall filtering rules */
#define AUDIT_TRIM 1014 /* Trim junk from watched tree */
#define AUDIT_MAKE_EQUIV 1015 /* Append to watched tree */
#define AUDIT_TTY_GET 1016 /* Get TTY auditing status */ #define AUDIT_TTY_GET 1016 /* Get TTY auditing status */
#define AUDIT_TTY_SET 1017 /* Set TTY auditing status */ #define AUDIT_TTY_SET 1017 /* Set TTY auditing status */
...@@ -203,6 +205,7 @@ ...@@ -203,6 +205,7 @@
#define AUDIT_SUCCESS 104 /* exit >= 0; value ignored */ #define AUDIT_SUCCESS 104 /* exit >= 0; value ignored */
#define AUDIT_WATCH 105 #define AUDIT_WATCH 105
#define AUDIT_PERM 106 #define AUDIT_PERM 106
#define AUDIT_DIR 107
#define AUDIT_ARG0 200 #define AUDIT_ARG0 200
#define AUDIT_ARG1 (AUDIT_ARG0+1) #define AUDIT_ARG1 (AUDIT_ARG0+1)
...@@ -366,8 +369,8 @@ extern void audit_syscall_entry(int arch, ...@@ -366,8 +369,8 @@ extern void audit_syscall_entry(int arch,
extern void audit_syscall_exit(int failed, long return_code); extern void audit_syscall_exit(int failed, long return_code);
extern void __audit_getname(const char *name); extern void __audit_getname(const char *name);
extern void audit_putname(const char *name); extern void audit_putname(const char *name);
extern void __audit_inode(const char *name, const struct inode *inode); extern void __audit_inode(const char *name, const struct dentry *dentry);
extern void __audit_inode_child(const char *dname, const struct inode *inode, extern void __audit_inode_child(const char *dname, const struct dentry *dentry,
const struct inode *parent); const struct inode *parent);
extern void __audit_ptrace(struct task_struct *t); extern void __audit_ptrace(struct task_struct *t);
...@@ -381,15 +384,15 @@ static inline void audit_getname(const char *name) ...@@ -381,15 +384,15 @@ static inline void audit_getname(const char *name)
if (unlikely(!audit_dummy_context())) if (unlikely(!audit_dummy_context()))
__audit_getname(name); __audit_getname(name);
} }
static inline void audit_inode(const char *name, const struct inode *inode) { static inline void audit_inode(const char *name, const struct dentry *dentry) {
if (unlikely(!audit_dummy_context())) if (unlikely(!audit_dummy_context()))
__audit_inode(name, inode); __audit_inode(name, dentry);
} }
static inline void audit_inode_child(const char *dname, static inline void audit_inode_child(const char *dname,
const struct inode *inode, const struct dentry *dentry,
const struct inode *parent) { const struct inode *parent) {
if (unlikely(!audit_dummy_context())) if (unlikely(!audit_dummy_context()))
__audit_inode_child(dname, inode, parent); __audit_inode_child(dname, dentry, parent);
} }
void audit_core_dumps(long signr); void audit_core_dumps(long signr);
...@@ -477,9 +480,9 @@ extern int audit_signals; ...@@ -477,9 +480,9 @@ extern int audit_signals;
#define audit_dummy_context() 1 #define audit_dummy_context() 1
#define audit_getname(n) do { ; } while (0) #define audit_getname(n) do { ; } while (0)
#define audit_putname(n) do { ; } while (0) #define audit_putname(n) do { ; } while (0)
#define __audit_inode(n,i) do { ; } while (0) #define __audit_inode(n,d) do { ; } while (0)
#define __audit_inode_child(d,i,p) do { ; } while (0) #define __audit_inode_child(d,i,p) do { ; } while (0)
#define audit_inode(n,i) do { ; } while (0) #define audit_inode(n,d) do { ; } while (0)
#define audit_inode_child(d,i,p) do { ; } while (0) #define audit_inode_child(d,i,p) do { ; } while (0)
#define audit_core_dumps(i) do { ; } while (0) #define audit_core_dumps(i) do { ; } while (0)
#define auditsc_get_stamp(c,t,s) do { BUG(); } while (0) #define auditsc_get_stamp(c,t,s) do { BUG(); } while (0)
......
...@@ -178,6 +178,7 @@ d_iput: no no no yes ...@@ -178,6 +178,7 @@ d_iput: no no no yes
#define DCACHE_INOTIFY_PARENT_WATCHED 0x0020 /* Parent inode is watched */ #define DCACHE_INOTIFY_PARENT_WATCHED 0x0020 /* Parent inode is watched */
extern spinlock_t dcache_lock; extern spinlock_t dcache_lock;
extern seqlock_t rename_lock;
/** /**
* d_drop - drop a dentry * d_drop - drop a dentry
......
...@@ -1470,6 +1470,8 @@ extern long do_mount(char *, char *, char *, unsigned long, void *); ...@@ -1470,6 +1470,8 @@ extern long do_mount(char *, char *, char *, unsigned long, void *);
extern struct vfsmount *copy_tree(struct vfsmount *, struct dentry *, int); extern struct vfsmount *copy_tree(struct vfsmount *, struct dentry *, int);
extern void mnt_set_mountpoint(struct vfsmount *, struct dentry *, extern void mnt_set_mountpoint(struct vfsmount *, struct dentry *,
struct vfsmount *); struct vfsmount *);
extern struct vfsmount *collect_mounts(struct vfsmount *, struct dentry *);
extern void drop_collected_mounts(struct vfsmount *);
extern int vfs_statfs(struct dentry *, struct kstatfs *); extern int vfs_statfs(struct dentry *, struct kstatfs *);
......
...@@ -41,8 +41,9 @@ static inline void fsnotify_d_move(struct dentry *entry) ...@@ -41,8 +41,9 @@ static inline void fsnotify_d_move(struct dentry *entry)
*/ */
static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir, static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir,
const char *old_name, const char *new_name, const char *old_name, const char *new_name,
int isdir, struct inode *target, struct inode *source) int isdir, struct inode *target, struct dentry *moved)
{ {
struct inode *source = moved->d_inode;
u32 cookie = inotify_get_cookie(); u32 cookie = inotify_get_cookie();
if (old_dir == new_dir) if (old_dir == new_dir)
...@@ -67,7 +68,7 @@ static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir, ...@@ -67,7 +68,7 @@ static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir,
if (source) { if (source) {
inotify_inode_queue_event(source, IN_MOVE_SELF, 0, NULL, NULL); inotify_inode_queue_event(source, IN_MOVE_SELF, 0, NULL, NULL);
} }
audit_inode_child(new_name, source, new_dir); audit_inode_child(new_name, moved, new_dir);
} }
/* /*
...@@ -98,7 +99,7 @@ static inline void fsnotify_create(struct inode *inode, struct dentry *dentry) ...@@ -98,7 +99,7 @@ static inline void fsnotify_create(struct inode *inode, struct dentry *dentry)
inode_dir_notify(inode, DN_CREATE); inode_dir_notify(inode, DN_CREATE);
inotify_inode_queue_event(inode, IN_CREATE, 0, dentry->d_name.name, inotify_inode_queue_event(inode, IN_CREATE, 0, dentry->d_name.name,
dentry->d_inode); dentry->d_inode);
audit_inode_child(dentry->d_name.name, dentry->d_inode, inode); audit_inode_child(dentry->d_name.name, dentry, inode);
} }
/* /*
...@@ -109,7 +110,7 @@ static inline void fsnotify_mkdir(struct inode *inode, struct dentry *dentry) ...@@ -109,7 +110,7 @@ static inline void fsnotify_mkdir(struct inode *inode, struct dentry *dentry)
inode_dir_notify(inode, DN_CREATE); inode_dir_notify(inode, DN_CREATE);
inotify_inode_queue_event(inode, IN_CREATE | IN_ISDIR, 0, inotify_inode_queue_event(inode, IN_CREATE | IN_ISDIR, 0,
dentry->d_name.name, dentry->d_inode); dentry->d_name.name, dentry->d_inode);
audit_inode_child(dentry->d_name.name, dentry->d_inode, inode); audit_inode_child(dentry->d_name.name, dentry, inode);
} }
/* /*
......
...@@ -120,6 +120,8 @@ extern __s32 inotify_find_update_watch(struct inotify_handle *, struct inode *, ...@@ -120,6 +120,8 @@ extern __s32 inotify_find_update_watch(struct inotify_handle *, struct inode *,
u32); u32);
extern __s32 inotify_add_watch(struct inotify_handle *, struct inotify_watch *, extern __s32 inotify_add_watch(struct inotify_handle *, struct inotify_watch *,
struct inode *, __u32); struct inode *, __u32);
extern __s32 inotify_clone_watch(struct inotify_watch *, struct inotify_watch *);
extern void inotify_evict_watch(struct inotify_watch *);
extern int inotify_rm_watch(struct inotify_handle *, struct inotify_watch *); extern int inotify_rm_watch(struct inotify_handle *, struct inotify_watch *);
extern int inotify_rm_wd(struct inotify_handle *, __u32); extern int inotify_rm_wd(struct inotify_handle *, __u32);
extern void inotify_remove_watch_locked(struct inotify_handle *, extern void inotify_remove_watch_locked(struct inotify_handle *,
......
...@@ -234,6 +234,10 @@ config AUDITSYSCALL ...@@ -234,6 +234,10 @@ config AUDITSYSCALL
such as SELinux. To use audit's filesystem watch feature, please such as SELinux. To use audit's filesystem watch feature, please
ensure that INOTIFY is configured. ensure that INOTIFY is configured.
config AUDIT_TREE
def_bool y
depends on AUDITSYSCALL && INOTIFY
config IKCONFIG config IKCONFIG
tristate "Kernel .config support" tristate "Kernel .config support"
---help--- ---help---
......
...@@ -676,7 +676,7 @@ asmlinkage long sys_mq_open(const char __user *u_name, int oflag, mode_t mode, ...@@ -676,7 +676,7 @@ asmlinkage long sys_mq_open(const char __user *u_name, int oflag, mode_t mode,
if (oflag & O_CREAT) { if (oflag & O_CREAT) {
if (dentry->d_inode) { /* entry already exists */ if (dentry->d_inode) { /* entry already exists */
audit_inode(name, dentry->d_inode); audit_inode(name, dentry);
error = -EEXIST; error = -EEXIST;
if (oflag & O_EXCL) if (oflag & O_EXCL)
goto out; goto out;
...@@ -689,7 +689,7 @@ asmlinkage long sys_mq_open(const char __user *u_name, int oflag, mode_t mode, ...@@ -689,7 +689,7 @@ asmlinkage long sys_mq_open(const char __user *u_name, int oflag, mode_t mode,
error = -ENOENT; error = -ENOENT;
if (!dentry->d_inode) if (!dentry->d_inode)
goto out; goto out;
audit_inode(name, dentry->d_inode); audit_inode(name, dentry);
filp = do_open(dentry, oflag); filp = do_open(dentry, oflag);
} }
...@@ -837,7 +837,7 @@ asmlinkage long sys_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr, ...@@ -837,7 +837,7 @@ asmlinkage long sys_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr,
if (unlikely(filp->f_op != &mqueue_file_operations)) if (unlikely(filp->f_op != &mqueue_file_operations))
goto out_fput; goto out_fput;
info = MQUEUE_I(inode); info = MQUEUE_I(inode);
audit_inode(NULL, inode); audit_inode(NULL, filp->f_path.dentry);
if (unlikely(!(filp->f_mode & FMODE_WRITE))) if (unlikely(!(filp->f_mode & FMODE_WRITE)))
goto out_fput; goto out_fput;
...@@ -921,7 +921,7 @@ asmlinkage ssize_t sys_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr, ...@@ -921,7 +921,7 @@ asmlinkage ssize_t sys_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr,
if (unlikely(filp->f_op != &mqueue_file_operations)) if (unlikely(filp->f_op != &mqueue_file_operations))
goto out_fput; goto out_fput;
info = MQUEUE_I(inode); info = MQUEUE_I(inode);
audit_inode(NULL, inode); audit_inode(NULL, filp->f_path.dentry);
if (unlikely(!(filp->f_mode & FMODE_READ))) if (unlikely(!(filp->f_mode & FMODE_READ)))
goto out_fput; goto out_fput;
......
...@@ -46,6 +46,7 @@ obj-$(CONFIG_IKCONFIG) += configs.o ...@@ -46,6 +46,7 @@ obj-$(CONFIG_IKCONFIG) += configs.o
obj-$(CONFIG_STOP_MACHINE) += stop_machine.o obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
obj-$(CONFIG_AUDIT) += audit.o auditfilter.o obj-$(CONFIG_AUDIT) += audit.o auditfilter.o
obj-$(CONFIG_AUDITSYSCALL) += auditsc.o obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
obj-$(CONFIG_AUDIT_TREE) += audit_tree.o
obj-$(CONFIG_KPROBES) += kprobes.o obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_SYSFS) += ksysfs.o obj-$(CONFIG_SYSFS) += ksysfs.o
obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o
......
...@@ -468,6 +468,21 @@ int audit_send_list(void *_dest) ...@@ -468,6 +468,21 @@ int audit_send_list(void *_dest)
return 0; return 0;
} }
#ifdef CONFIG_AUDIT_TREE
static int prune_tree_thread(void *unused)
{
mutex_lock(&audit_cmd_mutex);
audit_prune_trees();
mutex_unlock(&audit_cmd_mutex);
return 0;
}
void audit_schedule_prune(void)
{
kthread_run(prune_tree_thread, NULL, "audit_prune_tree");
}
#endif
struct sk_buff *audit_make_reply(int pid, int seq, int type, int done, struct sk_buff *audit_make_reply(int pid, int seq, int type, int done,
int multi, void *payload, int size) int multi, void *payload, int size)
{ {
...@@ -540,6 +555,8 @@ static int audit_netlink_ok(struct sk_buff *skb, u16 msg_type) ...@@ -540,6 +555,8 @@ static int audit_netlink_ok(struct sk_buff *skb, u16 msg_type)
case AUDIT_SIGNAL_INFO: case AUDIT_SIGNAL_INFO:
case AUDIT_TTY_GET: case AUDIT_TTY_GET:
case AUDIT_TTY_SET: case AUDIT_TTY_SET:
case AUDIT_TRIM:
case AUDIT_MAKE_EQUIV:
if (security_netlink_recv(skb, CAP_AUDIT_CONTROL)) if (security_netlink_recv(skb, CAP_AUDIT_CONTROL))
err = -EPERM; err = -EPERM;
break; break;
...@@ -756,6 +773,76 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh) ...@@ -756,6 +773,76 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
uid, seq, data, nlmsg_len(nlh), uid, seq, data, nlmsg_len(nlh),
loginuid, sid); loginuid, sid);
break; break;
case AUDIT_TRIM:
audit_trim_trees();
ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE);
if (!ab)
break;
audit_log_format(ab, "auid=%u", loginuid);
if (sid) {
u32 len;
ctx = NULL;
if (selinux_sid_to_string(sid, &ctx, &len))
audit_log_format(ab, " ssid=%u", sid);
else
audit_log_format(ab, " subj=%s", ctx);
kfree(ctx);
}
audit_log_format(ab, " op=trim res=1");
audit_log_end(ab);
break;
case AUDIT_MAKE_EQUIV: {
void *bufp = data;
u32 sizes[2];
size_t len = nlmsg_len(nlh);
char *old, *new;
err = -EINVAL;
if (len < 2 * sizeof(u32))
break;
memcpy(sizes, bufp, 2 * sizeof(u32));
bufp += 2 * sizeof(u32);
len -= 2 * sizeof(u32);
old = audit_unpack_string(&bufp, &len, sizes[0]);
if (IS_ERR(old)) {
err = PTR_ERR(old);
break;
}
new = audit_unpack_string(&bufp, &len, sizes[1]);
if (IS_ERR(new)) {
err = PTR_ERR(new);
kfree(old);
break;
}
/* OK, here comes... */
err = audit_tag_tree(old, new);
ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE);
if (!ab) {
kfree(old);
kfree(new);
break;
}
audit_log_format(ab, "auid=%u", loginuid);
if (sid) {
u32 len;
ctx = NULL;
if (selinux_sid_to_string(sid, &ctx, &len))
audit_log_format(ab, " ssid=%u", sid);
else
audit_log_format(ab, " subj=%s", ctx);
kfree(ctx);
}
audit_log_format(ab, " op=make_equiv old=");
audit_log_untrustedstring(ab, old);
audit_log_format(ab, " new=");
audit_log_untrustedstring(ab, new);
audit_log_format(ab, " res=%d", !err);
audit_log_end(ab);
kfree(old);
kfree(new);
break;
}
case AUDIT_SIGNAL_INFO: case AUDIT_SIGNAL_INFO:
err = selinux_sid_to_string(audit_sig_sid, &ctx, &len); err = selinux_sid_to_string(audit_sig_sid, &ctx, &len);
if (err) if (err)
......
...@@ -73,6 +73,9 @@ struct audit_field { ...@@ -73,6 +73,9 @@ struct audit_field {
struct selinux_audit_rule *se_rule; struct selinux_audit_rule *se_rule;
}; };
struct audit_tree;
struct audit_chunk;
struct audit_krule { struct audit_krule {
int vers_ops; int vers_ops;
u32 flags; u32 flags;
...@@ -86,7 +89,8 @@ struct audit_krule { ...@@ -86,7 +89,8 @@ struct audit_krule {
struct audit_field *arch_f; /* quick access to arch field */ struct audit_field *arch_f; /* quick access to arch field */
struct audit_field *inode_f; /* quick access to an inode field */ struct audit_field *inode_f; /* quick access to an inode field */
struct audit_watch *watch; /* associated watch */ struct audit_watch *watch; /* associated watch */
struct list_head rlist; /* entry in audit_watch.rules list */ struct audit_tree *tree; /* associated watched tree */
struct list_head rlist; /* entry in audit_{watch,tree}.rules list */
}; };
struct audit_entry { struct audit_entry {
...@@ -130,6 +134,34 @@ extern void audit_handle_ievent(struct inotify_watch *, u32, u32, u32, ...@@ -130,6 +134,34 @@ extern void audit_handle_ievent(struct inotify_watch *, u32, u32, u32,
const char *, struct inode *); const char *, struct inode *);
extern int selinux_audit_rule_update(void); extern int selinux_audit_rule_update(void);
extern struct mutex audit_filter_mutex;
extern void audit_free_rule_rcu(struct rcu_head *);
#ifdef CONFIG_AUDIT_TREE
extern struct audit_chunk *audit_tree_lookup(const struct inode *);
extern void audit_put_chunk(struct audit_chunk *);
extern int audit_tree_match(struct audit_chunk *, struct audit_tree *);
extern int audit_make_tree(struct audit_krule *, char *, u32);
extern int audit_add_tree_rule(struct audit_krule *);
extern int audit_remove_tree_rule(struct audit_krule *);
extern void audit_trim_trees(void);
extern int audit_tag_tree(char *old, char *new);
extern void audit_schedule_prune(void);
extern void audit_prune_trees(void);
extern const char *audit_tree_path(struct audit_tree *);
extern void audit_put_tree(struct audit_tree *);
#else
#define audit_remove_tree_rule(rule) BUG()
#define audit_add_tree_rule(rule) -EINVAL
#define audit_make_tree(rule, str, op) -EINVAL
#define audit_trim_trees() (void)0
#define audit_put_tree(tree) (void)0
#define audit_tag_tree(old, new) -EINVAL
#define audit_tree_path(rule) "" /* never called */
#endif
extern char *audit_unpack_string(void **, size_t *, size_t);
#ifdef CONFIG_AUDITSYSCALL #ifdef CONFIG_AUDITSYSCALL
extern int __audit_signal_info(int sig, struct task_struct *t); extern int __audit_signal_info(int sig, struct task_struct *t);
static inline int audit_signal_info(int sig, struct task_struct *t) static inline int audit_signal_info(int sig, struct task_struct *t)
......
#include "audit.h"
#include <linux/inotify.h>
#include <linux/namei.h>
#include <linux/mount.h>
struct audit_tree;
struct audit_chunk;
struct audit_tree {
atomic_t count;
int goner;
struct audit_chunk *root;
struct list_head chunks;
struct list_head rules;
struct list_head list;
struct list_head same_root;
struct rcu_head head;
char pathname[];
};
struct audit_chunk {
struct list_head hash;
struct inotify_watch watch;
struct list_head trees; /* with root here */
int dead;
int count;
struct rcu_head head;
struct node {
struct list_head list;
struct audit_tree *owner;
unsigned index; /* index; upper bit indicates 'will prune' */
} owners[];
};
static LIST_HEAD(tree_list);
static LIST_HEAD(prune_list);
/*
* One struct chunk is attached to each inode of interest.
* We replace struct chunk on tagging/untagging.
* Rules have pointer to struct audit_tree.
* Rules have struct list_head rlist forming a list of rules over
* the same tree.
* References to struct chunk are collected at audit_inode{,_child}()
* time and used in AUDIT_TREE rule matching.
* These references are dropped at the same time we are calling
* audit_free_names(), etc.
*
* Cyclic lists galore:
* tree.chunks anchors chunk.owners[].list hash_lock
* tree.rules anchors rule.rlist audit_filter_mutex
* chunk.trees anchors tree.same_root hash_lock
* chunk.hash is a hash with middle bits of watch.inode as
* a hash function. RCU, hash_lock
*
* tree is refcounted; one reference for "some rules on rules_list refer to
* it", one for each chunk with pointer to it.
*
* chunk is refcounted by embedded inotify_watch.
*
* node.index allows to get from node.list to containing chunk.
* MSB of that sucker is stolen to mark taggings that we might have to
* revert - several operations have very unpleasant cleanup logics and
* that makes a difference. Some.
*/
static struct inotify_handle *rtree_ih;
static struct audit_tree *alloc_tree(const char *s)
{
struct audit_tree *tree;
tree = kmalloc(sizeof(struct audit_tree) + strlen(s) + 1, GFP_KERNEL);
if (tree) {
atomic_set(&tree->count, 1);
tree->goner = 0;
INIT_LIST_HEAD(&tree->chunks);
INIT_LIST_HEAD(&tree->rules);
INIT_LIST_HEAD(&tree->list);
INIT_LIST_HEAD(&tree->same_root);
tree->root = NULL;
strcpy(tree->pathname, s);
}
return tree;
}
static inline void get_tree(struct audit_tree *tree)
{
atomic_inc(&tree->count);
}
static void __put_tree(struct rcu_head *rcu)
{
struct audit_tree *tree = container_of(rcu, struct audit_tree, head);
kfree(tree);
}
static inline void put_tree(struct audit_tree *tree)
{
if (atomic_dec_and_test(&tree->count))
call_rcu(&tree->head, __put_tree);
}
/* to avoid bringing the entire thing in audit.h */
const char *audit_tree_path(struct audit_tree *tree)
{
return tree->pathname;
}
static struct audit_chunk *alloc_chunk(int count)
{
struct audit_chunk *chunk;
size_t size;
int i;
size = offsetof(struct audit_chunk, owners) + count * sizeof(struct node);
chunk = kzalloc(size, GFP_KERNEL);
if (!chunk)
return NULL;
INIT_LIST_HEAD(&chunk->hash);
INIT_LIST_HEAD(&chunk->trees);
chunk->count = count;
for (i = 0; i < count; i++) {
INIT_LIST_HEAD(&chunk->owners[i].list);
chunk->owners[i].index = i;
}
inotify_init_watch(&chunk->watch);
return chunk;
}
static void __free_chunk(struct rcu_head *rcu)
{
struct audit_chunk *chunk = container_of(rcu, struct audit_chunk, head);
int i;
for (i = 0; i < chunk->count; i++) {
if (chunk->owners[i].owner)
put_tree(chunk->owners[i].owner);
}
kfree(chunk);
}
static inline void free_chunk(struct audit_chunk *chunk)
{
call_rcu(&chunk->head, __free_chunk);
}
void audit_put_chunk(struct audit_chunk *chunk)
{
put_inotify_watch(&chunk->watch);
}
enum {HASH_SIZE = 128};
static struct list_head chunk_hash_heads[HASH_SIZE];
static __cacheline_aligned_in_smp DEFINE_SPINLOCK(hash_lock);
static inline struct list_head *chunk_hash(const struct inode *inode)
{
unsigned long n = (unsigned long)inode / L1_CACHE_BYTES;
return chunk_hash_heads + n % HASH_SIZE;
}
/* hash_lock is held by caller */
static void insert_hash(struct audit_chunk *chunk)
{
struct list_head *list = chunk_hash(chunk->watch.inode);
list_add_rcu(&chunk->hash, list);
}
/* called under rcu_read_lock */
struct audit_chunk *audit_tree_lookup(const struct inode *inode)
{
struct list_head *list = chunk_hash(inode);
struct list_head *pos;
list_for_each_rcu(pos, list) {
struct audit_chunk *p = container_of(pos, struct audit_chunk, hash);
if (p->watch.inode == inode) {
get_inotify_watch(&p->watch);
return p;
}
}
return NULL;
}
int audit_tree_match(struct audit_chunk *chunk, struct audit_tree *tree)
{
int n;
for (n = 0; n < chunk->count; n++)
if (chunk->owners[n].owner == tree)
return 1;
return 0;
}
/* tagging and untagging inodes with trees */
static void untag_chunk(struct audit_chunk *chunk, struct node *p)
{
struct audit_chunk *new;
struct audit_tree *owner;
int size = chunk->count - 1;
int i, j;
mutex_lock(&chunk->watch.inode->inotify_mutex);
if (chunk->dead) {
mutex_unlock(&chunk->watch.inode->inotify_mutex);
return;
}
owner = p->owner;
if (!size) {
chunk->dead = 1;
spin_lock(&hash_lock);
list_del_init(&chunk->trees);
if (owner->root == chunk)
owner->root = NULL;
list_del_init(&p->list);
list_del_rcu(&chunk->hash);
spin_unlock(&hash_lock);
inotify_evict_watch(&chunk->watch);
mutex_unlock(&chunk->watch.inode->inotify_mutex);
put_inotify_watch(&chunk->watch);
return;
}
new = alloc_chunk(size);
if (!new)
goto Fallback;
if (inotify_clone_watch(&chunk->watch, &new->watch) < 0) {
free_chunk(new);
goto Fallback;
}
chunk->dead = 1;
spin_lock(&hash_lock);
list_replace_init(&chunk->trees, &new->trees);
if (owner->root == chunk) {
list_del_init(&owner->same_root);
owner->root = NULL;
}
for (i = j = 0; i < size; i++, j++) {
struct audit_tree *s;
if (&chunk->owners[j] == p) {
list_del_init(&p->list);
i--;
continue;
}
s = chunk->owners[j].owner;
new->owners[i].owner = s;
new->owners[i].index = chunk->owners[j].index - j + i;
if (!s) /* result of earlier fallback */
continue;
get_tree(s);
list_replace_init(&chunk->owners[i].list, &new->owners[j].list);
}
list_replace_rcu(&chunk->hash, &new->hash);
list_for_each_entry(owner, &new->trees, same_root)
owner->root = new;
spin_unlock(&hash_lock);
inotify_evict_watch(&chunk->watch);
mutex_unlock(&chunk->watch.inode->inotify_mutex);
put_inotify_watch(&chunk->watch);
return;
Fallback:
// do the best we can
spin_lock(&hash_lock);
if (owner->root == chunk) {
list_del_init(&owner->same_root);
owner->root = NULL;
}
list_del_init(&p->list);
p->owner = NULL;
put_tree(owner);
spin_unlock(&hash_lock);
mutex_unlock(&chunk->watch.inode->inotify_mutex);
}
static int create_chunk(struct inode *inode, struct audit_tree *tree)
{
struct audit_chunk *chunk = alloc_chunk(1);
if (!chunk)
return -ENOMEM;
if (inotify_add_watch(rtree_ih, &chunk->watch, inode, IN_IGNORED | IN_DELETE_SELF) < 0) {
free_chunk(chunk);
return -ENOSPC;
}
mutex_lock(&inode->inotify_mutex);
spin_lock(&hash_lock);
if (tree->goner) {
spin_unlock(&hash_lock);
chunk->dead = 1;
inotify_evict_watch(&chunk->watch);
mutex_unlock(&inode->inotify_mutex);
put_inotify_watch(&chunk->watch);
return 0;
}
chunk->owners[0].index = (1U << 31);
chunk->owners[0].owner = tree;
get_tree(tree);
list_add(&chunk->owners[0].list, &tree->chunks);
if (!tree->root) {
tree->root = chunk;
list_add(&tree->same_root, &chunk->trees);
}
insert_hash(chunk);
spin_unlock(&hash_lock);
mutex_unlock(&inode->inotify_mutex);
return 0;
}
/* the first tagged inode becomes root of tree */
static int tag_chunk(struct inode *inode, struct audit_tree *tree)
{
struct inotify_watch *watch;
struct audit_tree *owner;
struct audit_chunk *chunk, *old;
struct node *p;
int n;
if (inotify_find_watch(rtree_ih, inode, &watch) < 0)
return create_chunk(inode, tree);
old = container_of(watch, struct audit_chunk, watch);
/* are we already there? */
spin_lock(&hash_lock);
for (n = 0; n < old->count; n++) {
if (old->owners[n].owner == tree) {
spin_unlock(&hash_lock);
put_inotify_watch(watch);
return 0;
}
}
spin_unlock(&hash_lock);
chunk = alloc_chunk(old->count + 1);
if (!chunk)
return -ENOMEM;
mutex_lock(&inode->inotify_mutex);
if (inotify_clone_watch(&old->watch, &chunk->watch) < 0) {
mutex_unlock(&inode->inotify_mutex);
free_chunk(chunk);
return -ENOSPC;
}
spin_lock(&hash_lock);
if (tree->goner) {
spin_unlock(&hash_lock);
chunk->dead = 1;
inotify_evict_watch(&chunk->watch);
mutex_unlock(&inode->inotify_mutex);
put_inotify_watch(&chunk->watch);
return 0;
}
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;
p->owner = s;
p->index = old->owners[n].index;
if (!s) /* result of fallback in untag */
continue;
get_tree(s);
list_replace_init(&old->owners[n].list, &p->list);
}
p->index = (chunk->count - 1) | (1U<<31);
p->owner = tree;
get_tree(tree);
list_add(&p->list, &tree->chunks);
list_replace_rcu(&old->hash, &chunk->hash);
list_for_each_entry(owner, &chunk->trees, same_root)
owner->root = chunk;
old->dead = 1;
if (!tree->root) {
tree->root = chunk;
list_add(&tree->same_root, &chunk->trees);
}
spin_unlock(&hash_lock);
inotify_evict_watch(&old->watch);
mutex_unlock(&inode->inotify_mutex);
put_inotify_watch(&old->watch);
return 0;
}
static struct audit_chunk *find_chunk(struct node *p)
{
int index = p->index & ~(1U<<31);
p -= index;
return container_of(p, struct audit_chunk, owners[0]);
}
static void kill_rules(struct audit_tree *tree)
{
struct audit_krule *rule, *next;
struct audit_entry *entry;
struct audit_buffer *ab;
list_for_each_entry_safe(rule, next, &tree->rules, rlist) {
entry = container_of(rule, struct audit_entry, rule);
list_del_init(&rule->rlist);
if (rule->tree) {
/* not a half-baked one */
ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE);
audit_log_format(ab, "op=remove rule dir=");
audit_log_untrustedstring(ab, rule->tree->pathname);
if (rule->filterkey) {
audit_log_format(ab, " key=");
audit_log_untrustedstring(ab, rule->filterkey);
} else
audit_log_format(ab, " key=(null)");
audit_log_format(ab, " list=%d res=1", rule->listnr);
audit_log_end(ab);
rule->tree = NULL;
list_del_rcu(&entry->list);
call_rcu(&entry->rcu, audit_free_rule_rcu);
}
}
}
/*
* finish killing struct audit_tree
*/
static void prune_one(struct audit_tree *victim)
{
spin_lock(&hash_lock);
while (!list_empty(&victim->chunks)) {
struct node *p;
struct audit_chunk *chunk;
p = list_entry(victim->chunks.next, struct node, list);
chunk = find_chunk(p);
get_inotify_watch(&chunk->watch);
spin_unlock(&hash_lock);
untag_chunk(chunk, p);
put_inotify_watch(&chunk->watch);
spin_lock(&hash_lock);
}
spin_unlock(&hash_lock);
put_tree(victim);
}
/* trim the uncommitted chunks from tree */
static void trim_marked(struct audit_tree *tree)
{
struct list_head *p, *q;
spin_lock(&hash_lock);
if (tree->goner) {
spin_unlock(&hash_lock);
return;
}
/* reorder */
for (p = tree->chunks.next; p != &tree->chunks; p = q) {
struct node *node = list_entry(p, struct node, list);
q = p->next;
if (node->index & (1U<<31)) {
list_del_init(p);
list_add(p, &tree->chunks);
}
}
while (!list_empty(&tree->chunks)) {
struct node *node;
struct audit_chunk *chunk;
node = list_entry(tree->chunks.next, struct node, list);
/* have we run out of marked? */
if (!(node->index & (1U<<31)))
break;
chunk = find_chunk(node);
get_inotify_watch(&chunk->watch);
spin_unlock(&hash_lock);
untag_chunk(chunk, node);
put_inotify_watch(&chunk->watch);
spin_lock(&hash_lock);
}
if (!tree->root && !tree->goner) {
tree->goner = 1;
spin_unlock(&hash_lock);
mutex_lock(&audit_filter_mutex);
kill_rules(tree);
list_del_init(&tree->list);
mutex_unlock(&audit_filter_mutex);
prune_one(tree);
} else {
spin_unlock(&hash_lock);
}
}
/* called with audit_filter_mutex */
int audit_remove_tree_rule(struct audit_krule *rule)
{
struct audit_tree *tree;
tree = rule->tree;
if (tree) {
spin_lock(&hash_lock);
list_del_init(&rule->rlist);
if (list_empty(&tree->rules) && !tree->goner) {
tree->root = NULL;
list_del_init(&tree->same_root);
tree->goner = 1;
list_move(&tree->list, &prune_list);
rule->tree = NULL;
spin_unlock(&hash_lock);
audit_schedule_prune();
return 1;
}
rule->tree = NULL;
spin_unlock(&hash_lock);
return 1;
}
return 0;
}
void audit_trim_trees(void)
{
struct list_head cursor;
mutex_lock(&audit_filter_mutex);
list_add(&cursor, &tree_list);
while (cursor.next != &tree_list) {
struct audit_tree *tree;
struct nameidata nd;
struct vfsmount *root_mnt;
struct node *node;
struct list_head list;
int err;
tree = container_of(cursor.next, struct audit_tree, list);
get_tree(tree);
list_del(&cursor);
list_add(&cursor, &tree->list);
mutex_unlock(&audit_filter_mutex);
err = path_lookup(tree->pathname, 0, &nd);
if (err)
goto skip_it;
root_mnt = collect_mounts(nd.mnt, nd.dentry);
path_release(&nd);
if (!root_mnt)
goto skip_it;
list_add_tail(&list, &root_mnt->mnt_list);
spin_lock(&hash_lock);
list_for_each_entry(node, &tree->chunks, list) {
struct audit_chunk *chunk = find_chunk(node);
struct inode *inode = chunk->watch.inode;
struct vfsmount *mnt;
node->index |= 1U<<31;
list_for_each_entry(mnt, &list, mnt_list) {
if (mnt->mnt_root->d_inode == inode) {
node->index &= ~(1U<<31);
break;
}
}
}
spin_unlock(&hash_lock);
trim_marked(tree);
put_tree(tree);
list_del_init(&list);
drop_collected_mounts(root_mnt);
skip_it:
mutex_lock(&audit_filter_mutex);
}
list_del(&cursor);
mutex_unlock(&audit_filter_mutex);
}
static int is_under(struct vfsmount *mnt, struct dentry *dentry,
struct nameidata *nd)
{
if (mnt != nd->mnt) {
for (;;) {
if (mnt->mnt_parent == mnt)
return 0;
if (mnt->mnt_parent == nd->mnt)
break;
mnt = mnt->mnt_parent;
}
dentry = mnt->mnt_mountpoint;
}
return is_subdir(dentry, nd->dentry);
}
int audit_make_tree(struct audit_krule *rule, char *pathname, u32 op)
{
if (pathname[0] != '/' ||
rule->listnr != AUDIT_FILTER_EXIT ||
op & ~AUDIT_EQUAL ||
rule->inode_f || rule->watch || rule->tree)
return -EINVAL;
rule->tree = alloc_tree(pathname);
if (!rule->tree)
return -ENOMEM;
return 0;
}
void audit_put_tree(struct audit_tree *tree)
{
put_tree(tree);
}
/* called with audit_filter_mutex */
int audit_add_tree_rule(struct audit_krule *rule)
{
struct audit_tree *seed = rule->tree, *tree;
struct nameidata nd;
struct vfsmount *mnt, *p;
struct list_head list;
int err;
list_for_each_entry(tree, &tree_list, list) {
if (!strcmp(seed->pathname, tree->pathname)) {
put_tree(seed);
rule->tree = tree;
list_add(&rule->rlist, &tree->rules);
return 0;
}
}
tree = seed;
list_add(&tree->list, &tree_list);
list_add(&rule->rlist, &tree->rules);
/* do not set rule->tree yet */
mutex_unlock(&audit_filter_mutex);
err = path_lookup(tree->pathname, 0, &nd);
if (err)
goto Err;
mnt = collect_mounts(nd.mnt, nd.dentry);
path_release(&nd);
if (!mnt) {
err = -ENOMEM;
goto Err;
}
list_add_tail(&list, &mnt->mnt_list);
get_tree(tree);
list_for_each_entry(p, &list, mnt_list) {
err = tag_chunk(p->mnt_root->d_inode, tree);
if (err)
break;
}
list_del(&list);
drop_collected_mounts(mnt);
if (!err) {
struct node *node;
spin_lock(&hash_lock);
list_for_each_entry(node, &tree->chunks, list)
node->index &= ~(1U<<31);
spin_unlock(&hash_lock);
} else {
trim_marked(tree);
goto Err;
}
mutex_lock(&audit_filter_mutex);
if (list_empty(&rule->rlist)) {
put_tree(tree);
return -ENOENT;
}
rule->tree = tree;
put_tree(tree);
return 0;
Err:
mutex_lock(&audit_filter_mutex);
list_del_init(&tree->list);
list_del_init(&tree->rules);
put_tree(tree);
return err;
}
int audit_tag_tree(char *old, char *new)
{
struct list_head cursor, barrier;
int failed = 0;
struct nameidata nd;
struct vfsmount *tagged;
struct list_head list;
struct vfsmount *mnt;
struct dentry *dentry;
int err;
err = path_lookup(new, 0, &nd);
if (err)
return err;
tagged = collect_mounts(nd.mnt, nd.dentry);
path_release(&nd);
if (!tagged)
return -ENOMEM;
err = path_lookup(old, 0, &nd);
if (err) {
drop_collected_mounts(tagged);
return err;
}
mnt = mntget(nd.mnt);
dentry = dget(nd.dentry);
path_release(&nd);
if (dentry == tagged->mnt_root && dentry == mnt->mnt_root)
follow_up(&mnt, &dentry);
list_add_tail(&list, &tagged->mnt_list);
mutex_lock(&audit_filter_mutex);
list_add(&barrier, &tree_list);
list_add(&cursor, &barrier);
while (cursor.next != &tree_list) {
struct audit_tree *tree;
struct vfsmount *p;
tree = container_of(cursor.next, struct audit_tree, list);
get_tree(tree);
list_del(&cursor);
list_add(&cursor, &tree->list);
mutex_unlock(&audit_filter_mutex);
err = path_lookup(tree->pathname, 0, &nd);
if (err) {
put_tree(tree);
mutex_lock(&audit_filter_mutex);
continue;
}
spin_lock(&vfsmount_lock);
if (!is_under(mnt, dentry, &nd)) {
spin_unlock(&vfsmount_lock);
path_release(&nd);
put_tree(tree);
mutex_lock(&audit_filter_mutex);
continue;
}
spin_unlock(&vfsmount_lock);
path_release(&nd);
list_for_each_entry(p, &list, mnt_list) {
failed = tag_chunk(p->mnt_root->d_inode, tree);
if (failed)
break;
}
if (failed) {
put_tree(tree);
mutex_lock(&audit_filter_mutex);
break;
}
mutex_lock(&audit_filter_mutex);
spin_lock(&hash_lock);
if (!tree->goner) {
list_del(&tree->list);
list_add(&tree->list, &tree_list);
}
spin_unlock(&hash_lock);
put_tree(tree);
}
while (barrier.prev != &tree_list) {
struct audit_tree *tree;
tree = container_of(barrier.prev, struct audit_tree, list);
get_tree(tree);
list_del(&tree->list);
list_add(&tree->list, &barrier);
mutex_unlock(&audit_filter_mutex);
if (!failed) {
struct node *node;
spin_lock(&hash_lock);
list_for_each_entry(node, &tree->chunks, list)
node->index &= ~(1U<<31);
spin_unlock(&hash_lock);
} else {
trim_marked(tree);
}
put_tree(tree);
mutex_lock(&audit_filter_mutex);
}
list_del(&barrier);
list_del(&cursor);
list_del(&list);
mutex_unlock(&audit_filter_mutex);
dput(dentry);
mntput(mnt);
drop_collected_mounts(tagged);
return failed;
}
/*
* That gets run when evict_chunk() ends up needing to kill audit_tree.
* Runs from a separate thread, with audit_cmd_mutex held.
*/
void audit_prune_trees(void)
{
mutex_lock(&audit_filter_mutex);
while (!list_empty(&prune_list)) {
struct audit_tree *victim;
victim = list_entry(prune_list.next, struct audit_tree, list);
list_del_init(&victim->list);
mutex_unlock(&audit_filter_mutex);
prune_one(victim);
mutex_lock(&audit_filter_mutex);
}
mutex_unlock(&audit_filter_mutex);
}
/*
* Here comes the stuff asynchronous to auditctl operations
*/
/* inode->inotify_mutex is locked */
static void evict_chunk(struct audit_chunk *chunk)
{
struct audit_tree *owner;
int n;
if (chunk->dead)
return;
chunk->dead = 1;
mutex_lock(&audit_filter_mutex);
spin_lock(&hash_lock);
while (!list_empty(&chunk->trees)) {
owner = list_entry(chunk->trees.next,
struct audit_tree, same_root);
owner->goner = 1;
owner->root = NULL;
list_del_init(&owner->same_root);
spin_unlock(&hash_lock);
kill_rules(owner);
list_move(&owner->list, &prune_list);
audit_schedule_prune();
spin_lock(&hash_lock);
}
list_del_rcu(&chunk->hash);
for (n = 0; n < chunk->count; n++)
list_del_init(&chunk->owners[n].list);
spin_unlock(&hash_lock);
mutex_unlock(&audit_filter_mutex);
}
static void handle_event(struct inotify_watch *watch, u32 wd, u32 mask,
u32 cookie, const char *dname, struct inode *inode)
{
struct audit_chunk *chunk = container_of(watch, struct audit_chunk, watch);
if (mask & IN_IGNORED) {
evict_chunk(chunk);
put_inotify_watch(watch);
}
}
static void destroy_watch(struct inotify_watch *watch)
{
struct audit_chunk *chunk = container_of(watch, struct audit_chunk, watch);
free_chunk(chunk);
}
static const struct inotify_operations rtree_inotify_ops = {
.handle_event = handle_event,
.destroy_watch = destroy_watch,
};
static int __init audit_tree_init(void)
{
int i;
rtree_ih = inotify_init(&rtree_inotify_ops);
if (IS_ERR(rtree_ih))
audit_panic("cannot initialize inotify handle for rectree watches");
for (i = 0; i < HASH_SIZE; i++)
INIT_LIST_HEAD(&chunk_hash_heads[i]);
return 0;
}
__initcall(audit_tree_init);
...@@ -87,7 +87,7 @@ struct list_head audit_filter_list[AUDIT_NR_FILTERS] = { ...@@ -87,7 +87,7 @@ struct list_head audit_filter_list[AUDIT_NR_FILTERS] = {
#endif #endif
}; };
static DEFINE_MUTEX(audit_filter_mutex); DEFINE_MUTEX(audit_filter_mutex);
/* Inotify handle */ /* Inotify handle */
extern struct inotify_handle *audit_ih; extern struct inotify_handle *audit_ih;
...@@ -145,7 +145,7 @@ static inline void audit_free_rule(struct audit_entry *e) ...@@ -145,7 +145,7 @@ static inline void audit_free_rule(struct audit_entry *e)
kfree(e); kfree(e);
} }
static inline void audit_free_rule_rcu(struct rcu_head *head) void audit_free_rule_rcu(struct rcu_head *head)
{ {
struct audit_entry *e = container_of(head, struct audit_entry, rcu); struct audit_entry *e = container_of(head, struct audit_entry, rcu);
audit_free_rule(e); audit_free_rule(e);
...@@ -217,7 +217,7 @@ static inline struct audit_entry *audit_init_entry(u32 field_count) ...@@ -217,7 +217,7 @@ static inline struct audit_entry *audit_init_entry(u32 field_count)
/* Unpack a filter field's string representation from user-space /* Unpack a filter field's string representation from user-space
* buffer. */ * buffer. */
static char *audit_unpack_string(void **bufp, size_t *remain, size_t len) char *audit_unpack_string(void **bufp, size_t *remain, size_t len)
{ {
char *str; char *str;
...@@ -247,7 +247,7 @@ static inline int audit_to_inode(struct audit_krule *krule, ...@@ -247,7 +247,7 @@ static inline int audit_to_inode(struct audit_krule *krule,
struct audit_field *f) struct audit_field *f)
{ {
if (krule->listnr != AUDIT_FILTER_EXIT || if (krule->listnr != AUDIT_FILTER_EXIT ||
krule->watch || krule->inode_f) krule->watch || krule->inode_f || krule->tree)
return -EINVAL; return -EINVAL;
krule->inode_f = f; krule->inode_f = f;
...@@ -266,7 +266,7 @@ static int audit_to_watch(struct audit_krule *krule, char *path, int len, ...@@ -266,7 +266,7 @@ static int audit_to_watch(struct audit_krule *krule, char *path, int len,
if (path[0] != '/' || path[len-1] == '/' || if (path[0] != '/' || path[len-1] == '/' ||
krule->listnr != AUDIT_FILTER_EXIT || krule->listnr != AUDIT_FILTER_EXIT ||
op & ~AUDIT_EQUAL || op & ~AUDIT_EQUAL ||
krule->inode_f || krule->watch) /* 1 inode # per rule, for hash */ krule->inode_f || krule->watch || krule->tree)
return -EINVAL; return -EINVAL;
watch = audit_init_watch(path); watch = audit_init_watch(path);
...@@ -622,6 +622,17 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data, ...@@ -622,6 +622,17 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
goto exit_free; goto exit_free;
} }
break; break;
case AUDIT_DIR:
str = audit_unpack_string(&bufp, &remain, f->val);
if (IS_ERR(str))
goto exit_free;
entry->rule.buflen += f->val;
err = audit_make_tree(&entry->rule, str, f->op);
kfree(str);
if (err)
goto exit_free;
break;
case AUDIT_INODE: case AUDIT_INODE:
err = audit_to_inode(&entry->rule, f); err = audit_to_inode(&entry->rule, f);
if (err) if (err)
...@@ -668,7 +679,7 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data, ...@@ -668,7 +679,7 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
} }
/* Pack a filter field's string representation into data block. */ /* Pack a filter field's string representation into data block. */
static inline size_t audit_pack_string(void **bufp, char *str) static inline size_t audit_pack_string(void **bufp, const char *str)
{ {
size_t len = strlen(str); size_t len = strlen(str);
...@@ -747,6 +758,11 @@ static struct audit_rule_data *audit_krule_to_data(struct audit_krule *krule) ...@@ -747,6 +758,11 @@ static struct audit_rule_data *audit_krule_to_data(struct audit_krule *krule)
data->buflen += data->values[i] = data->buflen += data->values[i] =
audit_pack_string(&bufp, krule->watch->path); audit_pack_string(&bufp, krule->watch->path);
break; break;
case AUDIT_DIR:
data->buflen += data->values[i] =
audit_pack_string(&bufp,
audit_tree_path(krule->tree));
break;
case AUDIT_FILTERKEY: case AUDIT_FILTERKEY:
data->buflen += data->values[i] = data->buflen += data->values[i] =
audit_pack_string(&bufp, krule->filterkey); audit_pack_string(&bufp, krule->filterkey);
...@@ -795,6 +811,11 @@ static int audit_compare_rule(struct audit_krule *a, struct audit_krule *b) ...@@ -795,6 +811,11 @@ static int audit_compare_rule(struct audit_krule *a, struct audit_krule *b)
if (strcmp(a->watch->path, b->watch->path)) if (strcmp(a->watch->path, b->watch->path))
return 1; return 1;
break; break;
case AUDIT_DIR:
if (strcmp(audit_tree_path(a->tree),
audit_tree_path(b->tree)))
return 1;
break;
case AUDIT_FILTERKEY: case AUDIT_FILTERKEY:
/* both filterkeys exist based on above type compare */ /* both filterkeys exist based on above type compare */
if (strcmp(a->filterkey, b->filterkey)) if (strcmp(a->filterkey, b->filterkey))
...@@ -897,6 +918,14 @@ static struct audit_entry *audit_dupe_rule(struct audit_krule *old, ...@@ -897,6 +918,14 @@ static struct audit_entry *audit_dupe_rule(struct audit_krule *old,
new->inode_f = old->inode_f; new->inode_f = old->inode_f;
new->watch = NULL; new->watch = NULL;
new->field_count = old->field_count; new->field_count = old->field_count;
/*
* note that we are OK with not refcounting here; audit_match_tree()
* never dereferences tree and we can't get false positives there
* since we'd have to have rule gone from the list *and* removed
* before the chunks found by lookup had been allocated, i.e. before
* the beginning of list scan.
*/
new->tree = old->tree;
memcpy(new->fields, old->fields, sizeof(struct audit_field) * fcount); memcpy(new->fields, old->fields, sizeof(struct audit_field) * fcount);
/* deep copy this information, updating the se_rule fields, because /* deep copy this information, updating the se_rule fields, because
...@@ -1217,6 +1246,7 @@ static inline int audit_add_rule(struct audit_entry *entry, ...@@ -1217,6 +1246,7 @@ static inline int audit_add_rule(struct audit_entry *entry,
struct audit_entry *e; struct audit_entry *e;
struct audit_field *inode_f = entry->rule.inode_f; struct audit_field *inode_f = entry->rule.inode_f;
struct audit_watch *watch = entry->rule.watch; struct audit_watch *watch = entry->rule.watch;
struct audit_tree *tree = entry->rule.tree;
struct nameidata *ndp = NULL, *ndw = NULL; struct nameidata *ndp = NULL, *ndw = NULL;
int h, err; int h, err;
#ifdef CONFIG_AUDITSYSCALL #ifdef CONFIG_AUDITSYSCALL
...@@ -1238,6 +1268,9 @@ static inline int audit_add_rule(struct audit_entry *entry, ...@@ -1238,6 +1268,9 @@ static inline int audit_add_rule(struct audit_entry *entry,
mutex_unlock(&audit_filter_mutex); mutex_unlock(&audit_filter_mutex);
if (e) { if (e) {
err = -EEXIST; err = -EEXIST;
/* normally audit_add_tree_rule() will free it on failure */
if (tree)
audit_put_tree(tree);
goto error; goto error;
} }
...@@ -1259,6 +1292,13 @@ static inline int audit_add_rule(struct audit_entry *entry, ...@@ -1259,6 +1292,13 @@ static inline int audit_add_rule(struct audit_entry *entry,
h = audit_hash_ino((u32)watch->ino); h = audit_hash_ino((u32)watch->ino);
list = &audit_inode_hash[h]; list = &audit_inode_hash[h];
} }
if (tree) {
err = audit_add_tree_rule(&entry->rule);
if (err) {
mutex_unlock(&audit_filter_mutex);
goto error;
}
}
if (entry->rule.flags & AUDIT_FILTER_PREPEND) { if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
list_add_rcu(&entry->list, list); list_add_rcu(&entry->list, list);
...@@ -1292,6 +1332,7 @@ static inline int audit_del_rule(struct audit_entry *entry, ...@@ -1292,6 +1332,7 @@ static inline int audit_del_rule(struct audit_entry *entry,
struct audit_entry *e; struct audit_entry *e;
struct audit_field *inode_f = entry->rule.inode_f; struct audit_field *inode_f = entry->rule.inode_f;
struct audit_watch *watch, *tmp_watch = entry->rule.watch; struct audit_watch *watch, *tmp_watch = entry->rule.watch;
struct audit_tree *tree = entry->rule.tree;
LIST_HEAD(inotify_list); LIST_HEAD(inotify_list);
int h, ret = 0; int h, ret = 0;
#ifdef CONFIG_AUDITSYSCALL #ifdef CONFIG_AUDITSYSCALL
...@@ -1336,6 +1377,9 @@ static inline int audit_del_rule(struct audit_entry *entry, ...@@ -1336,6 +1377,9 @@ static inline int audit_del_rule(struct audit_entry *entry,
} }
} }
if (e->rule.tree)
audit_remove_tree_rule(&e->rule);
list_del_rcu(&e->list); list_del_rcu(&e->list);
call_rcu(&e->rcu, audit_free_rule_rcu); call_rcu(&e->rcu, audit_free_rule_rcu);
...@@ -1354,6 +1398,8 @@ static inline int audit_del_rule(struct audit_entry *entry, ...@@ -1354,6 +1398,8 @@ static inline int audit_del_rule(struct audit_entry *entry,
out: out:
if (tmp_watch) if (tmp_watch)
audit_put_watch(tmp_watch); /* match initial get */ audit_put_watch(tmp_watch); /* match initial get */
if (tree)
audit_put_tree(tree); /* that's the temporary one */
return ret; return ret;
} }
...@@ -1737,6 +1783,7 @@ int selinux_audit_rule_update(void) ...@@ -1737,6 +1783,7 @@ int selinux_audit_rule_update(void)
{ {
struct audit_entry *entry, *n, *nentry; struct audit_entry *entry, *n, *nentry;
struct audit_watch *watch; struct audit_watch *watch;
struct audit_tree *tree;
int i, err = 0; int i, err = 0;
/* audit_filter_mutex synchronizes the writers */ /* audit_filter_mutex synchronizes the writers */
...@@ -1748,6 +1795,7 @@ int selinux_audit_rule_update(void) ...@@ -1748,6 +1795,7 @@ int selinux_audit_rule_update(void)
continue; continue;
watch = entry->rule.watch; watch = entry->rule.watch;
tree = entry->rule.tree;
nentry = audit_dupe_rule(&entry->rule, watch); nentry = audit_dupe_rule(&entry->rule, watch);
if (unlikely(IS_ERR(nentry))) { if (unlikely(IS_ERR(nentry))) {
/* save the first error encountered for the /* save the first error encountered for the
...@@ -1763,7 +1811,9 @@ int selinux_audit_rule_update(void) ...@@ -1763,7 +1811,9 @@ int selinux_audit_rule_update(void)
list_add(&nentry->rule.rlist, list_add(&nentry->rule.rlist,
&watch->rules); &watch->rules);
list_del(&entry->rule.rlist); list_del(&entry->rule.rlist);
} } else if (tree)
list_replace_init(&entry->rule.rlist,
&nentry->rule.rlist);
list_replace_rcu(&entry->list, &nentry->list); list_replace_rcu(&entry->list, &nentry->list);
} }
call_rcu(&entry->rcu, audit_free_rule_rcu); call_rcu(&entry->rcu, audit_free_rule_rcu);
......
...@@ -65,6 +65,7 @@ ...@@ -65,6 +65,7 @@
#include <linux/binfmts.h> #include <linux/binfmts.h>
#include <linux/highmem.h> #include <linux/highmem.h>
#include <linux/syscalls.h> #include <linux/syscalls.h>
#include <linux/inotify.h>
#include "audit.h" #include "audit.h"
...@@ -179,6 +180,11 @@ struct audit_aux_data_pids { ...@@ -179,6 +180,11 @@ struct audit_aux_data_pids {
int pid_count; int pid_count;
}; };
struct audit_tree_refs {
struct audit_tree_refs *next;
struct audit_chunk *c[31];
};
/* The per-task audit context. */ /* The per-task audit context. */
struct audit_context { struct audit_context {
int dummy; /* must be the first element */ int dummy; /* must be the first element */
...@@ -211,6 +217,9 @@ struct audit_context { ...@@ -211,6 +217,9 @@ struct audit_context {
pid_t target_pid; pid_t target_pid;
u32 target_sid; u32 target_sid;
struct audit_tree_refs *trees, *first_trees;
int tree_count;
#if AUDIT_DEBUG #if AUDIT_DEBUG
int put_count; int put_count;
int ino_count; int ino_count;
...@@ -265,6 +274,117 @@ static int audit_match_perm(struct audit_context *ctx, int mask) ...@@ -265,6 +274,117 @@ static int audit_match_perm(struct audit_context *ctx, int mask)
} }
} }
/*
* We keep a linked list of fixed-sized (31 pointer) arrays of audit_chunk *;
* ->first_trees points to its beginning, ->trees - to the current end of data.
* ->tree_count is the number of free entries in array pointed to by ->trees.
* Original condition is (NULL, NULL, 0); as soon as it grows we never revert to NULL,
* "empty" becomes (p, p, 31) afterwards. We don't shrink the list (and seriously,
* it's going to remain 1-element for almost any setup) until we free context itself.
* References in it _are_ dropped - at the same time we free/drop aux stuff.
*/
#ifdef CONFIG_AUDIT_TREE
static int put_tree_ref(struct audit_context *ctx, struct audit_chunk *chunk)
{
struct audit_tree_refs *p = ctx->trees;
int left = ctx->tree_count;
if (likely(left)) {
p->c[--left] = chunk;
ctx->tree_count = left;
return 1;
}
if (!p)
return 0;
p = p->next;
if (p) {
p->c[30] = chunk;
ctx->trees = p;
ctx->tree_count = 30;
return 1;
}
return 0;
}
static int grow_tree_refs(struct audit_context *ctx)
{
struct audit_tree_refs *p = ctx->trees;
ctx->trees = kzalloc(sizeof(struct audit_tree_refs), GFP_KERNEL);
if (!ctx->trees) {
ctx->trees = p;
return 0;
}
if (p)
p->next = ctx->trees;
else
ctx->first_trees = ctx->trees;
ctx->tree_count = 31;
return 1;
}
#endif
static void unroll_tree_refs(struct audit_context *ctx,
struct audit_tree_refs *p, int count)
{
#ifdef CONFIG_AUDIT_TREE
struct audit_tree_refs *q;
int n;
if (!p) {
/* we started with empty chain */
p = ctx->first_trees;
count = 31;
/* if the very first allocation has failed, nothing to do */
if (!p)
return;
}
n = count;
for (q = p; q != ctx->trees; q = q->next, n = 31) {
while (n--) {
audit_put_chunk(q->c[n]);
q->c[n] = NULL;
}
}
while (n-- > ctx->tree_count) {
audit_put_chunk(q->c[n]);
q->c[n] = NULL;
}
ctx->trees = p;
ctx->tree_count = count;
#endif
}
static void free_tree_refs(struct audit_context *ctx)
{
struct audit_tree_refs *p, *q;
for (p = ctx->first_trees; p; p = q) {
q = p->next;
kfree(p);
}
}
static int match_tree_refs(struct audit_context *ctx, struct audit_tree *tree)
{
#ifdef CONFIG_AUDIT_TREE
struct audit_tree_refs *p;
int n;
if (!tree)
return 0;
/* full ones */
for (p = ctx->first_trees; p != ctx->trees; p = p->next) {
for (n = 0; n < 31; n++)
if (audit_tree_match(p->c[n], tree))
return 1;
}
/* partial */
if (p) {
for (n = ctx->tree_count; n < 31; n++)
if (audit_tree_match(p->c[n], tree))
return 1;
}
#endif
return 0;
}
/* Determine if any context name data matches a rule's watch data */ /* Determine if any context name data matches a rule's watch data */
/* Compare a task_struct with an audit_rule. Return 1 on match, 0 /* Compare a task_struct with an audit_rule. Return 1 on match, 0
* otherwise. */ * otherwise. */
...@@ -379,6 +499,10 @@ static int audit_filter_rules(struct task_struct *tsk, ...@@ -379,6 +499,10 @@ static int audit_filter_rules(struct task_struct *tsk,
result = (name->dev == rule->watch->dev && result = (name->dev == rule->watch->dev &&
name->ino == rule->watch->ino); name->ino == rule->watch->ino);
break; break;
case AUDIT_DIR:
if (ctx)
result = match_tree_refs(ctx, rule->tree);
break;
case AUDIT_LOGINUID: case AUDIT_LOGINUID:
result = 0; result = 0;
if (ctx) if (ctx)
...@@ -727,6 +851,8 @@ static inline void audit_free_context(struct audit_context *context) ...@@ -727,6 +851,8 @@ static inline void audit_free_context(struct audit_context *context)
context->name_count, count); context->name_count, count);
} }
audit_free_names(context); audit_free_names(context);
unroll_tree_refs(context, NULL, 0);
free_tree_refs(context);
audit_free_aux(context); audit_free_aux(context);
kfree(context->filterkey); kfree(context->filterkey);
kfree(context); kfree(context);
...@@ -1270,6 +1396,7 @@ void audit_syscall_exit(int valid, long return_code) ...@@ -1270,6 +1396,7 @@ void audit_syscall_exit(int valid, long return_code)
tsk->audit_context = new_context; tsk->audit_context = new_context;
} else { } else {
audit_free_names(context); audit_free_names(context);
unroll_tree_refs(context, NULL, 0);
audit_free_aux(context); audit_free_aux(context);
context->aux = NULL; context->aux = NULL;
context->aux_pids = NULL; context->aux_pids = NULL;
...@@ -1281,6 +1408,95 @@ void audit_syscall_exit(int valid, long return_code) ...@@ -1281,6 +1408,95 @@ void audit_syscall_exit(int valid, long return_code)
} }
} }
static inline void handle_one(const struct inode *inode)
{
#ifdef CONFIG_AUDIT_TREE
struct audit_context *context;
struct audit_tree_refs *p;
struct audit_chunk *chunk;
int count;
if (likely(list_empty(&inode->inotify_watches)))
return;
context = current->audit_context;
p = context->trees;
count = context->tree_count;
rcu_read_lock();
chunk = audit_tree_lookup(inode);
rcu_read_unlock();
if (!chunk)
return;
if (likely(put_tree_ref(context, chunk)))
return;
if (unlikely(!grow_tree_refs(context))) {
printk(KERN_WARNING "out of memory, audit has lost a tree reference");
audit_set_auditable(context);
audit_put_chunk(chunk);
unroll_tree_refs(context, p, count);
return;
}
put_tree_ref(context, chunk);
#endif
}
static void handle_path(const struct dentry *dentry)
{
#ifdef CONFIG_AUDIT_TREE
struct audit_context *context;
struct audit_tree_refs *p;
const struct dentry *d, *parent;
struct audit_chunk *drop;
unsigned long seq;
int count;
context = current->audit_context;
p = context->trees;
count = context->tree_count;
retry:
drop = NULL;
d = dentry;
rcu_read_lock();
seq = read_seqbegin(&rename_lock);
for(;;) {
struct inode *inode = d->d_inode;
if (inode && unlikely(!list_empty(&inode->inotify_watches))) {
struct audit_chunk *chunk;
chunk = audit_tree_lookup(inode);
if (chunk) {
if (unlikely(!put_tree_ref(context, chunk))) {
drop = chunk;
break;
}
}
}
parent = d->d_parent;
if (parent == d)
break;
d = parent;
}
if (unlikely(read_seqretry(&rename_lock, seq) || drop)) { /* in this order */
rcu_read_unlock();
if (!drop) {
/* just a race with rename */
unroll_tree_refs(context, p, count);
goto retry;
}
audit_put_chunk(drop);
if (grow_tree_refs(context)) {
/* OK, got more space */
unroll_tree_refs(context, p, count);
goto retry;
}
/* too bad */
printk(KERN_WARNING
"out of memory, audit has lost a tree reference");
unroll_tree_refs(context, p, count);
audit_set_auditable(context);
return;
}
rcu_read_unlock();
#endif
}
/** /**
* audit_getname - add a name to the list * audit_getname - add a name to the list
* @name: name to add * @name: name to add
...@@ -1403,10 +1619,11 @@ static void audit_copy_inode(struct audit_names *name, const struct inode *inode ...@@ -1403,10 +1619,11 @@ static void audit_copy_inode(struct audit_names *name, const struct inode *inode
* *
* Called from fs/namei.c:path_lookup(). * Called from fs/namei.c:path_lookup().
*/ */
void __audit_inode(const char *name, const struct inode *inode) void __audit_inode(const char *name, const struct dentry *dentry)
{ {
int idx; int idx;
struct audit_context *context = current->audit_context; struct audit_context *context = current->audit_context;
const struct inode *inode = dentry->d_inode;
if (!context->in_syscall) if (!context->in_syscall)
return; return;
...@@ -1426,6 +1643,7 @@ void __audit_inode(const char *name, const struct inode *inode) ...@@ -1426,6 +1643,7 @@ void __audit_inode(const char *name, const struct inode *inode)
idx = context->name_count - 1; idx = context->name_count - 1;
context->names[idx].name = NULL; context->names[idx].name = NULL;
} }
handle_path(dentry);
audit_copy_inode(&context->names[idx], inode); audit_copy_inode(&context->names[idx], inode);
} }
...@@ -1443,17 +1661,20 @@ void __audit_inode(const char *name, const struct inode *inode) ...@@ -1443,17 +1661,20 @@ void __audit_inode(const char *name, const struct inode *inode)
* must be hooked prior, in order to capture the target inode during * must be hooked prior, in order to capture the target inode during
* unsuccessful attempts. * unsuccessful attempts.
*/ */
void __audit_inode_child(const char *dname, const struct inode *inode, void __audit_inode_child(const char *dname, const struct dentry *dentry,
const struct inode *parent) const struct inode *parent)
{ {
int idx; int idx;
struct audit_context *context = current->audit_context; struct audit_context *context = current->audit_context;
const char *found_parent = NULL, *found_child = NULL; const char *found_parent = NULL, *found_child = NULL;
const struct inode *inode = dentry->d_inode;
int dirlen = 0; int dirlen = 0;
if (!context->in_syscall) if (!context->in_syscall)
return; return;
if (inode)
handle_one(inode);
/* determine matching parent */ /* determine matching parent */
if (!dname) if (!dname)
goto add_names; goto add_names;
......
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