[RFC][PATCH] (#5, Update #2) filesystem auditing support

Timothy R. Chavez chavezt at gmail.com
Mon Mar 7 19:59:42 UTC 2005


Hello,

This patch incorporates a lot of feedback from various people (Serge
Hallyn, Steve Grubb, Dave Hansen, Mounir Bsaibes): mostly cosmetic. 
But there were some bug fixes in audit_create_wentry().

I'm going to spend a couple days testing this and writing the abstract
and then I want to put it on linux-fsdevel.  I'd really appreciate
some scrutiny and feedback on this patch during that time.  The goal
is to finally move on this and make it more visible this week.  There
is still one remaining feature that was requested that needs to be
implemented and I'll get to it eventually... before March is over (the
end of my development schedule).

Also, should I break this up into a patch set?

-Tim

diff -Nurp linux-2.6.11/fs/dcache.c linux-2.6.11-audit/fs/dcache.c
--- linux-2.6.11/fs/dcache.c	2005-03-02 01:37:48.000000000 -0600
+++ linux-2.6.11-audit/fs/dcache.c	2005-03-04 16:20:29.000000000 -0600
@@ -32,6 +32,7 @@
 #include <linux/seqlock.h>
 #include <linux/swap.h>
 #include <linux/bootmem.h>
+#include <linux/audit.h>
 
 /* #define DCACHE_DEBUG 1 */
 
@@ -800,6 +801,7 @@ void d_instantiate(struct dentry *entry,
 	entry->d_inode = inode;
 	spin_unlock(&dcache_lock);
 	security_d_instantiate(entry, inode);
+	audit_watch(entry, 0);
 }
 
 /**
@@ -975,6 +977,7 @@ struct dentry *d_splice_alias(struct ino
 		if (new) {
 			BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
 			spin_unlock(&dcache_lock);
+			audit_watch(dentry, 0);
 			security_d_instantiate(new, inode);
 			d_rehash(dentry);
 			d_move(new, dentry);
@@ -984,6 +987,7 @@ struct dentry *d_splice_alias(struct ino
 			list_add(&dentry->d_alias, &inode->i_dentry);
 			dentry->d_inode = inode;
 			spin_unlock(&dcache_lock);
+			audit_watch(dentry, 0);
 			security_d_instantiate(dentry, inode);
 			d_rehash(dentry);
 		}
@@ -1094,6 +1098,8 @@ next:
  	}
  	rcu_read_unlock();
 
+	audit_watch(found, 0);
+
  	return found;
 }
 
@@ -1333,6 +1339,7 @@ already_unhashed:
 	spin_unlock(&dentry->d_lock);
 	write_sequnlock(&rename_lock);
 	spin_unlock(&dcache_lock);
+	audit_watch(dentry, 1);
 }
 
 /**
diff -Nurp linux-2.6.11/fs/inode.c linux-2.6.11-audit/fs/inode.c
--- linux-2.6.11/fs/inode.c	2005-03-02 01:38:33.000000000 -0600
+++ linux-2.6.11-audit/fs/inode.c	2005-03-04 16:20:29.000000000 -0600
@@ -21,6 +21,7 @@
 #include <linux/pagemap.h>
 #include <linux/cdev.h>
 #include <linux/bootmem.h>
+#include <linux/audit.h>
 
 /*
  * This is needed for the following functions:
@@ -136,6 +137,7 @@ static struct inode *alloc_inode(struct 
 		inode->i_rdev = 0;
 		inode->i_security = NULL;
 		inode->dirtied_when = 0;
+		audit_inode_alloc(inode);
 		if (security_inode_alloc(inode)) {
 			if (inode->i_sb->s_op->destroy_inode)
 				inode->i_sb->s_op->destroy_inode(inode);
@@ -175,6 +177,7 @@ void destroy_inode(struct inode *inode) 
 	if (inode_has_buffers(inode))
 		BUG();
 	security_inode_free(inode);
+	audit_inode_free(inode);
 	if (inode->i_sb->s_op->destroy_inode)
 		inode->i_sb->s_op->destroy_inode(inode);
 	else
diff -Nurp linux-2.6.11/fs/namei.c linux-2.6.11-audit/fs/namei.c
--- linux-2.6.11/fs/namei.c	2005-03-02 01:37:55.000000000 -0600
+++ linux-2.6.11-audit/fs/namei.c	2005-03-07 13:06:33.000000000 -0600
@@ -214,6 +214,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;
 
@@ -344,6 +346,8 @@ static inline int exec_permission_lite(s
 {
 	umode_t	mode = inode->i_mode;
 
+	audit_notify_watch(inode, MAY_EXEC);
+
 	if (inode->i_op && inode->i_op->permission)
 		return -EAGAIN;
 
@@ -1703,6 +1707,9 @@ int vfs_rmdir(struct inode *dir, struct 
 {
 	int error = may_delete(dir, dentry, 1);
 
+	if (dentry)
+		audit_notify_watch(dentry->d_inode, 0);
+
 	if (error)
 		return error;
 
@@ -1778,6 +1785,9 @@ int vfs_unlink(struct inode *dir, struct
 {
 	int error = may_delete(dir, dentry, 0);
 
+	if (dentry)
+		audit_notify_watch(dentry->d_inode, 0);
+
 	if (error)
 		return error;
 
diff -Nurp linux-2.6.11/include/linux/audit.h
linux-2.6.11-audit/include/linux/audit.h
--- linux-2.6.11/include/linux/audit.h	2005-03-02 01:38:09.000000000 -0600
+++ linux-2.6.11-audit/include/linux/audit.h	2005-03-07 10:49:49.000000000 -0600
@@ -24,15 +24,23 @@
 #ifndef _LINUX_AUDIT_H_
 #define _LINUX_AUDIT_H_
 
+#ifdef __KERNEL__
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#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 information */
+#define AUDIT_WATCH_INS	1007    /* Insert file/dir watch entry */
+#define AUDIT_WATCH_REM	1008	/* Remove file/dir watch entry */
+#define AUDIT_KERNEL   	2000	/* Asynchronous audit record. NOT A REQUEST. */
 
 /* Rule flags */
 #define AUDIT_PER_TASK 0x01	/* Apply rule at task creation (not syscall) */
