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

[RFC][PATCH 1/2] (#6 U2) filesystem auditing



Hello,

Here is the patch that implements the filesystem auditing component of the 
audit subsystem.  For this list, attached as a file /w CHANGELOG.  Patched 
against linux-2.6.11.5 -- Please note, this is untested in SMP (sorry 
Stephen, haven't had the time; will do tomorrow).

CHANGELOG

+ Changed naming convention (most notably, removed my initials)
  -> linux-2.6.11.5-auditfs-tc1-6-2.patch to linux-2.6.11.5-auditfs.6.2.patch
+ Unconditionally allocate inode->i_audit, initialize to NULL
  -> Hook (1) in inode_alloc() : audit_inode_alloc()
  -> Hook (1) in destroy_inode() : audit_inode_free()
+ Additional placement of audit_notify_watch() hooks
  -> Hook (2) open_namei() : catch on succ/fail paths
  -> Hook (1) vfs_create() : catch post create
  -> Hook (1) vfs_mkdir()  : catch post mkdir
  -> Hook (1) vfs_mknod()  : catch post mknod 
  -> Hook (1) vfs_symlink(): catch post symlink
  -> Hook (1) vfs_link()   : catch post link
  -> Hook (1) vfs_rename_dir : post rename
  -> Hook (1) vfs_rename_other : post rename
+ Modified placement of audit_attach_watch() hooks
  -> Hook (1) d_move() : catch post move to attach watch
+ Added support for audit_context.aux (but did not add IPC portion)
+ Shifted audit_notify_watch() back to kernel/auditsc.c
  -> Store accessed audit_watch information on audit_context.aux to dump at
     audit_log_exit(), rather then instant delivery to user space.
+ Changed audit_watch *req field of audit_receive_watch() to void * 
+ Shifted sanity checking / copying of watch data from userspace back into
  audit_watch_insert() and audit_watch_remove()
+ Added AUDIT_WATCH_LST support
  -> Added to switch statement in audit_receive_msg(), audit_netlink_ok(), 
     and audit_receive_watch()
+ Added audit_watch_to_string() to convert watch to string
+ Added audit_list_watches() to send watches on a given directory back to user
  space in the form of strings
+ Added AUDIT_WATCH_ERR support - generic channel to send messages about watch
  actions to userspace; convert audit_receive_watch() to use this
+ Moved placement of locked audit_fetch_watch of parent above write_lock  on 
child to prevent deadlock if parent == child in audit_attach_watch.  Have not 
tested code on SMP, however.

-- 
-tim
diff -Nurp linux-2.6.11.5/fs/dcache.c linux-2.6.11.5~patch/fs/dcache.c
--- linux-2.6.11.5/fs/dcache.c	2005-03-19 00:34:52.000000000 -0600
+++ linux-2.6.11.5~patch/fs/dcache.c	2005-03-28 13:59:39.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 */
 
@@ -798,6 +799,7 @@ void d_instantiate(struct dentry *entry,
 	if (inode)
 		list_add(&entry->d_alias, &inode->i_dentry);
 	entry->d_inode = inode;
+	audit_attach_watch(entry, 0);
 	spin_unlock(&dcache_lock);
 	security_d_instantiate(entry, inode);
 }
@@ -974,6 +976,7 @@ struct dentry *d_splice_alias(struct ino
 		new = __d_find_alias(inode, 1);
 		if (new) {
 			BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
+			audit_attach_watch(new, 0);
 			spin_unlock(&dcache_lock);
 			security_d_instantiate(new, inode);
 			d_rehash(dentry);
@@ -983,6 +986,7 @@ struct dentry *d_splice_alias(struct ino
 			/* d_instantiate takes dcache_lock, so we do it by hand */
 			list_add(&dentry->d_alias, &inode->i_dentry);
 			dentry->d_inode = inode;
+			audit_attach_watch(dentry, 0);
 			spin_unlock(&dcache_lock);
 			security_d_instantiate(dentry, inode);
 			d_rehash(dentry);
@@ -1086,6 +1090,7 @@ struct dentry * __d_lookup(struct dentry
 		if (!d_unhashed(dentry)) {
 			atomic_inc(&dentry->d_count);
 			found = dentry;
+			audit_attach_watch(found, 0);
 		}
 		spin_unlock(&dentry->d_lock);
 		break;
@@ -1166,6 +1171,7 @@ void d_delete(struct dentry * dentry)
 	 */
 	spin_lock(&dcache_lock);
 	spin_lock(&dentry->d_lock);
+	audit_attach_watch(dentry, 1);
 	if (atomic_read(&dentry->d_count) == 1) {
 		dentry_iput(dentry);
 		return;
@@ -1295,6 +1301,8 @@ void d_move(struct dentry * dentry, stru
 		spin_lock(&target->d_lock);
 	}
 
+	audit_attach_watch(dentry, 1);
+
 	/* Move the dentry to the target hash queue, if on different bucket */
 	if (dentry->d_flags & DCACHE_UNHASHED)
 		goto already_unhashed;
@@ -1328,6 +1336,7 @@ already_unhashed:
 		list_add(&target->d_child, &target->d_parent->d_subdirs);
 	}
 
+	audit_attach_watch(dentry, 0);
 	list_add(&dentry->d_child, &dentry->d_parent->d_subdirs);
 	spin_unlock(&target->d_lock);
 	spin_unlock(&dentry->d_lock);
diff -Nurp linux-2.6.11.5/fs/inode.c linux-2.6.11.5~patch/fs/inode.c
--- linux-2.6.11.5/fs/inode.c	2005-03-19 00:35:04.000000000 -0600
+++ linux-2.6.11.5~patch/fs/inode.c	2005-03-25 10:52:16.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:
@@ -134,9 +135,11 @@ static struct inode *alloc_inode(struct 
 		inode->i_bdev = NULL;
 		inode->i_cdev = NULL;
 		inode->i_rdev = 0;
+		inode->i_audit = NULL;
 		inode->i_security = NULL;
 		inode->dirtied_when = 0;
-		if (security_inode_alloc(inode)) {
+		if (audit_inode_alloc(inode) || security_inode_alloc(inode)) {
+			audit_inode_free(inode);
 			if (inode->i_sb->s_op->destroy_inode)
 				inode->i_sb->s_op->destroy_inode(inode);
 			else
@@ -174,6 +177,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.5/fs/namei.c linux-2.6.11.5~patch/fs/namei.c
--- linux-2.6.11.5/fs/namei.c	2005-03-19 00:34:54.000000000 -0600
+++ linux-2.6.11.5~patch/fs/namei.c	2005-03-28 14:02:53.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;
 
@@ -347,6 +349,8 @@ static inline int exec_permission_lite(s
 	if (inode->i_op && inode->i_op->permission)
 		return -EAGAIN;
 
+	audit_notify_watch(inode, MAY_EXEC);
+
 	if (current->fsuid == inode->i_uid)
 		mode >>= 6;
 	else if (in_group_p(inode->i_gid))
@@ -1128,6 +1132,8 @@ static inline int may_delete(struct inod
 
 	BUG_ON(victim->d_parent->d_inode != dir);
 
+	audit_notify_watch(victim->d_inode, MAY_WRITE);
+
 	error = permission(dir,MAY_WRITE | MAY_EXEC, NULL);
 	if (error)
 		return error;
@@ -1252,6 +1258,7 @@ int vfs_create(struct inode *dir, struct
 	DQUOT_INIT(dir);
 	error = dir->i_op->create(dir, dentry, mode, nd);
 	if (!error) {
+		audit_notify_watch(dentry->d_inode, MAY_WRITE|MAY_EXEC);
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_create(dir, dentry, mode);
 	}
@@ -1453,11 +1460,13 @@ ok:
 	error = may_open(nd, acc_mode, flag);
 	if (error)
 		goto exit;
+	audit_notify_watch(nd->dentry->d_inode, MAY_READ);
 	return 0;
 
 exit_dput:
 	dput(dentry);
 exit:
+	audit_notify_watch(nd->dentry->d_inode, MAY_READ);
 	path_release(nd);
 	return error;
 
@@ -1557,6 +1566,7 @@ int vfs_mknod(struct inode *dir, struct 
 	DQUOT_INIT(dir);
 	error = dir->i_op->mknod(dir, dentry, mode, dev);
 	if (!error) {
+		audit_notify_watch(dentry->d_inode, MAY_WRITE|MAY_EXEC);
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_mknod(dir, dentry, mode, dev);
 	}
@@ -1630,6 +1640,7 @@ int vfs_mkdir(struct inode *dir, struct 
 	DQUOT_INIT(dir);
 	error = dir->i_op->mkdir(dir, dentry, mode);
 	if (!error) {
+		audit_notify_watch(dentry->d_inode, MAY_WRITE|MAY_EXEC);
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_mkdir(dir,dentry, mode);
 	}
@@ -1874,6 +1885,7 @@ int vfs_symlink(struct inode *dir, struc
 	DQUOT_INIT(dir);
 	error = dir->i_op->symlink(dir, dentry, oldname);
 	if (!error) {
+		audit_notify_watch(dentry->d_inode, MAY_WRITE);
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_symlink(dir, dentry, oldname);
 	}
@@ -1947,6 +1959,7 @@ int vfs_link(struct dentry *old_dentry, 
 	error = dir->i_op->link(old_dentry, dir, new_dentry);
 	up(&old_dentry->d_inode->i_sem);
 	if (!error) {
+		audit_notify_watch(new_dentry->d_inode, MAY_WRITE);
 		inode_dir_notify(dir, DN_CREATE);
 		security_inode_post_link(old_dentry, dir, new_dentry);
 	}
@@ -2070,6 +2083,7 @@ int vfs_rename_dir(struct inode *old_dir
 	}
 	if (!error) {
 		d_move(old_dentry,new_dentry);
+		audit_notify_watch(old_dentry->d_inode, MAY_WRITE);
 		security_inode_post_rename(old_dir, old_dentry,
 					   new_dir, new_dentry);
 	}
@@ -2098,6 +2112,7 @@ int vfs_rename_other(struct inode *old_d
 		/* The following d_move() should become unconditional */
 		if (!(old_dir->i_sb->s_type->fs_flags & FS_ODD_RENAME))
 			d_move(old_dentry, new_dentry);
+		audit_notify_watch(old_dentry->d_inode, MAY_WRITE);
 		security_inode_post_rename(old_dir, old_dentry, new_dir, new_dentry);
 	}
 	if (target)
diff -Nurp linux-2.6.11.5/include/linux/audit.h linux-2.6.11.5~patch/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~patch/include/linux/audit.h	2005-03-28 14:03:21.000000000 -0600
@@ -24,15 +24,25 @@
 #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_LST	1009	/* List all watches */
+#define AUDIT_WATCH_ERR	1010	/* Watch error channel */
+#define AUDIT_KERNEL   	2000	/* Asynchronous audit record. NOT A REQUEST. */
 
 /* Rule flags */
 #define AUDIT_PER_TASK 0x01	/* Apply rule at task creation (not syscall) */
@@ -96,6 +106,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 +136,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 +183,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 +193,25 @@ 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_receive_watch(int type, int pid, int uid, int seq, void *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_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 +228,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 +254,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~patch/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~patch/include/linux/fs.h	2005-03-24 13:57:28.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~patch/init/Kconfig
--- linux-2.6.11.5/init/Kconfig	2005-03-19 00:35:01.000000000 -0600
+++ linux-2.6.11.5~patch/init/Kconfig	2005-03-24 16:49:09.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~patch/kernel/Makefile
--- linux-2.6.11.5/kernel/Makefile	2005-03-19 00:34:53.000000000 -0600
+++ linux-2.6.11.5~patch/kernel/Makefile	2005-03-23 20:45:08.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~patch/kernel/audit.c
--- linux-2.6.11.5/kernel/audit.c	2005-03-19 00:35:05.000000000 -0600
+++ linux-2.6.11.5~patch/kernel/audit.c	2005-03-28 16:48:26.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,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_LST:
 		if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
 			err = -EPERM;
 		break;
@@ -413,6 +417,15 @@ 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;
+	case AUDIT_WATCH_LST:
+		err = audit_receive_watch(nlh->nlmsg_type,
+					  NETLINK_CB(skb).pid,
+					  uid, seq, data);
+		break;
 	default:
 		err = -EINVAL;
 		break;
@@ -557,6 +570,7 @@ int __init audit_init(void)
 
 	audit_initialized = 1;
 	audit_enabled = audit_default;
+	audit_filesystem_init();
 	audit_log(NULL, "initialized");
 	return 0;
 }
@@ -717,6 +731,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~patch/kernel/auditfs.c
--- linux-2.6.11.5/kernel/auditfs.c	1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.11.5~patch/kernel/auditfs.c	2005-03-28 16:36:50.000000000 -0600
@@ -0,0 +1,534 @@
+/* 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;
+
+/* 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(struct audit_watch *req)
+{
+	int ret;
+	char *path = NULL;
+	char *filterkey = NULL;
+	struct nameidata nd;
+
+	ret = -EINVAL;
+	path = getname(req->name);
+	if (IS_ERR(path)) {
+		ret = PTR_ERR(path);
+		goto audit_insert_watch_exit;
+	}
+
+	if (!path || strlen(path) + 1 > PATH_MAX)
+		goto audit_insert_watch_exit;
+
+	/* Includes terminating '\0' */
+	if (req->fklen > AUDIT_FILTERKEY_MAX)
+		goto audit_insert_watch_exit;
+
+	if (req->perms > (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND))
+		goto audit_insert_watch_exit;
+
+	if (req->fklen) {
+		ret = -ENOMEM;
+		filterkey = kmalloc(req->fklen, GFP_KERNEL);
+		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 || !nd.last.name)
+		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 inline int audit_remove_watch(struct audit_watch *req)
+{
+	int ret;
+	char *path = NULL;
+	struct nameidata nd;
+	struct audit_data *data;
+	struct audit_wentry *wentry;
+
+	path = getname(req->name);
+	if (IS_ERR(path)) {
+		ret = PTR_ERR(path);
+		goto audit_remove_watch_exit;
+	}
+
+	ret = -EINVAL;
+	if (!path || strlen(path) + 1 > 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;
+
+	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:
+	putname(path);
+	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 preformatted way to the userspace.
+ */
+static 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;
+}
+
+static inline int audit_list_watches(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_LST, 0, 1,
+				 watch, strlen(watch) + 1);
+		kfree(watch);
+		audit_wentry_put(wentry);
+	}
+	read_unlock(&data->lock);
+
+	audit_send_reply(pid, seq, AUDIT_WATCH_LST, 1, 1, NULL, 0);
+	ret = 0;
+
+audit_list_watches_release:
+	path_release(&nd);
+audit_list_watches_exit:
+	return ret;
+}
+
+/*
+ * 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_receive_watch(int type, int pid, int uid, int seq, void *req)
+{
+	int err[2];
+	int ret = 0;
+
+	err[0] = type;
+
+	switch (type) {
+	case AUDIT_WATCH_INS:
+		err[1] = audit_insert_watch(req);
+		break;
+	case AUDIT_WATCH_REM:
+		err[1] = audit_remove_watch(req);
+		break;
+	case AUDIT_WATCH_LST:
+		err[1] = audit_list_watches(pid, uid, seq, req);
+		break;
+	default:
+		ret = -EINVAL;
+		goto audit_receive_watch_exit;
+	}
+
+	audit_send_reply(pid, seq, AUDIT_WATCH_ERR, 0, 1, &err,
+			 sizeof(err));
+
+audit_receive_watch_exit:
+	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~patch/kernel/auditsc.c
--- linux-2.6.11.5/kernel/auditsc.c	2005-03-19 00:35:01.000000000 -0600
+++ linux-2.6.11.5~patch/kernel/auditsc.c	2005-03-28 14:25:31.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,51 @@ 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->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~patch/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~patch/security/selinux/nlmsgtab.c	2005-03-23 20:45:08.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 },
 };
 
 

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