[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[RFC][PATCH] Prelim in-kernel filesystem auditing support



Hello,

I know this one is a long time coming.  My last patch was critically
flawed in that the preallocation mechanism did not take into
consideration multiple references to an inode holding audit info. 
This caused a complete melt down of the code.  My first solution was a
miserable failure.  This one works much better.  I went through 40
different tests just to make sure things are behaving [more]
correctly.  Two major scenarios that need to be thought over and most
likely need a fixing are the following:

1.

watch /etc/foo,filterkey=foo
watch /etc/bar,filterkey=bar
touch /etc/foo
ln /etc/foo /etc/bar

As of right now, /etc/bar will lose /etc/foo's watch information and
gain /etc/bar's.  This is the natural behavior for all other scenarios
(ie: mv /etc/foo /etc/bar).  I do not believe this should be allowed
for hardlinks.  The filterkey=bar may be a weaker filtering key then
filterkey=foo and thus in any userspace filtering (we might decide to
do) could be subverted.  Hardlinks should retain, always, the filter
information of what they're linking against, regardless of where they
are at.  This also brings into consideration the need for multiple
watches on a single path?  This way we get information for /etc/bar
for both /etc/foo AND /etc/bar.

2.

watch /etc/foo
touch /etc/foo
ln /etc/foo /tmp/foo
(remains audited)
mv /tmp/foo /tmp/bar
(loses audit)

This is also a natural side effect of the code.  When we move we
always lose any watch information we are holding onto.  If we move
into a location that's being watched we gain that watch information. 
Since /tmp/bar was not being watched, the hardlink loses its watch to
/etc/foo.  Is this acceptable?  I don't think so.  From my above
statement, the hardlink must always retain it's watch information to
what it is linked against.. We wouldn't want a user to be able to
create a hardlink and then move it to lose the watch.

But, we must also keep in mind that eventually it gets unreasonable. 
For instance:

watch /etc/foo
ln /etc/foo /etc/bar
unwatch /etc/foo
rm /etc/foo
touch /etc/foo
watch /etc/foo

/etc/bar is no longer audited.  There's no reasonable way around this.

The data structure for this has become much more complex as I've
incorporated the preallocation mechanism into the audit_watch
structure itself with the assertion, "One chunk of audit_data memory
per watch" -- This effectively introduces a circular data structure
(the audit_data within the audit_wentry (at one time called
audit_watch) is pointing at the audit_wentry itself.  It'd take a long
time to explain why, but I intend on releasing to this list, diagrams
describing the data structure visually.  I'll take a picture of my
white board and post it for the time being.  You might get a good
laugh, I do.  :)

Please review and provide comments. 

PS:  I did not use static inlines as Stephen suggested yet to keep the
code consistent with what is already there.  So keep me in check.  I
know I'm notorious for providing the wrong number of arguments to my
macros! :(

Patch #4:

This patch will address the above issues in some way.  It will finally
take care of dynamic enable/disable of file system auditing from
userspace.  It will add permission masks filtering.  Fix bugs found in
Patch #3.  Begin official Docbook comments.  Figure out a good place
to hook so we can cover unlinks and similar syscalls that skip over
permission().  And anything else I'm presently forgetting :-) 
Hopefully by then I'll have some userspace code for the public.

I originally wanted to remove some of Rik's code that hooked
path_lookup(), but I actually kind of like it.  Even though the inode
he reports is wrong in some cases (ie: unlink()), the path information
is useful.   Especially when looking at hardlinks that are being
watched.  Might just take out the inode record.  How does everyone
feel about this?

-- 
- Timothy R. Chavez
diff -purN linux-2.6.10/Makefile linux-2.6.10-audit/Makefile
--- linux-2.6.10/Makefile	2004-12-24 15:35:01.000000000 -0600
+++ linux-2.6.10-audit/Makefile	2005-02-03 15:10:08.000000000 -0600
@@ -1,7 +1,7 @@
 VERSION = 2
 PATCHLEVEL = 6
 SUBLEVEL = 10
-EXTRAVERSION =
+EXTRAVERSION =-auditfs-tc1-3
 NAME=Woozy Numbat
 
 # *DOCUMENTATION*
diff -purN linux-2.6.10/arch/i386/defconfig linux-2.6.10-audit/arch/i386/defconfig
--- linux-2.6.10/arch/i386/defconfig	2004-12-24 15:35:01.000000000 -0600
+++ linux-2.6.10-audit/arch/i386/defconfig	2005-01-22 13:44:50.000000000 -0600
@@ -23,6 +23,7 @@ CONFIG_POSIX_MQUEUE=y
 CONFIG_SYSCTL=y
 CONFIG_AUDIT=y
 CONFIG_AUDITSYSCALL=y
+CONFIG_AUDITFILESYSTEM=n
 CONFIG_LOG_BUF_SHIFT=15
 CONFIG_HOTPLUG=y
 # CONFIG_IKCONFIG is not set
diff -purN linux-2.6.10/fs/dcache.c linux-2.6.10-audit/fs/dcache.c
--- linux-2.6.10/fs/dcache.c	2004-12-24 15:34:00.000000000 -0600
+++ linux-2.6.10-audit/fs/dcache.c	2005-02-03 13:54:06.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 */
 
@@ -781,6 +782,7 @@ void d_instantiate(struct dentry *entry,
 	entry->d_inode = inode;
 	spin_unlock(&dcache_lock);
 	security_d_instantiate(entry, inode);
+	audit_watch(entry, 1);
 }
 
 /**
@@ -920,6 +922,7 @@ struct dentry *d_splice_alias(struct ino
 			security_d_instantiate(dentry, inode);
 			d_rehash(dentry);
 		}
+		audit_watch(dentry, 1);
 	} else
 		d_add(dentry, inode);
 	return new;
@@ -1266,6 +1269,7 @@ already_unhashed:
 	spin_unlock(&dentry->d_lock);
 	write_sequnlock(&rename_lock);
 	spin_unlock(&dcache_lock);
+	audit_watch(dentry, 0);
 }
 
 /**
diff -purN linux-2.6.10/fs/inode.c linux-2.6.10-audit/fs/inode.c
--- linux-2.6.10/fs/inode.c	2004-12-24 15:35:40.000000000 -0600
+++ linux-2.6.10-audit/fs/inode.c	2005-02-02 15:33:24.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
@@ -1078,7 +1081,7 @@ static inline void iput_final(struct ino
 {
 	struct super_operations *op = inode->i_sb->s_op;
 	void (*drop)(struct inode *) = generic_drop_inode;
-
+	
 	if (op && op->drop_inode)
 		drop = op->drop_inode;
 	drop(inode);
diff -purN linux-2.6.10/fs/namei.c linux-2.6.10-audit/fs/namei.c
--- linux-2.6.10/fs/namei.c	2004-12-24 15:34:30.000000000 -0600
+++ linux-2.6.10-audit/fs/namei.c	2005-02-02 14:40:14.000000000 -0600
@@ -231,6 +231,8 @@ int permission(struct inode * inode,int 
 
 	/* Ordinary permission routines do not understand MAY_APPEND. */
 	submask = mask & ~MAY_APPEND;
+	
+	audit_notify_watch(current, inode, mask);
 
 	if (inode->i_op && inode->i_op->permission)
 		retval = inode->i_op->permission(inode, submask, nd);
@@ -342,6 +344,8 @@ static inline int exec_permission_lite(s
 {
 	umode_t	mode = inode->i_mode;
 
+	audit_notify_watch(current, inode, MAY_EXEC);
+
 	if (inode->i_op && inode->i_op->permission)
 		return -EAGAIN;
 
diff -purN linux-2.6.10/include/linux/audit.h linux-2.6.10-audit/include/linux/audit.h
--- linux-2.6.10/include/linux/audit.h	2005-01-19 15:18:52.000000000 -0600
+++ linux-2.6.10-audit/include/linux/audit.h	2005-02-04 16:37:05.000000000 -0600
@@ -25,14 +25,16 @@
 #define _LINUX_AUDIT_H_
 
 /* 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) */
@@ -74,7 +76,7 @@
 #define AUDIT_DEVMINOR	101
 #define AUDIT_INODE	102
 #define AUDIT_EXIT	103
-#define AUDIT_SUCCESS   104	/* exit >= 0; value ignored */
+#define AUDIT_SUCCESS	104	/* exit >= 0; value ignored */
 
 #define AUDIT_ARG0      200
 #define AUDIT_ARG1      (AUDIT_ARG0+1)
@@ -96,6 +98,9 @@
 #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;
@@ -129,6 +134,26 @@ struct audit_rule {		/* for AUDIT_LIST, 
 	__u32		values[AUDIT_MAX_FIELDS];
 };
 
+struct audit_watch_request {
+	char	*path;
+	char	*filterkey;
+};
+
+struct audit_data {
+	struct audit_wentry	*wentry;
+	struct list_head 	watchlist;
+	rwlock_t 		watchlist_lock;
+	atomic_t		count;
+};
+
+struct audit_wentry {
+	char 			*w_name;
+	char 			*w_filterkey;
+	struct audit_data 	*w_data;
+	struct list_head 	w_list;
+	atomic_t 		w_count;
+};
+	
 #ifdef __KERNEL__
 
 #ifdef CONFIG_AUDIT
@@ -137,6 +162,8 @@ 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);
@@ -155,6 +182,8 @@ extern int  audit_receive_filter(int typ
 extern void audit_get_stamp(struct audit_context *ctx,
 			    struct timespec *t, int *serial);
 extern int  audit_set_loginuid(struct audit_context *ctx, uid_t loginuid);
+extern int audit_notify_watch(struct task_struct *tsk, 
+			      struct inode *inode, int mask);
 #else
 #define audit_alloc(t) ({ 0; })
 #define audit_free(t) do { ; } while (0)
@@ -163,8 +192,28 @@ extern int  audit_set_loginuid(struct au
 #define audit_getname(n) do { ; } while (0)
 #define audit_putname(n) do { ; } while (0)
 #define audit_inode(n,i,d) do { ; } while (0)
+#define audit_notify_watch(t,i,m) ({ 0; })
 #endif
 
+#ifdef CONFIG_AUDITFILESYSTEM
+extern int audit_receive_watch(int type, int pid, int uid, int seq, 
+			       struct audit_watch_request *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 preserve);
+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) do { ; } while(0)
+#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(d,p) 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 -purN linux-2.6.10/include/linux/fs.h linux-2.6.10-audit/include/linux/fs.h
--- linux-2.6.10/include/linux/fs.h	2004-12-24 15:34:27.000000000 -0600
+++ linux-2.6.10-audit/include/linux/fs.h	2005-01-27 21:23:10.000000000 -0600
@@ -18,6 +18,7 @@
 #include <linux/cache.h>
 #include <linux/prio_tree.h>
 #include <linux/kobject.h>
+#include <linux/audit.h>
 #include <asm/atomic.h>
 
 struct iovec;
@@ -223,7 +224,6 @@ extern int dir_notify_enable;
 
 #include <linux/list.h>
 #include <linux/radix-tree.h>
-#include <linux/audit.h>
 #include <linux/init.h>
 #include <asm/semaphore.h>
 #include <asm/byteorder.h>
@@ -459,6 +459,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 -purN linux-2.6.10/init/Kconfig linux-2.6.10-audit/init/Kconfig
--- linux-2.6.10/init/Kconfig	2004-12-24 15:35:24.000000000 -0600
+++ linux-2.6.10-audit/init/Kconfig	2005-01-16 22:35:08.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 -purN linux-2.6.10/kernel/Makefile linux-2.6.10-audit/kernel/Makefile
--- linux-2.6.10/kernel/Makefile	2004-12-24 15:34:26.000000000 -0600
+++ linux-2.6.10-audit/kernel/Makefile	2005-01-05 15:23:14.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 -purN linux-2.6.10/kernel/audit.c linux-2.6.10-audit/kernel/audit.c
--- linux-2.6.10/kernel/audit.c	2004-12-24 15:35:50.000000000 -0600
+++ linux-2.6.10-audit/kernel/audit.c	2005-02-03 15:36:47.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 redhat com>
+ * Modified by Timothy R. Chavez <chavezt us ibm com>
  *
  * Goals: 1) Integrate fully with SELinux.
  *	  2) Minimal run-time overhead:
@@ -46,7 +47,6 @@
 #include <asm/types.h>
 #include <linux/mm.h>
 #include <linux/module.h>
-
 #include <linux/audit.h>
 
 #include <net/sock.h>
@@ -60,6 +60,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_filesystem != 0 */
+int		audit_filesystem;
+
 /* Default state when kernel boots without any parameters. */
 static int	audit_default;
 
@@ -394,6 +397,15 @@ static int audit_receive_msg(struct sk_b
 		err = -EOPNOTSUPP;
 #endif
 		break;
+#ifdef CONFIG_AUDITFILESYSTEM
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
+		err = audit_receive_watch(nlh->nlmsg_type, pid, uid, seq,
+					  data);
+#else
+		err = -EOPNOTSUPP;
+#endif
+		break;
 	default:
 		err = -EINVAL;
 		break;
@@ -533,6 +545,18 @@ int __init audit_init(void)
 
 	audit_initialized = 1;
 	audit_enabled = audit_default;
+
+/* FIXME:
+ * We should be able to enable/disable filesystem auditing dynamically,
+ * after we boot up.  Configuring support for it should != automatic
+ * enablement.
+ */
+#ifdef CONFIG_AUDITFILESYSTEM
+	audit_filesystem = 1;
+	audit_filesystem_init();
+#else
+	audit_filesystem = 0;
+#endif
 	audit_log(NULL, "initialized");
 	return 0;
 }
diff -purN linux-2.6.10/kernel/auditfs.c linux-2.6.10-audit/kernel/auditfs.c
--- linux-2.6.10/kernel/auditfs.c	1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.10-audit/kernel/auditfs.c	2005-02-07 15:28:57.000000000 -0600
@@ -0,0 +1,510 @@
+/* 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 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/mm.h>
+#include <linux/audit.h>
+
+#include <asm/types.h>
+#include <asm/uaccess.h>
+
+kmem_cache_t *audit_wentry_cache;
+kmem_cache_t *audit_data_cache;
+
+static LIST_HEAD(audit_data_check);
+
+/* This version of the function is used when we are already inside a
+ * locked section.
+ *
+ * Caller must hold i_audit->watchlist_lock
+ */
+static 
+inline struct audit_wentry *audit_wentry_fetch(const char *name,
+					       struct audit_data *data)
+{
+	struct audit_wentry *wentry, *ret = NULL;
+
+	if (!data)
+		goto audit_wentry_fetch_exit;
+
+	list_for_each_entry(wentry, &data->watchlist, w_list) {
+		if (!strcmp(wentry->w_name, name)) {
+			ret = audit_wentry_get(wentry);
+			break;
+		}
+	}
+
+      audit_wentry_fetch_exit:
+	return ret;
+}
+
+/* This version of the function is used when we are outside a
+ * locked section.
+ */
+static
+inline struct audit_wentry *audit_wentry_fetch_lock(const char *name, 
+						    struct audit_data *data)
+{
+	unsigned long flags;
+	struct audit_wentry *wentry, *ret = NULL;
+
+	if (!data)
+		goto audit_wentry_fetch_lock_exit;
+
+	read_lock_irqsave(&data->watchlist_lock, flags);
+	list_for_each_entry(wentry, &data->watchlist, w_list)
+	    if (!strcmp(wentry->w_name, name)) {
+		ret = audit_wentry_get(wentry);
+		break;
+	}
+	read_unlock_irqrestore(&data->watchlist_lock, flags);
+
+      audit_wentry_fetch_lock_exit:
+	return ret;
+}
+
+/* IMPORTANT: We return the first reference */
+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;
+}
+
+static inline void audit_data_free(struct audit_data *data)
+{
+	unsigned long flags;
+	struct audit_wentry *wentry, *tmp;
+
+	if (!data)
+		return;
+
+	write_lock_irqsave(&data->watchlist_lock, flags);
+
+	list_for_each_entry_safe(wentry, tmp, &data->watchlist, w_list) {
+		list_del(&wentry->w_list);
+		audit_wentry_put(wentry);
+	}
+	
+	if (data->wentry) {
+		audit_wentry_put(data->wentry);
+		data->wentry = NULL;	
+	}
+	
+	kmem_cache_free(audit_data_cache, data);
+	write_unlock_irqrestore(&data->watchlist_lock, flags);
+}
+
+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)
+		return;
+
+	if (atomic_dec_and_test(&data->count)) {
+		audit_data_free(data);
+	}
+}
+
+/* IMPORTANT:  We return the first reference */
+
+static inline struct audit_wentry *audit_wentry_alloc(void)
+{
+	struct audit_wentry *wentry;
+
+	wentry = kmem_cache_alloc(audit_wentry_cache, GFP_KERNEL);
+	if (wentry) {
+		wentry->w_name = NULL;
+		wentry->w_filterkey = NULL;
+		wentry->w_data = NULL;
+		atomic_set(&wentry->w_count, 1);
+		printk("audit_wentry allocated\n");
+	}
+
+	return wentry;
+}
+
+static inline void audit_wentry_free(struct audit_wentry *wentry)
+{
+	if (wentry) {
+		kfree(wentry->w_name);
+		kfree(wentry->w_filterkey);
+		kmem_cache_free(audit_wentry_cache, wentry);
+	}
+}
+
+/* We will check whether or not the watch entry already exists when
+ * it's time to add it to the watchlist.  We wait until then because
+ * we are under the protection of our lock and have a smaller critical
+ * section then if we were to put a lock around the entire function.
+ */
+static int audit_create_wentry(const char *name, const char *filterkey,
+			       struct audit_data *data)
+{
+	int ret = 0;
+	unsigned long flags;
+	struct audit_wentry *wentry = NULL;
+	struct audit_wentry *new_wentry = NULL;
+
+	new_wentry = audit_wentry_alloc();
+	if (!new_wentry) {
+		ret = -ENOMEM;
+		goto audit_create_wentry_fail;
+	}
+
+	new_wentry->w_name = kmalloc(strlen(name) + 1, GFP_KERNEL);
+	if (!new_wentry->w_name) {
+		ret = -ENOMEM;
+		goto audit_create_wentry_fail;
+	}
+
+	new_wentry->w_filterkey =
+	    kmalloc(strlen(filterkey) + 1, GFP_KERNEL);
+	if (!new_wentry->w_filterkey) {
+		ret = -ENOMEM;
+		goto audit_create_wentry_fail;
+	}
+
+	new_wentry->w_data = audit_data_alloc();
+	if (!new_wentry->w_data) {
+		ret = -ENOMEM;
+		goto audit_create_wentry_fail;
+	}
+
+	strcpy(new_wentry->w_name, name);
+	strcpy(new_wentry->w_filterkey, filterkey);
+
+	/* Circular goodness */
+	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);
+		write_unlock_irqrestore(&data->watchlist_lock, flags);
+		goto audit_create_wentry_exit;
+	}
+	audit_wentry_put(wentry);
+	write_unlock_irqrestore(&data->watchlist_lock, flags);
+
+	ret = -EEXIST;
+	audit_wentry_put(new_wentry->w_data->wentry);
+	new_wentry->w_data->wentry = NULL;
+
+      audit_create_wentry_fail:
+	audit_wentry_put(new_wentry);
+	if (!data->wentry && list_empty(&data->watchlist))
+		audit_data_free(data);
+      audit_create_wentry_exit:
+	return ret;
+}
+/* When we destroy the watch entry we have to make sure we release our 
+ * reference to w_data.  If we don't, we might leak a reference.
+ */
+static int audit_destroy_wentry(const char *name, struct audit_data *data)
+{
+	int ret = 0;
+	unsigned long flags;
+	struct audit_wentry *wentry;
+
+	write_lock_irqsave(&data->watchlist_lock, flags);
+	wentry = audit_wentry_fetch(name, data);
+	if (!wentry) {
+		ret = -EACCES;
+		write_unlock_irqrestore(&data->watchlist_lock, flags);
+		goto audit_destroy_wentry_exit;
+	}
+	audit_wentry_put(wentry);
+	/* Put back wentry's reference to w_data */
+	audit_data_put(wentry->w_data);
+	list_del_init(&wentry->w_list);
+	/* Put back watchlist's reference of wentry */
+	audit_wentry_put(wentry);
+	write_unlock_irqrestore(&data->watchlist_lock, flags);
+
+      audit_destroy_wentry_exit:
+	return ret;
+}
+
+static int audit_insert_watch(struct audit_watch_request *req)
+{
+	int ret;
+	char *path = NULL;
+	char *filterkey = NULL;
+	struct audit_wentry *wentry;
+	struct dentry *dentry;
+	struct nameidata nd;
+
+	path = getname(req->path);
+	if (IS_ERR(path)) {
+		ret = PTR_ERR(path);
+		goto audit_insert_watch_exit;
+	}
+
+	if (strlen(path) > PATH_MAX) {
+		ret = -EINVAL;
+		goto audit_insert_watch_exit;
+	}
+
+	filterkey = getname(req->filterkey);
+	if (IS_ERR(filterkey)) {
+		ret = PTR_ERR(filterkey);
+		goto audit_insert_watch_exit;
+	}
+
+	if (strlen(filterkey) > AUDIT_FILTERKEY_MAX) {
+		ret = -EINVAL;
+		goto audit_insert_watch_exit;
+	}
+
+	ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_insert_watch_exit;
+
+	if (nd.last_type != LAST_NORM) {
+		ret = -EPERM;
+		goto audit_insert_watch_release;
+	}
+
+	if (!nd.dentry->d_inode->i_audit) {
+		nd.dentry->d_inode->i_audit = audit_data_alloc();
+		if (!nd.dentry->d_inode->i_audit) {
+			ret = -ENOMEM;
+			goto audit_insert_watch_release;
+		}
+	}
+
+	ret = audit_create_wentry(nd.last.name, filterkey,
+				  nd.dentry->d_inode->i_audit);
+	if (ret < 0)
+		goto audit_insert_watch_release;
+
+	dentry = d_lookup(nd.dentry, &nd.last);
+	if (dentry && dentry->d_inode) {
+		if (dentry->d_inode->i_audit) {
+			wentry = 
+			    audit_wentry_fetch_lock(dentry->d_name.name,
+						    nd.dentry->d_inode->
+						    i_audit);
+			audit_data_put(wentry->w_data);
+			wentry->w_data =
+			    audit_data_get(dentry->d_inode->i_audit);
+			dentry->d_inode->i_audit->wentry = wentry;
+		} else
+			audit_watch(dentry, 0);
+	}
+	dput(dentry);
+
+      audit_insert_watch_release:
+	path_release(&nd);
+      audit_insert_watch_exit:
+	putname(path);
+	putname(filterkey);
+	return ret;
+}
+
+static int audit_remove_watch(struct audit_watch_request *req)
+{
+	int ret;
+	char *path = NULL;
+	struct nameidata nd;
+
+	path = getname(req->path);
+	if (IS_ERR(path)) {
+		ret = PTR_ERR(path);
+		goto audit_remove_watch_exit;
+	}
+
+	if (strlen(path) > PATH_MAX) {
+		ret = -EINVAL;
+		goto audit_remove_watch_exit;
+	}
+
+	ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_remove_watch_exit;
+
+	ret =
+	    audit_destroy_wentry(nd.last.name,
+				 nd.dentry->d_inode->i_audit);
+	if (ret < 0)
+		goto audit_remove_watch_release;
+
+	if (!nd.dentry->d_inode->i_audit->wentry &&
+	    list_empty(&nd.dentry->d_inode->i_audit->watchlist)) {
+		printk("about to put parent audit_data\n");
+		audit_data_put(nd.dentry->d_inode->i_audit);
+		nd.dentry->d_inode->i_audit = NULL;
+	}
+
+      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)
+		return;
+
+	if (atomic_dec_and_test(&wentry->w_count)) {
+		audit_wentry_free(wentry);
+	}
+}
+
+/* If child exists in parent's watchlist, the child will point
+ * into the parent's watchlist at its own entry.  Otherwise,
+ * it will point to NULL
+ *
+ * As of right now, if we create a hardlink in another directory, the watch
+ * remains.  However, if we move that watch, we lose it.  This happens because
+ * we remove the watch on every move.  I might have to change this.
+ *
+ * Also, when we create a hardlink to one watched file in the location of
+ * another watch, we inherit the watch of the location we're at, effectively
+ * losing the watch on the file we're hardlinking against.  This could cause
+ * a subversion in userspace filtering, if the filterkey of the file we're
+ * linking against points to a more comprehensive filtering scheme.
+ *
+ * Hook appears in: fs/dcache.c:d_instantiate(), d_move(), and d_splice_alias()
+ */
+void audit_watch(struct dentry *dentry, int preserve)
+{
+	struct audit_wentry *wentry;
+
+	if (!dentry || !dentry->d_inode)
+		return;
+
+	wentry = audit_wentry_fetch_lock(dentry->d_name.name,
+					 dentry->d_parent->d_inode->
+					 i_audit);
+	if (!wentry) {
+		if (!preserve)
+			audit_inode_free(dentry->d_inode);
+		return;
+	}
+
+	if (!dentry->d_inode->i_audit)
+		dentry->d_inode->i_audit = audit_data_get(wentry->w_data);
+	else {
+		audit_data_put(dentry->d_inode->i_audit);
+		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_request *req)
+{
+	int ret;
+
+	switch (type) {
+	case AUDIT_WATCH_INS:
+		ret = audit_insert_watch(req);
+		break;
+	case AUDIT_WATCH_REM:
+		ret = audit_remove_watch(req);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	/* Need to return something more meaningful to userspace and
+	 * update comment that states audit_send_reply is only called
+	 * from auditsc.c
+	 */
+	if (!ret)
+		audit_send_reply(pid, seq, type, 0, 1, NULL, 0);
+
+	return ret;
+}
+
+void audit_inode_alloc(struct inode *inode)
+{
+	if (!inode)
+		return;
+
+	inode->i_audit = NULL;
+
+}
+
+void audit_inode_free(struct inode *inode)
+{
+	if (!inode || !inode->i_audit)
+		return;
+
+	audit_data_put(inode->i_audit);
+	inode->i_audit = NULL;
+
+}
+
+void audit_filesystem_init()
+{
+	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 -purN linux-2.6.10/kernel/auditsc.c linux-2.6.10-audit/kernel/auditsc.c
--- linux-2.6.10/kernel/auditsc.c	2004-12-24 15:35:24.000000000 -0600
+++ linux-2.6.10-audit/kernel/auditsc.c	2005-02-04 16:41:25.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 redhat com>
+ * Modified by Timothy R. Chavez <chavezt us ibm com>
  *
  * Many of the ideas implemented here are from Stephen C. Tweedie,
  * especially the idea of avoiding a copy by using getname.
@@ -49,6 +50,11 @@
 /* No syscall auditing will take place unless audit_enabled != 0. */
 extern int audit_enabled;
 
+/* No syscall will collect information about auditable file/dirs
+ * unless audit_filesystem != 0
+ */
+extern int audit_filesystem;
+
 /* AUDIT_NAMES is the number of slots we reserve in the audit_context
  * for saving names from getname(). */
 #define AUDIT_NAMES    20
@@ -113,6 +119,7 @@ struct audit_context {
 	uid_t		    uid, euid, suid, fsuid;
 	gid_t		    gid, egid, sgid, fsgid;
 	unsigned long	    personality;
+	struct list_head    watched; /* The auditable files/dirs accessed */
 
 #if AUDIT_DEBUG
 	int		    put_count;
@@ -134,6 +141,21 @@ 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 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)
@@ -506,6 +528,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->watched, list) {
+	        list_del(&file->list);
+		audit_file_free(file);
+	}
+}
+
 static inline void audit_zero_context(struct audit_context *context,
 				      enum audit_state state)
 {
@@ -514,6 +567,7 @@ static inline void audit_zero_context(st
 	memset(context, 0, sizeof(*context));
 	context->state      = state;
 	context->loginuid   = loginuid;
+	INIT_LIST_HEAD(&context->watched);
 }
 
 static inline struct audit_context *audit_alloc_context(enum audit_state state)
@@ -572,6 +626,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);
@@ -582,6 +637,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);
@@ -591,7 +647,7 @@ static void audit_log_exit(struct audit_
 	if (context->personality != PER_LINUX)
 		audit_log_format(ab, " per=%lx", context->personality);
 	if (context->return_valid)
-		audit_log_format(ab, " exit=%u", context->return_code);
+		audit_log_format(ab, " exit=%d",  context->return_code);
 	audit_log_format(ab,
 		  " a0=%lx a1=%lx a2=%lx a3=%lx items=%d"
 		  " pid=%d loginuid=%d uid=%d gid=%d"
@@ -628,6 +684,25 @@ static void audit_log_exit(struct audit_
 					 MINOR(context->names[i].rdev));
 		audit_log_end(ab);
 	}
+
+	if (!audit_filesystem)
+		return;
+	
+	list_for_each_entry(file, &context->watched, list) {
+		ab = audit_log_start(context);
+		if (!ab)
+			continue;
+		
+		/* Do we need more information? */
+		audit_log_format(ab,
+			"name=%s filter_key=%s inode=%lu inode_mode=%d "
+			"inode_uid=%d inode_gid=%d inode_dev=%02x:%02x "
+			"mask=%d",
+			file->wentry->w_name, file->wentry->w_filterkey, 
+                        file->ino, file->mode, file->uid, file->gid,
+                        MAJOR(file->rdev), MINOR(file->rdev), file->mask);
+		audit_log_end(ab);
+	}
 }
 
 /* Free a per-task audit context.  Called from copy_process and
@@ -791,6 +866,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;
 	}
@@ -914,3 +990,56 @@ int audit_set_loginuid(struct audit_cont
 	}
 	return 0;
 }
+
+/* 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 located in: fs/namie.c:permission()
+ */
+
+int audit_notify_watch(struct task_struct *tsk, struct inode *inode, int mask)
+{
+	struct audit_context *context;
+	struct audit_file *file;
+	
+	context = tsk->audit_context;
+	
+	/* Do we have a context and are we in a syscall? */
+	if (!context || !context->in_syscall)
+		return 0;
+ 
+	/* Do we care about file system auditing? */
+	if (!audit_filesystem)
+		return 0;
+
+	/* Are we being watched? */
+	if (!inode->i_audit || !inode->i_audit->wentry)
+		return 0;
+	
+	/* Are we still in an auditable state? 
+	 * If we arrive here and our wentry reference count is equal to 1,
+	 * it means that we're being held onto by inode->i_audit->wentry,
+	 * but we are actually no longer in a watchlist.  This means that
+	 * our watch was removed and we should not generate audit records.
+	 */
+	if (atomic_read(&inode->i_audit->wentry->w_count) == 1)
+		return 0;
+
+	/* Setup */
+	file = audit_file_alloc();
+	if (!file)
+		return -ENOMEM;
+
+	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->rdev = inode->i_rdev;
+	file->mask = mask;
+
+	list_add(&file->list, &tsk->audit_context->watched);
+
+	return 0;
+}
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]