[RFC][PATCH 1/3] CAPP-compliant file system auditing

Timothy R. Chavez tinytim at us.ibm.com
Thu Mar 31 18:17:52 UTC 2005


.:: Overview ::.

Due to the subjective nature of "name", the rules for auditing a file system 
object are fairly strict.  In terms of CAPP, the "name" is any identifier 
that a user may specify to access an object in some fashion.

In user space, the identifier will be specified as a path name in which the 
last component is the filename of the object we want to audit 
(ie: /etc/shadow, ../shadow, shadow).  Once translated into the kernel, the 
path is walked to the parent directory of the last component (nd.last.name) 
and is added to the "watchlist" there.  Each entry in the "watchlist" 
describes a "watch point", by name, directly beneath the parent.

If an object should manifest at a "watch point", it will search its parent's 
"watchlist" for the correct entry.  Once found, d_inode will point into the 
"watchlist" at it (except for a hardlink being implicitly "watched").  This 
effectively means that the object is being "watched" and "watched" objects 
are auditable objects (from any namespace too).  If the object either leaves 
this "watch point" or the "watch point" is removed, it will lose this 
"watch".

I suspect there will be questions framed around specific parts of this design 
and I will address them as they come.  However, please keep in mind that we 
are not auditing based on content, but "name".

This is _not_ a general purpose file system auditing solution.

--

PART 1
This patch was diffed against linux-2.6.11.5 and introduces the new 
functionality to the kernel's audit subsystem.  

diff -Nurp linux-2.6.11.5/include/linux/audit.h 
linux-2.6.11.5~auditfs/include/linux/audit.h
--- linux-2.6.11.5/include/linux/audit.h	2005-03-19 00:34:57.000000000 -0600
+++ linux-2.6.11.5~auditfs/include/linux/audit.h	2005-03-31 11:52:46.000000000 
-0600
@@ -24,15 +24,24 @@
 #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_WATCH_LIST	1009 /* List all watches */
+#define AUDIT_KERNEL		2000 /* Asynchronous record. NOT A REQUEST. */
 
 /* Rule flags */
 #define AUDIT_PER_TASK 0x01	/* Apply rule at task creation (not syscall) */
@@ -96,6 +105,9 @@
 #define AUDIT_FAIL_PRINTK	1
 #define AUDIT_FAIL_PANIC	2
 
