Hello,
I've pulled the latest git-audit tree but I need to take some time to make a
clean patch against it. Â So, in the meantime, I've patched the 2.6.12-rc3-mm3
tree for comments. Patch appears at bottom of message.
Let's discuss locking first. Â I've tried to incorporate the BANGing feedback I
received *slaps knee* as well as making some adjustments of my own. Â
* I've removed the path_lookup() that was nestled between the global
audit_master_watchlist_lock spinlock in audit_to_transport. Â This was
admittedly stupid and is now an exercise for user space.
* I've completely removed the audit_master_watchlist_lock spinlock to protect
the master watchlist, in favor of RCU locking. Â To protect against concurrent
watch removals from the master watchlist, we conveniently use the local
data->lock rw_lock in place of another spinlock. Â Thus, only one removal can
occur at a time. Â To prevent the "rug being pulled out form under us" while
we're reading the master watchlist, we defer putting our reference back to
the wentry until the current reader is finished.
Â
I'm thinking about removing the local locks on inode audit data in favor of a
global spinlock in auditfs to dispell contention between multiple writers and
switching its list primitives to RCU as well. Â Any opinions on this matter?
* Since we can only ever have one insertion into the watchlist at a time, we
never have to worry about racing with another insertion.. so the locking here
was removed. Â Objections?
And now, the pinning.
* struct audit_wentry was augmented with struct dentry *w_dentry which holds a
reference to the pinned dentry.
* Three new functions were added: audit_pin, audit_unpin, and
audit_dentry_unpin. Â
audit_pin:
Grab a reference to a dentry and store it at its wentry->w_dentry. Â The only
time we can truly know when to pin a dentry is in the audit_update_watch()
hook.
audit_unpin:
Put back the wentry->w_dentry reference. Â For most cases we're able to unpin
memory from within the auditfs mechanism itself. Â For instance, when the
watch is removed, but the file still it exists, or when the watch is removed
and while the file does not exist.
audit_dentry_unpin:
This hook is called from d_delete so that we may release the wentry->w_dentry
should it have a reference... otherwise very bad things will happen :-):-) Â This
is to cover the case when the watch exists, but the file is "removed" (when
the dentry is deleted), which cannot be handled internally by audit_unpin.
Finally, there were some other minor fixes and cleanups introduced by Steve
which I've absorbed (and cleaned up where necessary).
-tim
diff -Nurp linux-2.6.12-rc3-mm3~orig/fs/dcache.c linux-2.6.12-rc3-mm3~audit/fs/dcache.c
--- linux-2.6.12-rc3-mm3~orig/fs/dcache.c 2005-05-06 13:05:08.000000000 -0500
+++ linux-2.6.12-rc3-mm3~audit/fs/dcache.c 2005-05-10 23:37:50.000000000 -0500
@@ -32,6 +32,7 @@
#include <linux/seqlock.h>
#include <linux/swap.h>
#include <linux/bootmem.h>
+#include <linux/audit.h>
/* #define DCACHE_DEBUG 1 */
@@ -97,6 +98,7 @@ static inline void dentry_iput(struct de
{
struct inode *inode = dentry->d_inode;
if (inode) {
+ audit_update_watch(dentry, 1);
dentry->d_inode = NULL;
list_del_init(&dentry->d_alias);
spin_unlock(&dentry->d_lock);
@@ -802,6 +804,7 @@ void d_instantiate(struct dentry *entry,
if (inode)
list_add(&entry->d_alias, &inode->i_dentry);
entry->d_inode = inode;
+ audit_update_watch(entry, 0);
spin_unlock(&dcache_lock);
security_d_instantiate(entry, inode);
}
@@ -978,6 +981,7 @@ struct dentry *d_splice_alias(struct ino
new = __d_find_alias(inode, 1);
if (new) {
BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
+ audit_update_watch(new, 0);
spin_unlock(&dcache_lock);
security_d_instantiate(new, inode);
d_rehash(dentry);
@@ -987,6 +991,7 @@ struct dentry *d_splice_alias(struct ino
/* d_instantiate takes dcache_lock, so we do it by hand */
list_add(&dentry->d_alias, &inode->i_dentry);
dentry->d_inode = inode;
+ audit_update_watch(dentry, 0);
spin_unlock(&dcache_lock);
security_d_instantiate(dentry, inode);
d_rehash(dentry);
@@ -1090,6 +1095,7 @@ struct dentry * __d_lookup(struct dentry
if (!d_unhashed(dentry)) {
atomic_inc(&dentry->d_count);
found = dentry;
+ audit_update_watch(found, 0);
}
spin_unlock(&dentry->d_lock);
break;
@@ -1170,6 +1176,9 @@ void d_delete(struct dentry * dentry)
*/
spin_lock(&dcache_lock);
spin_lock(&dentry->d_lock);
+
+ audit_dentry_unpin(dentry);
+
if (atomic_read(&dentry->d_count) == 1) {
dentry_iput(dentry);
return;
@@ -1299,6 +1308,8 @@ void d_move(struct dentry * dentry, stru
spin_lock(&target->d_lock);
}
+ audit_update_watch(dentry, 1);
+
/* Move the dentry to the target hash queue, if on different bucket */
if (dentry->d_flags & DCACHE_UNHASHED)
goto already_unhashed;
@@ -1332,6 +1343,7 @@ already_unhashed:
list_add(&target->d_child, &target->d_parent->d_subdirs);
}
+ audit_update_watch(dentry, 0);
list_add(&dentry->d_child, &dentry->d_parent->d_subdirs);
spin_unlock(&target->d_lock);
spin_unlock(&dentry->d_lock);
diff -Nurp linux-2.6.12-rc3-mm3~orig/fs/inode.c linux-2.6.12-rc3-mm3~audit/fs/inode.c
--- linux-2.6.12-rc3-mm3~orig/fs/inode.c 2005-05-06 13:05:08.000000000 -0500
+++ linux-2.6.12-rc3-mm3~audit/fs/inode.c 2005-05-06 14:29:04.000000000 -0500
@@ -22,6 +22,7 @@
#include <linux/cdev.h>
#include <linux/bootmem.h>
#include <linux/inotify.h>
+#include <linux/audit.h>
/*
* This is needed for the following functions:
@@ -139,9 +140,11 @@ static struct inode *alloc_inode(struct
inode->i_bdev = NULL;
inode->i_cdev = NULL;
inode->i_rdev = 0;
+ inode->i_audit = NULL;
inode->i_security = NULL;
inode->dirtied_when = 0;
- if (security_inode_alloc(inode)) {
+ if (audit_inode_alloc(inode) || security_inode_alloc(inode)) {
+ audit_inode_free(inode);
if (inode->i_sb->s_op->destroy_inode)
inode->i_sb->s_op->destroy_inode(inode);
else
@@ -179,6 +182,7 @@ void destroy_inode(struct inode *inode)
{
if (inode_has_buffers(inode))
BUG();
+ audit_inode_free(inode);
security_inode_free(inode);
if (inode->i_sb->s_op->destroy_inode)
inode->i_sb->s_op->destroy_inode(inode);
diff -Nurp linux-2.6.12-rc3-mm3~orig/fs/namei.c linux-2.6.12-rc3-mm3~audit/fs/namei.c
--- linux-2.6.12-rc3-mm3~orig/fs/namei.c 2005-05-06 13:05:09.000000000 -0500
+++ linux-2.6.12-rc3-mm3~audit/fs/namei.c 2005-05-06 14:29:04.000000000 -0500
@@ -225,6 +225,8 @@ int permission(struct inode *inode, int
{
int retval, submask;
+ audit_notify_watch(inode, mask);
+
if (mask & MAY_WRITE) {
umode_t mode = inode->i_mode;
@@ -358,6 +360,8 @@ static inline int exec_permission_lite(s
if (inode->i_op && inode->i_op->permission)
return -EAGAIN;
+ audit_notify_watch(inode, MAY_EXEC);
+
if (current->fsuid == inode->i_uid)
mode >>= 6;
else if (in_group_p(inode->i_gid))
@@ -1172,6 +1176,8 @@ static inline int may_delete(struct inod
BUG_ON(victim->d_parent->d_inode != dir);
+ audit_notify_watch(victim->d_inode, MAY_WRITE);
+
error = permission(dir,MAY_WRITE | MAY_EXEC, NULL);
if (error)
return error;
@@ -1296,6 +1302,7 @@ int vfs_create(struct inode *dir, struct
DQUOT_INIT(dir);
error = dir->i_op->create(dir, dentry, mode, nd);
if (!error) {
+ audit_notify_watch(dentry->d_inode, MAY_WRITE);
fsnotify_create(dir, dentry->d_name.name);
security_inode_post_create(dir, dentry, mode);
}
@@ -1601,6 +1608,7 @@ int vfs_mknod(struct inode *dir, struct
DQUOT_INIT(dir);
error = dir->i_op->mknod(dir, dentry, mode, dev);
if (!error) {
+ audit_notify_watch(dentry->d_inode, MAY_WRITE);
fsnotify_create(dir, dentry->d_name.name);
security_inode_post_mknod(dir, dentry, mode, dev);
}
@@ -1674,6 +1682,7 @@ int vfs_mkdir(struct inode *dir, struct
DQUOT_INIT(dir);
error = dir->i_op->mkdir(dir, dentry, mode);
if (!error) {
+ audit_notify_watch(dentry->d_inode, MAY_WRITE);
fsnotify_mkdir(dir, dentry->d_name.name);
security_inode_post_mkdir(dir,dentry, mode);
}
@@ -1915,6 +1924,7 @@ int vfs_symlink(struct inode *dir, struc
DQUOT_INIT(dir);
error = dir->i_op->symlink(dir, dentry, oldname);
if (!error) {
+ audit_notify_watch(dentry->d_inode, MAY_WRITE);
fsnotify_create(dir, dentry->d_name.name);
security_inode_post_symlink(dir, dentry, oldname);
}
@@ -1988,6 +1998,7 @@ int vfs_link(struct dentry *old_dentry,
error = dir->i_op->link(old_dentry, dir, new_dentry);
up(&old_dentry->d_inode->i_sem);
if (!error) {
+ audit_notify_watch(new_dentry->d_inode, MAY_WRITE);
fsnotify_create(dir, new_dentry->d_name.name);
security_inode_post_link(old_dentry, dir, new_dentry);
}
@@ -2111,6 +2122,7 @@ static int vfs_rename_dir(struct inode *
}
if (!error) {
d_move(old_dentry,new_dentry);
+ audit_notify_watch(old_dentry->d_inode, MAY_WRITE);
security_inode_post_rename(old_dir, old_dentry,
new_dir, new_dentry);
}
@@ -2139,6 +2151,7 @@ static int vfs_rename_other(struct inode
/* The following d_move() should become unconditional */
if (!(old_dir->i_sb->s_type->fs_flags & FS_ODD_RENAME))
d_move(old_dentry, new_dentry);
+ audit_notify_watch(old_dentry->d_inode, MAY_WRITE);
security_inode_post_rename(old_dir, old_dentry, new_dir, new_dentry);
}
if (target)
diff -Nurp linux-2.6.12-rc3-mm3~orig/include/linux/audit.h
linux-2.6.12-rc3-mm3~audit/include/linux/audit.h
--- linux-2.6.12-rc3-mm3~orig/include/linux/audit.h 2005-05-06 13:05:14.000000000 -0500
+++ linux-2.6.12-rc3-mm3~audit/include/linux/audit.h 2005-05-10 23:46:30.000000000 -0500
@@ -24,18 +24,29 @@
#ifndef _LINUX_AUDIT_H_
#define _LINUX_AUDIT_H_
+#ifdef __KERNEL__
#include <linux/sched.h>
#include <linux/elf.h>
+
+struct hlist_head;
+struct hlist_node;
+struct dentry;
+struct atomic_t;
+#endif
+
/* Request and reply types */
-#define AUDIT_GET 1000 /* Get status */
-#define AUDIT_SET 1001 /* Set status (enable/disable/auditd) */
-#define AUDIT_LIST 1002 /* List filtering rules */
-#define AUDIT_ADD 1003 /* Add filtering rule */
-#define AUDIT_DEL 1004 /* Delete filtering rule */
-#define AUDIT_USER 1005 /* Send a message from user-space */
-#define AUDIT_LOGIN 1006 /* Define the login id and informaiton */
-#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */
+#define AUDIT_GET 1000 /* Get status */
+#define AUDIT_SET 1001 /* Set status (enable/disable/auditd) */
+#define AUDIT_LIST 1002 /* List filtering rules */
+#define AUDIT_ADD 1003 /* Add filtering rule */
+#define AUDIT_DEL 1004 /* Delete filtering rule */
+#define AUDIT_USER 1005 /* Send a message from user-space */
+#define AUDIT_LOGIN 1006 /* Define the login id and informaiton */
+#define AUDIT_WATCH_INS 1007 /* Insert file/dir watch entry */
+#define AUDIT_WATCH_REM 1008 /* Remove file/dir watch entry */
+#define AUDIT_WATCH_LIST 1009 /* List all watches */
+#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */
/* Rule flags */
#define AUDIT_PER_TASK 0x01 /* Apply rule at task creation (not syscall) */
@@ -132,6 +143,9 @@
#define AUDIT_ARCH_V850 (EM_V850|__AUDIT_ARCH_LE)
#define AUDIT_ARCH_X86_64 (EM_X86_64|__AUDIT_ARCH_64BIT|__AUDIT_ARCH_LE)
+/* 32 byte max key size */
+#define AUDIT_FILTERKEY_MAX 32
+
#ifndef __KERNEL__
struct audit_message {
struct nlmsghdr nlh;
@@ -159,8 +173,46 @@ struct audit_rule { /* for AUDIT_LIST,
__u32 values[AUDIT_MAX_FIELDS];
};
+/* Structure to transport watch data to and from the kernel */
+
+struct watch_transport {
+ __u32 dev_major;
+ __u32 dev_minor;
+ __u32 perms;
+ __u32 valid;
+ __u32 pathlen;
+ __u32 fklen;
+ char buf[0];
+};
+
#ifdef __KERNEL__
+/* Structure associated with inode->i_audit */
+
+struct audit_inode_data {
+ 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 audit_buffer;
struct audit_context;
struct inode;
@@ -190,6 +242,7 @@ extern void audit_get_stamp(struct audit
extern int audit_set_loginuid(struct task_struct *task, uid_t loginuid);
extern uid_t audit_get_loginuid(struct audit_context *ctx);
extern int audit_ipc_perms(unsigned long qbytes, uid_t uid, gid_t gid, mode_t mode);
+extern int audit_notify_watch(struct inode *inode, int mask);
#else
#define audit_alloc(t) ({ 0; })
#define audit_free(t) do { ; } while (0)
@@ -198,8 +251,32 @@ extern int audit_ipc_perms(unsigned long
#define audit_getname(n) do { ; } while (0)
#define audit_putname(n) do { ; } while (0)
#define audit_inode(n,i) do { ; } while (0)
+#define audit_receive_filter(t,p,u,s,d,l) ({ -EOPNOTSUPP; })
#define audit_get_loginuid(c) ({ -1; })
#define audit_ipc_perms(q,u,g,m) ({ 0; })
+#define audit_notify_watch(i,m) ({ 0; })
+#endif
+
+#ifdef CONFIG_AUDITFILESYSTEM
+extern int audit_filesystem_init(void);
+extern int audit_list_watches(int pid, int seq);
+extern int audit_receive_watch(int type, int pid, int uid, int seq,
+ struct watch_transport *req, uid_t loginuid);
+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_dentry_unpin(struct dentry *dentry);
+extern struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry);
+#else
+#define audit_filesystem_init() ({ 0; })
+#define audit_list_watches(p,s) ({ -EOPNOTSUPP; })
+#define audit_receive_watch(t,p,u,s,r,l) ({ -EOPNOTSUPP; })
+#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; })
#endif
#ifdef CONFIG_AUDIT
diff -Nurp linux-2.6.12-rc3-mm3~orig/include/linux/fs.h
linux-2.6.12-rc3-mm3~audit/include/linux/fs.h
--- linux-2.6.12-rc3-mm3~orig/include/linux/fs.h 2005-05-06 13:05:14.000000000 -0500
+++ linux-2.6.12-rc3-mm3~audit/include/linux/fs.h 2005-05-06 14:29:04.000000000 -0500
@@ -226,6 +226,7 @@ struct poll_table_struct;
struct kstatfs;
struct vm_area_struct;
struct vfsmount;
+struct audit_inode_data;
/* Used to be a macro which just called the function, now just a function */
extern void update_atime (struct inode *);
@@ -477,6 +478,7 @@ struct inode {
struct semaphore inotify_sem; /* protects the watches list */
#endif
+ struct audit_inode_data *i_audit;
unsigned long i_state;
unsigned long dirtied_when; /* jiffies of first dirtying */
diff -Nurp linux-2.6.12-rc3-mm3~orig/init/Kconfig linux-2.6.12-rc3-mm3~audit/init/Kconfig
--- linux-2.6.12-rc3-mm3~orig/init/Kconfig 2005-05-06 13:05:15.000000000 -0500
+++ linux-2.6.12-rc3-mm3~audit/init/Kconfig 2005-05-06 14:29:04.000000000 -0500
@@ -180,6 +180,16 @@ config AUDITSYSCALL
can be used independently or with another kernel subsystem,
such as SELinux.
+config AUDITFILESYSTEM
+ bool "Enable file system auditing support"
+ depends on AUDITSYSCALL
+ default n
+ help
+ Enable file system auditing for regular files and directories.
+ When a targeted file or directory is accessed, an audit record
+ is generated describing the inode accessed, how it was accessed,
+ and by whom (ie: pid and system call).
+
config HOTPLUG
bool "Support for hot-pluggable devices" if !ARCH_S390
default ARCH_S390
diff -Nurp linux-2.6.12-rc3-mm3~orig/kernel/Makefile
linux-2.6.12-rc3-mm3~audit/kernel/Makefile
--- linux-2.6.12-rc3-mm3~orig/kernel/Makefile 2005-05-06 13:05:15.000000000 -0500
+++ linux-2.6.12-rc3-mm3~audit/kernel/Makefile 2005-05-06 14:29:04.000000000 -0500
@@ -25,6 +25,7 @@ obj-$(CONFIG_IKCONFIG_PROC) += configs.o
obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
obj-$(CONFIG_AUDIT) += audit.o
obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
+obj-$(CONFIG_AUDITFILESYSTEM) += auditfs.o
obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_SYSFS) += ksysfs.o
obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o
diff -Nurp linux-2.6.12-rc3-mm3~orig/kernel/audit.c
linux-2.6.12-rc3-mm3~audit/kernel/audit.c
--- linux-2.6.12-rc3-mm3~orig/kernel/audit.c 2005-05-06 13:05:15.000000000 -0500
+++ linux-2.6.12-rc3-mm3~audit/kernel/audit.c 2005-05-10 11:41:42.000000000 -0500
@@ -112,8 +112,13 @@ static LIST_HEAD(audit_entlist);
static LIST_HEAD(audit_extlist);
/* The netlink socket is only to be read by 1 CPU, which lets us assume
- * that list additions and deletions never happen simultaneiously in
- * auditsc.c */
+ * that list additions and deletions, and watch insertions never happen
+ * simultaneiously in auditsc.c and auditfs.c respectively.
+ *
+ * Even though there can only be one watch removal via netlink at a time,
+ * there is still a chance of watch removal via audit_update_watch(). In
+ * this case, the semaphore is not enough.
+ */
static DECLARE_MUTEX(audit_netlink_sem);
/* AUDIT_BUFSIZ is the size of the temporary buffer used for formatting
@@ -321,6 +326,9 @@ static int audit_netlink_ok(kernel_cap_t
case AUDIT_SET:
case AUDIT_ADD:
case AUDIT_DEL:
+ case AUDIT_WATCH_LIST:
+ case AUDIT_WATCH_INS:
+ case AUDIT_WATCH_REM:
if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
err = -EPERM;
break;
@@ -412,12 +420,19 @@ static int audit_receive_msg(struct sk_b
return -EINVAL;
/* fallthrough */
case AUDIT_LIST:
-#ifdef CONFIG_AUDITSYSCALL
err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid,
uid, seq, data, loginuid);
-#else
- err = -EOPNOTSUPP;
-#endif
+ break;
+ case AUDIT_WATCH_LIST:
+ err = audit_list_watches(pid, seq);
+ break;
+ case AUDIT_WATCH_INS:
+ case AUDIT_WATCH_REM:
+ if (nlh->nlmsg_len < sizeof(struct watch_transport))
+ return -EINVAL;
+ err = audit_receive_watch(nlh->nlmsg_type,
+ NETLINK_CB(skb).pid,
+ uid, seq, data, loginuid);
break;
default:
err = -EINVAL;
@@ -559,6 +574,7 @@ static int __init audit_init(void)
audit_panic("cannot initialize netlink socket");
audit_initialized = 1;
+ audit_filesystem_init();
audit_enabled = audit_default;
audit_log(NULL, "initialized");
return 0;
@@ -586,6 +602,7 @@ int __init audit_init(void)
audit_pid = 0;
audit_initialized = 1;
+ audit_filesystem_init();
audit_enabled = audit_default;
audit_log(NULL, "initialized");
return 0;
diff -Nurp linux-2.6.12-rc3-mm3~orig/kernel/auditfs.c
linux-2.6.12-rc3-mm3~audit/kernel/auditfs.c
--- linux-2.6.12-rc3-mm3~orig/kernel/auditfs.c 1969-12-31 18:00:00.000000000 -0600
+++ linux-2.6.12-rc3-mm3~audit/kernel/auditfs.c 2005-05-11 11:35:27.000000000 -0500
@@ -0,0 +1,674 @@
+/* auditfs.c -- Filesystem auditing support -*- linux-c -*-
+ * Implements filesystem auditing support, depends on kernel/auditsc.c
+ *
+ * Copyright 2005 International Business Machines Corp. (IBM)
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ *
+ * Written by Timothy R. Chavez <chavezt(a)us.ibm.com>
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/audit.h>
+#include <asm/uaccess.h>
+
+
+extern int audit_enabled;
+
+static kmem_cache_t *audit_watch_cache;
+static kmem_cache_t *audit_wentry_cache;
+
+/* Read-heavy list */
+HLIST_HEAD(audit_master_watchlist);
+
+
+/* Private Interface */
+
+/* Unpin the dentry stored at wentry->w_dentry. */
+static inline void audit_unpin(struct audit_wentry *wentry)
+{
+ if (wentry && wentry->w_dentry) {
+ dput(wentry->w_dentry);
+ wentry->w_dentry = NULL;
+ }
+}
+
+/* Pin the dentry and store it at wentry->w_dentry. */
+static inline void audit_pin(struct audit_wentry *wentry,
+ struct dentry *dentry)
+{
+ if (wentry && !wentry->w_dentry)
+ wentry->w_dentry = dget(dentry);
+}
+
+static inline struct audit_wentry *audit_wentry_fetch(const char *name,
+ struct audit_inode_data *data)
+{
+ struct audit_wentry *wentry, *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);
+ break;
+ }
+
+ return ret;
+}
+
+static inline struct audit_wentry *audit_wentry_fetch_lock(const char *name,
+ struct audit_inode_data *data)
+{
+ struct audit_wentry *ret = NULL;
+
+ if (name && data) {
+ read_lock(&data->lock);
+ ret = audit_wentry_fetch(name, data);
+ read_unlock(&data->lock);
+ }
+
+ return ret;
+}
+
+static inline struct audit_watch *audit_watch_alloc(void)
+{
+ struct audit_watch *watch;
+
+ watch = kmem_cache_alloc(audit_watch_cache, GFP_KERNEL);
+ if (watch) {
+ watch->name = NULL;
+ watch->path = NULL;
+ watch->filterkey = NULL;
+ watch->perms = 0;
+ }
+
+ return watch;
+}
+
+static inline void audit_watch_free(struct audit_watch *watch)
+{
+ if (watch) {
+ kfree(watch->name);
+ kfree(watch->path);
+ kfree(watch->filterkey);
+ 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)
+{
+ unsigned int offset;
+ struct watch_transport *t;
+ struct audit_watch *watch;
+
+ watch = audit_watch_alloc();
+ if (!watch)
+ goto audit_to_watch_exit;
+
+ t = (struct watch_transport *)memblk;
+
+ watch->perms = t->perms;
+
+ offset = sizeof(struct watch_transport);
+ watch->filterkey = kmalloc(t->fklen, GFP_KERNEL);
+ if (!watch->filterkey)
+ goto audit_to_watch_fail;
+ watch->filterkey[0] = 0;
+ strncat(watch->filterkey, memblk + offset, t->fklen);
+
+ offset += t->fklen;
+ watch->path = kmalloc(t->pathlen, GFP_KERNEL);
+ if (!watch->path)
+ goto audit_to_watch_fail;
+ watch->path[0] = 0;
+ strncat(watch->path, memblk + offset, t->pathlen);
+
+ goto audit_to_watch_exit;
+
+audit_to_watch_fail:
+ audit_watch_free(watch);
+ watch = NULL;
+audit_to_watch_exit:
+ return watch;
+}
+
+/*
+ * Convert a kernel audit_watch structure into a watch_transport structure.
+ * We do this to send watch information back to user space.
+ */
+static inline void *audit_to_transport(struct audit_watch *watch, size_t size)
+{
+ unsigned int offset;
+ struct watch_transport t;
+ void *memblk;
+
+ memblk = kmalloc(size, GFP_KERNEL);
+ if (!memblk)
+ goto audit_to_transport_exit;
+
+ 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);
+
+audit_to_transport_exit:
+ return memblk;
+}
+
+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;
+ }
+
+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);
+ }
+}
+
+/*
+ * Protected by audit_netlink_sem, thus there will never be a race on
+ * insertions as all insertion requests must come through netlink.
+ */
+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)
+{
+ 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;
+ }
+
+ ret = -ENOMEM;
+ new = audit_wentry_alloc();
+ if (!new)
+ goto audit_create_wentry_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;
+ }
+
+ ret = 0;
+
+ new->w_watch = watch;
+ audit_wentry_get(new);
+
+ hlist_add_head_rcu(&new->w_master, &audit_master_watchlist);
+ hlist_add_head(&new->w_node, &data->watchlist);
+
+audit_create_wentry_exit:
+ return ret;
+}
+
+static inline void audit_wentry_rcu_put(struct rcu_head *head)
+{
+ struct audit_wentry *wentry;
+
+ wentry = container_of(head, struct audit_wentry, w_rcu);
+ audit_wentry_put(wentry);
+}
+
+/*
+ * There's three ways we can arrive at audit_destroy_wentry. 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
+ * 2) A directory with an "active" watchlist is rename()'ed
+ * 3) The inode is in the process of being destroyed
+ *
+ * In ways 1) and 2) the data->lock must be held to safely enter. Because
+ * of this, we're able to remove the watchlist entry from the master list
+ * as well, without needing an additional spinlock.
+ *
+ * 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
+ * hold the data->lock.
+ */
+static inline void audit_destroy_wentry(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ hlist_del_init(&wentry->w_node);
+ audit_wentry_put(wentry);
+ hlist_del_rcu(&wentry->w_master);
+ call_rcu(&wentry->w_rcu, audit_wentry_rcu_put);
+ }
+}
+
+
+/*
+ * We only drain the watchlist in two ways. They are:
+ *
+ * 1) Upon deletion of the inode (no contention)
+ * 2) Upon rename()'ing a directory with an "active" watchlist
(contention)
+ *
+ * Caller should hold data->lock where contention is possible.
+ */
+static inline void audit_drain_watchlist(struct audit_inode_data *data)
+{
+ struct audit_wentry *wentry;
+ struct hlist_node *pos, *tmp;
+
+ hlist_for_each_entry_safe(wentry, pos, tmp, &data->watchlist, w_node)
+ audit_destroy_wentry(wentry);
+}
+
+static inline struct audit_inode_data *audit_data_alloc(void)
+{
+ struct audit_inode_data *data;
+
+ data = kmalloc(sizeof(struct audit_inode_data), GFP_KERNEL);
+ if (data) {
+ data->wentry = NULL;
+ INIT_HLIST_HEAD(&data->watchlist);
+ data->lock = RW_LOCK_UNLOCKED;
+ }
+
+ return data;
+}
+
+static inline void audit_data_free(struct audit_inode_data *data)
+{
+ if (data) {
+ audit_drain_watchlist(data);
+ audit_wentry_put(data->wentry);
+ kfree(data);
+ }
+}
+
+static inline int audit_insert_watch(struct audit_watch *watch, uid_t loginuid)
+{
+ int ret;
+ struct nameidata nd;
+
+ ret = path_lookup(watch->path, LOOKUP_PARENT, &nd);
+ if (ret < 0)
+ goto audit_insert_watch_exit;
+
+ ret = -EPERM;
+ 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,
+ nd.dentry->d_inode->i_audit);
+ if (ret < 0)
+ goto audit_insert_watch_release;
+
+ audit_log(NULL, "auid %u inserted watch\n", loginuid);
+ /* __d_lookup will attach the audit data, if nd.last exists. */
+ dput(d_lookup(nd.dentry, &nd.last));
+
+audit_insert_watch_release:
+ path_release(&nd);
+audit_insert_watch_exit:
+ return ret;
+}
+
+/*
+ * We hold the data->lock here to eliminate contention between this user space
+ * request and an action taken by audit_update_watch() in which watch removal
+ * is also possible when a directory with an "active" watchlist is
rename()'ed.
+ *
+ */
+static inline int audit_remove_watch(struct audit_watch *watch, uid_t loginuid)
+{
+ int ret;
+ struct nameidata nd;
+ struct audit_inode_data *data;
+ struct audit_wentry *wentry;
+
+ ret = path_lookup(watch->path, LOOKUP_PARENT, &nd);
+ if (ret < 0)
+ goto audit_remove_watch_exit;
+
+ ret = -EPERM;
+ if (nd.last_type != LAST_NORM || !nd.last.name)
+ goto audit_remove_watch_release;
+
+ data = nd.dentry->d_inode->i_audit;
+
+ write_lock(&data->lock);
+ wentry = audit_wentry_fetch(nd.last.name, data);
+ if (!wentry) {
+ write_unlock(&data->lock);
+ goto audit_remove_watch_release;
+ }
+ audit_destroy_wentry(wentry);
+ audit_wentry_put(wentry);
+ audit_log(NULL, "auid %u removed watch\n", loginuid);
+ write_unlock(&data->lock);
+
+ ret = 0;
+
+audit_remove_watch_release:
+ path_release(&nd);
+audit_remove_watch_exit:
+ return ret;
+}
+
+struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ BUG_ON(!atomic_read(&wentry->w_count));
+ atomic_inc(&wentry->w_count);
+ }
+
+ return wentry;
+}
+
+void audit_wentry_put(struct audit_wentry *wentry)
+{
+ if (wentry && atomic_dec_and_test(&wentry->w_count))
+ audit_wentry_free(wentry);
+}
+
+/*
+ * The update hook is responsible for watching and unwatching d_inodes during
+ * their lifetimes in dcache. Each d_inode being watched is pinned in memory.
+ * As soon as a d_inode becomes unwatched (ie: dentry is destroyed, watch is
+ * unhashed / removed from watchlist, dentry is moved out of watch path).
+ *
+ * Hook appears in fs/dcache.c:
+ * d_move(),
+ * dentry_iput(),
+ * d_instantiate(),
+ * d_splice_alias()
+ * __d_lookup()
+ */
+void audit_update_watch(struct dentry *dentry, int remove)
+{
+ struct audit_wentry *wentry;
+ struct audit_inode_data *data, *parent;
+
+ if (likely(!audit_enabled))
+ return;
+
+ if (!dentry || !dentry->d_inode)
+ return;
+
+ if (!dentry->d_parent || !dentry->d_parent->d_inode)
+ return;
+
+ data = dentry->d_inode->i_audit;
+ parent = dentry->d_parent->d_inode->i_audit;
+
+ wentry = audit_wentry_fetch_lock(dentry->d_name.name, parent);
+
+ write_lock(&data->lock);
+ /* FIXME: long watchlist == too much spinning? */
+ if (remove) {
+ audit_drain_watchlist(data);
+ if ((wentry && data->wentry) && (wentry == data->wentry)) {
+ audit_wentry_put(data->wentry);
+ data->wentry = 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);
+ }
+ write_unlock(&data->lock);
+
+ audit_wentry_put(wentry);
+}
+
+int audit_send_watch(int pid, int seq, struct audit_watch *watch)
+{
+ int ret;
+ size_t size;
+ void *memblk;
+
+ /* We must include space for both "\0" */
+ size = sizeof(struct watch_transport) + strlen(watch->path) +
+ strlen(watch->filterkey) + 2;
+
+ ret = -ENOMEM;
+ memblk = audit_to_transport(watch, size);
+ if (!memblk)
+ goto audit_send_watch_exit;
+
+ ret = 0;
+ audit_send_reply(pid, seq, AUDIT_WATCH_LIST, 0, 1, memblk, size);
+ kfree(memblk);
+
+audit_send_watch_exit:
+ return ret;
+}
+
+/*
+ * Read the "master watchlist" which is a watchlist of all watches in the
+ * 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
+ * watch removal, until after we're done reading. So, the potential
+ * for the rug being pulled out from under us is NIL.
+ *
+ * This list is only a "snapshot in time". It is not gospel.
+ */
+int audit_list_watches(int pid, int seq)
+{
+ int ret = 0;
+ struct audit_wentry *wentry;
+ struct hlist_node *pos;
+
+ rcu_read_lock();
+ hlist_for_each_entry_rcu(wentry, pos, &audit_master_watchlist, w_master) {
+ ret = audit_send_watch(pid, seq, wentry->w_watch);
+ if (ret < 0)
+ break;
+ }
+ rcu_read_unlock();
+
+ audit_send_reply(pid, seq, AUDIT_WATCH_LIST, 1, 1, NULL, 0);
+
+ return ret;
+}
+
+int audit_receive_watch(int type, int pid, int uid, int seq,
+ struct watch_transport *req, uid_t loginuid)
+{
+ int ret = 0;
+ struct audit_watch *watch = NULL;
+
+ ret = -EINVAL;
+ if (req->pathlen > PATH_MAX || req->pathlen == 0)
+ goto audit_receive_watch_exit;
+
+ if (req->fklen > AUDIT_FILTERKEY_MAX)
+ goto audit_receive_watch_exit;
+
+ if (req->perms > (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND))
+ goto audit_receive_watch_exit;
+
+ ret = -ENOMEM;
+ watch = audit_to_watch(req);
+ if (!watch)
+ goto audit_receive_watch_exit;
+
+ switch (type) {
+ case AUDIT_WATCH_INS:
+ ret = audit_insert_watch(watch, loginuid);
+ break;
+ case AUDIT_WATCH_REM:
+ ret = audit_remove_watch(watch, loginuid);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+audit_receive_watch_exit:
+ audit_watch_free(watch);
+ return ret;
+}
+
+int audit_inode_alloc(struct inode *inode)
+{
+ if (inode) {
+ inode->i_audit = audit_data_alloc();
+ if (!inode->i_audit)
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+void audit_inode_free(struct inode *inode)
+{
+ if (inode)
+ audit_data_free(inode->i_audit);
+}
+/*
+ * When we delete a dentry we check to see whether or not we're being
+ * watched. If we are watched, we have to put back our reference to
+ * the dentry to unpin it from memory or bad things happen.
+ *
+ * Hook appears in fs/dcache.c:d_delete()
+ */
+void audit_dentry_unpin(struct dentry *dentry)
+{
+ struct audit_inode_data *parent;
+ struct audit_wentry *wentry;
+
+ parent = dentry->d_parent->d_inode->i_audit;
+ wentry = audit_wentry_fetch_lock(dentry->d_name.name, parent);
+ if (wentry) {
+ audit_unpin(dentry->d_inode->i_audit->wentry);
+ audit_wentry_put(wentry);
+ }
+}
+
+int audit_filesystem_init(void)
+{
+ int ret;
+
+ ret = -ENOMEM;
+ audit_watch_cache =
+ kmem_cache_create("audit_watch_cache",
+ sizeof(struct audit_watch), 0, 0, NULL, NULL);
+ 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;
+
+ ret = 0;
+ goto audit_filesystem_init_exit;
+
+audit_filesystem_init_fail:
+ kmem_cache_destroy(audit_watch_cache);
+ kmem_cache_destroy(audit_wentry_cache);
+audit_filesystem_init_exit:
+ return ret;
+}
diff -Nurp linux-2.6.12-rc3-mm3~orig/kernel/auditsc.c
linux-2.6.12-rc3-mm3~audit/kernel/auditsc.c
--- linux-2.6.12-rc3-mm3~orig/kernel/auditsc.c 2005-05-06 13:05:15.000000000 -0500
+++ linux-2.6.12-rc3-mm3~audit/kernel/auditsc.c 2005-05-06 14:29:04.000000000 -0500
@@ -102,6 +102,7 @@ struct audit_aux_data {
};
#define AUDIT_AUX_IPCPERM 0
+#define AUDIT_AUX_WATCH 1
struct audit_aux_data_ipcctl {
struct audit_aux_data d;
@@ -112,6 +113,16 @@ struct audit_aux_data_ipcctl {
mode_t mode;
};
+struct audit_aux_data_watched {
+ struct audit_aux_data link;
+ struct audit_wentry *wentry;
+ unsigned long ino;
+ int mask;
+ uid_t uid;
+ gid_t gid;
+ dev_t dev;
+ dev_t rdev;
+};
/* The per-task audit context. */
struct audit_context {
@@ -696,6 +707,24 @@ static void audit_log_exit(struct audit_
audit_log_format(ab,
" qbytes=%lx uid=%d gid=%d mode=%x",
axi->qbytes, axi->uid, axi->gid, axi->mode);
+ break;
+ }
+
+ case AUDIT_AUX_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_format(ab,
+ " filterkey=%s perm=%u perm_mask=%d"
+ " inode=%lu inode_uid=%d inode_gid=%d"
+ " inode_dev=%02x:%02x inode_rdev=%02x:%02x",
+ axi->wentry->w_watch->filterkey,
+ axi->wentry->w_watch->perms,
+ axi->mask, axi->ino, axi->uid, axi->gid,
+ MAJOR(axi->dev), MINOR(axi->dev),
+ MAJOR(axi->rdev), MINOR(axi->rdev));
+ audit_wentry_put(axi->wentry);
+ break;
}
}
audit_log_end(ab);
@@ -1056,3 +1085,52 @@ int audit_ipc_perms(unsigned long qbytes
context->aux = (void *)ax;
return 0;
}
+
+int audit_notify_watch(struct inode *inode, int mask)
+{
+ int ret = 0;
+ struct audit_context *context = current->audit_context;
+ struct audit_aux_data_watched *ax;
+ struct audit_wentry *wentry = NULL;
+
+ if (likely(!audit_enabled))
+ goto audit_notify_watch_exit;
+
+ if (!inode)
+ goto audit_notify_watch_exit;
+
+ wentry = audit_wentry_get(inode->i_audit->wentry);
+ if (!wentry)
+ goto audit_notify_watch_exit;
+
+ if (mask && (wentry->w_watch->perms &&
!(wentry->w_watch->perms&mask)))
+ goto audit_notify_watch_fail;
+
+ ret = -ENOMEM;
+ ax = kmalloc(sizeof(*ax), GFP_KERNEL);
+ if (!ax)
+ goto audit_notify_watch_fail;
+
+ ret = 0;
+ if (context->in_syscall && !context->auditable)
+ context->auditable = 1;
+
+ ax->wentry = wentry;
+ ax->mask = mask;
+ ax->ino = inode->i_ino;
+ ax->uid = inode->i_uid;
+ ax->gid = inode->i_gid;
+ ax->dev = inode->i_sb->s_dev;
+ ax->rdev = inode->i_rdev;
+
+ ax->link.type = AUDIT_AUX_WATCH;
+ ax->link.next = context->aux;
+ context->aux = (void *)ax;
+
+ goto audit_notify_watch_exit;
+
+audit_notify_watch_fail:
+ audit_wentry_put(wentry);
+audit_notify_watch_exit:
+ return ret;
+}
diff -Nurp linux-2.6.12-rc3-mm3~orig/security/selinux/nlmsgtab.c
linux-2.6.12-rc3-mm3~audit/security/selinux/nlmsgtab.c
--- linux-2.6.12-rc3-mm3~orig/security/selinux/nlmsgtab.c 2005-05-06 13:05:16.000000000
-0500
+++ linux-2.6.12-rc3-mm3~audit/security/selinux/nlmsgtab.c 2005-05-06 14:29:04.000000000
-0500
@@ -97,6 +97,9 @@ static struct nlmsg_perm nlmsg_audit_per
{ AUDIT_ADD, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
{ AUDIT_DEL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
{ AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_RELAY },
+ { AUDIT_WATCH_INS, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
+ { AUDIT_WATCH_REM, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
+ { AUDIT_WATCH_LIST, NETLINK_AUDIT_SOCKET__NLMSG_READPRIV },
};