@@ -91,11 +99,15 @@
 #define AUDIT_STATUS_PID		0x0004
 #define AUDIT_STATUS_RATE_LIMIT		0x0008
 #define AUDIT_STATUS_BACKLOG_LIMIT	0x0010
+#define AUDIT_STATUS_FSENABLED		0x0020
 				/* Failure-to-log actions */
 #define AUDIT_FAIL_SILENT	0
 #define AUDIT_FAIL_PRINTK	1
 #define AUDIT_FAIL_PANIC	2
 
+/* 32 byte max key size */
+#define AUDIT_FILTERKEY_MAX	32
+
 #ifndef __KERNEL__
 struct audit_message {
 	struct nlmsghdr nlh;
@@ -106,6 +118,7 @@ struct audit_message {
 struct audit_status {
 	__u32		mask;		/* Bit mask for valid entries */
 	__u32		enabled;	/* 1 = enabled, 0 = disbaled */
+	__u32		fs_enabled;	/* 1 = fs auditing on, 0 = off */
 	__u32		failure;	/* Failure-to-log action */
 	__u32		pid;		/* pid of auditd process */
 	__u32		rate_limit;	/* messages rate limit (per second) */
@@ -123,14 +136,38 @@ struct audit_rule {		/* for AUDIT_LIST, 
 	__u32		values[AUDIT_MAX_FIELDS];
 };
 
+struct audit_watch {
+	int	namelen;
+	int 	fklen;
+	char	*name;
+	char	*filterkey;
+	__u32	perms;
+};
+
 #ifdef __KERNEL__
 
+struct audit_data {
+	struct audit_wentry	*wentry;
+	struct list_head 	watchlist;
+	rwlock_t 		watchlist_lock;
+	atomic_t		count;
+};
+
+struct audit_wentry {
+	struct list_head 	w_list;
+	atomic_t 		w_count;
+	struct audit_data 	*w_data;
+	struct audit_watch	*w_watch;
+	int			w_valid;
+};
+
 #ifdef CONFIG_AUDIT
 struct audit_buffer;
 struct audit_context;
 #endif
 
 #ifdef CONFIG_AUDITSYSCALL
+struct inode;
 /* These are defined in auditsc.c */
 				/* Public API */
 extern int  audit_alloc(struct task_struct *task);
@@ -150,6 +187,7 @@ extern void audit_get_stamp(struct audit
 			    struct timespec *t, int *serial);
 extern int  audit_set_loginuid(struct audit_context *ctx, uid_t loginuid);
 extern uid_t audit_get_loginuid(struct audit_context *ctx);
+extern int audit_notify_watch(struct inode *inode, int mask);
 #else
 #define audit_alloc(t) ({ 0; })
 #define audit_free(t) do { ; } while (0)
@@ -159,8 +197,28 @@ extern uid_t audit_get_loginuid(struct a
 #define audit_putname(n) do { ; } while (0)
 #define audit_inode(n,i,d) do { ; } while (0)
 #define audit_get_loginuid(c) ({ -1; })
+#define audit_notify_watch(i,m) ({ 0; })
 #endif
 
+#ifdef CONFIG_AUDITFILESYSTEM
+extern int audit_receive_watch(int type, int pid, int uid, int seq,
+			       struct audit_watch *req);
+extern void audit_filesystem_init(void);
+extern void audit_inode_alloc(struct inode *inode);
+extern void audit_inode_free(struct inode *inode);
+extern void audit_watch(struct dentry *dentry, int drain);
+extern void audit_wentry_put(struct audit_wentry *wentry);
+extern struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry);
+#else
+#define audit_receive_watch(t,p,u,s,r) ({ -EOPNOTSUPP; })
+#define audit_filesystem_init() do { ; } while(0)
+#define audit_inode_alloc(i) do { ; } while(0)
+#define audit_inode_free(i) do { ; } while(0)
+#define audit_watch(dt,d) do { ; } while (0)
+#define audit_watch_put(w) do { ; } while(0)
+#define audit_watch_get(w) ({ 0; })
+#endif
+
 #ifdef CONFIG_AUDIT
 /* These are defined in audit.c */
 				/* Public API */
diff -Nurp linux-2.6.11/include/linux/fs.h linux-2.6.11-audit/include/linux/fs.h
--- linux-2.6.11/include/linux/fs.h	2005-03-02 01:37:50.000000000 -0600
+++ linux-2.6.11-audit/include/linux/fs.h	2005-03-04 16:20:29.000000000 -0600
@@ -457,6 +457,9 @@ struct inode {
 #ifdef CONFIG_QUOTA
 	struct dquot		*i_dquot[MAXQUOTAS];
 #endif
+#ifdef CONFIG_AUDITFILESYSTEM
+	struct audit_data	*i_audit;
+#endif
 	/* These three should probably be a union */
 	struct list_head	i_devices;
 	struct pipe_inode_info	*i_pipe;
diff -Nurp linux-2.6.11/init/Kconfig linux-2.6.11-audit/init/Kconfig
--- linux-2.6.11/init/Kconfig	2005-03-02 01:38:19.000000000 -0600
+++ linux-2.6.11-audit/init/Kconfig	2005-03-04 16:20:29.000000000 -0600
@@ -174,6 +174,17 @@ config AUDITSYSCALL
 	  can be used independently or with another kernel subsystem,
 	  such as SELinux.
 
+config AUDITFILESYSTEM
+	bool "Enable filesystem auditing support"
+	depends on AUDITSYSCALL
+	default n
+	help
+	  Generate audit records for regular files or directories that
+	  are being watched for access by audited syscalls.  To insert
+	  and remove watch points into the filesystem you may use the
+	  auditctl program provided with auditd.  For more information,
+	  'man auditctl'
+
 config LOG_BUF_SHIFT
 	int "Kernel log buffer size (16 => 64KB, 17 => 128KB)" if DEBUG_KERNEL
 	range 12 21
diff -Nurp linux-2.6.11/kernel/Makefile linux-2.6.11-audit/kernel/Makefile
--- linux-2.6.11/kernel/Makefile	2005-03-02 01:37:50.000000000 -0600
+++ linux-2.6.11-audit/kernel/Makefile	2005-03-04 16:20:29.000000000 -0600
@@ -23,6 +23,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_GENERIC_HARDIRQS) += irq/
diff -Nurp linux-2.6.11/kernel/audit.c linux-2.6.11-audit/kernel/audit.c
--- linux-2.6.11/kernel/audit.c	2005-03-02 01:38:33.000000000 -0600
+++ linux-2.6.11-audit/kernel/audit.c	2005-03-07 13:14:10.000000000 -0600
@@ -20,6 +20,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  * Written by Rickard E. (Rik) Faith <faith at redhat.com>
+ * Modified by Timothy R. Chavez <chavezt at us.ibm.com>
  *
  * Goals: 1) Integrate fully with SELinux.
  *	  2) Minimal run-time overhead:
@@ -60,6 +61,9 @@ static int	audit_initialized;
 /* No syscall auditing will take place unless audit_enabled != 0. */
 int		audit_enabled;
 
+/* No filesystem auditing will take place unless audit_fsenabled != 0 */
+int		auditfs_enabled = 1;
+
 /* Default state when kernel boots without any parameters. */
 static int	audit_default;
 
@@ -265,6 +269,17 @@ int audit_set_enabled(int state)
 	return old;
 }
 
+int audit_set_fsenabled(int state)
+{
+	int old		= auditfs_enabled;
+	if (state != 0 && state != 1)
+		return -EINVAL;
+	auditfs_enabled = state;
+	audit_log(current->audit_context, "auditfs_enabled=%d old=%d",
+		auditfs_enabled, old);
+	return old;
+}
+
 int audit_set_failure(int state)
 {
 	int old		 = audit_failure;
@@ -319,6 +334,8 @@ static int audit_netlink_ok(kernel_cap_t
 	case AUDIT_SET:
 	case AUDIT_ADD:
 	case AUDIT_DEL:
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
 		if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
 			err = -EPERM;
 		break;
@@ -354,6 +371,7 @@ static int audit_receive_msg(struct sk_b
 	switch (msg_type) {
 	case AUDIT_GET:
 		status_set.enabled	 = audit_enabled;
+		status_set.fs_enabled	 = auditfs_enabled;
 		status_set.failure	 = audit_failure;
 		status_set.pid		 = audit_pid;
 		status_set.rate_limit	 = audit_rate_limit;
@@ -371,6 +389,10 @@ static int audit_receive_msg(struct sk_b
 			err = audit_set_enabled(status_get->enabled);
 			if (err < 0) return err;
 		}
+		if (status_get->mask & AUDIT_STATUS_FSENABLED) {
+			err = audit_set_fsenabled(status_get->fs_enabled);
+			if (err < 0) return err;
+		}
 		if (status_get->mask & AUDIT_STATUS_FAILURE) {
 			err = audit_set_failure(status_get->failure);
 			if (err < 0) return err;
@@ -413,6 +435,12 @@ static int audit_receive_msg(struct sk_b
 		err = -EOPNOTSUPP;
 #endif
 		break;
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
+		err = audit_receive_watch(nlh->nlmsg_type,
+					  NETLINK_CB(skb).pid,
+					  uid, seq, data);
+		break;
 	default:
 		err = -EINVAL;
 		break;
@@ -557,6 +585,7 @@ int __init audit_init(void)
 
 	audit_initialized = 1;
 	audit_enabled = audit_default;
+	audit_filesystem_init();
 	audit_log(NULL, "initialized");
 	return 0;
 }
diff -Nurp linux-2.6.11/kernel/auditfs.c linux-2.6.11-audit/kernel/auditfs.c
--- linux-2.6.11/kernel/auditfs.c	1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.11-audit/kernel/auditfs.c	2005-03-07 13:19:21.000000000 -0600
@@ -0,0 +1,583 @@
+/* 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 at 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>
+
+kmem_cache_t *audit_watch_cache;
+kmem_cache_t *audit_wentry_cache;
+kmem_cache_t *audit_data_cache;
+
+extern int auditfs_enabled;
+
+/* Private Interface */
+
+static void audit_data_put(struct audit_data *data);
+void audit_wentry_put(struct audit_wentry *wentry);
+
+/*
+ * We remove this wentry from the watchlist and mark it as being invalid.  When
+ * we invalidate a wentry, we're telling the audit subsystem to ignore any refs
+ * to this wentry that may still exist when auditing.  We're also giving it the
+ * permission to remove the reference and attach a new watch if there is one
+ * available.
+ *
+ * We must drop our reference to the inode's audit_data here.  Otherwise, we'll
+ * leak a reference and audit_data_free() will never be called.
+ *
+ * Caller must hold i_audit->watchlist_lock
+ */
+static inline void audit_destroy_wentry(struct audit_wentry *wentry)
+{
+	if (wentry) {
+		list_del_init(&wentry->w_list);
+		wentry->w_valid = 0;
+		audit_data_put(wentry->w_data);
+		audit_wentry_put(wentry);
+	}
+}
+
+/*
+ * This is the core function for determining whether or not "name"
+ * is in the parent's watchlist (data->watchlist).
+ *
+ * NOTE: Should only be called from a secure source.
+ *
+ * Caller must hold i_audit->watchlist_lock and is responsible for
+ * putting back the returned reference
+ */
+static inline struct audit_wentry *audit_wentry_fetch(const char *name,
+						      struct audit_data *data)
+{
+	struct audit_wentry *wentry, *ret = NULL;
+
+	list_for_each_entry(wentry, &data->watchlist, w_list)
+		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_data *data)
+{
+	unsigned long flags;
+	struct audit_wentry *ret = NULL;
+
+	if (name && data) {
+		read_lock_irqsave(&data->watchlist_lock, flags);
+		ret = audit_wentry_fetch(name, data);
+		read_unlock_irqrestore(&data->watchlist_lock, flags);
+	}
+
+	return ret;
+}
+
+/*
+ * First reference of audit_data is returned on allocation
+ */
+static inline struct audit_data *audit_data_alloc(void)
+{
+	struct audit_data *data;
+
+	data = kmem_cache_alloc(audit_data_cache, GFP_KERNEL);
+	if (data) {
+		data->wentry = NULL;
+		atomic_set(&data->count, 1);
+		INIT_LIST_HEAD(&data->watchlist);
+		data->watchlist_lock = RW_LOCK_UNLOCKED;
+
+	}
+
+	return data;
+}
+
+/*
+ * NOTE:  Should only be called from a secure source
+ *
+ * Caller must hold i_audit->watchlist_lock
+ */
+static inline void audit_drain_watchlist(struct audit_data *data)
+{
+	struct audit_wentry *wentry, *tmp;
+
+	list_for_each_entry_safe(wentry, tmp, &data->watchlist, w_list)
+		audit_destroy_wentry(wentry);
+}
+
+static inline void audit_drain_watchlist_lock(struct audit_data *data)
+{
+	unsigned long flags;
+	struct audit_wentry *wentry, *tmp;
+
+	if (data) {
+		write_lock_irqsave(&data->watchlist_lock, flags);
+
+		list_for_each_entry_safe(wentry, tmp, &data->watchlist, w_list)
+			audit_destroy_wentry(wentry);
+
+		write_unlock_irqrestore(&data->watchlist_lock, flags);
+	}
+}
+
+static inline void audit_data_free(struct audit_data *data)
+{
+	unsigned long flags;
+
+	if (data) {
+		write_lock_irqsave(&data->watchlist_lock, flags);
+
+		audit_drain_watchlist(data);
+
+		if (data->wentry) {
+			audit_wentry_put(data->wentry);
+			data->wentry = NULL;
+		}
+
+		write_unlock_irqrestore(&data->watchlist_lock,flags);
+
+		kmem_cache_free(audit_data_cache, data);
+	}
+}
+
+static struct audit_data *audit_data_get(struct audit_data *data)
+{
+	if (data) {
+		BUG_ON(!atomic_read(&data->count));
+		atomic_inc(&data->count);
+	}
+
+	return data;
+}
+
+static void audit_data_put(struct audit_data *data)
+{
+	if (data && atomic_dec_and_test(&data->count))
+		audit_data_free(data);
+}
+
+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->namelen = 0;
+		watch->fklen = 0;
+		watch->name = 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->filterkey);
+		kmem_cache_free(audit_watch_cache, watch);
+	}
+}
+
+static struct audit_watch *audit_create_watch(const char *name,
+					      const char *filterkey,
+					      __u32 perms)
+{
+	struct audit_watch *err = NULL;
+	struct audit_watch *watch = NULL;
+
+	err = ERR_PTR(-EINVAL);
+
+	if (!name || strlen(name) + 1 > PATH_MAX)
+		goto audit_create_watch_fail;
+
+	if (filterkey && strlen(filterkey) + 1 > AUDIT_FILTERKEY_MAX)
+		goto audit_create_watch_fail;
+
+	if (perms > 15)
+		goto audit_create_watch_fail;
+
+	err = ERR_PTR(-ENOMEM);
+
+	watch = audit_watch_alloc();
+	if (watch) {
+		watch->namelen = strlen(name) + 1;
+		watch->name = kmalloc(watch->namelen, GFP_KERNEL);
+		if (!watch->name)
+			goto audit_create_watch_fail;
+		strcpy(watch->name, name);
+
+		if (filterkey) {
+			watch->fklen = strlen(filterkey) + 1;
+			watch->filterkey = kmalloc(watch->fklen, GFP_KERNEL);
+			if (!watch->filterkey)
+				goto audit_create_watch_fail;
+			strcpy(watch->filterkey, filterkey);
+		}
+
+		watch->perms = perms;
+
+		goto audit_create_watch_exit;
+	}
+
+
+audit_create_watch_fail:
+	audit_watch_free(watch);
+	watch = err;
+audit_create_watch_exit:
+	return watch;
+}
+
+/*
+ * First reference of audit_wentry is returned on allocation
+ */
+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_valid = 0;
+		wentry->w_watch = NULL;
+		wentry->w_data = NULL;
+	}
+
+	return wentry;
+}
+
+/*
+ * Because of the circular nature of the design, in order to arrive here,
+ * we may be getting called from within audit_data_free().  The only way
+ * this could happen is if there were no more references to inode->i_audit.
+ * Thus, we couldn't possibly put back the wentry's reference to w_data here.
+ */
+static inline void audit_wentry_free(struct audit_wentry *wentry)
+{
+	if (wentry) {
+		audit_watch_free(wentry->w_watch);
+		kmem_cache_free(audit_wentry_cache, wentry);
+	}
+}
+
+/*
+ * The wentry holds both the watch (w_watch) and the inode's i_audit memory
+ * (w_data).  The inode's memory contains a pointer to the wentry that holds
+ * it.  This produces a circular effect.
+ *
+ * We do this for the following reasons:
+ * 	1) the memory for an inode may not be allocated in our audit_watch()
+ *	   hook because there may be no reasonable way to handle an -ENOMEM.
+ *	2) there are cases when multiple inode's will refer to the same watch.
+ * 	3) the inode associated w/ "name" may or may not exist
+ *
+ */
+static int audit_create_wentry(const char *name,
+			       const char *filterkey,
+			       unsigned int perms, struct audit_data *data)
+{
+	int ret;
+	unsigned long flags;
+	struct audit_wentry *wentry = NULL;
+	struct audit_wentry *new_wentry = NULL;
+
+	ret = -ENOMEM;
+
+	new_wentry = audit_wentry_alloc();
+	if (!new_wentry)
+		goto audit_create_wentry_fail;
+
+	new_wentry->w_data = audit_data_alloc();
+	if (!new_wentry->w_data)
+		goto audit_create_wentry_fail;
+
+	new_wentry->w_watch = audit_create_watch(name, filterkey, perms);
+	if (IS_ERR(new_wentry->w_watch)) {
+		ret = PTR_ERR(new_wentry->w_watch);
+		new_wentry->w_watch = NULL;
+		goto audit_create_wentry_fail;
+	}
+
+	new_wentry->w_data->wentry = audit_wentry_get(new_wentry);
+
+	write_lock_irqsave(&data->watchlist_lock, flags);
+
+	wentry = audit_wentry_fetch(name, data);
+	if (!wentry) {
+		list_add(&new_wentry->w_list, &data->watchlist);
+		new_wentry->w_valid = 1;
+		write_unlock_irqrestore(&data->watchlist_lock, flags);
+		ret = 0;
+		goto audit_create_wentry_exit;
+	}
+	audit_wentry_put(wentry);
+
+	write_unlock_irqrestore(&data->watchlist_lock, flags);
+
+	ret = -EEXIST;
+
+audit_create_wentry_fail:
+	audit_data_put(new_wentry->w_data);
+	audit_wentry_put(new_wentry);
+	if (!data->wentry && list_empty(&data->watchlist))
+		audit_data_put(data);
+audit_create_wentry_exit:
+	return ret;
+}
+
+static int audit_insert_watch(struct audit_watch *req)
+{
+	int ret;
+	char *path = NULL;
+	char *filterkey = NULL;
+	struct nameidata nd;
+
+	path = getname(req->name);
+	if (IS_ERR(req->name)) {
+		ret = PTR_ERR(req->name);
+		goto audit_insert_watch_exit;
+	}
+
+	if (req->fklen) {
+		filterkey = kmalloc(req->fklen, GFP_KERNEL);
+		ret = -ENOMEM;
+		if (!filterkey)
+			goto audit_insert_watch_exit;
+
+		ret = strncpy_from_user(filterkey, req->filterkey, req->fklen);
+		if (ret < 0)
+			goto audit_insert_watch_exit;
+	}
+
+	ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_insert_watch_release;
+
+	ret = -EPERM;
+	if (nd.last_type != LAST_NORM)
+		goto audit_insert_watch_release;
+
+	if (!nd.dentry->d_inode->i_audit) {
+		nd.dentry->d_inode->i_audit = audit_data_alloc();
+		ret = -ENOMEM;
+		if (!nd.dentry->d_inode->i_audit)
+			goto audit_insert_watch_release;
+	}
+
+	ret = audit_create_wentry(nd.last.name, filterkey, req->perms,
+				  nd.dentry->d_inode->i_audit);
+
+audit_insert_watch_release:
+	path_release(&nd);
+audit_insert_watch_exit:
+	putname(path);
+	kfree(filterkey);
+	return ret;
+}
+
+static int audit_remove_watch(struct audit_watch *req)
+{
+	int ret;
+	unsigned long flags;
+	char *path = NULL;
+	struct nameidata nd;
+	struct audit_data *data;
+	struct audit_wentry *wentry;
+
+	path = getname(req->name);
+	if (IS_ERR(req->name)) {
+		ret = PTR_ERR(req->name);
+		goto audit_remove_watch_exit;
+	}
+
+	ret = -EINVAL;
+	if (!path || (path && strlen(path) > PATH_MAX))
+		goto audit_remove_watch_exit;
+
+	ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_remove_watch_release;
+
+	ret = -EPERM;
+	if (nd.last_type != LAST_NORM || !nd.last.name)
+		goto audit_remove_watch_release;
+
+	ret = -EACCES;
+
+	data = nd.dentry->d_inode->i_audit;
+	if (!data)
+		goto audit_remove_watch_release;
+
+	write_lock_irqsave(&data->watchlist_lock, flags);
+
+	wentry = audit_wentry_fetch(nd.last.name, data);
+	if (!wentry) {
+		write_unlock_irqrestore(&data->watchlist_lock, flags);
+		goto audit_remove_watch_release;
+	}
+	audit_destroy_wentry(wentry);
+	audit_wentry_put(wentry);
+
+	write_unlock_irqrestore(&data->watchlist_lock, flags);
+
+	if (!data->wentry && list_empty(&data->watchlist)) {
+		audit_data_put(data);
+		nd.dentry->d_inode->i_audit = NULL;
+	}
+
+	ret = 0;
+
+audit_remove_watch_release:
+	path_release(&nd);
+audit_remove_watch_exit:
+	putname(path);
+	return ret;
+}
+
+/* Public interface */
+
+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);
+}
+
+/*
+ * Hook appears in:
+ * fs/dcache.c:d_instantiate(), d_move(), d_lookup(), and d_splice_alias()
+ */
+void audit_watch(struct dentry *dentry, int drain)
+{
+	struct audit_wentry *wentry;
+	struct audit_data *data;
+
+	if (!dentry || !dentry->d_inode)
+		return;
+
+	if (!dentry->d_parent || !dentry->d_parent->d_inode)
+		return;
+
+	wentry = audit_wentry_fetch_lock(dentry->d_name.name,
+					 dentry->d_parent->d_inode->i_audit);
+
+	data = dentry->d_inode->i_audit;
+	if (data) {
+		if (drain)
+			audit_drain_watchlist_lock(data);
+		if (wentry && !list_empty(&data->watchlist)) {
+			audit_data_put(wentry->w_data);
+			wentry->w_data = audit_data_get(data);
+			audit_wentry_put(wentry->w_data->wentry);
+			wentry->w_data->wentry = audit_wentry_get(wentry);
+			/* Keep get/put's consistent, stealing is bad :( */
+			audit_data_put(data);
+			dentry->d_inode->i_audit =
+			    audit_data_get(wentry->w_data);
+		} else if (wentry && !data->wentry->w_valid) {
+			audit_data_put(data);
+			dentry->d_inode->i_audit =
+			    audit_data_get(wentry->w_data);
+		}
+	} else if (wentry)
+		dentry->d_inode->i_audit = audit_data_get(wentry->w_data);
+
+	audit_wentry_put(wentry);
+}
+
+int audit_receive_watch(int type, int pid, int uid, int seq,
+			 struct audit_watch *req)
+{
+	int err;
+
+	if (auditfs_enabled) {
+
+		switch (type) {
+		case AUDIT_WATCH_INS:
+			err = audit_insert_watch(req);
+			break;
+		case AUDIT_WATCH_REM:
+			err = audit_remove_watch(req);
+			break;
+		default:
+			err = -EINVAL;
+		}
+
+		audit_send_reply(pid, seq, type, 0, 1, &err, sizeof(int));
+
+	} else
+		err = -EOPNOTSUPP;
+
+	return err;
+}
+
+void audit_inode_alloc(struct inode *inode)
+{
+	if (inode)
+		inode->i_audit = NULL;
+}
+
+void audit_inode_free(struct inode *inode)
+{
+	if (inode && inode->i_audit) {
+		audit_data_put(inode->i_audit);
+		inode->i_audit = NULL;
+	}
+}
+
+void audit_filesystem_init()
+{
+	audit_watch_cache =
+	    kmem_cache_create("audit_watch_cache",
+			      sizeof(struct audit_watch), 0, 0, NULL, NULL);
+	audit_wentry_cache =
+	    kmem_cache_create("audit_wentry_cache",
+			      sizeof(struct audit_wentry), 0, 0, NULL, NULL);
+	audit_data_cache =
+	    kmem_cache_create("audit_data_cache",
+			      sizeof(struct audit_data), 0, 0, NULL, NULL);
+}
diff -Nurp linux-2.6.11/kernel/auditsc.c linux-2.6.11-audit/kernel/auditsc.c
--- linux-2.6.11/kernel/auditsc.c	2005-03-02 01:38:17.000000000 -0600
+++ linux-2.6.11-audit/kernel/auditsc.c	2005-03-07 12:31:06.000000000 -0600
@@ -19,6 +19,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  * Written by Rickard E. (Rik) Faith <faith at redhat.com>
+ * Modified by Timothy R. Chavez <chavezt at us.ibm.com>
  *
  * Many of the ideas implemented here are from Stephen C. Tweedie,
  * especially the idea of avoiding a copy by using getname.
