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

[RFC][PATCH] (#5) prelim auditfs



Hello,

Before we get started I just have to tell the world:  It's a beautiful
day here in Austin, TX.

There were enough changes for this patch that I felt it'd be OK and
simpler if I just called it patch #5.  In this patch I fix the logic
which was pretty broken last time (man was it broken).  I also cleaned
up the code: reduced more code redundancy, tightened up helper
functions, fixed minor and major bugs, and attempted to make the code
more readable.

Two improvements:

1.

I did something which might be kind of ballsy, but it seems to work just fine.  

In a very tired state I tried explaining this one over the phone.  My
guess is that it made little to no sense -- sorry :( perhaps I'll be a
little more eloquent via e-mail.

A file/directory that is already being watched gives that watch
precedence on that inode.  This means that if we move a watched file,
/tmp/foo, to /tmp/bar and then watch /tmp/bar, /tmp/bar will still
report watch information for /tmp/foo (Likewise, if we moved /tmp/foo
to /tmp/bar and a watch already exists on /tmp/bar, the watch for
/tmp/foo would remain).  This is only semi-cool.  To become fully-cool
we'd also like to be able to adopt (automatically) the watch on
/tmp/bar if and when the watch on /tmp/foo is ever removed.  Hooking
d_lookup seemed like the best place to do this.

2.

There are three kinds of inodes that make use of the audit_data inode
struct: watched, watching, both.  When we watch an inode that is also
watching, it'd be a mistake to overwrite it with the preallocated
memory associated with that watch entry.  There goes the watchlist,
doh!  So, in this case, we simply transfer into the preallocated
memory the inode's current audit_data.  This has a nifty side-effect. 
Iff the inode is stationary in the filesystem (movement destroys
watchlists), then if we were to delete and recreate the directory, its
watches would still be intact.  If we remove the acutally watch, the
watchlist will automatically be drained.  To remove individual
watches, the directory would have to first be resurrected.

What is left?

- Feature:  List all watches currently in the filesystem
- Comments:  Add [better | more] comments
- Test, test, test

Testing:

Starting tomorrow, Loulwa S. will officially be joining this project,
and will be helping me with the functional test case development
needed for the auditfs piece.

Chris, I wasn't really able to find much on the umount() problem the
Inotify guys were having.  I found a conversation / beat down which
alluded to it, but that's it.  Still, I hadn't actually tested the
behavior when I umount a device that has watches on it, so I figured
I'd at least do this test:

I added watches to a mount, removed the mount, and saw all the watches
putting back all their references and being freed / put back into
their respective caches.  This is the correct behavior in my book. 
Was it something more / different?

Still unable to provide userspace piece for testing.  I am going to
port it to audit-0.6.4

Please, please, please... look this over and give comments, feedback,
and suggestions.  Don't hold back, let me have it.

-- 
- Timothy R. Chavez
diff -Nurp 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-28 12:50:36.000000000 -0600
@@ -1,7 +1,7 @@
 VERSION = 2
 PATCHLEVEL = 6
 SUBLEVEL = 10
-EXTRAVERSION =
+EXTRAVERSION =-auditfs-tc1-5
 NAME=Woozy Numbat
 
 # *DOCUMENTATION*
diff -Nurp 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-02-22 09:04:45.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 -Nurp 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-25 14:51:55.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, 0);
 }
 
 /**
@@ -920,6 +922,7 @@ struct dentry *d_splice_alias(struct ino
 			security_d_instantiate(dentry, inode);
 			d_rehash(dentry);
 		}
+		audit_watch(dentry, 0);
 	} else
 		d_add(dentry, inode);
 	return new;
@@ -1027,6 +1030,8 @@ next:
  	}
  	rcu_read_unlock();
 
+	audit_watch(found, 0);
+
  	return found;
 }
 
@@ -1266,6 +1271,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.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-28 13:03:45.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.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-24 10:30:35.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(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(inode, MAY_EXEC);
+
 	if (inode->i_op && inode->i_op->permission)
 		return -EAGAIN;
 
@@ -1704,6 +1708,9 @@ int vfs_rmdir(struct inode *dir, struct 
 	if (error)
 		return error;
 
+	if (dentry)
+		audit_notify_watch(dentry->d_inode, 0);
+
 	if (!dir->i_op || !dir->i_op->rmdir)
 		return -EPERM;
 
diff -Nurp 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	2004-12-24 15:34:57.000000000 -0600
+++ linux-2.6.10-audit/include/linux/audit.h	2005-02-24 20:18:34.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)
@@ -91,11 +93,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 +112,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) */
@@ -129,6 +136,29 @@ struct audit_rule {		/* for AUDIT_LIST, 
 	__u32		values[AUDIT_MAX_FIELDS];
 };
 
+struct audit_watch {
+	int	namelen;
+	int 	fklen;
+	char	*name;
+	char	*filterkey;
+	__u32	perms;
+};
+
+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 __KERNEL__
 
 #ifdef CONFIG_AUDIT
@@ -137,6 +167,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 +187,7 @@ 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 inode *inode, int mask);
 #else
 #define audit_alloc(t) ({ 0; })
 #define audit_free(t) do { ; } while (0)
@@ -163,8 +196,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(i,m) ({ 0; })
 #endif
 
+#ifdef CONFIG_AUDITFILESYSTEM
+extern void 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) 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,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.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-02-22 09:04:45.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 -Nurp 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-02-22 10:05:37.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.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-02-22 09:04:45.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.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-28 13:10:21.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:
@@ -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;
 
@@ -260,6 +264,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;
@@ -317,6 +332,7 @@ static int audit_receive_msg(struct sk_b
 	switch (nlh->nlmsg_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;
@@ -334,6 +350,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;
@@ -394,6 +414,15 @@ static int audit_receive_msg(struct sk_b
 		err = -EOPNOTSUPP;
 #endif
 		break;
+#ifdef CONFIG_AUDITFILESYSTEM
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
+		audit_receive_watch(nlh->nlmsg_type, pid, uid, seq,
+			       	    data);
+#else
+		err = -EOPNOTSUPP;
+#endif
+		break;
 	default:
 		err = -EINVAL;
 		break;
@@ -533,6 +562,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.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-28 12:27:28.000000000 -0600
@@ -0,0 +1,557 @@
+/* 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/audit.h>
+
+kmem_cache_t *audit_watch_cache;
+kmem_cache_t *audit_wentry_cache;
+kmem_cache_t *audit_data_cache;
+
+static inline void audit_destroy_wentry(struct audit_wentry *wentry);
+
+/* Private Interface */
+
+/*
+ * 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 (name && data) {
+		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 *wentry, *ret = NULL;
+
+	if (name && data) {
+		read_lock_irqsave(&data->watchlist_lock, flags);
+		list_for_each_entry(wentry, &data->watchlist, w_list)
+		    if (!strcmp(wentry->w_watch->name, name)) {
+			ret = audit_wentry_get(wentry);
+			break;
+		}
+		read_unlock_irqrestore(&data->watchlist_lock, flags);
+	}
+
+	return ret;
+}
+
+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;
+}
+
+/*
+ * Caller must hold i_audit->watchlist_lock
+ */
+static inline void audit_drain_watchlist(struct audit_data *data)
+{
+	struct audit_wentry *wentry, *tmp;
+
+	if (data) {
+		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)
+		return;
+
+	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,
+					      unsigned int perms)
+{
+	struct audit_watch *err = NULL;
+	struct audit_watch *watch = NULL;
+
+	/* Must have a name */
+	if (!name || strlen(name) > PATH_MAX) {
+		err = ERR_PTR(-EINVAL);
+		goto audit_create_watch_fail;
+	}
+
+	/* Filterkey's are optional */
+	if (filterkey && strlen(filterkey) > AUDIT_FILTERKEY_MAX) {
+		err = ERR_PTR(-EINVAL);
+		goto audit_create_watch_fail;
+	}
+
+	if (perms > 15) {
+		err = ERR_PTR(-EINVAL);
+		goto audit_create_watch_fail;
+	}
+
+	watch = audit_watch_alloc();
+	if (watch) {
+		watch->namelen = strlen(name) + 1;
+		watch->name = kmalloc(watch->namelen, GFP_KERNEL);
+		if (!watch->name) {
+			err = ERR_PTR(-ENOMEM);
+			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) {
+				err = ERR_PTR(-ENOMEM);
+				goto audit_create_watch_fail;
+			}
+			strcpy(watch->filterkey, filterkey);
+		}
+
+		watch->perms = perms;
+
+		goto audit_create_watch_exit;
+	}
+
+	err = ERR_PTR(-ENOMEM);
+
+      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_valid = 0;
+		wentry->w_watch = NULL;
+		wentry->w_data = NULL;
+	}
+
+	return wentry;
+}
+
+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);
+	}
+}
+
+static int audit_create_wentry(const char *name,
+			       const char *filterkey,
+			       unsigned int perms, struct audit_data *data)
+{
+	int ret = 0;
+	unsigned long flags;
+	struct audit_wentry *wentry = NULL;
+	struct audit_wentry *new_wentry = NULL;
+
+	/* This first reference belongs to the watchlist */
+	new_wentry = audit_wentry_alloc();
+	if (!new_wentry) {
+		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;
+	}
+
+	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);
+		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_put(data);
+      audit_create_wentry_exit:
+	return ret;
+}
+
+/*
+ * Caller must hold i_audit->watchlist_lock
+ */
+static inline void audit_destroy_wentry(struct audit_wentry *wentry)
+{
+	list_del_init(&wentry->w_list);
+	wentry->w_valid = 0;
+	audit_data_put(wentry->w_data);
+	audit_wentry_put(wentry);
+}
+
+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);
+		if (!filterkey) {
+			ret = -ENOMEM;
+			goto audit_insert_watch_exit;
+		}
+		strncpy(filterkey, req->filterkey, req->fklen);
+	}
+
+	ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_insert_watch_release;
+
+	if (nd.last_type != LAST_NORM) {
+		ret = -EPERM;
+		goto audit_insert_watch_release;
+	}
+
+	/* Allocate space on our parent to hold the watch, if space
+	 * does not already exist
+	 */
+	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, 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;
+	}
+
+	if (!path || (path && 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_release;
+
+	data = nd.dentry->d_inode->i_audit;
+	if (!data) {
+		ret = -EACCES;
+		goto audit_remove_watch_release;
+	}
+
+	write_lock_irqsave(&data->watchlist_lock, flags);
+
+	wentry = audit_wentry_fetch(nd.last.name, data);
+	if (!wentry) {
+		ret = -EACCES;
+		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;
+	}
+
+      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(), 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);
+			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);
+}
+
+void audit_receive_watch(int type, int pid, int uid, int seq,
+			 struct audit_watch *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;
+	}
+
+	audit_send_reply(pid, seq, type, 0, 1, &ret, sizeof(int));
+}
+
+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_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.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-27 18:13:53.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 auditfs_enabled != 0
+ */
+extern int auditfs_enabled;
+
 /* 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,10 @@ struct audit_context {
 	uid_t		    uid, euid, suid, fsuid;
 	gid_t		    gid, egid, sgid, fsgid;
 	unsigned long	    personality;
+	struct list_head    trail; /* 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 +144,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 +531,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->trail, 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 +570,7 @@ static inline void audit_zero_context(st
 	memset(context, 0, sizeof(*context));
 	context->state      = state;
 	context->loginuid   = loginuid;
+	INIT_LIST_HEAD(&context->trail);
 }
 
 static inline struct audit_context *audit_alloc_context(enum audit_state state)
@@ -572,6 +629,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 +640,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 +650,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 +687,27 @@ 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->trail, 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",
+			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->rdev), MINOR(file->rdev));
+		audit_log_end(ab);
+	}
 }
 
 /* Free a per-task audit context.  Called from copy_process and
@@ -791,6 +871,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 +995,64 @@ 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 inode *inode, int mask)
+{
+	int ret = 0;
+	struct audit_context *context;
+	struct audit_file *file;
+
+	/* Do we care about filesystem auditing? */
+	if (!auditfs_enabled)
+		goto audit_notify_watch_exit;
+
+
+	/* Do we have a context and are we in a syscall? */
+	context = current->audit_context;
+	if (!context || !context->in_syscall)
+		goto audit_notify_watch_exit;
+
+	/* Are we being watched? */
+	if (inode && (!inode->i_audit || !inode->i_audit->wentry))
+		goto audit_notify_watch_exit;
+
+	/* If we're pointing at an invalid watchlist entry ignore us */
+	if (!inode->i_audit->wentry->w_valid)
+		goto audit_notify_watch_exit;
+
+	/* Permissions (READ/WRITE/EXEC/APPEND) filter.
+	 * If mask || w_watch->perms == 0, no filtering, audit on all
+	 */
+	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;
+	}
+
+	/* Setup */
+	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->rdev = inode->i_rdev;
+	file->mask = mask;
+
+	list_add(&file->list, &context->trail);
+
+	audit_notify_watch_exit:
+	return ret;
+}
+

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