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

[RFC][PATCH] (#2) Prelim in-kernel file system auditing support



Alright,

Once again, thank you to Serge, Chris, and David for all the insight. 
Here's the latest patch incorporating many of the changes you all
suggested.  There are still some things missing and not fully tested
(for instance, the locking).

TODO:

* Make filesystem auditing enabled/disabled at runtime
* Re-add comments with proper DocBook formatting
* Remove Makefile changes
* Move struct audit_file to a slab cache

Am I forgetting something? (Soooo tired ;-))

I'd appreciate any and all comments / feedback.  Thank you.

-- 
- Timothy R. Chavez
diff -Nrup 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-01-22 18:09:30.000000000 -0600
@@ -1,7 +1,7 @@
 VERSION = 2
 PATCHLEVEL = 6
 SUBLEVEL = 10
-EXTRAVERSION =
+EXTRAVERSION =-auditfs-tc1-1
 NAME=Woozy Numbat
 
 # *DOCUMENTATION*
diff -Nrup 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 -Nrup 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-01-18 14:20:18.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);
 }
 
 /**
@@ -920,6 +922,7 @@ struct dentry *d_splice_alias(struct ino
 			security_d_instantiate(dentry, inode);
 			d_rehash(dentry);
 		}
+		audit_watch(dentry);
 	} 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);
 }
 
 /**
diff -Nrup 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-01-24 23:33:49.000000000 -0600
@@ -135,6 +135,9 @@ static struct inode *alloc_inode(struct 
 		inode->i_cdev = NULL;
 		inode->i_rdev = 0;
 		inode->i_security = NULL;
+#ifdef CONFIG_AUDITFILESYSTEM
+		inode->i_audit = NULL;
+#endif
 		inode->dirtied_when = 0;
 		if (security_inode_alloc(inode)) {
 			if (inode->i_sb->s_op->destroy_inode)
@@ -175,6 +178,9 @@ void destroy_inode(struct inode *inode) 
 	if (inode_has_buffers(inode))
 		BUG();
 	security_inode_free(inode);
+#ifdef CONFIG_AUDITFILESYSTEM	
+	audit_inode_free(inode);
+#endif
 	if (inode->i_sb->s_op->destroy_inode)
 		inode->i_sb->s_op->destroy_inode(inode);
 	else
diff -Nrup 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-01-21 10:30:20.000000000 -0600
@@ -232,6 +232,10 @@ int permission(struct inode * inode,int 
 	/* Ordinary permission routines do not understand MAY_APPEND. */
 	submask = mask & ~MAY_APPEND;
 
+	retval = audit_notify_watch(inode, mask);
+	if (retval < 0)
+		return retval;
+
 	if (inode->i_op && inode->i_op->permission)
 		retval = inode->i_op->permission(inode, submask, nd);
 	else
diff -Nrup 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-01-24 15:25:53.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
 