@@ -48,6 +49,7 @@
 
 /* No syscall auditing will take place unless audit_enabled != 0. */
 extern int audit_enabled;
+extern int auditfs_enabled;
 
 /* AUDIT_NAMES is the number of slots we reserve in the audit_context
  * for saving names from getname(). */
@@ -113,6 +115,10 @@ struct audit_context {
 	uid_t		    uid, euid, suid, fsuid;
 	gid_t		    gid, egid, sgid, fsgid;
 	unsigned long	    personality;
+	struct list_head    wtrail; /* The list of watched files/dirs that were
+				     * accessed and determined to be valid and
+				     * unfiltered in this audit_context
+				     */
 
 #if AUDIT_DEBUG
 	int		    put_count;
@@ -134,6 +140,22 @@ struct audit_entry {
 	struct audit_rule rule;
 };
 
+/* The structure that stores information about files/directories being
+ * watched in the filesystem, that the syscall accessed.
+ */
+
+struct audit_file {
+	struct audit_wentry *wentry;
+	struct list_head list;
+	unsigned long ino;
+	umode_t mode;
+	uid_t uid;
+	gid_t gid;
+	dev_t dev;
+	dev_t rdev;
+	int mask;
+};
+
 /* Check to see if two rules are identical.  It is called from
  * audit_del_rule during AUDIT_DEL. */
 static int audit_compare_rule(struct audit_rule *a, struct audit_rule *b)
@@ -504,6 +526,37 @@ static inline void audit_free_names(stru
 	context->name_count = 0;
 }
 
+static struct audit_file *audit_file_alloc(void)
+{
+	struct audit_file *file;
+
+	file = kmalloc(sizeof(struct audit_file), GFP_KERNEL);
+
+	if (file)
+		file->wentry = NULL;
+
+	return file;
+}
+
+static void audit_file_free(struct audit_file *file)
+{
+	if (file) {
+		audit_wentry_put(file->wentry);
+		file->wentry = NULL;
+		kfree(file);
+	}
+}
+
+static inline void audit_free_files(struct audit_context *context)
+{
+	struct audit_file *file, *tmp;
+
+	list_for_each_entry_safe(file, tmp, &context->wtrail, list) {
+	        list_del(&file->list);
+		audit_file_free(file);
+	}
+}
+
 static inline void audit_zero_context(struct audit_context *context,
 				      enum audit_state state)
 {
@@ -512,6 +565,7 @@ static inline void audit_zero_context(st
 	memset(context, 0, sizeof(*context));
 	context->state      = state;
 	context->loginuid   = loginuid;
+	INIT_LIST_HEAD(&context->wtrail);
 }
 
 static inline struct audit_context *audit_alloc_context(enum audit_state state)
@@ -570,6 +624,7 @@ static inline void audit_free_context(st
 			       context->name_count, count);
 		}
 		audit_free_names(context);
+		audit_free_files(context);
 		kfree(context);
 		context  = previous;
 	} while (context);
