Hello,
This patch incorporates a lot of feedback from various people (Serge
Hallyn, Steve Grubb, Dave Hansen, Mounir Bsaibes): mostly cosmetic.
But there were some bug fixes in audit_create_wentry().
I'm going to spend a couple days testing this and writing the abstract
and then I want to put it on linux-fsdevel. I'd really appreciate
some scrutiny and feedback on this patch during that time. The goal
is to finally move on this and make it more visible this week. There
is still one remaining feature that was requested that needs to be
implemented and I'll get to it eventually... before March is over (the
end of my development schedule).
Also, should I break this up into a patch set?
-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-04 16:20:29.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 */
@@ -800,6 +801,7 @@ void d_instantiate(struct dentry *entry,
entry->d_inode = inode;
spin_unlock(&dcache_lock);
security_d_instantiate(entry, inode);
+ audit_watch(entry, 0);
}
/**
@@ -975,6 +977,7 @@ struct dentry *d_splice_alias(struct ino
if (new) {
BUG_ON(!(new->d_flags & DCACHE_DISCONNECTED));
spin_unlock(&dcache_lock);
+ audit_watch(dentry, 0);
security_d_instantiate(new, inode);
d_rehash(dentry);
d_move(new, dentry);
@@ -984,6 +987,7 @@ struct dentry *d_splice_alias(struct ino
list_add(&dentry->d_alias, &inode->i_dentry);
dentry->d_inode = inode;
spin_unlock(&dcache_lock);
+ audit_watch(dentry, 0);
security_d_instantiate(dentry, inode);
d_rehash(dentry);
}
@@ -1094,6 +1098,8 @@ next:
}
rcu_read_unlock();
+ audit_watch(found, 0);
+
return found;
}
@@ -1333,6 +1339,7 @@ already_unhashed:
spin_unlock(&dentry->d_lock);
write_sequnlock(&rename_lock);
spin_unlock(&dcache_lock);
+ audit_watch(dentry, 1);
}
/**
diff -Nurp linux-2.6.11/fs/inode.c linux-2.6.11-audit/fs/inode.c
--- linux-2.6.11/fs/inode.c 2005-03-02 01:38:33.000000000 -0600
+++ linux-2.6.11-audit/fs/inode.c 2005-03-04 16:20: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);
@@ -175,6 +177,7 @@ void destroy_inode(struct inode *inode)
if (inode_has_buffers(inode))
BUG();
security_inode_free(inode);
+ audit_inode_free(inode);
if (inode->i_sb->s_op->destroy_inode)
inode->i_sb->s_op->destroy_inode(inode);
else
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-07 13:06:33.000000000 -0600
@@ -214,6 +214,8 @@ int permission(struct inode *inode, int
{
int retval, submask;
+ audit_notify_watch(inode, mask);
+
if (mask & MAY_WRITE) {
umode_t mode = inode->i_mode;
@@ -344,6 +346,8 @@ static inline int exec_permission_lite(s
{
umode_t mode = inode->i_mode;
+ audit_notify_watch(inode, MAY_EXEC);
+
if (inode->i_op && inode->i_op->permission)
return -EAGAIN;
@@ -1703,6 +1707,9 @@ int vfs_rmdir(struct inode *dir, struct
{
int error = may_delete(dir, dentry, 1);
+ if (dentry)
+ audit_notify_watch(dentry->d_inode, 0);
+
if (error)
return error;
@@ -1778,6 +1785,9 @@ int vfs_unlink(struct inode *dir, struct
{
int error = may_delete(dir, dentry, 0);
+ if (dentry)
+ audit_notify_watch(dentry->d_inode, 0);
+
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-07 10:49:49.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
+#define AUDIT_STATUS_FSENABLED 0x0020
/* 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;
@@ -106,6 +118,7 @@ struct audit_message {
struct audit_status {
__u32 mask; /* Bit mask for valid entries */
__u32 enabled; /* 1 = enabled, 0 = disbaled */
+ __u32 fs_enabled; /* 1 = fs auditing on, 0 = off */
__u32 failure; /* Failure-to-log action */
__u32 pid; /* pid of auditd process */
__u32 rate_limit; /* messages rate limit (per second) */
@@ -123,14 +136,38 @@ struct audit_rule { /* for AUDIT_LIST,
__u32 values[AUDIT_MAX_FIELDS];
};
+struct audit_watch {
+ int namelen;
+ int fklen;
+ char *name;
+ char *filterkey;
+ __u32 perms;
+};
+
#ifdef __KERNEL__
+struct audit_data {
+ struct audit_wentry *wentry;
+ struct list_head watchlist;
+ rwlock_t watchlist_lock;
+ atomic_t count;
+};
+
+struct audit_wentry {
+ struct list_head w_list;
+ atomic_t w_count;
+ struct audit_data *w_data;
+ struct audit_watch *w_watch;
+ int w_valid;
+};
+
#ifdef CONFIG_AUDIT
struct audit_buffer;
struct audit_context;
#endif
#ifdef CONFIG_AUDITSYSCALL
+struct inode;
/* These are defined in auditsc.c */
/* Public API */
extern int audit_alloc(struct task_struct *task);
@@ -150,6 +187,7 @@ extern void audit_get_stamp(struct audit
struct timespec *t, int *serial);
extern int audit_set_loginuid(struct audit_context *ctx, uid_t loginuid);
extern uid_t audit_get_loginuid(struct audit_context *ctx);
+extern int audit_notify_watch(struct inode *inode, int mask);
#else
#define audit_alloc(t) ({ 0; })
#define audit_free(t) do { ; } while (0)
@@ -159,8 +197,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_receive_watch(int type, int pid, int uid, int seq,
+ struct audit_watch *req);
+extern void audit_filesystem_init(void);
+extern void audit_inode_alloc(struct inode *inode);
+extern void audit_inode_free(struct inode *inode);
+extern void audit_watch(struct dentry *dentry, int drain);
+extern void audit_wentry_put(struct audit_wentry *wentry);
+extern struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry);
+#else
+#define audit_receive_watch(t,p,u,s,r) ({ -EOPNOTSUPP; })
+#define audit_filesystem_init() do { ; } while(0)
+#define audit_inode_alloc(i) do { ; } while(0)
+#define audit_inode_free(i) do { ; } while(0)
+#define audit_watch(dt,d) do { ; } while (0)
+#define audit_watch_put(w) do { ; } while(0)
+#define audit_watch_get(w) ({ 0; })
+#endif
+
#ifdef CONFIG_AUDIT
/* These are defined in audit.c */
/* Public API */
diff -Nurp linux-2.6.11/include/linux/fs.h linux-2.6.11-audit/include/linux/fs.h
--- linux-2.6.11/include/linux/fs.h 2005-03-02 01:37:50.000000000 -0600
+++ linux-2.6.11-audit/include/linux/fs.h 2005-03-04 16:20:29.000000000 -0600
@@ -457,6 +457,9 @@ struct inode {
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
+#ifdef CONFIG_AUDITFILESYSTEM
+ struct audit_data *i_audit;
+#endif
/* These three should probably be a union */
struct list_head i_devices;
struct pipe_inode_info *i_pipe;
diff -Nurp linux-2.6.11/init/Kconfig linux-2.6.11-audit/init/Kconfig
--- linux-2.6.11/init/Kconfig 2005-03-02 01:38:19.000000000 -0600
+++ linux-2.6.11-audit/init/Kconfig 2005-03-04 16:20:29.000000000 -0600
@@ -174,6 +174,17 @@ config AUDITSYSCALL
can be used independently or with another kernel subsystem,
such as SELinux.
+config AUDITFILESYSTEM
+ bool "Enable filesystem auditing support"
+ depends on AUDITSYSCALL
+ default n
+ help
+ Generate audit records for regular files or directories that
+ are being watched for access by audited syscalls. To insert
+ and remove watch points into the filesystem you may use the
+ auditctl program provided with auditd. For more information,
+ 'man auditctl'
+
config LOG_BUF_SHIFT
int "Kernel log buffer size (16 => 64KB, 17 => 128KB)" if DEBUG_KERNEL
range 12 21
diff -Nurp linux-2.6.11/kernel/Makefile linux-2.6.11-audit/kernel/Makefile
--- linux-2.6.11/kernel/Makefile 2005-03-02 01:37:50.000000000 -0600
+++ linux-2.6.11-audit/kernel/Makefile 2005-03-04 16:20:29.000000000 -0600
@@ -23,6 +23,7 @@ obj-$(CONFIG_IKCONFIG_PROC) += configs.o
obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
obj-$(CONFIG_AUDIT) += audit.o
obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
+obj-$(CONFIG_AUDITFILESYSTEM) += auditfs.o
obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_SYSFS) += ksysfs.o
obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
diff -Nurp linux-2.6.11/kernel/audit.c linux-2.6.11-audit/kernel/audit.c
--- linux-2.6.11/kernel/audit.c 2005-03-02 01:38:33.000000000 -0600
+++ linux-2.6.11-audit/kernel/audit.c 2005-03-07 13:14:10.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:
@@ -60,6 +61,9 @@ static int audit_initialized;
/* No syscall auditing will take place unless audit_enabled != 0. */
int audit_enabled;
+/* No filesystem auditing will take place unless audit_fsenabled != 0 */
+int auditfs_enabled = 1;
+
/* Default state when kernel boots without any parameters. */
static int audit_default;
@@ -265,6 +269,17 @@ int audit_set_enabled(int state)
return old;
}
+int audit_set_fsenabled(int state)
+{
+ int old = auditfs_enabled;
+ if (state != 0 && state != 1)
+ return -EINVAL;
+ auditfs_enabled = state;
+ audit_log(current->audit_context, "auditfs_enabled=%d old=%d",
+ auditfs_enabled, old);
+ return old;
+}
+
int audit_set_failure(int state)
{
int old = audit_failure;
@@ -319,6 +334,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;
@@ -354,6 +371,7 @@ static int audit_receive_msg(struct sk_b
switch (msg_type) {
case AUDIT_GET:
status_set.enabled = audit_enabled;
+ status_set.fs_enabled = auditfs_enabled;
status_set.failure = audit_failure;
status_set.pid = audit_pid;
status_set.rate_limit = audit_rate_limit;
@@ -371,6 +389,10 @@ static int audit_receive_msg(struct sk_b
err = audit_set_enabled(status_get->enabled);
if (err < 0) return err;
}
+ if (status_get->mask & AUDIT_STATUS_FSENABLED) {
+ err = audit_set_fsenabled(status_get->fs_enabled);
+ if (err < 0) return err;
+ }
if (status_get->mask & AUDIT_STATUS_FAILURE) {
err = audit_set_failure(status_get->failure);
if (err < 0) return err;
@@ -413,6 +435,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 +585,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-07 13:19:21.000000000 -0600
@@ -0,0 +1,583 @@
+/* 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;
+
+extern int auditfs_enabled;
+
+/* Private Interface */
+
+static void audit_data_put(struct audit_data *data);
+void audit_wentry_put(struct audit_wentry *wentry);
+
+/*
+ * We remove this wentry from the watchlist and mark it as being invalid. When
+ * we invalidate a wentry, we're telling the audit subsystem to ignore any refs
+ * to this wentry that may still exist when auditing. We're also giving it the
+ * permission to remove the reference and attach a new watch if there is one
+ * available.
+ *
+ * We must drop our reference to the inode's audit_data here. Otherwise, we'll
+ * leak a reference and audit_data_free() will never be called.
+ *
+ * Caller must hold i_audit->watchlist_lock
+ */
+static inline void audit_destroy_wentry(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ list_del_init(&wentry->w_list);
+ wentry->w_valid = 0;
+ audit_data_put(wentry->w_data);
+ audit_wentry_put(wentry);
+ }
+}
+
+/*
+ * This is the core function for determining whether or not "name"
+ * is in the parent's watchlist (data->watchlist).
+ *
+ * NOTE: Should only be called from a secure source.
+ *
+ * Caller must hold i_audit->watchlist_lock and is responsible for
+ * putting back the returned reference
+ */
+static inline struct audit_wentry *audit_wentry_fetch(const char *name,
+ struct audit_data *data)
+{
+ struct audit_wentry *wentry, *ret = NULL;
+
+ list_for_each_entry(wentry, &data->watchlist, w_list)
+ if (!strcmp(wentry->w_watch->name, name)) {
+ ret = audit_wentry_get(wentry);
+ break;
+ }
+
+ return ret;
+}
+
+static
+inline struct audit_wentry *audit_wentry_fetch_lock(const char *name,
+ struct audit_data *data)
+{
+ unsigned long flags;
+ struct audit_wentry *ret = NULL;
+
+ if (name && data) {
+ read_lock_irqsave(&data->watchlist_lock, flags);
+ ret = audit_wentry_fetch(name, data);
+ read_unlock_irqrestore(&data->watchlist_lock, flags);
+ }
+
+ return ret;
+}
+
+/*
+ * First reference of audit_data is returned on allocation
+ */
+static inline struct audit_data *audit_data_alloc(void)
+{
+ struct audit_data *data;
+
+ data = kmem_cache_alloc(audit_data_cache, GFP_KERNEL);
+ if (data) {
+ data->wentry = NULL;
+ atomic_set(&data->count, 1);
+ INIT_LIST_HEAD(&data->watchlist);
+ data->watchlist_lock = RW_LOCK_UNLOCKED;
+
+ }
+
+ return data;
+}
+
+/*
+ * NOTE: Should only be called from a secure source
+ *
+ * Caller must hold i_audit->watchlist_lock
+ */
+static inline void audit_drain_watchlist(struct audit_data *data)
+{
+ struct audit_wentry *wentry, *tmp;
+
+ list_for_each_entry_safe(wentry, tmp, &data->watchlist, w_list)
+ audit_destroy_wentry(wentry);
+}
+
+static inline void audit_drain_watchlist_lock(struct audit_data *data)
+{
+ unsigned long flags;
+ struct audit_wentry *wentry, *tmp;
+
+ if (data) {
+ write_lock_irqsave(&data->watchlist_lock, flags);
+
+ list_for_each_entry_safe(wentry, tmp, &data->watchlist, w_list)
+ audit_destroy_wentry(wentry);
+
+ write_unlock_irqrestore(&data->watchlist_lock, flags);
+ }
+}
+
+static inline void audit_data_free(struct audit_data *data)
+{
+ unsigned long flags;
+
+ if (data) {
+ write_lock_irqsave(&data->watchlist_lock, flags);
+
+ audit_drain_watchlist(data);
+
+ if (data->wentry) {
+ audit_wentry_put(data->wentry);
+ data->wentry = NULL;
+ }
+
+ write_unlock_irqrestore(&data->watchlist_lock,flags);
+
+ kmem_cache_free(audit_data_cache, data);
+ }
+}
+
+static struct audit_data *audit_data_get(struct audit_data *data)
+{
+ if (data) {
+ BUG_ON(!atomic_read(&data->count));
+ atomic_inc(&data->count);
+ }
+
+ return data;
+}
+
+static void audit_data_put(struct audit_data *data)
+{
+ if (data && atomic_dec_and_test(&data->count))
+ audit_data_free(data);
+}
+
+static inline struct audit_watch *audit_watch_alloc(void)
+{
+ struct audit_watch *watch;
+
+ watch = kmem_cache_alloc(audit_watch_cache, GFP_KERNEL);
+ if (watch) {
+ watch->namelen = 0;
+ watch->fklen = 0;
+ watch->name = NULL;
+ watch->filterkey = NULL;
+ watch->perms = 0;
+ }
+
+ return watch;
+}
+
+static inline void audit_watch_free(struct audit_watch *watch)
+{
+ if (watch) {
+ kfree(watch->name);
+ kfree(watch->filterkey);
+ kmem_cache_free(audit_watch_cache, watch);
+ }
+}
+
+static struct audit_watch *audit_create_watch(const char *name,
+ const char *filterkey,
+ __u32 perms)
+{
+ struct audit_watch *err = NULL;
+ struct audit_watch *watch = NULL;
+
+ err = ERR_PTR(-EINVAL);
+
+ if (!name || strlen(name) + 1 > PATH_MAX)
+ goto audit_create_watch_fail;
+
+ if (filterkey && strlen(filterkey) + 1 > AUDIT_FILTERKEY_MAX)
+ goto audit_create_watch_fail;
+
+ if (perms > 15)
+ goto audit_create_watch_fail;
+
+ err = ERR_PTR(-ENOMEM);
+
+ watch = audit_watch_alloc();
+ if (watch) {
+ watch->namelen = strlen(name) + 1;
+ watch->name = kmalloc(watch->namelen, GFP_KERNEL);
+ if (!watch->name)
+ goto audit_create_watch_fail;
+ strcpy(watch->name, name);
+
+ if (filterkey) {
+ watch->fklen = strlen(filterkey) + 1;
+ watch->filterkey = kmalloc(watch->fklen, GFP_KERNEL);
+ if (!watch->filterkey)
+ goto audit_create_watch_fail;
+ strcpy(watch->filterkey, filterkey);
+ }
+
+ watch->perms = perms;
+
+ goto audit_create_watch_exit;
+ }
+
+
+audit_create_watch_fail:
+ audit_watch_free(watch);
+ watch = err;
+audit_create_watch_exit:
+ return watch;
+}
+
+/*
+ * First reference of audit_wentry is returned on allocation
+ */
+static inline struct audit_wentry *audit_wentry_alloc(void)
+{
+ struct audit_wentry *wentry;
+
+ wentry = kmem_cache_alloc(audit_wentry_cache, GFP_KERNEL);
+ if (wentry) {
+ atomic_set(&wentry->w_count, 1);
+ wentry->w_valid = 0;
+ wentry->w_watch = NULL;
+ wentry->w_data = NULL;
+ }
+
+ return wentry;
+}
+
+/*
+ * Because of the circular nature of the design, in order to arrive here,
+ * we may be getting called from within audit_data_free(). The only way
+ * this could happen is if there were no more references to inode->i_audit.
+ * Thus, we couldn't possibly put back the wentry's reference to w_data here.
+ */
+static inline void audit_wentry_free(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ audit_watch_free(wentry->w_watch);
+ kmem_cache_free(audit_wentry_cache, wentry);
+ }
+}
+
+/*
+ * The wentry holds both the watch (w_watch) and the inode's i_audit memory
+ * (w_data). The inode's memory contains a pointer to the wentry that holds
+ * it. This produces a circular effect.
+ *
+ * We do this for the following reasons:
+ * 1) the memory for an inode may not be allocated in our audit_watch()
+ * hook because there may be no reasonable way to handle an -ENOMEM.
+ * 2) there are cases when multiple inode's will refer to the same watch.
+ * 3) the inode associated w/ "name" may or may not exist
+ *
+ */
+static int audit_create_wentry(const char *name,
+ const char *filterkey,
+ unsigned int perms, struct audit_data *data)
+{
+ int ret;
+ unsigned long flags;
+ struct audit_wentry *wentry = NULL;
+ struct audit_wentry *new_wentry = NULL;
+
+ ret = -ENOMEM;
+
+ new_wentry = audit_wentry_alloc();
+ if (!new_wentry)
+ goto audit_create_wentry_fail;
+
+ new_wentry->w_data = audit_data_alloc();
+ if (!new_wentry->w_data)
+ goto audit_create_wentry_fail;
+
+ new_wentry->w_watch = audit_create_watch(name, filterkey, perms);
+ if (IS_ERR(new_wentry->w_watch)) {
+ ret = PTR_ERR(new_wentry->w_watch);
+ new_wentry->w_watch = NULL;
+ goto audit_create_wentry_fail;
+ }
+
+ new_wentry->w_data->wentry = audit_wentry_get(new_wentry);
+
+ write_lock_irqsave(&data->watchlist_lock, flags);
+
+ wentry = audit_wentry_fetch(name, data);
+ if (!wentry) {
+ list_add(&new_wentry->w_list, &data->watchlist);
+ new_wentry->w_valid = 1;
+ write_unlock_irqrestore(&data->watchlist_lock, flags);
+ ret = 0;
+ goto audit_create_wentry_exit;
+ }
+ audit_wentry_put(wentry);
+
+ write_unlock_irqrestore(&data->watchlist_lock, flags);
+
+ ret = -EEXIST;
+
+audit_create_wentry_fail:
+ audit_data_put(new_wentry->w_data);
+ audit_wentry_put(new_wentry);
+ if (!data->wentry && list_empty(&data->watchlist))
+ audit_data_put(data);
+audit_create_wentry_exit:
+ return ret;
+}
+
+static int audit_insert_watch(struct audit_watch *req)
+{
+ int ret;
+ char *path = NULL;
+ char *filterkey = NULL;
+ struct nameidata nd;
+
+ path = getname(req->name);
+ if (IS_ERR(req->name)) {
+ ret = PTR_ERR(req->name);
+ goto audit_insert_watch_exit;
+ }
+
+ if (req->fklen) {
+ filterkey = kmalloc(req->fklen, GFP_KERNEL);
+ ret = -ENOMEM;
+ if (!filterkey)
+ goto audit_insert_watch_exit;
+
+ ret = strncpy_from_user(filterkey, req->filterkey, req->fklen);
+ if (ret < 0)
+ goto audit_insert_watch_exit;
+ }
+
+ ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+ if (ret < 0)
+ goto audit_insert_watch_release;
+
+ ret = -EPERM;
+ if (nd.last_type != LAST_NORM)
+ goto audit_insert_watch_release;
+
+ if (!nd.dentry->d_inode->i_audit) {
+ nd.dentry->d_inode->i_audit = audit_data_alloc();
+ ret = -ENOMEM;
+ if (!nd.dentry->d_inode->i_audit)
+ goto audit_insert_watch_release;
+ }
+
+ ret = audit_create_wentry(nd.last.name, filterkey, req->perms,
+ nd.dentry->d_inode->i_audit);
+
+audit_insert_watch_release:
+ path_release(&nd);
+audit_insert_watch_exit:
+ putname(path);
+ kfree(filterkey);
+ return ret;
+}
+
+static int audit_remove_watch(struct audit_watch *req)
+{
+ int ret;
+ unsigned long flags;
+ char *path = NULL;
+ struct nameidata nd;
+ struct audit_data *data;
+ struct audit_wentry *wentry;
+
+ path = getname(req->name);
+ if (IS_ERR(req->name)) {
+ ret = PTR_ERR(req->name);
+ goto audit_remove_watch_exit;
+ }
+
+ ret = -EINVAL;
+ if (!path || (path && strlen(path) > PATH_MAX))
+ goto audit_remove_watch_exit;
+
+ ret = path_lookup(path, LOOKUP_PARENT | LOOKUP_FOLLOW, &nd);
+ if (ret < 0)
+ goto audit_remove_watch_release;
+
+ ret = -EPERM;
+ if (nd.last_type != LAST_NORM || !nd.last.name)
+ goto audit_remove_watch_release;
+
+ ret = -EACCES;
+
+ data = nd.dentry->d_inode->i_audit;
+ if (!data)
+ goto audit_remove_watch_release;
+
+ write_lock_irqsave(&data->watchlist_lock, flags);
+
+ wentry = audit_wentry_fetch(nd.last.name, data);
+ if (!wentry) {
+ write_unlock_irqrestore(&data->watchlist_lock, flags);
+ goto audit_remove_watch_release;
+ }
+ audit_destroy_wentry(wentry);
+ audit_wentry_put(wentry);
+
+ write_unlock_irqrestore(&data->watchlist_lock, flags);
+
+ if (!data->wentry && list_empty(&data->watchlist)) {
+ audit_data_put(data);
+ nd.dentry->d_inode->i_audit = NULL;
+ }
+
+ ret = 0;
+
+audit_remove_watch_release:
+ path_release(&nd);
+audit_remove_watch_exit:
+ putname(path);
+ return ret;
+}
+
+/* Public interface */
+
+struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ BUG_ON(!atomic_read(&wentry->w_count));
+ atomic_inc(&wentry->w_count);
+ }
+
+ return wentry;
+}
+
+void audit_wentry_put(struct audit_wentry *wentry)
+{
+ if (wentry && atomic_dec_and_test(&wentry->w_count))
+ audit_wentry_free(wentry);
+}
+
+/*
+ * Hook appears in:
+ * fs/dcache.c:d_instantiate(), d_move(), d_lookup(), and d_splice_alias()
+ */
+void audit_watch(struct dentry *dentry, int drain)
+{
+ struct audit_wentry *wentry;
+ struct audit_data *data;
+
+ if (!dentry || !dentry->d_inode)
+ return;
+
+ if (!dentry->d_parent || !dentry->d_parent->d_inode)
+ return;
+
+ wentry = audit_wentry_fetch_lock(dentry->d_name.name,
+ dentry->d_parent->d_inode->i_audit);
+
+ data = dentry->d_inode->i_audit;
+ if (data) {
+ if (drain)
+ audit_drain_watchlist_lock(data);
+ if (wentry && !list_empty(&data->watchlist)) {
+ audit_data_put(wentry->w_data);
+ wentry->w_data = audit_data_get(data);
+ audit_wentry_put(wentry->w_data->wentry);
+ wentry->w_data->wentry = audit_wentry_get(wentry);
+ /* Keep get/put's consistent, stealing is bad :( */
+ audit_data_put(data);
+ dentry->d_inode->i_audit =
+ audit_data_get(wentry->w_data);
+ } else if (wentry && !data->wentry->w_valid) {
+ audit_data_put(data);
+ dentry->d_inode->i_audit =
+ audit_data_get(wentry->w_data);
+ }
+ } else if (wentry)
+ dentry->d_inode->i_audit = audit_data_get(wentry->w_data);
+
+ audit_wentry_put(wentry);
+}
+
+int audit_receive_watch(int type, int pid, int uid, int seq,
+ struct audit_watch *req)
+{
+ int err;
+
+ if (auditfs_enabled) {
+
+ 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));
+
+ } else
+ err = -EOPNOTSUPP;
+
+ return err;
+}
+
+void audit_inode_alloc(struct inode *inode)
+{
+ if (inode)
+ inode->i_audit = NULL;
+}
+
+void audit_inode_free(struct inode *inode)
+{
+ if (inode && inode->i_audit) {
+ audit_data_put(inode->i_audit);
+ inode->i_audit = NULL;
+ }
+}
+
+void audit_filesystem_init()
+{
+ audit_watch_cache =
+ kmem_cache_create("audit_watch_cache",
+ sizeof(struct audit_watch), 0, 0, NULL, NULL);
+ audit_wentry_cache =
+ kmem_cache_create("audit_wentry_cache",
+ sizeof(struct audit_wentry), 0, 0, NULL, NULL);
+ audit_data_cache =
+ kmem_cache_create("audit_data_cache",
+ sizeof(struct audit_data), 0, 0, NULL, NULL);
+}
diff -Nurp linux-2.6.11/kernel/auditsc.c linux-2.6.11-audit/kernel/auditsc.c
--- linux-2.6.11/kernel/auditsc.c 2005-03-02 01:38:17.000000000 -0600
+++ linux-2.6.11-audit/kernel/auditsc.c 2005-03-07 12:31:06.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.
@@ -48,6 +49,7 @@
/* No syscall auditing will take place unless audit_enabled != 0. */
extern int audit_enabled;
+extern int auditfs_enabled;
/* AUDIT_NAMES is the number of slots we reserve in the audit_context
* for saving names from getname(). */
@@ -113,6 +115,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 +140,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 +526,37 @@ static inline void audit_free_names(stru
context->name_count = 0;
}
+static struct audit_file *audit_file_alloc(void)
+{
+ struct audit_file *file;
+
+ file = kmalloc(sizeof(struct audit_file), GFP_KERNEL);
+
+ if (file)
+ file->wentry = NULL;
+
+ return file;
+}
+
+static void audit_file_free(struct audit_file *file)
+{
+ if (file) {
+ audit_wentry_put(file->wentry);
+ file->wentry = NULL;
+ kfree(file);
+ }
+}
+
+static inline void audit_free_files(struct audit_context *context)
+{
+ struct audit_file *file, *tmp;
+
+ list_for_each_entry_safe(file, tmp, &context->wtrail, list) {
+ list_del(&file->list);
+ audit_file_free(file);
+ }
+}
+
static inline void audit_zero_context(struct audit_context *context,
enum audit_state state)
{
@@ -512,6 +565,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 +624,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 +635,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 +682,28 @@ static void audit_log_exit(struct audit_
MINOR(context->names[i].rdev));
audit_log_end(ab);
}
+
+ if (!auditfs_enabled)
+ return;
+
+ list_for_each_entry(file, &context->wtrail, list) {
+ ab = audit_log_start(context);
+ if (!ab)
+ continue;
+
+ /* Do we need more information? */
+ audit_log_format(ab,
+ "name=%s filter_key=%s perm_mask=%u perm=%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 +867,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 +1006,59 @@ uid_t audit_get_loginuid(struct audit_co
{
return ctx ? ctx->loginuid : -1;
}
+
+/* If file/dir has an audit_context and has filesystem auditing
+ * turned on, then if this inode is being watched, collect info
+ * about it.
+ *
+ * Hook appears in:
+ * fs/namie.c:permission(), exec_permission_lite(), vfs_unlink/rmdir
+ *
+ */
+
+int audit_notify_watch(struct inode *inode, int mask)
+{
+ int ret = 0;
+ struct audit_context *context;
+ struct audit_file *file;
+
+ context = current->audit_context;
+
+ if (!auditfs_enabled)
+ goto audit_notify_watch_exit;
+
+ 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;
+}
+