Hello,
It's been a little while. This patch has enough changes in it to be called
patch #6. The major difference between this patch and the last patch #5,
other then the updates based on feedback, falls back on the core logic.
Basically, this implementation has a more concise definition of what is and
isn't allowed. The advantage is that it is less complex and easier to
describe and think about. I feel a little saner, personally ;-)
The only thing I'm a little concerned about in this patch has to do with
audit_watch_insert(). I think I need to lock around where I check to see if
the watchlist is empty and then possibly update the inode. Other then that,
I think I have it pretty much all covered. I feel more comfortable with the
locking in patch #6 then I did in patch #5. I'll make some updates on
audit_watch_insert() and wait for others comments and feedback. Hopefully
with one update on patch #6, we can submit the and idea to linux-fsdevel for
critiqueing.
The hooks appear in:
fs/inode.c: destroy_inode(), alloc_inode()
fs/dcache.c: d_move(), d_delete(), __d_lookup()
fs/namei.c: permission(), exec_permission_lite(), may_delete()
I think I've corrected the d_move() leakage by placing two hooks to
audit_watch() in d_move() that works on the source dentry and the target
dentry.
I'd really appreciate feedback and comments. I made a lot of changes to other
portions of the code based on feedback and suggestions I received from patch
#5.
-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-14 16:00:31.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 */
@@ -1086,6 +1087,7 @@ struct dentry * __d_lookup(struct dentry
if (!d_unhashed(dentry)) {
atomic_inc(&dentry->d_count);
found = dentry;
+ audit_watch(found, 0);
}
spin_unlock(&dentry->d_lock);
break;
@@ -1094,6 +1096,7 @@ next:
}
rcu_read_unlock();
+
return found;
}
@@ -1166,6 +1169,7 @@ void d_delete(struct dentry * dentry)
*/
spin_lock(&dcache_lock);
spin_lock(&dentry->d_lock);
+ audit_watch(dentry, 1);
if (atomic_read(&dentry->d_count) == 1) {
dentry_iput(dentry);
return;
@@ -1295,6 +1299,8 @@ void d_move(struct dentry * dentry, stru
spin_lock(&target->d_lock);
}
+ audit_watch(dentry, 1);
+
/* Move the dentry to the target hash queue, if on different bucket */
if (dentry->d_flags & DCACHE_UNHASHED)
goto already_unhashed;
@@ -1308,6 +1314,8 @@ already_unhashed:
/* Unhash the target: dput() will then get rid of it */
__d_drop(target);
+ audit_watch(target, 1);
+
list_del(&dentry->d_child);
list_del(&target->d_child);
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-14 16:39:15.000000000 -0600
@@ -214,6 +214,10 @@ int permission(struct inode *inode, int
{
int retval, submask;
+ /* Can only return either 0 or -ENOMEM */
+ if (audit_notify_watch(inode, mask) < 0)
+ return -ENOMEM;
+
if (mask & MAY_WRITE) {
umode_t mode = inode->i_mode;
@@ -344,6 +348,10 @@ static inline int exec_permission_lite(s
{
umode_t mode = inode->i_mode;
+ /* Can only return either 0 or -ENOMEM */
+ if (audit_notify_watch(inode, MAY_EXEC) < 0)
+ return -ENOMEM;
+
if (inode->i_op && inode->i_op->permission)
return -EAGAIN;
@@ -1128,6 +1136,10 @@ static inline int may_delete(struct inod
BUG_ON(victim->d_parent->d_inode != dir);
+ /* Can only return either 0 or -ENOMEM */
+ if (audit_notify_watch(victim->d_inode, 0) < 0)
+ return -ENOMEM;
+
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-14 15:30:51.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,8 +135,32 @@ 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;
+ struct list_head link;
+};
+
+struct audit_wentry {
+ struct list_head w_list;
+ atomic_t w_count;
+ struct audit_watch *w_watch;
+ unsigned int w_valid;
+ unsigned int w_cached;
+
+};
+
#ifdef CONFIG_AUDIT
struct audit_buffer;
struct audit_context;
@@ -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)
@@ -161,6 +198,26 @@ extern uid_t audit_get_loginuid(struct a
#define audit_get_loginuid(c) ({ -1; })
#endif
+#ifdef CONFIG_AUDITFILESYSTEM
+extern int audit_receive_watch(int type, int pid, int uid, int seq,
+ struct audit_watch *req);
+extern int 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 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) do { ; } while(0)
+#define audit_inode_free(i) do { ; } while(0)
+#define audit_watch(d,r) do { ; } while (0)
+#define audit_wentry_put(w) do { ; } while(0)
+#define audit_wentry_get(w) ({ 0; })
+#define audit_notify_watch(i,m) ({ 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-14 14:21:59.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(a)redhat.com>
+ * Modified by Timothy R. Chavez <chavezt(a)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:
+ err = 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-14 14:23:46.000000000 -0600
@@ -0,0 +1,601 @@
+/* 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(a)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;
+
+LIST_HEAD(audit_data_stack);
+
+/* Private Interface */
+
+void audit_wentry_put(struct audit_wentry *wentry);
+static void audit_data_free(struct audit_data *data);
+static void audit_data_stack_push(struct audit_data *data);
+static struct audit_data *audit_data_stack_pop(void);
+
+/*
+ * Ultimately, any detatchment of a watchlist entry (wentry) occurs at this
+ * point. Once the wentry has been detatched from its watchlist, it becomes
+ * invalid. This means that any remaining references to it are invalidated
+ * and will be ignored by the audit subsystem and removed when determined to
+ * be possible. If the file/directory associated with the wentry does not
+ * exist, w_cached should be set to 1 and we'll pop it off the stack cache
+ * and free it. This prevents any leakage of memory in the stack cache.
+ *
+ * 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;
+ if (wentry->w_cached)
+ audit_data_free(audit_data_stack_pop());
+ audit_wentry_put(wentry);
+ }
+
+}
+/*
+ * 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;
+
+ if (data) {
+ write_lock_irqsave(&data->watchlist_lock, flags);
+ audit_drain_watchlist(data);
+ write_unlock_irqrestore(&data->watchlist_lock, flags);
+ }
+}
+
+/*
+ * The stack cache is a simple structure for keeping memory available for
+ * watched files and directories. Because we cannot correctly handled memory
+ * allocations in our dcache hook (audit_watch), we preallocate memory upon
+ * watch creation and push it onto the stack cache. Then, instead of trying
+ * to allocate memory in audit_watch, we simply pop it off the stack cache.
+ * If the file or directory then leaves the watch and the watchlist entry
+ * remains, we push the memory back onto the stack cache.
+ *
+ */
+static void audit_data_stack_push(struct audit_data *data)
+{
+ if (data) {
+ audit_wentry_put(data->wentry);
+ audit_drain_watchlist_lock(data);
+ list_add(&data->link, &audit_data_stack);
+ printk("Pushed data on cache stack\n");
+ }
+}
+
+static struct audit_data *audit_data_stack_pop(void)
+{
+ struct audit_data *data;
+
+ BUG_ON(list_empty(&audit_data_stack));
+
+ data = list_entry(audit_data_stack.next, struct audit_data, link);
+ list_del_init(audit_data_stack.next);
+ printk("Popped data off cache stack\n");
+
+ return data;
+}
+
+/*
+ * 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;
+}
+
+static 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;
+ INIT_LIST_HEAD(&data->watchlist);
+ data->watchlist_lock = RW_LOCK_UNLOCKED;
+ }
+
+ return data;
+}
+
+static void audit_data_free(struct audit_data *data)
+{
+ if (data) {
+ audit_drain_watchlist_lock(data);
+
+ if (data->wentry)
+ audit_wentry_put(data->wentry);
+
+ kmem_cache_free(audit_data_cache, 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;
+ }
+
+ 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);
+ }
+}
+
+/* The wentry is inaccessible until added to the watchlist */
+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;
+ struct audit_data *cached_data;
+
+ ret = -ENOMEM;
+ cached_data = audit_data_alloc();
+ if (!cached_data)
+ goto audit_create_wentry_exit;
+ audit_data_stack_push(cached_data);
+
+ 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;
+ }
+
+ new_wentry->w_valid = 1;
+ new_wentry->w_cached = 1;
+
+ ret = 0;
+ write_lock_irqsave(&data->watchlist_lock, flags);
+ wentry = audit_wentry_fetch(name, data);
+ if (!wentry) {
+ list_add(&new_wentry->w_list, &data->watchlist);
+ write_unlock_irqrestore(&data->watchlist_lock, flags);
+ goto audit_create_wentry_exit;
+ }
+ audit_wentry_put(wentry);
+ write_unlock_irqrestore(&data->watchlist_lock, flags);
+
+ ret = -EEXIST;
+
+audit_create_wentry_fail:
+ audit_data_free(audit_data_stack_pop());
+ audit_wentry_put(new_wentry);
+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;
+ struct audit_data *data;
+
+ path = getname(req->name);
+ if (IS_ERR(req->name)) {
+ ret = PTR_ERR(req->name);
+ 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 = -ENOMEM;
+ if (!nd.dentry->d_inode->i_audit) {
+ nd.dentry->d_inode->i_audit = audit_data_alloc();
+ if (!nd.dentry->d_inode->i_audit)
+ goto audit_insert_watch_release;
+ }
+
+ data = nd.dentry->d_inode->i_audit;
+ ret = audit_create_wentry(nd.last.name, filterkey, req->perms, data);
+ if (ret < 0) {
+ if (!data->wentry && list_empty(&data->watchlist)) {
+ audit_data_free(nd.dentry->d_inode->i_audit);
+ nd.dentry->d_inode->i_audit = NULL;
+ }
+ goto audit_insert_watch_release;
+ }
+
+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_wentry *wentry;
+ struct audit_data *data;
+
+ 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;
+
+ data = nd.dentry->d_inode->i_audit;
+ if (!data)
+ goto audit_remove_watch_release;
+
+ wentry = audit_wentry_fetch_lock(nd.last.name, data);
+ if (!wentry)
+ goto audit_remove_watch_release;
+
+ spin_lock(&nd.dentry->d_lock);
+ write_lock_irqsave(&data->watchlist_lock, flags);
+ audit_destroy_wentry(wentry);
+ write_unlock_irqrestore(&data->watchlist_lock, flags);
+
+ if (!data->wentry && list_empty(&data->watchlist)) {
+ audit_data_free(nd.dentry->d_inode->i_audit);
+ nd.dentry->d_inode->i_audit = NULL;
+ }
+ spin_unlock(&nd.dentry->d_lock);
+
+
+ audit_wentry_put(wentry);
+
+ ret = 0;
+
+audit_remove_watch_release:
+ path_release(&nd);
+audit_remove_watch_exit:
+ putname(path);
+ return ret;
+}
+
+/* Public interface */
+
+/* There are four ways we can get a reference to a wentry:
+ * 1. When we insert a watch, a wentry is created, and a reference to it
is
+ * held by the watchlist. When we remove a watch, we put back this
+ * reference.
+ * 2. When a file/directory is at a watch point, it gets a reference to
its
+ * wentry. When it leaves that watch point, it puts back this
reference.
+ * 2. We perform a search on the watchlist for a particular watch. If
found
+ * we get a reference to the wentry and return it. After we are done
w/
+ * this reference, we put it back.
+ * 3. An audited syscall accesses the watched file or directory. As soon
+ * as the audit subsystem generates a record, it puts this reference
+ * back.
+ */
+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);
+}
+
+/*
+ * We are protected by dcache locking and audit_remove_watch() locking.
+ *
+ * Hook appears in fs/dcache.c:
+ * d_move()
+ * d_delete(),
+ * __d_lookup(),
+ */
+void audit_watch(struct dentry *dentry, int remove)
+{
+ struct audit_wentry *wentry;
+ struct audit_data *data;
+
+ if (!dentry || !dentry->d_inode)
+ return;
+
+ if (!dentry->d_parent || !dentry->d_parent->d_inode)
+ return;
+
+ data = dentry->d_inode->i_audit;
+ wentry = audit_wentry_fetch_lock(dentry->d_name.name,
+ dentry->d_parent->d_inode->i_audit);
+ if (remove && data) {
+ if (wentry && data->wentry) {
+ dentry->d_inode->i_audit->wentry->w_cached = 1;
+ audit_data_stack_push(dentry->d_inode->i_audit);
+ } else
+ audit_data_free(dentry->d_inode->i_audit);
+ dentry->d_inode->i_audit = NULL;
+ }
+
+ if (wentry) {
+ if (data) {
+ if (!data->wentry) {
+ dentry->d_inode->i_audit->wentry =
+ audit_wentry_get(wentry);
+ } else if (!data->wentry->w_valid) {
+ audit_data_free(dentry->d_inode->i_audit);
+ dentry->d_inode->i_audit = NULL;
+ }
+ } else {
+ dentry->d_inode->i_audit = audit_data_stack_pop();
+ dentry->d_inode->i_audit->wentry =
+ audit_wentry_get(wentry);
+ dentry->d_inode->i_audit->wentry->w_cached = 0;
+ }
+ }
+
+ audit_wentry_put(wentry);
+}
+
+/* If CONFIG_AUDITFILESYSTEM 'N', audit_receive_watch() resolves to
+ * -EOPNOTSUPP via a macro.
+ */
+int 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));
+
+ return 0;
+}
+
+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_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;
+
+ audit_data_cache =
+ kmem_cache_create("audit_data_cache",
+ sizeof(struct audit_data), 0, 0, NULL, NULL);
+ if (!audit_data_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);
+ kmem_cache_destroy(audit_data_cache);
+audit_filesystem_init_exit:
+ return ret;
+
+}
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-14 15:31:19.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(a)redhat.com>
+ * Modified by Timothy R. Chavez <chavezt(a)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 inline 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 inline 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,24 @@ 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 +862,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 +1001,57 @@ 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
+ *
+ */
+#ifdef CONFIG_AUDITFILESYSTEM
+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;
+}
+#endif
+