@@ -580,6 +635,7 @@ static inline void audit_free_context(st
 static void audit_log_exit(struct audit_context *context)
 {
 	int i;
+	struct audit_file *file;
 	struct audit_buffer *ab;
 
 	ab = audit_log_start(context);
@@ -626,6 +682,28 @@ static void audit_log_exit(struct audit_
 					 MINOR(context->names[i].rdev));
 		audit_log_end(ab);
 	}
+
+	if (!auditfs_enabled)
+		return;
+
+	list_for_each_entry(file, &context->wtrail, list) {
+		ab = audit_log_start(context);
+		if (!ab)
+			continue;
+
+		/* Do we need more information? */
+		audit_log_format(ab,
+			"name=%s filter_key=%s perm_mask=%u perm=%d inode=%lu "
+			"inode_mode=%d inode_uid=%d inode_gid=%d "
+			"inode_dev=%02x:%02x inode_rdev=%02x:%02x",
+			file->wentry->w_watch->name,
+			file->wentry->w_watch->filterkey,
+			file->wentry->w_watch->perms,
+			file->mask, file->ino, file->mode, file->uid,
+			file->gid, MAJOR(file->dev), MINOR(file->dev),
+			MAJOR(file->rdev), MINOR(file->rdev));
+		audit_log_end(ab);
+	}
 }
 
 /* Free a per-task audit context.  Called from copy_process and
@@ -789,6 +867,7 @@ void audit_syscall_exit(struct task_stru
 		tsk->audit_context = new_context;
 	} else {
 		audit_free_names(context);
+		audit_free_files(context);
 		audit_zero_context(context, context->state);
 		tsk->audit_context = context;
 	}
@@ -927,3 +1006,59 @@ uid_t audit_get_loginuid(struct audit_co
 {
 	return ctx ? ctx->loginuid : -1;
 }
+
+/* If file/dir has an audit_context and has filesystem auditing
+ * turned on, then if this inode is being watched, collect info
+ * about it.
+ *
+ * Hook appears in:
+ * fs/namie.c:permission(), exec_permission_lite(), vfs_unlink/rmdir
+ *
+ */
+
+int audit_notify_watch(struct inode *inode, int mask)
+{
+	int ret = 0;
+	struct audit_context *context;
+	struct audit_file *file;
+
+	context = current->audit_context;
+
+	if (!auditfs_enabled)
+		goto audit_notify_watch_exit;
+
+	if (!context || !context->in_syscall)
+		goto audit_notify_watch_exit;
+
+	if (!inode || !inode->i_audit)
+		goto audit_notify_watch_exit;
+
+	if (!inode->i_audit->wentry || !inode->i_audit->wentry->w_valid)
+		goto audit_notify_watch_exit;
+
+	if (!mask || (inode->i_audit->wentry->w_watch->perms &&
+	    !(inode->i_audit->wentry->w_watch->perms & mask))) {
+	    	audit_free_files(context);
+		goto audit_notify_watch_exit;
+	}
+
+	file = audit_file_alloc();
+	if (!file) {
+		ret = -ENOMEM;
+		goto audit_notify_watch_exit;
+	}
+
+	file->wentry = audit_wentry_get(inode->i_audit->wentry);
+	file->ino = inode->i_ino;
+	file->uid = inode->i_uid;
+	file->gid = inode->i_gid;
+	file->dev = inode->i_sb->s_dev;
+	file->rdev = inode->i_rdev;
+	file->mask = mask;
+
+	list_add(&file->list, &context->wtrail);
+
+	audit_notify_watch_exit:
+	return ret;
+}
+




More information about the Linux-audit mailing list