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

[RFC][PATCH] (#5, U3) filesystem auditing



Hello,

- This patch reorders the hooks to be consistently placed above the security 
hooks (Thanks Serge).  

- It also removes the audit_notify_watch() hooks from vfs_rmdir and vfs_unlink 
and places it in may_delete() (Thanks Stephen).

- Stripped out the auditfs_enable feature because there was no outstanding 
use-case for it.  We determined it was sufficient to have the compile time 
configure option only.  (Thanks David, Thanks Steve).

Sorry I didn't get this out sooner David.

Also, what I'm doing with reference counting and locking doesn't seem to be 
too clear (and it really isn't) -- I apologize for that.  Though this will 
all be described in detail in a document, I'll go ahead and send out an 
e-mail explaining what's going on.


-tim

-- 
-tim
diff -Nurp linux-2.6.11/fs/dcache.c linux-2.6.11-audit/fs/dcache.c
--- linux-2.6.11/fs/dcache.c	2005-03-02 01:37:48.000000000 -0600
+++ linux-2.6.11-audit/fs/dcache.c	2005-03-08 11:57:57.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 */
 
@@ -799,6 +800,7 @@ void d_instantiate(struct dentry *entry,
 		list_add(&entry->d_alias, &inode->i_dentry);
 	entry->d_inode = inode;
 	spin_unlock(&dcache_lock);
+	audit_watch(entry, 0);
 	security_d_instantiate(entry, inode);
 }
 
@@ -975,6 +977,7 @@ struct dentry *d_splice_alias(struct ino
 		if (new) {
 			BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
 			spin_unlock(&dcache_lock);
+			audit_watch(dentry, 0);
 			security_d_instantiate(new, inode);
 			d_rehash(dentry);
 			d_move(new, dentry);
@@ -984,6 +987,7 @@ struct dentry *d_splice_alias(struct ino
 			list_add(&dentry->d_alias, &inode->i_dentry);
 			dentry->d_inode = inode;
 			spin_unlock(&dcache_lock);
+			audit_watch(dentry, 0);
 			security_d_instantiate(dentry, inode);
 			d_rehash(dentry);
 		}
@@ -1094,6 +1098,8 @@ next:
  	}
  	rcu_read_unlock();
 
+	audit_watch(found, 0);
+
  	return found;
 }
 
@@ -1333,6 +1339,7 @@ already_unhashed:
 	spin_unlock(&dentry->d_lock);
 	write_sequnlock(&rename_lock);
 	spin_unlock(&dcache_lock);
+	audit_watch(dentry, 1);
 }
 
 /**
diff -Nurp linux-2.6.11/fs/inode.c linux-2.6.11-audit/fs/inode.c
--- linux-2.6.11/fs/inode.c	2005-03-02 01:38:33.000000000 -0600
+++ linux-2.6.11-audit/fs/inode.c	2005-03-08 11:56:29.000000000 -0600
@@ -21,6 +21,7 @@
 #include <linux/pagemap.h>
 #include <linux/cdev.h>
 #include <linux/bootmem.h>
+#include <linux/audit.h>
 
 /*
  * This is needed for the following functions:
@@ -136,6 +137,7 @@ static struct inode *alloc_inode(struct 
 		inode->i_rdev = 0;
 		inode->i_security = NULL;
 		inode->dirtied_when = 0;
+		audit_inode_alloc(inode);
 		if (security_inode_alloc(inode)) {
 			if (inode->i_sb->s_op->destroy_inode)
 				inode->i_sb->s_op->destroy_inode(inode);
@@ -174,6 +176,7 @@ void destroy_inode(struct inode *inode) 
 {
 	if (inode_has_buffers(inode))
 		BUG();
+	audit_inode_free(inode);
 	security_inode_free(inode);
 	if (inode->i_sb->s_op->destroy_inode)
 		inode->i_sb->s_op->destroy_inode(inode);
diff -Nurp linux-2.6.11/fs/namei.c linux-2.6.11-audit/fs/namei.c
--- linux-2.6.11/fs/namei.c	2005-03-02 01:37:55.000000000 -0600
+++ linux-2.6.11-audit/fs/namei.c	2005-03-08 14:37:28.000000000 -0600
@@ -214,6 +214,8 @@ int permission(struct inode *inode, int 
 {
 	int retval, submask;
 
+	audit_notify_watch(inode, mask);
+
 	if (mask & MAY_WRITE) {
 		umode_t mode = inode->i_mode;
 
@@ -344,6 +346,8 @@ static inline int exec_permission_lite(s
 {
 	umode_t	mode = inode->i_mode;
 
+	audit_notify_watch(inode, MAY_EXEC);
+
 	if (inode->i_op && inode->i_op->permission)
 		return -EAGAIN;
 
@@ -1128,6 +1132,8 @@ static inline int may_delete(struct inod
 
 	BUG_ON(victim->d_parent->d_inode != dir);
 
+	audit_notify_watch(victim, 0);
+
 	error = permission(dir,MAY_WRITE | MAY_EXEC, NULL);
 	if (error)
 		return error;
diff -Nurp linux-2.6.11/include/linux/audit.h linux-2.6.11-audit/include/linux/audit.h
--- linux-2.6.11/include/linux/audit.h	2005-03-02 01:38:09.000000000 -0600
+++ linux-2.6.11-audit/include/linux/audit.h	2005-03-08 12:05:25.000000000 -0600
@@ -24,15 +24,23 @@
 #ifndef _LINUX_AUDIT_H_
 #define _LINUX_AUDIT_H_
 
+#ifdef __KERNEL__
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#endif
+
 /* Request and reply types */
-#define AUDIT_GET      1000	/* Get status */
-#define AUDIT_SET      1001	/* Set status (enable/disable/auditd) */
-#define AUDIT_LIST     1002	/* List filtering rules */
-#define AUDIT_ADD      1003	/* Add filtering rule */
-#define AUDIT_DEL      1004	/* Delete filtering rule */
-#define AUDIT_USER     1005	/* Send a message from user-space */
-#define AUDIT_LOGIN    1006     /* Define the login id and informaiton */
-#define AUDIT_KERNEL   2000	/* Asynchronous audit record. NOT A REQUEST. */
+#define AUDIT_GET      	1000	/* Get status */
+#define AUDIT_SET      	1001	/* Set status (enable/disable/auditd) */
+#define AUDIT_LIST     	1002	/* List filtering rules */
+#define AUDIT_ADD      	1003	/* Add filtering rule */
+#define AUDIT_DEL      	1004	/* Delete filtering rule */
+#define AUDIT_USER     	1005	/* Send a message from user-space */
+#define AUDIT_LOGIN    	1006    /* Define the login id and information */
+#define AUDIT_WATCH_INS	1007    /* Insert file/dir watch entry */
+#define AUDIT_WATCH_REM	1008	/* Remove file/dir watch entry */
+#define AUDIT_KERNEL   	2000	/* Asynchronous audit record. NOT A REQUEST. */
 
 /* Rule flags */
 #define AUDIT_PER_TASK 0x01	/* Apply rule at task creation (not syscall) */
@@ -91,11 +99,15 @@
 #define AUDIT_STATUS_PID		0x0004
 #define AUDIT_STATUS_RATE_LIMIT		0x0008
 #define AUDIT_STATUS_BACKLOG_LIMIT	0x0010
+
 				/* 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;
@@ -123,14 +135,38 @@ struct audit_rule {		/* for AUDIT_LIST, 
 	__u32		values[AUDIT_MAX_FIELDS];
 };
 
+struct audit_watch {
+	int	namelen;
+	int 	fklen;
+	char	*name;
+	char	*filterkey;
+	__u32	perms;
+};
+
 #ifdef __KERNEL__
 
+struct audit_data {
+	struct audit_wentry	*wentry;
+	struct list_head 	watchlist;
+	rwlock_t 		watchlist_lock;
+	atomic_t		count;
+};
+
+struct audit_wentry {
+	struct list_head 	w_list;
+	atomic_t 		w_count;
+	struct audit_data 	*w_data;
+	struct audit_watch	*w_watch;
+	int			w_valid;
+};
+
 #ifdef CONFIG_AUDIT
 struct audit_buffer;
 struct audit_context;
 #endif
 
 #ifdef CONFIG_AUDITSYSCALL
+struct inode;
 /* These are defined in auditsc.c */
 				/* Public API */
 extern int  audit_alloc(struct task_struct *task);
@@ -150,6 +186,7 @@ extern void audit_get_stamp(struct audit
 			    struct timespec *t, int *serial);
 extern int  audit_set_loginuid(struct audit_context *ctx, uid_t loginuid);
 extern uid_t audit_get_loginuid(struct audit_context *ctx);
+extern int audit_notify_watch(struct inode *inode, int mask);
 #else
 #define audit_alloc(t) ({ 0; })
 #define audit_free(t) do { ; } while (0)
@@ -159,8 +196,28 @@ extern uid_t audit_get_loginuid(struct a
 #define audit_putname(n) do { ; } while (0)
 #define audit_inode(n,i,d) do { ; } while (0)
 #define audit_get_loginuid(c) ({ -1; })
+#define audit_notify_watch(i,m) ({ 0; })
 #endif
 
+#ifdef CONFIG_AUDITFILESYSTEM
+extern 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) ({ -EOPNOTSUPP; })
+#define audit_filesystem_init() do { ; } while(0)
+#define audit_inode_alloc(i) do { ; } while(0)
+#define audit_inode_free(i) do { ; } while(0)
+#define audit_watch(dt,d) do { ; } while (0)
+#define audit_watch_put(w) do { ; } while(0)
+#define audit_watch_get(w) ({ 0; })
+#endif
+
 #ifdef CONFIG_AUDIT
 /* These are defined in audit.c */
 				/* Public API */
diff -Nurp linux-2.6.11/include/linux/fs.h linux-2.6.11-audit/include/linux/fs.h
--- linux-2.6.11/include/linux/fs.h	2005-03-02 01:37:50.000000000 -0600
+++ linux-2.6.11-audit/include/linux/fs.h	2005-03-04 16:20:29.000000000 -0600
@@ -457,6 +457,9 @@ struct inode {
 #ifdef CONFIG_QUOTA
 	struct dquot		*i_dquot[MAXQUOTAS];
 #endif
+#ifdef CONFIG_AUDITFILESYSTEM
+	struct audit_data	*i_audit;
+#endif
 	/* These three should probably be a union */
 	struct list_head	i_devices;
 	struct pipe_inode_info	*i_pipe;
diff -Nurp linux-2.6.11/init/Kconfig linux-2.6.11-audit/init/Kconfig
--- linux-2.6.11/init/Kconfig	2005-03-02 01:38:19.000000000 -0600
+++ linux-2.6.11-audit/init/Kconfig	2005-03-04 16:20:29.000000000 -0600
@@ -174,6 +174,17 @@ config AUDITSYSCALL
 	  can be used independently or with another kernel subsystem,
 	  such as SELinux.
 
+config AUDITFILESYSTEM
+	bool "Enable filesystem auditing support"
+	depends on AUDITSYSCALL
+	default n
+	help
+	  Generate audit records for regular files or directories that
+	  are being watched for access by audited syscalls.  To insert
+	  and remove watch points into the filesystem you may use the
+	  auditctl program provided with auditd.  For more information,
+	  'man auditctl'
+
 config LOG_BUF_SHIFT
 	int "Kernel log buffer size (16 => 64KB, 17 => 128KB)" if DEBUG_KERNEL
 	range 12 21
diff -Nurp linux-2.6.11/kernel/Makefile linux-2.6.11-audit/kernel/Makefile
--- linux-2.6.11/kernel/Makefile	2005-03-02 01:37:50.000000000 -0600
+++ linux-2.6.11-audit/kernel/Makefile	2005-03-04 16:20:29.000000000 -0600
@@ -23,6 +23,7 @@ obj-$(CONFIG_IKCONFIG_PROC) += configs.o
 obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
 obj-$(CONFIG_AUDIT) += audit.o
 obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
+obj-$(CONFIG_AUDITFILESYSTEM) += auditfs.o
 obj-$(CONFIG_KPROBES) += kprobes.o
 obj-$(CONFIG_SYSFS) += ksysfs.o
 obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
diff -Nurp linux-2.6.11/kernel/audit.c linux-2.6.11-audit/kernel/audit.c
--- linux-2.6.11/kernel/audit.c	2005-03-02 01:38:33.000000000 -0600
+++ linux-2.6.11-audit/kernel/audit.c	2005-03-08 12:12:58.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:
@@ -319,6 +320,8 @@ static int audit_netlink_ok(kernel_cap_t
 	case AUDIT_SET:
 	case AUDIT_ADD:
 	case AUDIT_DEL:
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
 		if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
 			err = -EPERM;
 		break;
@@ -413,6 +416,12 @@ static int audit_receive_msg(struct sk_b
 		err = -EOPNOTSUPP;
 #endif
 		break;
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
+		audit_receive_watch(nlh->nlmsg_type,
+				    NETLINK_CB(skb).pid,
+				    uid, seq, data);
+		break;
 	default:
 		err = -EINVAL;
 		break;
@@ -557,6 +566,7 @@ int __init audit_init(void)
 
 	audit_initialized = 1;
 	audit_enabled = audit_default;
+	audit_filesystem_init();
 	audit_log(NULL, "initialized");
 	return 0;
 }
diff -Nurp linux-2.6.11/kernel/auditfs.c linux-2.6.11-audit/kernel/auditfs.c
--- linux-2.6.11/kernel/auditfs.c	1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.11-audit/kernel/auditfs.c	2005-03-08 17:05:51.000000000 -0600
@@ -0,0 +1,570 @@
+/* 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>
+#include <asm/uaccess.h>
+
+kmem_cache_t *audit_watch_cache;
+kmem_cache_t *audit_wentry_cache;
+kmem_cache_t *audit_data_cache;
+
+/* Private Interface */
+
+static void audit_data_put(struct audit_data *data);
+void audit_wentry_put(struct audit_wentry *wentry);
+
+/*
+ * We remove this wentry from the watchlist and mark it as being invalid.  When
+ * we invalidate a wentry, we're telling the audit subsystem to ignore any refs
+ * to this wentry that may still exist when auditing.  We're also giving it the
+ * permission to remove the reference and attach a new watch if there is one
+ * available.
+ *
+ * We must drop our reference to the inode's audit_data here.  Otherwise, we'll
+ * leak a reference and audit_data_free() will never be called.
+ *
+ * Caller must hold i_audit->watchlist_lock
+ */
+static inline void audit_destroy_wentry(struct audit_wentry *wentry)
+{
+	if (wentry) {
+		list_del_init(&wentry->w_list);
+		wentry->w_valid = 0;
+		audit_data_put(wentry->w_data);
+		audit_wentry_put(wentry);
+	}
+}
+
+/*
+ * This is the core function for determining whether or not "name"
+ * is in the parent's watchlist (data->watchlist).
+ *
+ * NOTE: Should only be called from a secure source.
+ *
+ * Caller must hold i_audit->watchlist_lock and is responsible for
+ * putting back the returned reference
+ */
+static inline struct audit_wentry *audit_wentry_fetch(const char *name,
+						      struct audit_data *data)
+{
+	struct audit_wentry *wentry, *ret = NULL;
+
+	list_for_each_entry(wentry, &data->watchlist, w_list)
+		if (!strcmp(wentry->w_watch->name, name)) {
+			ret = audit_wentry_get(wentry);
+			break;
+		}
+
+	return ret;
+}
+
+static
+inline struct audit_wentry *audit_wentry_fetch_lock(const char *name,
+						    struct audit_data *data)
+{
+	unsigned long flags;
+	struct audit_wentry *ret = NULL;
+
+	if (name && data) {
+		read_lock_irqsave(&data->watchlist_lock, flags);
+		ret = audit_wentry_fetch(name, data);
+		read_unlock_irqrestore(&data->watchlist_lock, flags);
+	}
+
+	return ret;
+}
+
+/*
+ * First reference of audit_data is returned on allocation
+ */
+static inline struct audit_data *audit_data_alloc(void)
+{
+	struct audit_data *data;
+
+	data = kmem_cache_alloc(audit_data_cache, GFP_KERNEL);
+	if (data) {
+		data->wentry = NULL;
+		atomic_set(&data->count, 1);
+		INIT_LIST_HEAD(&data->watchlist);
+		data->watchlist_lock = RW_LOCK_UNLOCKED;
+
+	}
+
+	return data;
+}
+
+/*
+ * NOTE:  Should only be called from a secure source
+ *
+ * Caller must hold i_audit->watchlist_lock
+ */
+static inline void audit_drain_watchlist(struct audit_data *data)
+{
+	struct audit_wentry *wentry, *tmp;
+
+	list_for_each_entry_safe(wentry, tmp, &data->watchlist, w_list)
+		audit_destroy_wentry(wentry);
+}
+
+static inline void audit_drain_watchlist_lock(struct audit_data *data)
+{
+	unsigned long flags;
+	struct audit_wentry *wentry, *tmp;
+
+	if (data) {
+		write_lock_irqsave(&data->watchlist_lock, flags);
+
+		list_for_each_entry_safe(wentry, tmp, &data->watchlist, w_list)
+			audit_destroy_wentry(wentry);
+
+		write_unlock_irqrestore(&data->watchlist_lock, flags);
+	}
+}
+
+static inline void audit_data_free(struct audit_data *data)
+{
+	if (data) {
+		audit_drain_watchlist_lock(data);
+
+		if (data->wentry) {
+			audit_wentry_put(data->wentry);
+			data->wentry = NULL;
+		}
+
+
+		kmem_cache_free(audit_data_cache, data);
+	}
+}
+
+static struct audit_data *audit_data_get(struct audit_data *data)
+{
+	if (data) {
+		BUG_ON(!atomic_read(&data->count));
+		atomic_inc(&data->count);
+	}
+
+	return data;
+}
+
+static void audit_data_put(struct audit_data *data)
+{
+	if (data && atomic_dec_and_test(&data->count))
+		audit_data_free(data);
+}
+
+static inline struct audit_watch *audit_watch_alloc(void)
+{
+	struct audit_watch *watch;
+
+	watch = kmem_cache_alloc(audit_watch_cache, GFP_KERNEL);
+	if (watch) {
+		watch->namelen = 0;
+		watch->fklen = 0;
+		watch->name = NULL;
+		watch->filterkey = NULL;
+		watch->perms = 0;
+	}
+
+	return watch;
+}
+
+static inline void audit_watch_free(struct audit_watch *watch)
+{
+	if (watch) {
+		kfree(watch->name);
+		kfree(watch->filterkey);
+		kmem_cache_free(audit_watch_cache, watch);
+	}
+}
+
+static struct audit_watch *audit_create_watch(const char *name,
+					      const char *filterkey,
+					      __u32 perms)
+{
+	struct audit_watch *err = NULL;
+	struct audit_watch *watch = NULL;
+
+	err = ERR_PTR(-EINVAL);
+
+	if (!name || strlen(name) + 1 > PATH_MAX)
+		goto audit_create_watch_fail;
+
+	if (filterkey && strlen(filterkey) + 1 > AUDIT_FILTERKEY_MAX)
+		goto audit_create_watch_fail;
+
+	if (perms > 15)
+		goto audit_create_watch_fail;
+
+	err = ERR_PTR(-ENOMEM);
+
+	watch = audit_watch_alloc();
+	if (watch) {
+		watch->namelen = strlen(name) + 1;
+		watch->name = kmalloc(watch->namelen, GFP_KERNEL);
+		if (!watch->name)
+			goto audit_create_watch_fail;
+		strcpy(watch->name, name);
+
+		if (filterkey) {
+			watch->fklen = strlen(filterkey) + 1;
+			watch->filterkey = kmalloc(watch->fklen, GFP_KERNEL);
+			if (!watch->filterkey)
+				goto audit_create_watch_fail;
+			strcpy(watch->filterkey, filterkey);
+		}
+
+		watch->perms = perms;
+
+		goto audit_create_watch_exit;
+	}
+
+
+audit_create_watch_fail:
+	audit_watch_free(watch);
+	watch = err;
+audit_create_watch_exit:
+	return watch;
+}
+
+/*
+ * First reference of audit_wentry is returned on allocation
+ */
+static inline struct audit_wentry *audit_wentry_alloc(void)
+{
+	struct audit_wentry *wentry;
+
+	wentry = kmem_cache_alloc(audit_wentry_cache, GFP_KERNEL);
+	if (wentry) {
+		atomic_set(&wentry->w_count, 1);
+		wentry->w_valid = 0;
+		wentry->w_watch = NULL;
+		wentry->w_data = NULL;
+	}
+
+	return wentry;
+}
+
+/*
+ * Because of the circular nature of the design, in order to arrive here,
+ * we may be getting called from within audit_data_free().  The only way
+ * this could happen is if there were no more references to inode->i_audit.
+ * Thus, we couldn't possibly put back the wentry's reference to w_data here.
+ */
+static inline void audit_wentry_free(struct audit_wentry *wentry)
+{
+	if (wentry) {
+		audit_watch_free(wentry->w_watch);
+		kmem_cache_free(audit_wentry_cache, wentry);
+	}
+}
+
+/*
+ * The wentry holds both the watch (w_watch) and the inode's i_audit memory
+ * (w_data).  The inode's memory contains a pointer to the wentry that holds
+ * it.  This produces a circular effect.
+ *
+ * We do this for the following reasons:
+ * 	1) the memory for an inode may not be allocated in our audit_watch()
+ *	   hook because there may be no reasonable way to handle an -ENOMEM.
+ *	2) there are cases when multiple inode's will refer to the same watch.
+ * 	3) the inode associated w/ "name" may or may not exist
+ *
+ */
+static int audit_create_wentry(const char *name,
+			       const char *filterkey,
+			       unsigned int perms, struct audit_data *data)
+{
+	int ret;
+	unsigned long flags;
+	struct audit_wentry *wentry = NULL;
+	struct audit_wentry *new_wentry = NULL;
+
+	ret = -ENOMEM;
+
+	new_wentry = audit_wentry_alloc();
+	if (!new_wentry)
+		goto audit_create_wentry_fail;
+
+	new_wentry->w_data = audit_data_alloc();
+	if (!new_wentry->w_data)
+		goto audit_create_wentry_fail;
+
+	new_wentry->w_watch = audit_create_watch(name, filterkey, perms);
+	if (IS_ERR(new_wentry->w_watch)) {
+		ret = PTR_ERR(new_wentry->w_watch);
+		new_wentry->w_watch = NULL;
+		goto audit_create_wentry_fail;
+	}
+
+	new_wentry->w_data->wentry = audit_wentry_get(new_wentry);
+
+	write_lock_irqsave(&data->watchlist_lock, flags);
+
+	wentry = audit_wentry_fetch(name, data);
+	if (!wentry) {
+		list_add(&new_wentry->w_list, &data->watchlist);
+		new_wentry->w_valid = 1;
+		write_unlock_irqrestore(&data->watchlist_lock, flags);
+		ret = 0;
+		goto audit_create_wentry_exit;
+	}
+	audit_wentry_put(wentry);
+
+	write_unlock_irqrestore(&data->watchlist_lock, flags);
+
+	ret = -EEXIST;
+
+audit_create_wentry_fail:
+	audit_data_put(new_wentry->w_data);
+	audit_wentry_put(new_wentry);
+	if (!data->wentry && list_empty(&data->watchlist))
+		audit_data_put(data);
+audit_create_wentry_exit:
+	return ret;
+}
+
+static int audit_insert_watch(struct audit_watch *req)
+{
+	int ret;
+	char *path = NULL;
+	char *filterkey = NULL;
+	struct nameidata nd;
+
+	path = getname(req->name);
+	if (IS_ERR(req->name)) {
+		ret = PTR_ERR(req->name);
+		goto audit_insert_watch_exit;
+	}
+
+	if (req->fklen) {
+		filterkey = kmalloc(req->fklen, GFP_KERNEL);
+		ret = -ENOMEM;
+		if (!filterkey)
+			goto audit_insert_watch_exit;
+
+		ret = strncpy_from_user(filterkey, req->filterkey, req->fklen);
+		if (ret < 0)
+			goto audit_insert_watch_exit;
+	}
+
+	ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_insert_watch_release;
+
+	ret = -EPERM;
+	if (nd.last_type != LAST_NORM)
+		goto audit_insert_watch_release;
+
+	if (!nd.dentry->d_inode->i_audit) {
+		nd.dentry->d_inode->i_audit = audit_data_alloc();
+		ret = -ENOMEM;
+		if (!nd.dentry->d_inode->i_audit)
+			goto audit_insert_watch_release;
+	}
+
+	ret = audit_create_wentry(nd.last.name, filterkey, req->perms,
+				  nd.dentry->d_inode->i_audit);
+
+audit_insert_watch_release:
+	path_release(&nd);
+audit_insert_watch_exit:
+	putname(path);
+	kfree(filterkey);
+	return ret;
+}
+
+static int audit_remove_watch(struct audit_watch *req)
+{
+	int ret;
+	unsigned long flags;
+	char *path = NULL;
+	struct nameidata nd;
+	struct audit_data *data;
+	struct audit_wentry *wentry;
+
+	path = getname(req->name);
+	if (IS_ERR(req->name)) {
+		ret = PTR_ERR(req->name);
+		goto audit_remove_watch_exit;
+	}
+
+	ret = -EINVAL;
+	if (!path || (path && strlen(path) > PATH_MAX))
+		goto audit_remove_watch_exit;
+
+	ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_remove_watch_release;
+
+	ret = -EPERM;
+	if (nd.last_type != LAST_NORM || !nd.last.name)
+		goto audit_remove_watch_release;
+
+	ret = -EACCES;
+
+	data = nd.dentry->d_inode->i_audit;
+	if (!data)
+		goto audit_remove_watch_release;
+
+	write_lock_irqsave(&data->watchlist_lock, flags);
+
+	wentry = audit_wentry_fetch(nd.last.name, data);
+	if (!wentry) {
+		write_unlock_irqrestore(&data->watchlist_lock, flags);
+		goto audit_remove_watch_release;
+	}
+	audit_destroy_wentry(wentry);
+	audit_wentry_put(wentry);
+
+	write_unlock_irqrestore(&data->watchlist_lock, flags);
+
+	if (!data->wentry && list_empty(&data->watchlist)) {
+		audit_data_put(data);
+		nd.dentry->d_inode->i_audit = NULL;
+	}
+
+	ret = 0;
+
+audit_remove_watch_release:
+	path_release(&nd);
+audit_remove_watch_exit:
+	putname(path);
+	return ret;
+}
+
+/* Public interface */
+
+struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry)
+{
+	if (wentry) {
+		BUG_ON(!atomic_read(&wentry->w_count));
+		atomic_inc(&wentry->w_count);
+	}
+
+	return wentry;
+}
+
+void audit_wentry_put(struct audit_wentry *wentry)
+{
+	if (wentry && atomic_dec_and_test(&wentry->w_count))
+		audit_wentry_free(wentry);
+}
+
+/*
+ * Hook appears in:
+ * fs/dcache.c:d_instantiate(), d_move(), d_lookup(), and d_splice_alias()
+ */
+void audit_watch(struct dentry *dentry, int drain)
+{
+	struct audit_wentry *wentry;
+	struct audit_data *data;
+
+	if (!dentry || !dentry->d_inode)
+		return;
+
+	if (!dentry->d_parent || !dentry->d_parent->d_inode)
+		return;
+
+	wentry = audit_wentry_fetch_lock(dentry->d_name.name,
+					 dentry->d_parent->d_inode->i_audit);
+
+	data = dentry->d_inode->i_audit;
+	if (data) {
+		if (drain)
+			audit_drain_watchlist_lock(data);
+		if (wentry && !list_empty(&data->watchlist)) {
+			audit_data_put(wentry->w_data);
+			wentry->w_data = audit_data_get(data);
+			audit_wentry_put(wentry->w_data->wentry);
+			wentry->w_data->wentry = audit_wentry_get(wentry);
+			/* Keep get/put's consistent, stealing is bad :( */
+			audit_data_put(data);
+			dentry->d_inode->i_audit =
+			    audit_data_get(wentry->w_data);
+		} else if (wentry && !data->wentry->w_valid) {
+			audit_data_put(data);
+			dentry->d_inode->i_audit =
+			    audit_data_get(wentry->w_data);
+		}
+	} else if (wentry)
+		dentry->d_inode->i_audit = audit_data_get(wentry->w_data);
+
+	audit_wentry_put(wentry);
+}
+
+void audit_receive_watch(int type, int pid, int uid, int seq,
+			 struct audit_watch *req)
+{
+	int err;
+
+	switch (type) {
+	case AUDIT_WATCH_INS:
+		err = audit_insert_watch(req);
+		break;
+	case AUDIT_WATCH_REM:
+		err = audit_remove_watch(req);
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+	audit_send_reply(pid, seq, type, 0, 1, &err, sizeof(int));
+}
+
+void audit_inode_alloc(struct inode *inode)
+{
+	if (inode)
+		inode->i_audit = NULL;
+}
+
+void audit_inode_free(struct inode *inode)
+{
+	if (inode && inode->i_audit) {
+
+		audit_data_put(inode->i_audit);
+		inode->i_audit = NULL;
+	}
+}
+
+void audit_filesystem_init()
+{
+	audit_watch_cache =
+	    kmem_cache_create("audit_watch_cache",
+			      sizeof(struct audit_watch), 0, 0, NULL, NULL);
+	audit_wentry_cache =
+	    kmem_cache_create("audit_wentry_cache",
+			      sizeof(struct audit_wentry), 0, 0, NULL, NULL);
+	audit_data_cache =
+	    kmem_cache_create("audit_data_cache",
+			      sizeof(struct audit_data), 0, 0, NULL, NULL);
+}
diff -Nurp linux-2.6.11/kernel/auditsc.c linux-2.6.11-audit/kernel/auditsc.c
--- linux-2.6.11/kernel/auditsc.c	2005-03-02 01:38:17.000000000 -0600
+++ linux-2.6.11-audit/kernel/auditsc.c	2005-03-08 11:48:34.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.
@@ -113,6 +114,10 @@ struct audit_context {
 	uid_t		    uid, euid, suid, fsuid;
 	gid_t		    gid, egid, sgid, fsgid;
 	unsigned long	    personality;
+	struct list_head    wtrail; /* The list of watched files/dirs that were
+				     * accessed and determined to be valid and
+				     * unfiltered in this audit_context
+				     */
 
 #if AUDIT_DEBUG
 	int		    put_count;
@@ -134,6 +139,22 @@ struct audit_entry {
 	struct audit_rule rule;
 };
 
+/* The structure that stores information about files/directories being
+ * watched in the filesystem, that the syscall accessed.
+ */
+
+struct audit_file {
+	struct audit_wentry *wentry;
+	struct list_head list;
+	unsigned long ino;
+	umode_t mode;
+	uid_t uid;
+	gid_t gid;
+	dev_t dev;
+	dev_t rdev;
+	int mask;
+};
+
 /* Check to see if two rules are identical.  It is called from
  * audit_del_rule during AUDIT_DEL. */
 static int audit_compare_rule(struct audit_rule *a, struct audit_rule *b)
@@ -504,6 +525,37 @@ static inline void audit_free_names(stru
 	context->name_count = 0;
 }
 
+static struct audit_file *audit_file_alloc(void)
+{
+	struct audit_file *file;
+
+	file = kmalloc(sizeof(struct audit_file), GFP_KERNEL);
+
+	if (file)
+		file->wentry = NULL;
+
+	return file;
+}
+
+static void audit_file_free(struct audit_file *file)
+{
+	if (file) {
+		audit_wentry_put(file->wentry);
+		file->wentry = NULL;
+		kfree(file);
+	}
+}
+
+static inline void audit_free_files(struct audit_context *context)
+{
+	struct audit_file *file, *tmp;
+
+	list_for_each_entry_safe(file, tmp, &context->wtrail, list) {
+	        list_del(&file->list);
+		audit_file_free(file);
+	}
+}
+
 static inline void audit_zero_context(struct audit_context *context,
 				      enum audit_state state)
 {
@@ -512,6 +564,7 @@ static inline void audit_zero_context(st
 	memset(context, 0, sizeof(*context));
 	context->state      = state;
 	context->loginuid   = loginuid;
+	INIT_LIST_HEAD(&context->wtrail);
 }
 
 static inline struct audit_context *audit_alloc_context(enum audit_state state)
@@ -570,6 +623,7 @@ static inline void audit_free_context(st
 			       context->name_count, count);
 		}
 		audit_free_names(context);
+		audit_free_files(context);
 		kfree(context);
 		context  = previous;
 	} while (context);
@@ -580,6 +634,7 @@ static inline void audit_free_context(st
 static void audit_log_exit(struct audit_context *context)
 {
 	int i;
+	struct audit_file *file;
 	struct audit_buffer *ab;
 
 	ab = audit_log_start(context);
@@ -626,6 +681,23 @@ static void audit_log_exit(struct audit_
 					 MINOR(context->names[i].rdev));
 		audit_log_end(ab);
 	}
+	list_for_each_entry(file, &context->wtrail, list) {
+		ab = audit_log_start(context);
+		if (!ab)
+			continue;
+
+		audit_log_format(ab,
+			"name=%s filter_key=%s perm=%u perm_mask=%d "
+			"inode=%lu inode_mode=%d inode_uid=%d inode_gid=%d "
+			"inode_dev=%02x:%02x inode_rdev=%02x:%02x",
+			file->wentry->w_watch->name,
+			file->wentry->w_watch->filterkey,
+			file->wentry->w_watch->perms,
+			file->mask, file->ino, file->mode, file->uid,
+			file->gid, MAJOR(file->dev), MINOR(file->dev),
+			MAJOR(file->rdev), MINOR(file->rdev));
+		audit_log_end(ab);
+	}
 }
 
 /* Free a per-task audit context.  Called from copy_process and
@@ -789,6 +861,7 @@ void audit_syscall_exit(struct task_stru
 		tsk->audit_context = new_context;
 	} else {
 		audit_free_names(context);
+		audit_free_files(context);
 		audit_zero_context(context, context->state);
 		tsk->audit_context = context;
 	}
@@ -927,3 +1000,56 @@ uid_t audit_get_loginuid(struct audit_co
 {
 	return ctx ? ctx->loginuid : -1;
 }
+
+/* If file/dir has an audit_context and has filesystem auditing
+ * turned on, then if this inode is being watched, collect info
+ * about it.
+ *
+ * Hook appears in:
+ * fs/namie.c:permission(), exec_permission_lite(), vfs_unlink/rmdir
+ *
+ */
+
+int audit_notify_watch(struct inode *inode, int mask)
+{
+	int ret = 0;
+	struct audit_context *context;
+	struct audit_file *file;
+
+	context = current->audit_context;
+
+	if (!context || !context->in_syscall)
+		goto audit_notify_watch_exit;
+
+	if (!inode || !inode->i_audit)
+		goto audit_notify_watch_exit;
+
+	if (!inode->i_audit->wentry || !inode->i_audit->wentry->w_valid)
+		goto audit_notify_watch_exit;
+
+	if (!mask || (inode->i_audit->wentry->w_watch->perms &&
+	    !(inode->i_audit->wentry->w_watch->perms & mask))) {
+	    	audit_free_files(context);
+		goto audit_notify_watch_exit;
+	}
+
+	file = audit_file_alloc();
+	if (!file) {
+		ret = -ENOMEM;
+		goto audit_notify_watch_exit;
+	}
+
+	file->wentry = audit_wentry_get(inode->i_audit->wentry);
+	file->ino = inode->i_ino;
+	file->uid = inode->i_uid;
+	file->gid = inode->i_gid;
+	file->dev = inode->i_sb->s_dev;
+	file->rdev = inode->i_rdev;
+	file->mask = mask;
+
+	list_add(&file->list, &context->wtrail);
+
+	audit_notify_watch_exit:
+	return ret;
+}
+

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