+/* Arbitrary at this point */
+#define AUDIT_FILTERKEY_SIZE		32
+
 #ifndef __KERNEL__
 struct audit_message {
 	struct nlmsghdr nlh;
@@ -129,6 +134,27 @@ struct audit_rule {		/* for AUDIT_LIST, 
 	__u32		values[AUDIT_MAX_FIELDS];
 };
 
+/* Input structure for fs object auditing */
+
+struct audit_watch_request {
+	char	*path;
+	char	*filterkey;
+};
+
+struct audit_watch {
+	char *w_name;
+	char *w_filterkey;
+	struct list_head w_list;
+	atomic_t w_count;
+};
+
+struct audit_data {
+	struct audit_watch	*watch;
+	struct list_head 	watchlist;
+	rwlock_t 		watchlist_lock;
+	struct list_head 	list;
+};
+	
 #ifdef __KERNEL__
 
 #ifdef CONFIG_AUDIT
@@ -137,6 +163,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 +183,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 +192,26 @@ 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_watch(struct dentry *dentry);
+extern void audit_inode_free(struct inode *inode);
+extern void audit_watch_put(struct audit_watch *watch);
+extern struct audit_watch *audit_watch_get(struct audit_watch *watch);
+#else
+#define audit_receive_watch(t,p,u,s,r) do { ; } while(0)
+#define audit_filesystem_init() do { ; } while(0)
+#define audit_watch(d) do { ; } while (0)
+#define audit_inode_free(i) 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 -Nrup 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-20 11:07:35.000000000 -0600
@@ -27,6 +27,7 @@ struct poll_table_struct;
 struct kstatfs;
 struct vm_area_struct;
 struct vfsmount;
+struct audit_data;
 
 /*
  * It's silly to have NR_OPEN bigger than NR_FILE, but you can change
@@ -459,6 +460,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 -Nrup 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 -Nrup 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 -Nrup 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-01-24 21:45:30.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,17 @@ 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 -Nrup 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-01-24 23:16:21.000000000 -0600
@@ -0,0 +1,466 @@
+/* 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_watch_cache;
+kmem_cache_t *audit_data_cache;
+
+/* Kind of like a coat check, but for audit_data :-)*/
+static LIST_HEAD(audit_data_check);
+
+/* NOTE:
+ * If a struct audit_watch is returned, its reference count is
+ * incremented.  Use audit_watch_put() to decrement the reference
+ * count.
+ */
+
+static struct audit_watch *audit_fetch_watch(const char *name,
+					     struct dentry *parent)
+{
+	unsigned long flags;
+	struct audit_data *data;
+	struct audit_watch *watch, *ret = NULL;
+
+	data = parent->d_inode->i_audit;
+	if (!data)
+		goto audit_fetch_watch_exit;
+
+	read_lock_irqsave(&data->watchlist_lock, flags);
+	list_for_each_entry(watch, &data->watchlist, w_list) {
+		if (!strcmp(watch->w_name, name)) {
+			ret = audit_watch_get(watch);
+			break;
+		}
+	}
+	read_unlock_irqrestore(&data->watchlist_lock, flags);
+     
+     audit_fetch_watch_exit:
+	return ret;
+}
+
+static inline struct audit_watch *audit_watch_alloc(void)
+{
+	struct audit_watch *watch;
+
+	watch = kmem_cache_alloc(audit_watch_cache, GFP_KERNEL);
+	if (watch) {
+		watch->w_name = NULL;
+		watch->w_filterkey = NULL;
+	}
+
+	return watch;
+}
+
+static inline void audit_watch_free(struct audit_watch *watch)
+{
+	if (watch) {
+		kfree(watch->w_name);
+		kfree(watch->w_filterkey);
+		kmem_cache_free(audit_watch_cache, watch); 
+	}
+}
+
+/* We preallocate audit_data for inode's and check it into the
+ * audit_data_check.  When we want the memory, we check it out.
+ * We do not want to handle -ENOMEM from our hook, so we must
+ * preallocate memory for the inode, before we enter the hook.
+ */
+
+/* NOTE:
+ * It is up to the caller to ensure that data is empty prior
+ * to check in.  Memory is checked out as-is.
+ */
+static inline struct audit_data *audit_data_checkout(void)
+{
+	struct audit_data *data;
+
+	BUG_ON(list_empty(&audit_data_check));
+
+	data = list_entry(audit_data_check.next, struct audit_data, list);
+	list_del_init(&data->list);
+
+	return data;
+}
+
+/* NOTE: 
+ * It is up to the caller to ensure that data is empty prior
+ * to check in.  Memory is checked in as-is.
+ */
+static inline void audit_data_checkin(struct audit_data *data)
+{
+	if (!data)
+		return;
+
+	list_add(&data->list, &audit_data_check);
+}
+
+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->watch = NULL;
+		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_watch *watch, *tmp;
+
+	if (!data)
+		return;
+
+	/* Drain watchlist and clean out stale audit_data in the audit_data 
+	 * check.  We a hold a lock because we must have any empty watchlist, 
+	 * if we free the memory or check the memory. 
+	 */
+	write_lock_irqsave(&data->watchlist_lock, flags);
+	list_for_each_entry_safe(watch, tmp, &data->watchlist, w_list) {
+		list_del(&watch->w_list);
+		kmem_cache_free(audit_data_cache, audit_data_checkout());
+	}
+	
+	/* If we are being watched, store our memory */
+	if (data->watch) { 
+		audit_watch_put(data->watch);
+		data->watch = NULL;
+		audit_data_checkin(data);
+	} else
+		kmem_cache_free(audit_data_cache, data); 	
+	write_unlock_irqrestore(&data->watchlist_lock, flags);
+}
+
+static int
+audit_create_watch(const char *name, const char *filterkey,
+		   struct dentry *parent)
+{
+	unsigned long flags;
+	struct audit_watch *watch;
+
+	/* Do we already exist? */
+	watch = audit_fetch_watch(name, parent);
+	if (watch) {
+		audit_watch_put(watch);
+		return -EEXIST;
+	}
+
+	/* Setup */
+	if (!parent->d_inode->i_audit) {
+		parent->d_inode->i_audit = audit_data_alloc();
+		if (!parent->d_inode->i_audit)
+			return -ENOMEM;
+	}
+
+	watch = audit_watch_alloc();
+	if (!watch)
+		goto audit_create_watch_fail;
+
+	watch->w_name = kmalloc(strlen(name) + 1, GFP_KERNEL);
+	if (!watch->w_name)
+		goto audit_create_watch_fail;
+	
+	watch->w_filterkey = kmalloc(strlen(filterkey) + 1, GFP_KERNEL);
+	if (!watch->w_filterkey)
+		goto audit_create_watch_fail;
+
+	strcpy(watch->w_name, name);
+	strcpy(watch->w_filterkey, filterkey);
+	atomic_set(&watch->w_count, 1);
+	
+	write_lock_irqsave(&parent->d_inode->i_audit->watchlist_lock, flags);
+	list_add(&watch->w_list, &parent->d_inode->i_audit->watchlist);
+	write_unlock_irqrestore(&parent->d_inode->i_audit->watchlist_lock, 
+				flags);
+	
+	return 0;
+	
+      audit_create_watch_fail:
+	audit_watch_free(watch);
+	audit_data_free(parent->d_inode->i_audit);
+	return -ENOMEM;
+}
+
+static int audit_destroy_watch(const char *name, struct dentry *parent)
+{
+	struct audit_watch *watch;
+
+	/* Do we exist? */
+	watch = audit_fetch_watch(name, parent);
+	if (!watch)
+		return -EACCES;
+
+	/* Detach watch entry from watchlist
+	 * and decrement the reference count
+	 */
+	list_del_init(&watch->w_list);
+	audit_watch_put(watch);
+
+	/* Take down */
+	if (!parent->d_inode->i_audit->watch &&
+	    list_empty(&parent->d_inode->i_audit->watchlist)) {
+		audit_data_free(parent->d_inode->i_audit);
+		parent->d_inode->i_audit = NULL;
+	}
+
+	/* Decrement the reference count from the audit_fetch */
+	audit_watch_put(watch);
+	return 0;
+}
+
+static int audit_insert_watch(struct audit_watch_request *req)
+{
+	int ret = 0;
+	char *path = NULL;
+	char *filterkey = NULL;
+	struct nameidata nd;
+	struct dentry *dentry;
+	struct audit_data *data;
+
+	/* Setup */
+	path = getname(req->path);
+	if (IS_ERR(path)) {
+		ret = PTR_ERR(path);
+		goto audit_insert_watch_exit;
+	}
+
+	filterkey = getname(req->filterkey);
+	if (IS_ERR(filterkey)) {
+		ret = PTR_ERR(filterkey);
+		goto audit_insert_watch_exit;
+	}
+	
+	/* The root directory cannot be watched */
+	if (!strcmp(path, "/")) {
+		ret = -EPERM;
+		goto audit_insert_watch_exit;
+	}
+
+	ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_insert_watch_exit;
+	
+	/* Insert watch into parent's watchlist */
+	ret = audit_create_watch(nd.last.name, filterkey, nd.dentry);
+	if (ret < 0)
+		goto audit_insert_watch_release;
+	
+	/* Preallocate audit_data memory for inode */
+	data = audit_data_alloc();
+	if (!data) {
+		ret = -ENOMEM;
+		goto audit_insert_watch_release;
+	}
+
+	dentry = d_lookup(nd.dentry, &nd.last);
+	if (dentry && dentry->d_inode) 
+		if (dentry->d_inode->i_audit) {
+			dentry->d_inode->i_audit->watch =
+			    audit_fetch_watch(dentry->d_name.name,
+					      dentry->d_parent);
+			audit_data_free(data);
+		} else {
+			audit_data_checkin(data);
+			audit_watch(dentry);
+		}
+	else
+		audit_data_checkin(data);
+
+	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;
+	struct dentry *dentry;
+
+	/* Setup */
+	path = getname(req->path);
+	if (IS_ERR(path)) {
+		ret = PTR_ERR(path);
+		goto audit_remove_watch_exit;
+	}
+
+	/* The root directory cannot be watched */
+	if (!strcmp(path, "/")) {
+		ret = -EPERM;
+		goto audit_remove_watch_exit;
+	}
+
+	ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_remove_watch_exit;
+
+	/* Remove watch from parent's watchlist */
+	ret = audit_destroy_watch(nd.last.name, nd.dentry);
+	if (ret < 0)
+		goto audit_remove_watch_release;
+
+	dentry = d_lookup(nd.dentry, &nd.last);
+	if (dentry && dentry->d_inode) {
+		audit_watch_put(dentry->d_inode->i_audit->watch);
+		dentry->d_inode->i_audit->watch = NULL;
+		if (list_empty(&dentry->d_inode->i_audit->watchlist)) {
+			audit_data_free(dentry->d_inode->i_audit);
+			dentry->d_inode->i_audit = NULL;
+		}
+	} else
+		kmem_cache_free(audit_data_cache, audit_data_checkout());
+	dput(dentry);
+
+      audit_remove_watch_release:
+	path_release(&nd);
+      audit_remove_watch_exit:
+	putname(path);
+	return ret;
+}
+
+/* Public interface */
+void audit_inode_free(struct inode *inode) 
+{
+	if (!inode)
+		return;
+
+	audit_data_free(inode->i_audit);
+}
+
+
+struct audit_watch *audit_watch_get(struct audit_watch *watch)
+{
+	if (watch) {
+		BUG_ON(!atomic_read(&watch->w_count));
+		atomic_inc(&watch->w_count);
+	}
+
+	return watch;
+}
+
+void audit_watch_put(struct audit_watch *watch)
+{
+	if (!watch)
+		return;
+
+	if (atomic_dec_and_test(&watch->w_count))
+		audit_watch_free(watch);
+}
+
+/* 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
+ *
+ * Hook appears in: fs/dcache.c:d_instantiate(), d_move(), and d_splice_alias()
+ */
+void audit_watch(struct dentry *dentry)
+{
+	struct audit_watch *watch;
+
+	if (!dentry || !dentry->d_inode)
+		return;
+
+	watch = audit_fetch_watch(dentry->d_name.name, dentry->d_parent);
+	if (!watch)
+		return;
+
+	/* Fetch preallocated audit_data and attach to inode. */
+	if (!dentry->d_inode->i_audit)
+		dentry->d_inode->i_audit = audit_data_checkout();
+	else
+		audit_watch_put(dentry->d_inode->i_audit->watch);
+
+	dentry->d_inode->i_audit->watch = watch;
+}
+
+/* Called from kernel/audit.c by audit_receive_msg()
+ * 
+ * Code path for watch insertions:
+ *  audit_receive_watch()->audit_insert_watch()->audit_create_watch()
+ * Code path for watch removals:
+ *  audit_receive_watch()->audit_remove_watch()->audit_destroy_watch()
+ *
+ * FIXME:  When more serious userspace development to support this
+ * feature takes place, this function must be fixed up.
+ */
+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:
+		return -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_filesystem_init()
+{
+	audit_watch_cache = kmem_cache_create("audit_watch_cache",
+					      sizeof(struct audit_watch),
+					      0, 0, NULL, NULL);
+	audit_data_cache = kmem_cache_create("audit_data_cache",
+					     sizeof(struct audit_data),
+					     0, 0, NULL, NULL);
+}
diff -Nrup 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-01-24 23:47:56.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_watch *watch;
+	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->watch = NULL;
+         
+	return file;
+}
+
+static void audit_file_free(struct audit_file *file)
+{
+	if (file) {
+		audit_watch_put(file->watch);
+		file->watch = 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); /* was: %u*/
 	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,24 @@ 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->watch->w_name, file->watch->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 +865,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 +989,47 @@ 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)
+{
+	struct audit_context *context;
+	struct audit_file *file;
+	
+	context = current->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->watch)
+		return 0;
+
+	/* Setup */
+	file = audit_file_alloc();
+	if (!file)
+		return -ENOMEM;
+
+	file->watch = audit_watch_get(inode->i_audit->watch);
+	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, &current->audit_context->watched);
+
+	return 0;
+}
+

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