+/* 32 byte max key size */
+#define AUDIT_FILTERKEY_MAX	32
+
 #ifndef __KERNEL__
 struct audit_message {
 	struct nlmsghdr nlh;
@@ -123,8 +135,28 @@ struct audit_rule {		/* for AUDIT_LIST, 
 	__u32		values[AUDIT_MAX_FIELDS];
 };
 
+struct audit_watch {
+	__u32	namelen;
+	__u32 	fklen;
+	char	*name;
+	char	*filterkey;
+	__u32	perms;
+};
+
 #ifdef __KERNEL__
 
+struct audit_data {
+	struct audit_wentry	*wentry;
+	struct hlist_head 	watchlist;
+	rwlock_t		lock;
+};
+
+struct audit_wentry {
+	struct hlist_node 	w_node;
+	struct audit_watch	*w_watch;
+	atomic_t		w_count;
+};
+
 #ifdef CONFIG_AUDIT
 struct audit_buffer;
 struct audit_context;
@@ -150,6 +182,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,6 +192,28 @@ extern uid_t audit_get_loginuid(struct a
 #define audit_putname(n) do { ; } while (0)
 #define audit_inode(n,i,d) do { ; } while (0)
 #define audit_get_loginuid(c) ({ -1; })
+#define audit_notify_watch(i,m) ({ 0; })
+#endif
+
+#ifdef CONFIG_AUDITFILESYSTEM
+extern int audit_list_watches(int type, int pid, int uid, int seq, char 
*path); 
+extern int audit_receive_watch(int type, int pid, int uid, int seq, 
+			       struct audit_watch *req);
+extern int audit_filesystem_init(void);
+extern int audit_inode_alloc(struct inode *inode);
+extern void audit_inode_free(struct inode *inode);
+extern void audit_attach_watch(struct dentry *dentry, int remove);
+extern void audit_wentry_put(struct audit_wentry *wentry);
+extern struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry);
+#else
+#define audit_list_watches(t,p,u,s,r) ({ -EOPNOTSUPP; })
+#define audit_receive_watch(t,p,u,s,r) ({ -EOPNOTSUPP; })
+#define audit_filesystem_init() ({ 0; })
+#define audit_inode_alloc(i) ({ 0; })
+#define audit_inode_free(i) do { ; } while(0)
+#define audit_attach_watch(d,r) do { ; } while (0)
+#define audit_wentry_put(w) do { ; } while(0)
+#define audit_wentry_get(w) ({ 0; })
 #endif
 
 #ifdef CONFIG_AUDIT
@@ -175,6 +230,10 @@ extern void		    audit_log_format(struct
 extern void		    audit_log_end(struct audit_buffer *ab);
 extern void		    audit_log_end_fast(struct audit_buffer *ab);
 extern void		    audit_log_end_irq(struct audit_buffer *ab);
+extern void		    audit_log_hex(struct audit_buffer *ab, const unsigned char 
*buf,
+					  size_t len);
+extern void		    audit_log_untrustedstring(struct audit_buffer *ab,
+						      const char *string);
 extern void		    audit_log_d_path(struct audit_buffer *ab,
 					     const char *prefix,
 					     struct dentry *dentry,
@@ -197,6 +256,8 @@ extern void		    audit_log_lost(const ch
 #define audit_log_end(b) do { ; } while (0)
 #define audit_log_end_fast(b) do { ; } while (0)
 #define audit_log_end_irq(b) do { ; } while (0)
+#define audit_log_hex(a,b,l) do { ; } while (0)
+#define audit_log_untrustedstring(a,s) do { ; } while (0)
 #define audit_log_d_path(b,p,d,v) do { ; } while (0)
 #define audit_set_rate_limit(l) do { ; } while (0)
 #define audit_set_backlog_limit(l) do { ; } while (0)
diff -Nurp linux-2.6.11.5/include/linux/fs.h 
linux-2.6.11.5~auditfs/include/linux/fs.h
--- linux-2.6.11.5/include/linux/fs.h	2005-03-19 00:34:53.000000000 -0600
+++ linux-2.6.11.5~auditfs/include/linux/fs.h	2005-03-31 11:38:03.000000000 
-0600
@@ -477,6 +477,7 @@ struct inode {
 	unsigned int		i_flags;
 
 	atomic_t		i_writecount;
+	struct audit_data	*i_audit;
 	void			*i_security;
 	union {
 		void		*generic_ip;
diff -Nurp linux-2.6.11.5/init/Kconfig linux-2.6.11.5~auditfs/init/Kconfig
--- linux-2.6.11.5/init/Kconfig	2005-03-31 11:35:27.000000000 -0600
+++ linux-2.6.11.5~auditfs/init/Kconfig	2005-03-31 11:36:33.000000000 -0600
@@ -174,6 +174,16 @@ config AUDITSYSCALL
 	  can be used independently or with another kernel subsystem,
 	  such as SELinux.
 
+config AUDITFILESYSTEM
+	bool "Enable file system auditing support"
+	depends on AUDITSYSCALL
+	default n
+	help
+	  Enable file system auditing for regular files and directories.
+	  When a targeted file or directory is accessed, an audit record
+	  is generated describing the inode accessed, how it was accessed,
+	  and by whom (ie: pid and system call).
+
 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.5/kernel/Makefile 
linux-2.6.11.5~auditfs/kernel/Makefile
--- linux-2.6.11.5/kernel/Makefile	2005-03-19 00:34:53.000000000 -0600
+++ linux-2.6.11.5~auditfs/kernel/Makefile	2005-03-31 11:37:26.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.5/kernel/audit.c linux-2.6.11.5~auditfs/kernel/audit.c
--- linux-2.6.11.5/kernel/audit.c	2005-03-19 00:35:05.000000000 -0600
+++ linux-2.6.11.5~auditfs/kernel/audit.c	2005-03-31 11:30:54.000000000 -0600
@@ -20,6 +20,7 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  * Written by Rickard E. (Rik) Faith <faith at redhat.com>
+ * Modified by Timothy R. Chavez <chavezt at us.ibm.com>
  *
  * Goals: 1) Integrate fully with SELinux.
  *	  2) Minimal run-time overhead:
@@ -319,6 +320,9 @@ 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:
+	case AUDIT_WATCH_LIST:
 		if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
 			err = -EPERM;
 		break;
@@ -413,6 +417,18 @@ static int audit_receive_msg(struct sk_b
 		err = -EOPNOTSUPP;
 #endif
 		break;
+	case AUDIT_WATCH_INS:
+	case AUDIT_WATCH_REM:
+		if (nlh->nlmsg_len < sizeof(struct audit_watch))
+			return -EINVAL;
+		err = audit_receive_watch(nlh->nlmsg_type,
+					  NETLINK_CB(skb).pid,
+					  uid, seq, data);
+		break;
+	case AUDIT_WATCH_LIST:
+		err = audit_list_watches(nlh->nlmsg_type,
+					 NETLINK_CB(skb).pid,
+					 uid, seq, data);
 	default:
 		err = -EINVAL;
 		break;
@@ -557,6 +573,7 @@ int __init audit_init(void)
 
 	audit_initialized = 1;
 	audit_enabled = audit_default;
+	audit_filesystem_init();
 	audit_log(NULL, "initialized");
 	return 0;
 }
@@ -717,6 +734,28 @@ void audit_log_format(struct audit_buffe
 	va_end(args);
 }
 
+void audit_log_hex(struct audit_buffer *ab, const unsigned char *buf, size_t 
len)
+{
+	int i;
+
+	for (i=0; i<len; i++)
+		audit_log_format(ab, "%02x", buf[i]);
+}
+
+void audit_log_untrustedstring(struct audit_buffer *ab, const char *string)
+{
+	const unsigned char *p = string;
+
+	while (*p) {
+		if (*p == '"' || *p == ' ' || *p < 0x20 || *p > 0x7f) {
+			audit_log_hex(ab, string, strlen(string));
+			return;
+		}
+		p++;
+	}
+	audit_log_format(ab, "\"%s\"", string);
+}
+
 /* This is a helper-function to print the d_path without using a static
  * buffer or allocating another buffer in addition to the one in
  * audit_buffer. */
diff -Nurp linux-2.6.11.5/kernel/auditfs.c 
linux-2.6.11.5~auditfs/kernel/auditfs.c
--- linux-2.6.11.5/kernel/auditfs.c	1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.11.5~auditfs/kernel/auditfs.c	2005-03-31 11:30:54.000000000 
-0600
@@ -0,0 +1,511 @@
+/* auditfs.c -- Filesystem auditing support -*- linux-c -*-
+ * Implements filesystem auditing support, depends on kernel/auditsc.c
+ *
+ * Copyright 2005 International Business Machines Corp. (IBM)
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307  USA
+ *
+ * Written by Timothy R. Chavez <chavezt at us.ibm.com>
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/audit.h>
+#include <asm/uaccess.h>
+
+kmem_cache_t *audit_watch_cache;
+kmem_cache_t *audit_wentry_cache;
+
+/* Private Interface */
+
+static inline struct audit_wentry *audit_wentry_fetch(const char *name,
+						      struct audit_data *data)
+{
+	struct audit_wentry *wentry, *ret = NULL;
+	struct hlist_node *pos;
+
+	hlist_for_each_entry(wentry, pos, &data->watchlist, w_node)
+		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)
+{
+	struct audit_wentry *ret = NULL;
+
+	if (name && data) {
+		read_lock(&data->lock);
+		ret = audit_wentry_fetch(name, data);
+		read_unlock(&data->lock);
+	}
+
+	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->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(-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;
+}
+
+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_watch = 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 inline void audit_destroy_wentry(struct audit_wentry *wentry)
+{
+	if (wentry) {
+		hlist_del_init(&wentry->w_node);
+		audit_wentry_put(wentry);
+	}
+}
+
+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);
+}
+
+/* The only time the new wentry gets updated is when it is inaccessible
+ * (out of the list).
+ */
+static int audit_create_wentry(const char *name,
+			       const char *filterkey,
+			       __u32 perms, struct audit_data *data)
+{
+	int ret;
+	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_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;
+	}
+
+	ret = 0;
+	write_lock(&data->lock);
+	wentry = audit_wentry_fetch(name, data);
+	if (!wentry) {
+		hlist_add_head(&new_wentry->w_node, &data->watchlist);
+		write_unlock(&data->lock);
+		goto audit_create_wentry_exit;
+	}
+	audit_wentry_put(wentry);
+	write_unlock(&data->lock);
+
+	ret = -EEXIST;
+
+audit_create_wentry_fail:
+	audit_wentry_put(new_wentry);
+audit_create_wentry_exit:
+	return ret;
+}
+
+/* Caller must hold data->lock */
+
+static inline void audit_drain_watchlist(struct audit_data *data)
+{
+	struct audit_wentry *wentry;
+	struct hlist_node *pos, *buf;
+
+	hlist_for_each_entry_safe(wentry, pos, buf, &data->watchlist, w_node)
+		audit_destroy_wentry(wentry);
+}
+
+static inline struct audit_data *audit_data_alloc(void)
+{
+	struct audit_data *data;
+
+	data = kmalloc(sizeof(struct audit_data), GFP_KERNEL);
+	if (data) {
+		data->wentry = NULL;
+		INIT_HLIST_HEAD(&data->watchlist);
+		data->lock = RW_LOCK_UNLOCKED;
+	}
+
+	return data;
+}
+
+static inline void audit_data_free(struct audit_data *data)
+{
+	if (data) {
+		write_lock(&data->lock);
+		audit_drain_watchlist(data);
+		audit_wentry_put(data->wentry);
+		write_unlock(&data->lock);
+		kfree(data);
+	}
+}
+
+static inline int audit_insert_watch(char *path, char *filterkey, __u32 
perms)
+{
+	int ret;
+	struct nameidata nd;
+
+	ret = path_lookup(path, LOOKUP_PARENT|LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_insert_watch_exit;
+	
+	ret = -EPERM;
+	if (nd.last_type != LAST_NORM || !nd.last.name)
+		goto audit_insert_watch_release;
+
+	ret = audit_create_wentry(nd.last.name, filterkey, perms,
+				  nd.dentry->d_inode->i_audit);
+	
+audit_insert_watch_release:
+	path_release(&nd);
+audit_insert_watch_exit:
+	return ret;
+}
+
+static inline int audit_remove_watch(char *path)
+{
+	int ret;
+	struct nameidata nd;
+	struct audit_data *data;
+	struct audit_wentry *wentry;
+
+	ret = path_lookup(path, LOOKUP_PARENT|LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_remove_watch_exit;
+
+	ret = -EPERM;
+	if (nd.last_type != LAST_NORM || !nd.last.name)
+		goto audit_remove_watch_release;
+
+	data = nd.dentry->d_inode->i_audit;
+
+	write_lock(&data->lock);
+	wentry = audit_wentry_fetch(nd.last.name, data);
+	if (!wentry) {
+		write_unlock(&data->lock);
+		goto audit_remove_watch_release;
+	}
+	audit_destroy_wentry(wentry);
+	audit_wentry_put(wentry);
+	write_unlock(&data->lock);
+
+	ret = 0;
+
+audit_remove_watch_release:
+	path_release(&nd);
+audit_remove_watch_exit:
+	return ret;
+}
+
+/* Eventually we'll want to have serialization routines to send audit_watch
+ * structures to and from the kernel.  For the time being, we send back a
+ * watch in a formatted string to the userspace.
+ */
+static inline char *audit_watch_to_string(struct audit_watch *watch) 
+{
+	int buflen;
+	char *buf = NULL;;
+
+	if (!watch)
+		goto audit_watch_to_string_exit;
+
+	buflen = watch->namelen + (watch->fklen ? watch->fklen : 6) + 22;
+	buf = kmalloc(buflen, GFP_KERNEL);
+	if (!buf)
+		goto audit_watch_to_string_exit;
+	
+	snprintf(buf, buflen, "%s(filterkey=%s, perms=%d)", 
+		  watch->name, watch->filterkey, watch->perms);
+	
+audit_watch_to_string_exit:
+	return buf;
+}
+
+/*
+ * Hook appears in fs/dcache.c:
+ *	d_move(),
+ * 	d_delete(),
+ *	d_instantiate(),
+ *	d_splice_alias()
+ *	__d_lookup()
+ */
+void audit_attach_watch(struct dentry *dentry, int remove)
+{
+	struct audit_wentry *wentry;
+	struct audit_data *data, *parent;
+
+	if (!dentry || !dentry->d_inode)
+		return;
+
+	if (!dentry->d_parent || !dentry->d_parent->d_inode)
+		return;
+
+	data = dentry->d_inode->i_audit;
+	parent = dentry->d_parent->d_inode->i_audit;
+
+	wentry = audit_wentry_fetch_lock(dentry->d_name.name, parent);
+	
+	write_lock(&data->lock);
+	/* FIXME: long watchlist == too much spinning? */
+	if (remove) {
+		audit_drain_watchlist(data);
+		if (wentry && data->wentry) {
+			if (!strcmp(wentry->w_watch->name,
+				    data->wentry->w_watch->name)) {
+				audit_wentry_put(data->wentry);
+				dentry->d_inode->i_audit->wentry = NULL;
+			}
+		}
+	} else if (!data->wentry || hlist_unhashed(&data->wentry->w_node)) {
+		audit_wentry_put(data->wentry);
+		dentry->d_inode->i_audit->wentry = audit_wentry_get(wentry);
+	}
+	audit_wentry_put(wentry);
+	write_unlock(&data->lock);
+}
+
+int audit_list_watches(int type, int pid, int uid, int seq, char *path)
+{
+	int ret;
+	char *watch = NULL;
+	struct nameidata nd;
+	struct hlist_node *pos;
+	struct audit_data *data;
+	struct audit_wentry *wentry;
+
+	ret = path_lookup(path, LOOKUP_FOLLOW, &nd);
+	if (ret < 0)
+		goto audit_list_watches_exit;
+
+	ret = -ENOMEM;
+	data = nd.dentry->d_inode->i_audit;
+	read_lock(&data->lock);
+	hlist_for_each_entry(wentry, pos, &data->watchlist, w_node) {
+		audit_wentry_get(wentry);
+		watch = audit_watch_to_string(wentry->w_watch);
+		if (!watch)
+			goto audit_list_watches_release;
+		audit_send_reply(pid, seq, AUDIT_WATCH_LIST, 0, 1, 
+				 watch, strlen(watch) + 1);
+		kfree(watch);
+		audit_wentry_put(wentry);
+	}
+	read_unlock(&data->lock);
+
+	audit_send_reply(pid, seq, AUDIT_WATCH_LIST, 1, 1, NULL, 0);
+	ret = 0;
+
+audit_list_watches_release:	
+	path_release(&nd);	
+audit_list_watches_exit:
+	return ret;
+}
+
+int audit_receive_watch(int type, int pid, int uid, int seq,
+			struct audit_watch *req)
+{
+	int ret = 0;
+	char *path = NULL;
+	char *filterkey = NULL;
+	
+	ret = -EINVAL;
+	path = getname(req->name);
+	if (IS_ERR(path)) {
+		ret = PTR_ERR(path);
+		goto audit_receive_watch_exit;
+	}
+
+	if (!path || strlen(path) + 1 > PATH_MAX)
+		goto audit_receive_watch_exit;
+
+	/* Includes terminating '\0' */
+	if (req->fklen > AUDIT_FILTERKEY_MAX)
+		goto audit_receive_watch_exit;
+
+	if (req->perms > (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND))
+		goto audit_receive_watch_exit;
+
+	if (req->fklen) {
+		ret = -ENOMEM;
+		filterkey = kmalloc(req->fklen, GFP_KERNEL);
+		if (!filterkey)
+			goto audit_receive_watch_exit;
+
+		ret = strncpy_from_user(filterkey, req->filterkey, req->fklen);
+		if (ret < 0)
+			goto audit_receive_watch_exit;
+	}
+	
+	switch (type) {
+	case AUDIT_WATCH_INS:
+		ret = audit_insert_watch(path, filterkey, req->perms);
+		break;
+	case AUDIT_WATCH_REM:
+		ret = audit_remove_watch(path);
+		break;
+	default:
+		ret = -EINVAL;
+	}	
+	
+audit_receive_watch_exit:
+	putname(path);
+	kfree(filterkey);
+	return ret;
+}
+
+int audit_inode_alloc(struct inode *inode)
+{
+	if (inode) {
+		inode->i_audit = audit_data_alloc();
+		if (!inode->i_audit)
+			return ENOMEM;
+	}
+
+	return 0;
+}
+
+void audit_inode_free(struct inode *inode)
+{
+	if (inode)
+		audit_data_free(inode->i_audit);
+}
+
+int audit_filesystem_init()
+{
+	int ret = 0;
+
+	audit_watch_cache =
+	    kmem_cache_create("audit_watch_cache",
+			      sizeof(struct audit_watch), 0, 0, NULL, NULL);
+	if (!audit_watch_cache)
+		goto audit_filesystem_init_fail;
+
+	audit_wentry_cache =
+	    kmem_cache_create("audit_wentry_cache",
+			      sizeof(struct audit_wentry), 0, 0, NULL, NULL);
+	if (!audit_wentry_cache)
+		goto audit_filesystem_init_fail;
+
+	goto audit_filesystem_init_exit;
+
+audit_filesystem_init_fail:
+	ret = -ENOMEM;
+	kmem_cache_destroy(audit_watch_cache);
+	kmem_cache_destroy(audit_wentry_cache);
+audit_filesystem_init_exit:
+	return ret;
+
+}
diff -Nurp linux-2.6.11.5/kernel/auditsc.c 
linux-2.6.11.5~auditfs/kernel/auditsc.c
--- linux-2.6.11.5/kernel/auditsc.c	2005-03-19 00:35:01.000000000 -0600
+++ linux-2.6.11.5~auditfs/kernel/auditsc.c	2005-03-31 11:30:54.000000000 
-0600
@@ -92,6 +92,24 @@ struct audit_names {
 	dev_t		rdev;
 };
 
+struct audit_aux_data {
+	struct audit_aux_data	*next;
+	int			type;
+};
+
+#define AUDIT_AUX_WATCH		1
+
+struct audit_aux_data_watched {
+	struct audit_aux_data	link;
+	struct audit_wentry 	*wentry;
+	unsigned long		ino;
+	int			mask;
+	uid_t			uid;
+	gid_t			gid;
+	dev_t			dev;
+	dev_t			rdev;
+};
+
 /* The per-task audit context. */
 struct audit_context {
 	int		    in_syscall;	/* 1 if task is in a syscall */
@@ -108,6 +126,7 @@ struct audit_context {
 	struct audit_names  names[AUDIT_NAMES];
 	struct audit_context *previous; /* For nested syscalls */
 
+	struct audit_aux_data *aux;
 				/* Save things to print about task_struct */
 	pid_t		    pid;
 	uid_t		    uid, euid, suid, fsuid;
@@ -134,6 +153,16 @@ struct audit_entry {
 	struct audit_rule rule;
 };
 
+static inline void audit_free_aux(struct audit_context *context)
+{
+	struct audit_aux_data *aux;
+
+	while ((aux = context->aux)) {
+		context->aux = aux->next;
+		kfree(aux);
+	}
+}
+
 /* 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)
@@ -570,6 +599,7 @@ static inline void audit_free_context(st
 			       context->name_count, count);
 		}
 		audit_free_names(context);
+		audit_free_aux(context);
 		kfree(context);
 		context  = previous;
 	} while (context);
@@ -607,14 +637,49 @@ static void audit_log_exit(struct audit_
 		  context->euid, context->suid, context->fsuid,
 		  context->egid, context->sgid, context->fsgid);
 	audit_log_end(ab);
+
+	while (context->aux) {
+		struct audit_aux_data *aux;
+
+		ab = audit_log_start(context);
+		if (!ab)
+			continue; /* audit_panic has been called */
+
+		aux = context->aux;
+		context->aux = aux->next;
+
+		audit_log_format(ab, "auxitem=%d", aux->type);
+		switch (aux->type) {
+		case AUDIT_AUX_WATCH: {
+			struct audit_aux_data_watched *axi = (void *)aux;
+			audit_log_format(ab, " name=");
+        		audit_log_untrustedstring(ab, axi->wentry->w_watch->name);
+        		audit_log_format(ab,
+                         	" filterkey=%s perm=%u perm_mask=%d"
+				" inode=%lu inode_uid=%d inode_gid=%d"
+				" inode_dev=%02x:%02x inode_rdev=%02x:%02x",
+                         	axi->wentry->w_watch->filterkey, 
+				axi->wentry->w_watch->perms, 
+				axi->mask, axi->ino, axi->uid, axi->gid,
+                        	MAJOR(axi->dev), MINOR(axi->dev),
+                         	MAJOR(axi->rdev), MINOR(axi->rdev));
+				audit_wentry_put(axi->wentry);
+		}
+		}
+		audit_log_end(ab);
+		kfree(aux);
+	}
+
 	for (i = 0; i < context->name_count; i++) {
 		ab = audit_log_start(context);
 		if (!ab)
 			continue; /* audit_panic has been called */
 		audit_log_format(ab, "item=%d", i);
-		if (context->names[i].name)
-			audit_log_format(ab, " name=%s",
-					 context->names[i].name);
+		if (context->names[i].name) {
+			audit_log_format(ab, " name=");
+			audit_log_untrustedstring(ab, context->names[i].name);
+		}
+
 		if (context->names[i].ino != (unsigned long)-1)
 			audit_log_format(ab, " inode=%lu",
 					 context->names[i].ino);
@@ -789,6 +854,7 @@ void audit_syscall_exit(struct task_stru
 		tsk->audit_context = new_context;
 	} else {
 		audit_free_names(context);
+		audit_free_aux(context);
 		audit_zero_context(context, context->state);
 		tsk->audit_context = context;
 	}
@@ -927,3 +993,52 @@ uid_t audit_get_loginuid(struct audit_co
 {
 	return ctx ? ctx->loginuid : -1;
 }
+
+int audit_notify_watch(struct inode *inode, int mask)
+{
+	int ret = 0;
+	struct audit_context *context = current->audit_context;
+	struct audit_aux_data_watched *ax;
+	struct audit_wentry *wentry = NULL;
+
+	if (likely(!context))
+		goto audit_notify_watch_fail;
+	
+	if (!inode)
+		goto audit_notify_watch_fail;
+
+	wentry = audit_wentry_get(inode->i_audit->wentry);
+	if (!wentry)
+		goto audit_notify_watch_fail;
+
+	if (mask && (wentry->w_watch->perms && !(wentry->w_watch->perms&mask)))
+	goto audit_notify_watch_fail;
+
+	ret = -ENOMEM;
+	ax = kmalloc(sizeof(*ax), GFP_KERNEL);
+	if (!ax)
+		goto audit_notify_watch_fail;
+
+	ret = 0;	
+	if (context->in_syscall && !context->auditable) 
+		context->auditable = 1;
+	
+	ax->wentry = wentry; 
+	ax->mask = mask;
+	ax->ino = inode->i_ino;
+	ax->uid = inode->i_uid;
+	ax->gid = inode->i_gid;
+	ax->dev = inode->i_sb->s_dev;
+	ax->rdev = inode->i_rdev;
+
+	ax->link.type = AUDIT_AUX_WATCH;
+	ax->link.next = context->aux;
+	context->aux = (void *)ax;
+
+	goto audit_notify_watch_exit;
+
+audit_notify_watch_fail:
+	audit_wentry_put(wentry);
+audit_notify_watch_exit:
+	return ret;
+}	
diff -Nurp linux-2.6.11.5/security/selinux/nlmsgtab.c 
linux-2.6.11.5~auditfs/security/selinux/nlmsgtab.c
--- linux-2.6.11.5/security/selinux/nlmsgtab.c	2005-03-19 00:35:01.000000000 
-0600
+++ linux-2.6.11.5~auditfs/security/selinux/nlmsgtab.c	2005-03-31 
11:36:52.000000000 -0600
@@ -98,6 +98,8 @@ static struct nlmsg_perm nlmsg_audit_per
 	{ AUDIT_DEL,		NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
 	{ AUDIT_USER,		NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
 	{ AUDIT_LOGIN,		NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
+	{ AUDIT_WATCH_INS,	NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
+	{ AUDIT_WATCH_REM,	NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
 };
 
 
-tim




More information about the Linux-audit mailing list