Discussion:
[RFC][PATCH 0/3] CAPP-compliant file system auditing
(too old to reply)
Timothy R. Chavez
2005-03-31 18:16:26 UTC
Permalink
Hello,

.:: Introduction ::.

In its present state, the Linux audit subsystem cannot be used in a Common
Criteria (ISO/IEC 15408)[1] CAPP/EAL4+[2] evaluation. This patch addresses a
blocking deficiency in the current implementation regarding the inability to
audit file system objects by "name". Currently, one is limited to using a
(inode,device) filter rule to audit syscall access to the object. This is
insufficient for CAPP because (1) the object is not being audited by "name"
nor (2) will it remain auditable if the underlying inode changes. What
follows from this requirement is the ability to better observe the _behavior_
of the "named" object, rather then just access to the "named" object.

Here is a relevant example show casing the deficiency:

The administrator audits "/etc/shadow". To do so, she adds the filter rule
using /etc/shadow's current inode and device. Then, she runs 'passwd' to
change her password. She consults the audit log and sees that some records
have been generated, but when she runs 'passwd' again, she notices that no
longer are audit records being generated. She does an 'ls -i /etc/shadow'
and notices that the inode has changed. She then decides to consult the
audit log and comes to the realization that what's there is incomplete and
does not tell the complete story of /etc/shadow during the execuation of
'passwd'.

The patch is broken into two parts.

Part 1: The actual implementation of the file system auditing piece
Part 2: The hooks

+ + + +

[1] Common Criteria is an internationally recognized ISO centered around IT
security evaluations (http://csrc.nist.gov/cc/)
[2] CAPP/EAL4 (Controlled Access Protection Profile)/Evaluation Assurance
Level 4+ is for generalized environments with a moderate level of risk to the
assets. For more information about CAPP requirements:
http://www.commoncriteriaportal.org/public/files/ppfiles/capp.pdf)

-tim
Timothy R. Chavez
2005-03-31 18:17:52 UTC
Permalink
.:: Overview ::.

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

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

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

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

This is _not_ a general purpose file system auditing solution.

--

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

diff -Nurp linux-2.6.11.5/include/linux/audit.h
linux-2.6.11.5~auditfs/include/linux/audit.h
--- linux-2.6.11.5/include/linux/audit.h 2005-03-19 00:34:57.000000000 -0600
+++ linux-2.6.11.5~auditfs/include/linux/audit.h 2005-03-31 11:52:46.000000000
-0600
@@ -24,15 +24,24 @@
#ifndef _LINUX_AUDIT_H_
#define _LINUX_AUDIT_H_

+#ifdef __KERNEL__
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#endif
+
/* Request and reply types */
-#define AUDIT_GET 1000 /* Get status */
-#define AUDIT_SET 1001 /* Set status (enable/disable/auditd) */
-#define AUDIT_LIST 1002 /* List filtering rules */
-#define AUDIT_ADD 1003 /* Add filtering rule */
-#define AUDIT_DEL 1004 /* Delete filtering rule */
-#define AUDIT_USER 1005 /* Send a message from user-space */
-#define AUDIT_LOGIN 1006 /* Define the login id and informaiton */
-#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */
+#define AUDIT_GET 1000 /* Get status */
+#define AUDIT_SET 1001 /* Set status (enable/disable/auditd) */
+#define AUDIT_LIST 1002 /* List filtering rules */
+#define AUDIT_ADD 1003 /* Add filtering rule */
+#define AUDIT_DEL 1004 /* Delete filtering rule */
+#define AUDIT_USER 1005 /* Send a message from user-space */
+#define AUDIT_LOGIN 1006 /* Define the login id and information */
+#define AUDIT_WATCH_INS 1007 /* Insert file/dir watch entry */
+#define AUDIT_WATCH_REM 1008 /* Remove file/dir watch entry */
+#define AUDIT_WATCH_LIST 1009 /* List all watches */
+#define AUDIT_KERNEL 2000 /* Asynchronous record. NOT A REQUEST. */

/* Rule flags */
#define AUDIT_PER_TASK 0x01 /* Apply rule at task creation (not syscall) */
@@ -96,6 +105,9 @@
#define AUDIT_FAIL_PRINTK 1
#define AUDIT_FAIL_PANIC 2

+/* 32 byte max key size */
+#define AUDIT_FILTERKEY_MAX 32
+
#ifndef __KERNEL__
struct audit_message {
struct nlmsghdr nlh;
@@ -123,8 +135,28 @@ struct audit_rule { /* for AUDIT_LIST,
__u32 values[AUDIT_MAX_FIELDS];
};

+struct audit_watch {
+ __u32 namelen;
+ __u32 fklen;
+ char *name;
+ char *filterkey;
+ __u32 perms;
+};
+
#ifdef __KERNEL__

+struct audit_data {
+ struct audit_wentry *wentry;
+ struct hlist_head watchlist;
+ rwlock_t lock;
+};
+
+struct audit_wentry {
+ struct hlist_node w_node;
+ struct audit_watch *w_watch;
+ atomic_t w_count;
+};
+
#ifdef CONFIG_AUDIT
struct audit_buffer;
struct audit_context;
@@ -150,6 +182,7 @@ extern void audit_get_stamp(struct audit
struct timespec *t, int *serial);
extern int audit_set_loginuid(struct audit_context *ctx, uid_t loginuid);
extern uid_t audit_get_loginuid(struct audit_context *ctx);
+extern int audit_notify_watch(struct inode *inode, int mask);
#else
#define audit_alloc(t) ({ 0; })
#define audit_free(t) do { ; } while (0)
@@ -159,6 +192,28 @@ extern uid_t audit_get_loginuid(struct a
#define audit_putname(n) do { ; } while (0)
#define audit_inode(n,i,d) do { ; } while (0)
#define audit_get_loginuid(c) ({ -1; })
+#define audit_notify_watch(i,m) ({ 0; })
+#endif
+
+#ifdef CONFIG_AUDITFILESYSTEM
+extern int audit_list_watches(int type, int pid, int uid, int seq, char
*path);
+extern int audit_receive_watch(int type, int pid, int uid, int seq,
+ struct audit_watch *req);
+extern int audit_filesystem_init(void);
+extern int audit_inode_alloc(struct inode *inode);
+extern void audit_inode_free(struct inode *inode);
+extern void audit_attach_watch(struct dentry *dentry, int remove);
+extern void audit_wentry_put(struct audit_wentry *wentry);
+extern struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry);
+#else
+#define audit_list_watches(t,p,u,s,r) ({ -EOPNOTSUPP; })
+#define audit_receive_watch(t,p,u,s,r) ({ -EOPNOTSUPP; })
+#define audit_filesystem_init() ({ 0; })
+#define audit_inode_alloc(i) ({ 0; })
+#define audit_inode_free(i) do { ; } while(0)
+#define audit_attach_watch(d,r) do { ; } while (0)
+#define audit_wentry_put(w) do { ; } while(0)
+#define audit_wentry_get(w) ({ 0; })
#endif

#ifdef CONFIG_AUDIT
@@ -175,6 +230,10 @@ extern void audit_log_format(struct
extern void audit_log_end(struct audit_buffer *ab);
extern void audit_log_end_fast(struct audit_buffer *ab);
extern void audit_log_end_irq(struct audit_buffer *ab);
+extern void audit_log_hex(struct audit_buffer *ab, const unsigned char
*buf,
+ size_t len);
+extern void audit_log_untrustedstring(struct audit_buffer *ab,
+ const char *string);
extern void audit_log_d_path(struct audit_buffer *ab,
const char *prefix,
struct dentry *dentry,
@@ -197,6 +256,8 @@ extern void audit_log_lost(const ch
#define audit_log_end(b) do { ; } while (0)
#define audit_log_end_fast(b) do { ; } while (0)
#define audit_log_end_irq(b) do { ; } while (0)
+#define audit_log_hex(a,b,l) do { ; } while (0)
+#define audit_log_untrustedstring(a,s) do { ; } while (0)
#define audit_log_d_path(b,p,d,v) do { ; } while (0)
#define audit_set_rate_limit(l) do { ; } while (0)
#define audit_set_backlog_limit(l) do { ; } while (0)
diff -Nurp linux-2.6.11.5/include/linux/fs.h
linux-2.6.11.5~auditfs/include/linux/fs.h
--- linux-2.6.11.5/include/linux/fs.h 2005-03-19 00:34:53.000000000 -0600
+++ linux-2.6.11.5~auditfs/include/linux/fs.h 2005-03-31 11:38:03.000000000
-0600
@@ -477,6 +477,7 @@ struct inode {
unsigned int i_flags;

atomic_t i_writecount;
+ struct audit_data *i_audit;
void *i_security;
union {
void *generic_ip;
diff -Nurp linux-2.6.11.5/init/Kconfig linux-2.6.11.5~auditfs/init/Kconfig
--- linux-2.6.11.5/init/Kconfig 2005-03-31 11:35:27.000000000 -0600
+++ linux-2.6.11.5~auditfs/init/Kconfig 2005-03-31 11:36:33.000000000 -0600
@@ -174,6 +174,16 @@ config AUDITSYSCALL
can be used independently or with another kernel subsystem,
such as SELinux.

+config AUDITFILESYSTEM
+ bool "Enable file system auditing support"
+ depends on AUDITSYSCALL
+ default n
+ help
+ Enable file system auditing for regular files and directories.
+ When a targeted file or directory is accessed, an audit record
+ is generated describing the inode accessed, how it was accessed,
+ and by whom (ie: pid and system call).
+
config LOG_BUF_SHIFT
int "Kernel log buffer size (16 => 64KB, 17 => 128KB)" if DEBUG_KERNEL
range 12 21
diff -Nurp linux-2.6.11.5/kernel/Makefile
linux-2.6.11.5~auditfs/kernel/Makefile
--- linux-2.6.11.5/kernel/Makefile 2005-03-19 00:34:53.000000000 -0600
+++ linux-2.6.11.5~auditfs/kernel/Makefile 2005-03-31 11:37:26.000000000 -0600
@@ -23,6 +23,7 @@ obj-$(CONFIG_IKCONFIG_PROC) += configs.o
obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
obj-$(CONFIG_AUDIT) += audit.o
obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
+obj-$(CONFIG_AUDITFILESYSTEM) += auditfs.o
obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_SYSFS) += ksysfs.o
obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
diff -Nurp linux-2.6.11.5/kernel/audit.c linux-2.6.11.5~auditfs/kernel/audit.c
--- linux-2.6.11.5/kernel/audit.c 2005-03-19 00:35:05.000000000 -0600
+++ linux-2.6.11.5~auditfs/kernel/audit.c 2005-03-31 11:30:54.000000000 -0600
@@ -20,6 +20,7 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Written by Rickard E. (Rik) Faith <***@redhat.com>
+ * Modified by Timothy R. Chavez <***@us.ibm.com>
*
* Goals: 1) Integrate fully with SELinux.
* 2) Minimal run-time overhead:
@@ -319,6 +320,9 @@ static int audit_netlink_ok(kernel_cap_t
case AUDIT_SET:
case AUDIT_ADD:
case AUDIT_DEL:
+ case AUDIT_WATCH_INS:
+ case AUDIT_WATCH_REM:
+ case AUDIT_WATCH_LIST:
if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
err = -EPERM;
break;
@@ -413,6 +417,18 @@ static int audit_receive_msg(struct sk_b
err = -EOPNOTSUPP;
#endif
break;
+ case AUDIT_WATCH_INS:
+ case AUDIT_WATCH_REM:
+ if (nlh->nlmsg_len < sizeof(struct audit_watch))
+ return -EINVAL;
+ err = audit_receive_watch(nlh->nlmsg_type,
+ NETLINK_CB(skb).pid,
+ uid, seq, data);
+ break;
+ case AUDIT_WATCH_LIST:
+ err = audit_list_watches(nlh->nlmsg_type,
+ NETLINK_CB(skb).pid,
+ uid, seq, data);
default:
err = -EINVAL;
break;
@@ -557,6 +573,7 @@ int __init audit_init(void)

audit_initialized = 1;
audit_enabled = audit_default;
+ audit_filesystem_init();
audit_log(NULL, "initialized");
return 0;
}
@@ -717,6 +734,28 @@ void audit_log_format(struct audit_buffe
va_end(args);
}

+void audit_log_hex(struct audit_buffer *ab, const unsigned char *buf, size_t
len)
+{
+ int i;
+
+ for (i=0; i<len; i++)
+ audit_log_format(ab, "%02x", buf[i]);
+}
+
+void audit_log_untrustedstring(struct audit_buffer *ab, const char *string)
+{
+ const unsigned char *p = string;
+
+ while (*p) {
+ if (*p == '"' || *p == ' ' || *p < 0x20 || *p > 0x7f) {
+ audit_log_hex(ab, string, strlen(string));
+ return;
+ }
+ p++;
+ }
+ audit_log_format(ab, "\"%s\"", string);
+}
+
/* This is a helper-function to print the d_path without using a static
* buffer or allocating another buffer in addition to the one in
* audit_buffer. */
diff -Nurp linux-2.6.11.5/kernel/auditfs.c
linux-2.6.11.5~auditfs/kernel/auditfs.c
--- linux-2.6.11.5/kernel/auditfs.c 1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.11.5~auditfs/kernel/auditfs.c 2005-03-31 11:30:54.000000000
-0600
@@ -0,0 +1,511 @@
+/* auditfs.c -- Filesystem auditing support -*- linux-c -*-
+ * Implements filesystem auditing support, depends on kernel/auditsc.c
+ *
+ * Copyright 2005 International Business Machines Corp. (IBM)
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ *
+ * Written by Timothy R. Chavez <***@us.ibm.com>
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/audit.h>
+#include <asm/uaccess.h>
+
+kmem_cache_t *audit_watch_cache;
+kmem_cache_t *audit_wentry_cache;
+
+/* Private Interface */
+
+static inline struct audit_wentry *audit_wentry_fetch(const char *name,
+ struct audit_data *data)
+{
+ struct audit_wentry *wentry, *ret = NULL;
+ struct hlist_node *pos;
+
+ hlist_for_each_entry(wentry, pos, &data->watchlist, w_node)
+ if(!strcmp(wentry->w_watch->name, name)) {
+ ret = audit_wentry_get(wentry);
+ break;
+ }
+
+ return ret;
+}
+
+static inline struct audit_wentry *audit_wentry_fetch_lock(const char *name,
+ struct audit_data *data)
+{
+ struct audit_wentry *ret = NULL;
+
+ if (name && data) {
+ read_lock(&data->lock);
+ ret = audit_wentry_fetch(name, data);
+ read_unlock(&data->lock);
+ }
+
+ return ret;
+}
+
+static inline struct audit_watch *audit_watch_alloc(void)
+{
+ struct audit_watch *watch;
+
+ watch = kmem_cache_alloc(audit_watch_cache, GFP_KERNEL);
+ if (watch) {
+ watch->namelen = 0;
+ watch->fklen = 0;
+ watch->name = NULL;
+ watch->filterkey = NULL;
+ watch->perms = 0;
+ }
+
+ return watch;
+}
+
+static inline void audit_watch_free(struct audit_watch *watch)
+{
+ if (watch) {
+ kfree(watch->name);
+ kfree(watch->filterkey);
+ kmem_cache_free(audit_watch_cache, watch);
+ }
+}
+
+static struct audit_watch *audit_create_watch(const char *name,
+ const char *filterkey,
+ __u32 perms)
+{
+ struct audit_watch *err = NULL;
+ struct audit_watch *watch = NULL;
+
+ err = ERR_PTR(-ENOMEM);
+ watch = audit_watch_alloc();
+ if (watch) {
+ watch->namelen = strlen(name) + 1;
+ watch->name = kmalloc(watch->namelen, GFP_KERNEL);
+ if (!watch->name)
+ goto audit_create_watch_fail;
+ strcpy(watch->name, name);
+
+ if (filterkey) {
+ watch->fklen = strlen(filterkey) + 1;
+ watch->filterkey = kmalloc(watch->fklen, GFP_KERNEL);
+ if (!watch->filterkey)
+ goto audit_create_watch_fail;
+ strcpy(watch->filterkey, filterkey);
+ }
+
+ watch->perms = perms;
+
+ goto audit_create_watch_exit;
+ }
+
+
+audit_create_watch_fail:
+ audit_watch_free(watch);
+ watch = err;
+audit_create_watch_exit:
+ return watch;
+}
+
+static inline struct audit_wentry *audit_wentry_alloc(void)
+{
+ struct audit_wentry *wentry;
+
+ wentry = kmem_cache_alloc(audit_wentry_cache, GFP_KERNEL);
+ if (wentry) {
+ atomic_set(&wentry->w_count, 1);
+ wentry->w_watch = NULL;
+ }
+
+ return wentry;
+}
+
+static inline void audit_wentry_free(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ audit_watch_free(wentry->w_watch);
+ kmem_cache_free(audit_wentry_cache, wentry);
+ }
+}
+
+static inline void audit_destroy_wentry(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ hlist_del_init(&wentry->w_node);
+ audit_wentry_put(wentry);
+ }
+}
+
+struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ BUG_ON(!atomic_read(&wentry->w_count));
+ atomic_inc(&wentry->w_count);
+ }
+
+ return wentry;
+}
+
+void audit_wentry_put(struct audit_wentry *wentry)
+{
+ if (wentry && atomic_dec_and_test(&wentry->w_count))
+ audit_wentry_free(wentry);
+}
+
+/* The only time the new wentry gets updated is when it is inaccessible
+ * (out of the list).
+ */
+static int audit_create_wentry(const char *name,
+ const char *filterkey,
+ __u32 perms, struct audit_data *data)
+{
+ int ret;
+ struct audit_wentry *wentry = NULL;
+ struct audit_wentry *new_wentry = NULL;
+
+ ret = -ENOMEM;
+ new_wentry = audit_wentry_alloc();
+ if (!new_wentry)
+ goto audit_create_wentry_fail;
+
+ new_wentry->w_watch = audit_create_watch(name, filterkey, perms);
+ if (IS_ERR(new_wentry->w_watch)) {
+ ret = PTR_ERR(new_wentry->w_watch);
+ new_wentry->w_watch = NULL;
+ goto audit_create_wentry_fail;
+ }
+
+ ret = 0;
+ write_lock(&data->lock);
+ wentry = audit_wentry_fetch(name, data);
+ if (!wentry) {
+ hlist_add_head(&new_wentry->w_node, &data->watchlist);
+ write_unlock(&data->lock);
+ goto audit_create_wentry_exit;
+ }
+ audit_wentry_put(wentry);
+ write_unlock(&data->lock);
+
+ ret = -EEXIST;
+
+audit_create_wentry_fail:
+ audit_wentry_put(new_wentry);
+audit_create_wentry_exit:
+ return ret;
+}
+
+/* Caller must hold data->lock */
+
+static inline void audit_drain_watchlist(struct audit_data *data)
+{
+ struct audit_wentry *wentry;
+ struct hlist_node *pos, *buf;
+
+ hlist_for_each_entry_safe(wentry, pos, buf, &data->watchlist, w_node)
+ audit_destroy_wentry(wentry);
+}
+
+static inline struct audit_data *audit_data_alloc(void)
+{
+ struct audit_data *data;
+
+ data = kmalloc(sizeof(struct audit_data), GFP_KERNEL);
+ if (data) {
+ data->wentry = NULL;
+ INIT_HLIST_HEAD(&data->watchlist);
+ data->lock = RW_LOCK_UNLOCKED;
+ }
+
+ return data;
+}
+
+static inline void audit_data_free(struct audit_data *data)
+{
+ if (data) {
+ write_lock(&data->lock);
+ audit_drain_watchlist(data);
+ audit_wentry_put(data->wentry);
+ write_unlock(&data->lock);
+ kfree(data);
+ }
+}
+
+static inline int audit_insert_watch(char *path, char *filterkey, __u32
perms)
+{
+ int ret;
+ struct nameidata nd;
+
+ ret = path_lookup(path, LOOKUP_PARENT|LOOKUP_FOLLOW, &nd);
+ if (ret < 0)
+ goto audit_insert_watch_exit;
+
+ ret = -EPERM;
+ if (nd.last_type != LAST_NORM || !nd.last.name)
+ goto audit_insert_watch_release;
+
+ ret = audit_create_wentry(nd.last.name, filterkey, perms,
+ nd.dentry->d_inode->i_audit);
+
+audit_insert_watch_release:
+ path_release(&nd);
+audit_insert_watch_exit:
+ return ret;
+}
+
+static inline int audit_remove_watch(char *path)
+{
+ int ret;
+ struct nameidata nd;
+ struct audit_data *data;
+ struct audit_wentry *wentry;
+
+ ret = path_lookup(path, LOOKUP_PARENT|LOOKUP_FOLLOW, &nd);
+ if (ret < 0)
+ goto audit_remove_watch_exit;
+
+ ret = -EPERM;
+ if (nd.last_type != LAST_NORM || !nd.last.name)
+ goto audit_remove_watch_release;
+
+ data = nd.dentry->d_inode->i_audit;
+
+ write_lock(&data->lock);
+ wentry = audit_wentry_fetch(nd.last.name, data);
+ if (!wentry) {
+ write_unlock(&data->lock);
+ goto audit_remove_watch_release;
+ }
+ audit_destroy_wentry(wentry);
+ audit_wentry_put(wentry);
+ write_unlock(&data->lock);
+
+ ret = 0;
+
+audit_remove_watch_release:
+ path_release(&nd);
+audit_remove_watch_exit:
+ return ret;
+}
+
+/* Eventually we'll want to have serialization routines to send audit_watch
+ * structures to and from the kernel. For the time being, we send back a
+ * watch in a formatted string to the userspace.
+ */
+static inline char *audit_watch_to_string(struct audit_watch *watch)
+{
+ int buflen;
+ char *buf = NULL;;
+
+ if (!watch)
+ goto audit_watch_to_string_exit;
+
+ buflen = watch->namelen + (watch->fklen ? watch->fklen : 6) + 22;
+ buf = kmalloc(buflen, GFP_KERNEL);
+ if (!buf)
+ goto audit_watch_to_string_exit;
+
+ snprintf(buf, buflen, "%s(filterkey=%s, perms=%d)",
+ watch->name, watch->filterkey, watch->perms);
+
+audit_watch_to_string_exit:
+ return buf;
+}
+
+/*
+ * Hook appears in fs/dcache.c:
+ * d_move(),
+ * d_delete(),
+ * d_instantiate(),
+ * d_splice_alias()
+ * __d_lookup()
+ */
+void audit_attach_watch(struct dentry *dentry, int remove)
+{
+ struct audit_wentry *wentry;
+ struct audit_data *data, *parent;
+
+ if (!dentry || !dentry->d_inode)
+ return;
+
+ if (!dentry->d_parent || !dentry->d_parent->d_inode)
+ return;
+
+ data = dentry->d_inode->i_audit;
+ parent = dentry->d_parent->d_inode->i_audit;
+
+ wentry = audit_wentry_fetch_lock(dentry->d_name.name, parent);
+
+ write_lock(&data->lock);
+ /* FIXME: long watchlist == too much spinning? */
+ if (remove) {
+ audit_drain_watchlist(data);
+ if (wentry && data->wentry) {
+ if (!strcmp(wentry->w_watch->name,
+ data->wentry->w_watch->name)) {
+ audit_wentry_put(data->wentry);
+ dentry->d_inode->i_audit->wentry = NULL;
+ }
+ }
+ } else if (!data->wentry || hlist_unhashed(&data->wentry->w_node)) {
+ audit_wentry_put(data->wentry);
+ dentry->d_inode->i_audit->wentry = audit_wentry_get(wentry);
+ }
+ audit_wentry_put(wentry);
+ write_unlock(&data->lock);
+}
+
+int audit_list_watches(int type, int pid, int uid, int seq, char *path)
+{
+ int ret;
+ char *watch = NULL;
+ struct nameidata nd;
+ struct hlist_node *pos;
+ struct audit_data *data;
+ struct audit_wentry *wentry;
+
+ ret = path_lookup(path, LOOKUP_FOLLOW, &nd);
+ if (ret < 0)
+ goto audit_list_watches_exit;
+
+ ret = -ENOMEM;
+ data = nd.dentry->d_inode->i_audit;
+ read_lock(&data->lock);
+ hlist_for_each_entry(wentry, pos, &data->watchlist, w_node) {
+ audit_wentry_get(wentry);
+ watch = audit_watch_to_string(wentry->w_watch);
+ if (!watch)
+ goto audit_list_watches_release;
+ audit_send_reply(pid, seq, AUDIT_WATCH_LIST, 0, 1,
+ watch, strlen(watch) + 1);
+ kfree(watch);
+ audit_wentry_put(wentry);
+ }
+ read_unlock(&data->lock);
+
+ audit_send_reply(pid, seq, AUDIT_WATCH_LIST, 1, 1, NULL, 0);
+ ret = 0;
+
+audit_list_watches_release:
+ path_release(&nd);
+audit_list_watches_exit:
+ return ret;
+}
+
+int audit_receive_watch(int type, int pid, int uid, int seq,
+ struct audit_watch *req)
+{
+ int ret = 0;
+ char *path = NULL;
+ char *filterkey = NULL;
+
+ ret = -EINVAL;
+ path = getname(req->name);
+ if (IS_ERR(path)) {
+ ret = PTR_ERR(path);
+ goto audit_receive_watch_exit;
+ }
+
+ if (!path || strlen(path) + 1 > PATH_MAX)
+ goto audit_receive_watch_exit;
+
+ /* Includes terminating '\0' */
+ if (req->fklen > AUDIT_FILTERKEY_MAX)
+ goto audit_receive_watch_exit;
+
+ if (req->perms > (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND))
+ goto audit_receive_watch_exit;
+
+ if (req->fklen) {
+ ret = -ENOMEM;
+ filterkey = kmalloc(req->fklen, GFP_KERNEL);
+ if (!filterkey)
+ goto audit_receive_watch_exit;
+
+ ret = strncpy_from_user(filterkey, req->filterkey, req->fklen);
+ if (ret < 0)
+ goto audit_receive_watch_exit;
+ }
+
+ switch (type) {
+ case AUDIT_WATCH_INS:
+ ret = audit_insert_watch(path, filterkey, req->perms);
+ break;
+ case AUDIT_WATCH_REM:
+ ret = audit_remove_watch(path);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+audit_receive_watch_exit:
+ putname(path);
+ kfree(filterkey);
+ return ret;
+}
+
+int audit_inode_alloc(struct inode *inode)
+{
+ if (inode) {
+ inode->i_audit = audit_data_alloc();
+ if (!inode->i_audit)
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+void audit_inode_free(struct inode *inode)
+{
+ if (inode)
+ audit_data_free(inode->i_audit);
+}
+
+int audit_filesystem_init()
+{
+ int ret = 0;
+
+ audit_watch_cache =
+ kmem_cache_create("audit_watch_cache",
+ sizeof(struct audit_watch), 0, 0, NULL, NULL);
+ if (!audit_watch_cache)
+ goto audit_filesystem_init_fail;
+
+ audit_wentry_cache =
+ kmem_cache_create("audit_wentry_cache",
+ sizeof(struct audit_wentry), 0, 0, NULL, NULL);
+ if (!audit_wentry_cache)
+ goto audit_filesystem_init_fail;
+
+ goto audit_filesystem_init_exit;
+
+audit_filesystem_init_fail:
+ ret = -ENOMEM;
+ kmem_cache_destroy(audit_watch_cache);
+ kmem_cache_destroy(audit_wentry_cache);
+audit_filesystem_init_exit:
+ return ret;
+
+}
diff -Nurp linux-2.6.11.5/kernel/auditsc.c
linux-2.6.11.5~auditfs/kernel/auditsc.c
--- linux-2.6.11.5/kernel/auditsc.c 2005-03-19 00:35:01.000000000 -0600
+++ linux-2.6.11.5~auditfs/kernel/auditsc.c 2005-03-31 11:30:54.000000000
-0600
@@ -92,6 +92,24 @@ struct audit_names {
dev_t rdev;
};

+struct audit_aux_data {
+ struct audit_aux_data *next;
+ int type;
+};
+
+#define AUDIT_AUX_WATCH 1
+
+struct audit_aux_data_watched {
+ struct audit_aux_data link;
+ struct audit_wentry *wentry;
+ unsigned long ino;
+ int mask;
+ uid_t uid;
+ gid_t gid;
+ dev_t dev;
+ dev_t rdev;
+};
+
/* The per-task audit context. */
struct audit_context {
int in_syscall; /* 1 if task is in a syscall */
@@ -108,6 +126,7 @@ struct audit_context {
struct audit_names names[AUDIT_NAMES];
struct audit_context *previous; /* For nested syscalls */

+ struct audit_aux_data *aux;
/* Save things to print about task_struct */
pid_t pid;
uid_t uid, euid, suid, fsuid;
@@ -134,6 +153,16 @@ struct audit_entry {
struct audit_rule rule;
};

+static inline void audit_free_aux(struct audit_context *context)
+{
+ struct audit_aux_data *aux;
+
+ while ((aux = context->aux)) {
+ context->aux = aux->next;
+ kfree(aux);
+ }
+}
+
/* Check to see if two rules are identical. It is called from
* audit_del_rule during AUDIT_DEL. */
static int audit_compare_rule(struct audit_rule *a, struct audit_rule *b)
@@ -570,6 +599,7 @@ static inline void audit_free_context(st
context->name_count, count);
}
audit_free_names(context);
+ audit_free_aux(context);
kfree(context);
context = previous;
} while (context);
@@ -607,14 +637,49 @@ static void audit_log_exit(struct audit_
context->euid, context->suid, context->fsuid,
context->egid, context->sgid, context->fsgid);
audit_log_end(ab);
+
+ while (context->aux) {
+ struct audit_aux_data *aux;
+
+ ab = audit_log_start(context);
+ if (!ab)
+ continue; /* audit_panic has been called */
+
+ aux = context->aux;
+ context->aux = aux->next;
+
+ audit_log_format(ab, "auxitem=%d", aux->type);
+ switch (aux->type) {
+ case AUDIT_AUX_WATCH: {
+ struct audit_aux_data_watched *axi = (void *)aux;
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, axi->wentry->w_watch->name);
+ audit_log_format(ab,
+ " filterkey=%s perm=%u perm_mask=%d"
+ " inode=%lu inode_uid=%d inode_gid=%d"
+ " inode_dev=%02x:%02x inode_rdev=%02x:%02x",
+ axi->wentry->w_watch->filterkey,
+ axi->wentry->w_watch->perms,
+ axi->mask, axi->ino, axi->uid, axi->gid,
+ MAJOR(axi->dev), MINOR(axi->dev),
+ MAJOR(axi->rdev), MINOR(axi->rdev));
+ audit_wentry_put(axi->wentry);
+ }
+ }
+ audit_log_end(ab);
+ kfree(aux);
+ }
+
for (i = 0; i < context->name_count; i++) {
ab = audit_log_start(context);
if (!ab)
continue; /* audit_panic has been called */
audit_log_format(ab, "item=%d", i);
- if (context->names[i].name)
- audit_log_format(ab, " name=%s",
- context->names[i].name);
+ if (context->names[i].name) {
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, context->names[i].name);
+ }
+
if (context->names[i].ino != (unsigned long)-1)
audit_log_format(ab, " inode=%lu",
context->names[i].ino);
@@ -789,6 +854,7 @@ void audit_syscall_exit(struct task_stru
tsk->audit_context = new_context;
} else {
audit_free_names(context);
+ audit_free_aux(context);
audit_zero_context(context, context->state);
tsk->audit_context = context;
}
@@ -927,3 +993,52 @@ uid_t audit_get_loginuid(struct audit_co
{
return ctx ? ctx->loginuid : -1;
}
+
+int audit_notify_watch(struct inode *inode, int mask)
+{
+ int ret = 0;
+ struct audit_context *context = current->audit_context;
+ struct audit_aux_data_watched *ax;
+ struct audit_wentry *wentry = NULL;
+
+ if (likely(!context))
+ goto audit_notify_watch_fail;
+
+ if (!inode)
+ goto audit_notify_watch_fail;
+
+ wentry = audit_wentry_get(inode->i_audit->wentry);
+ if (!wentry)
+ goto audit_notify_watch_fail;
+
+ if (mask && (wentry->w_watch->perms && !(wentry->w_watch->perms&mask)))
+ goto audit_notify_watch_fail;
+
+ ret = -ENOMEM;
+ ax = kmalloc(sizeof(*ax), GFP_KERNEL);
+ if (!ax)
+ goto audit_notify_watch_fail;
+
+ ret = 0;
+ if (context->in_syscall && !context->auditable)
+ context->auditable = 1;
+
+ ax->wentry = wentry;
+ ax->mask = mask;
+ ax->ino = inode->i_ino;
+ ax->uid = inode->i_uid;
+ ax->gid = inode->i_gid;
+ ax->dev = inode->i_sb->s_dev;
+ ax->rdev = inode->i_rdev;
+
+ ax->link.type = AUDIT_AUX_WATCH;
+ ax->link.next = context->aux;
+ context->aux = (void *)ax;
+
+ goto audit_notify_watch_exit;
+
+audit_notify_watch_fail:
+ audit_wentry_put(wentry);
+audit_notify_watch_exit:
+ return ret;
+}
diff -Nurp linux-2.6.11.5/security/selinux/nlmsgtab.c
linux-2.6.11.5~auditfs/security/selinux/nlmsgtab.c
--- linux-2.6.11.5/security/selinux/nlmsgtab.c 2005-03-19 00:35:01.000000000
-0600
+++ linux-2.6.11.5~auditfs/security/selinux/nlmsgtab.c 2005-03-31
11:36:52.000000000 -0600
@@ -98,6 +98,8 @@ static struct nlmsg_perm nlmsg_audit_per
{ AUDIT_DEL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
{ AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
{ AUDIT_LOGIN, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
+ { AUDIT_WATCH_INS, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
+ { AUDIT_WATCH_REM, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
};


-tim
Stephen Smalley
2005-03-31 19:08:15 UTC
Permalink
Post by Timothy R. Chavez
I suspect there will be questions framed around specific parts of this design
and I will address them as they come. However, please keep in mind that we
are not auditing based on content, but "name".
Or possibly location.
Post by Timothy R. Chavez
This is _not_ a general purpose file system auditing solution.
Ah, bad statement to make when seeking acceptance into a general purpose
operating system. Better to say that this is intended to complement the
existing support for auditing based on (device,inode) pair to fill a
specific gap, namely preservation of audit on particular locations
across transactions?
Post by Timothy R. Chavez
This patch was diffed against linux-2.6.11.5 and introduces the new
functionality to the kernel's audit subsystem.
Diffs against 2.6.11.5 might be fine for an RFC, but for real
submission, you need to be more bleeding edge, e.g. 2.6.12-rc1-mm4 or
whatever the latest one is. Especially as there are already audit-
related patches there.
--
Stephen Smalley <***@tycho.nsa.gov>
National Security Agency
Timothy R. Chavez
2005-03-31 18:55:12 UTC
Permalink
Post by Stephen Smalley
Post by Timothy R. Chavez
I suspect there will be questions framed around specific parts of this
design and I will address them as they come. However, please keep in
mind that we are not auditing based on content, but "name".
Or possibly location.
Post by Timothy R. Chavez
This is _not_ a general purpose file system auditing solution.
Ah, bad statement to make when seeking acceptance into a general purpose
operating system. Better to say that this is intended to complement the
existing support for auditing based on (device,inode) pair to fill a
specific gap, namely preservation of audit on particular locations
across transactions?
Can you tell that I'm an inexperienced kernel developer yet :)? Haha.

-tim
Stephen Smalley
2005-03-31 19:17:38 UTC
Permalink
Post by Timothy R. Chavez
PART 1
This patch was diffed against linux-2.6.11.5 and introduces the new
functionality to the kernel's audit subsystem.
The patch seems to have been line-wrapped.

patching file include/linux/audit.h
patch: **** malformed patch at line 203: *path);
--
Stephen Smalley <***@tycho.nsa.gov>
National Security Agency
Timothy R. Chavez
2005-03-31 19:38:41 UTC
Permalink
Let's try this...

diff -Nurp linux-2.6.11.5/include/linux/audit.h linux-2.6.11.5~auditfs/include/linux/audit.h
--- linux-2.6.11.5/include/linux/audit.h 2005-03-19 00:34:57.000000000 -0600
+++ linux-2.6.11.5~auditfs/include/linux/audit.h 2005-03-31 11:52:46.000000000 -0600
@@ -24,15 +24,24 @@
#ifndef _LINUX_AUDIT_H_
#define _LINUX_AUDIT_H_

+#ifdef __KERNEL__
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#endif
+
/* Request and reply types */
-#define AUDIT_GET 1000 /* Get status */
-#define AUDIT_SET 1001 /* Set status (enable/disable/auditd) */
-#define AUDIT_LIST 1002 /* List filtering rules */
-#define AUDIT_ADD 1003 /* Add filtering rule */
-#define AUDIT_DEL 1004 /* Delete filtering rule */
-#define AUDIT_USER 1005 /* Send a message from user-space */
-#define AUDIT_LOGIN 1006 /* Define the login id and informaiton */
-#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */
+#define AUDIT_GET 1000 /* Get status */
+#define AUDIT_SET 1001 /* Set status (enable/disable/auditd) */
+#define AUDIT_LIST 1002 /* List filtering rules */
+#define AUDIT_ADD 1003 /* Add filtering rule */
+#define AUDIT_DEL 1004 /* Delete filtering rule */
+#define AUDIT_USER 1005 /* Send a message from user-space */
+#define AUDIT_LOGIN 1006 /* Define the login id and information */
+#define AUDIT_WATCH_INS 1007 /* Insert file/dir watch entry */
+#define AUDIT_WATCH_REM 1008 /* Remove file/dir watch entry */
+#define AUDIT_WATCH_LIST 1009 /* List all watches */
+#define AUDIT_KERNEL 2000 /* Asynchronous record. NOT A REQUEST. */

/* Rule flags */
#define AUDIT_PER_TASK 0x01 /* Apply rule at task creation (not syscall) */
@@ -96,6 +105,9 @@
#define AUDIT_FAIL_PRINTK 1
#define AUDIT_FAIL_PANIC 2

+/* 32 byte max key size */
+#define AUDIT_FILTERKEY_MAX 32
+
#ifndef __KERNEL__
struct audit_message {
struct nlmsghdr nlh;
@@ -123,8 +135,28 @@ struct audit_rule { /* for AUDIT_LIST,
__u32 values[AUDIT_MAX_FIELDS];
};

+struct audit_watch {
+ __u32 namelen;
+ __u32 fklen;
+ char *name;
+ char *filterkey;
+ __u32 perms;
+};
+
#ifdef __KERNEL__

+struct audit_data {
+ struct audit_wentry *wentry;
+ struct hlist_head watchlist;
+ rwlock_t lock;
+};
+
+struct audit_wentry {
+ struct hlist_node w_node;
+ struct audit_watch *w_watch;
+ atomic_t w_count;
+};
+
#ifdef CONFIG_AUDIT
struct audit_buffer;
struct audit_context;
@@ -150,6 +182,7 @@ extern void audit_get_stamp(struct audit
struct timespec *t, int *serial);
extern int audit_set_loginuid(struct audit_context *ctx, uid_t loginuid);
extern uid_t audit_get_loginuid(struct audit_context *ctx);
+extern int audit_notify_watch(struct inode *inode, int mask);
#else
#define audit_alloc(t) ({ 0; })
#define audit_free(t) do { ; } while (0)
@@ -159,6 +192,28 @@ extern uid_t audit_get_loginuid(struct a
#define audit_putname(n) do { ; } while (0)
#define audit_inode(n,i,d) do { ; } while (0)
#define audit_get_loginuid(c) ({ -1; })
+#define audit_notify_watch(i,m) ({ 0; })
+#endif
+
+#ifdef CONFIG_AUDITFILESYSTEM
+extern int audit_list_watches(int type, int pid, int uid, int seq, char *path);
+extern int audit_receive_watch(int type, int pid, int uid, int seq,
+ struct audit_watch *req);
+extern int audit_filesystem_init(void);
+extern int audit_inode_alloc(struct inode *inode);
+extern void audit_inode_free(struct inode *inode);
+extern void audit_attach_watch(struct dentry *dentry, int remove);
+extern void audit_wentry_put(struct audit_wentry *wentry);
+extern struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry);
+#else
+#define audit_list_watches(t,p,u,s,r) ({ -EOPNOTSUPP; })
+#define audit_receive_watch(t,p,u,s,r) ({ -EOPNOTSUPP; })
+#define audit_filesystem_init() ({ 0; })
+#define audit_inode_alloc(i) ({ 0; })
+#define audit_inode_free(i) do { ; } while(0)
+#define audit_attach_watch(d,r) do { ; } while (0)
+#define audit_wentry_put(w) do { ; } while(0)
+#define audit_wentry_get(w) ({ 0; })
#endif

#ifdef CONFIG_AUDIT
@@ -175,6 +230,10 @@ extern void audit_log_format(struct
extern void audit_log_end(struct audit_buffer *ab);
extern void audit_log_end_fast(struct audit_buffer *ab);
extern void audit_log_end_irq(struct audit_buffer *ab);
+extern void audit_log_hex(struct audit_buffer *ab, const unsigned char *buf,
+ size_t len);
+extern void audit_log_untrustedstring(struct audit_buffer *ab,
+ const char *string);
extern void audit_log_d_path(struct audit_buffer *ab,
const char *prefix,
struct dentry *dentry,
@@ -197,6 +256,8 @@ extern void audit_log_lost(const ch
#define audit_log_end(b) do { ; } while (0)
#define audit_log_end_fast(b) do { ; } while (0)
#define audit_log_end_irq(b) do { ; } while (0)
+#define audit_log_hex(a,b,l) do { ; } while (0)
+#define audit_log_untrustedstring(a,s) do { ; } while (0)
#define audit_log_d_path(b,p,d,v) do { ; } while (0)
#define audit_set_rate_limit(l) do { ; } while (0)
#define audit_set_backlog_limit(l) do { ; } while (0)
diff -Nurp linux-2.6.11.5/include/linux/fs.h linux-2.6.11.5~auditfs/include/linux/fs.h
--- linux-2.6.11.5/include/linux/fs.h 2005-03-19 00:34:53.000000000 -0600
+++ linux-2.6.11.5~auditfs/include/linux/fs.h 2005-03-31 11:38:03.000000000 -0600
@@ -477,6 +477,7 @@ struct inode {
unsigned int i_flags;

atomic_t i_writecount;
+ struct audit_data *i_audit;
void *i_security;
union {
void *generic_ip;
diff -Nurp linux-2.6.11.5/init/Kconfig linux-2.6.11.5~auditfs/init/Kconfig
--- linux-2.6.11.5/init/Kconfig 2005-03-31 11:35:27.000000000 -0600
+++ linux-2.6.11.5~auditfs/init/Kconfig 2005-03-31 11:36:33.000000000 -0600
@@ -174,6 +174,16 @@ config AUDITSYSCALL
can be used independently or with another kernel subsystem,
such as SELinux.

+config AUDITFILESYSTEM
+ bool "Enable file system auditing support"
+ depends on AUDITSYSCALL
+ default n
+ help
+ Enable file system auditing for regular files and directories.
+ When a targeted file or directory is accessed, an audit record
+ is generated describing the inode accessed, how it was accessed,
+ and by whom (ie: pid and system call).
+
config LOG_BUF_SHIFT
int "Kernel log buffer size (16 => 64KB, 17 => 128KB)" if DEBUG_KERNEL
range 12 21
diff -Nurp linux-2.6.11.5/kernel/Makefile linux-2.6.11.5~auditfs/kernel/Makefile
--- linux-2.6.11.5/kernel/Makefile 2005-03-19 00:34:53.000000000 -0600
+++ linux-2.6.11.5~auditfs/kernel/Makefile 2005-03-31 11:37:26.000000000 -0600
@@ -23,6 +23,7 @@ obj-$(CONFIG_IKCONFIG_PROC) += configs.o
obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
obj-$(CONFIG_AUDIT) += audit.o
obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
+obj-$(CONFIG_AUDITFILESYSTEM) += auditfs.o
obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_SYSFS) += ksysfs.o
obj-$(CONFIG_GENERIC_HARDIRQS) += irq/
diff -Nurp linux-2.6.11.5/kernel/audit.c linux-2.6.11.5~auditfs/kernel/audit.c
--- linux-2.6.11.5/kernel/audit.c 2005-03-19 00:35:05.000000000 -0600
+++ linux-2.6.11.5~auditfs/kernel/audit.c 2005-03-31 11:30:54.000000000 -0600
@@ -20,6 +20,7 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Written by Rickard E. (Rik) Faith <***@redhat.com>
+ * Modified by Timothy R. Chavez <***@us.ibm.com>
*
* Goals: 1) Integrate fully with SELinux.
* 2) Minimal run-time overhead:
@@ -319,6 +320,9 @@ static int audit_netlink_ok(kernel_cap_t
case AUDIT_SET:
case AUDIT_ADD:
case AUDIT_DEL:
+ case AUDIT_WATCH_INS:
+ case AUDIT_WATCH_REM:
+ case AUDIT_WATCH_LIST:
if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
err = -EPERM;
break;
@@ -413,6 +417,18 @@ static int audit_receive_msg(struct sk_b
err = -EOPNOTSUPP;
#endif
break;
+ case AUDIT_WATCH_INS:
+ case AUDIT_WATCH_REM:
+ if (nlh->nlmsg_len < sizeof(struct audit_watch))
+ return -EINVAL;
+ err = audit_receive_watch(nlh->nlmsg_type,
+ NETLINK_CB(skb).pid,
+ uid, seq, data);
+ break;
+ case AUDIT_WATCH_LIST:
+ err = audit_list_watches(nlh->nlmsg_type,
+ NETLINK_CB(skb).pid,
+ uid, seq, data);
default:
err = -EINVAL;
break;
@@ -557,6 +573,7 @@ int __init audit_init(void)

audit_initialized = 1;
audit_enabled = audit_default;
+ audit_filesystem_init();
audit_log(NULL, "initialized");
return 0;
}
@@ -717,6 +734,28 @@ void audit_log_format(struct audit_buffe
va_end(args);
}

+void audit_log_hex(struct audit_buffer *ab, const unsigned char *buf, size_t len)
+{
+ int i;
+
+ for (i=0; i<len; i++)
+ audit_log_format(ab, "%02x", buf[i]);
+}
+
+void audit_log_untrustedstring(struct audit_buffer *ab, const char *string)
+{
+ const unsigned char *p = string;
+
+ while (*p) {
+ if (*p == '"' || *p == ' ' || *p < 0x20 || *p > 0x7f) {
+ audit_log_hex(ab, string, strlen(string));
+ return;
+ }
+ p++;
+ }
+ audit_log_format(ab, "\"%s\"", string);
+}
+
/* This is a helper-function to print the d_path without using a static
* buffer or allocating another buffer in addition to the one in
* audit_buffer. */
diff -Nurp linux-2.6.11.5/kernel/auditfs.c linux-2.6.11.5~auditfs/kernel/auditfs.c
--- linux-2.6.11.5/kernel/auditfs.c 1969-12-31 17:00:00.000000000 -0700
+++ linux-2.6.11.5~auditfs/kernel/auditfs.c 2005-03-31 11:30:54.000000000 -0600
@@ -0,0 +1,511 @@
+/* auditfs.c -- Filesystem auditing support -*- linux-c -*-
+ * Implements filesystem auditing support, depends on kernel/auditsc.c
+ *
+ * Copyright 2005 International Business Machines Corp. (IBM)
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ *
+ * Written by Timothy R. Chavez <***@us.ibm.com>
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/namei.h>
+#include <linux/mount.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/audit.h>
+#include <asm/uaccess.h>
+
+kmem_cache_t *audit_watch_cache;
+kmem_cache_t *audit_wentry_cache;
+
+/* Private Interface */
+
+static inline struct audit_wentry *audit_wentry_fetch(const char *name,
+ struct audit_data *data)
+{
+ struct audit_wentry *wentry, *ret = NULL;
+ struct hlist_node *pos;
+
+ hlist_for_each_entry(wentry, pos, &data->watchlist, w_node)
+ if(!strcmp(wentry->w_watch->name, name)) {
+ ret = audit_wentry_get(wentry);
+ break;
+ }
+
+ return ret;
+}
+
+static inline struct audit_wentry *audit_wentry_fetch_lock(const char *name,
+ struct audit_data *data)
+{
+ struct audit_wentry *ret = NULL;
+
+ if (name && data) {
+ read_lock(&data->lock);
+ ret = audit_wentry_fetch(name, data);
+ read_unlock(&data->lock);
+ }
+
+ return ret;
+}
+
+static inline struct audit_watch *audit_watch_alloc(void)
+{
+ struct audit_watch *watch;
+
+ watch = kmem_cache_alloc(audit_watch_cache, GFP_KERNEL);
+ if (watch) {
+ watch->namelen = 0;
+ watch->fklen = 0;
+ watch->name = NULL;
+ watch->filterkey = NULL;
+ watch->perms = 0;
+ }
+
+ return watch;
+}
+
+static inline void audit_watch_free(struct audit_watch *watch)
+{
+ if (watch) {
+ kfree(watch->name);
+ kfree(watch->filterkey);
+ kmem_cache_free(audit_watch_cache, watch);
+ }
+}
+
+static struct audit_watch *audit_create_watch(const char *name,
+ const char *filterkey,
+ __u32 perms)
+{
+ struct audit_watch *err = NULL;
+ struct audit_watch *watch = NULL;
+
+ err = ERR_PTR(-ENOMEM);
+ watch = audit_watch_alloc();
+ if (watch) {
+ watch->namelen = strlen(name) + 1;
+ watch->name = kmalloc(watch->namelen, GFP_KERNEL);
+ if (!watch->name)
+ goto audit_create_watch_fail;
+ strcpy(watch->name, name);
+
+ if (filterkey) {
+ watch->fklen = strlen(filterkey) + 1;
+ watch->filterkey = kmalloc(watch->fklen, GFP_KERNEL);
+ if (!watch->filterkey)
+ goto audit_create_watch_fail;
+ strcpy(watch->filterkey, filterkey);
+ }
+
+ watch->perms = perms;
+
+ goto audit_create_watch_exit;
+ }
+
+
+audit_create_watch_fail:
+ audit_watch_free(watch);
+ watch = err;
+audit_create_watch_exit:
+ return watch;
+}
+
+static inline struct audit_wentry *audit_wentry_alloc(void)
+{
+ struct audit_wentry *wentry;
+
+ wentry = kmem_cache_alloc(audit_wentry_cache, GFP_KERNEL);
+ if (wentry) {
+ atomic_set(&wentry->w_count, 1);
+ wentry->w_watch = NULL;
+ }
+
+ return wentry;
+}
+
+static inline void audit_wentry_free(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ audit_watch_free(wentry->w_watch);
+ kmem_cache_free(audit_wentry_cache, wentry);
+ }
+}
+
+static inline void audit_destroy_wentry(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ hlist_del_init(&wentry->w_node);
+ audit_wentry_put(wentry);
+ }
+}
+
+struct audit_wentry *audit_wentry_get(struct audit_wentry *wentry)
+{
+ if (wentry) {
+ BUG_ON(!atomic_read(&wentry->w_count));
+ atomic_inc(&wentry->w_count);
+ }
+
+ return wentry;
+}
+
+void audit_wentry_put(struct audit_wentry *wentry)
+{
+ if (wentry && atomic_dec_and_test(&wentry->w_count))
+ audit_wentry_free(wentry);
+}
+
+/* The only time the new wentry gets updated is when it is inaccessible
+ * (out of the list).
+ */
+static int audit_create_wentry(const char *name,
+ const char *filterkey,
+ __u32 perms, struct audit_data *data)
+{
+ int ret;
+ struct audit_wentry *wentry = NULL;
+ struct audit_wentry *new_wentry = NULL;
+
+ ret = -ENOMEM;
+ new_wentry = audit_wentry_alloc();
+ if (!new_wentry)
+ goto audit_create_wentry_fail;
+
+ new_wentry->w_watch = audit_create_watch(name, filterkey, perms);
+ if (IS_ERR(new_wentry->w_watch)) {
+ ret = PTR_ERR(new_wentry->w_watch);
+ new_wentry->w_watch = NULL;
+ goto audit_create_wentry_fail;
+ }
+
+ ret = 0;
+ write_lock(&data->lock);
+ wentry = audit_wentry_fetch(name, data);
+ if (!wentry) {
+ hlist_add_head(&new_wentry->w_node, &data->watchlist);
+ write_unlock(&data->lock);
+ goto audit_create_wentry_exit;
+ }
+ audit_wentry_put(wentry);
+ write_unlock(&data->lock);
+
+ ret = -EEXIST;
+
+audit_create_wentry_fail:
+ audit_wentry_put(new_wentry);
+audit_create_wentry_exit:
+ return ret;
+}
+
+/* Caller must hold data->lock */
+
+static inline void audit_drain_watchlist(struct audit_data *data)
+{
+ struct audit_wentry *wentry;
+ struct hlist_node *pos, *buf;
+
+ hlist_for_each_entry_safe(wentry, pos, buf, &data->watchlist, w_node)
+ audit_destroy_wentry(wentry);
+}
+
+static inline struct audit_data *audit_data_alloc(void)
+{
+ struct audit_data *data;
+
+ data = kmalloc(sizeof(struct audit_data), GFP_KERNEL);
+ if (data) {
+ data->wentry = NULL;
+ INIT_HLIST_HEAD(&data->watchlist);
+ data->lock = RW_LOCK_UNLOCKED;
+ }
+
+ return data;
+}
+
+static inline void audit_data_free(struct audit_data *data)
+{
+ if (data) {
+ write_lock(&data->lock);
+ audit_drain_watchlist(data);
+ audit_wentry_put(data->wentry);
+ write_unlock(&data->lock);
+ kfree(data);
+ }
+}
+
+static inline int audit_insert_watch(char *path, char *filterkey, __u32 perms)
+{
+ int ret;
+ struct nameidata nd;
+
+ ret = path_lookup(path, LOOKUP_PARENT|LOOKUP_FOLLOW, &nd);
+ if (ret < 0)
+ goto audit_insert_watch_exit;
+
+ ret = -EPERM;
+ if (nd.last_type != LAST_NORM || !nd.last.name)
+ goto audit_insert_watch_release;
+
+ ret = audit_create_wentry(nd.last.name, filterkey, perms,
+ nd.dentry->d_inode->i_audit);
+
+audit_insert_watch_release:
+ path_release(&nd);
+audit_insert_watch_exit:
+ return ret;
+}
+
+static inline int audit_remove_watch(char *path)
+{
+ int ret;
+ struct nameidata nd;
+ struct audit_data *data;
+ struct audit_wentry *wentry;
+
+ ret = path_lookup(path, LOOKUP_PARENT|LOOKUP_FOLLOW, &nd);
+ if (ret < 0)
+ goto audit_remove_watch_exit;
+
+ ret = -EPERM;
+ if (nd.last_type != LAST_NORM || !nd.last.name)
+ goto audit_remove_watch_release;
+
+ data = nd.dentry->d_inode->i_audit;
+
+ write_lock(&data->lock);
+ wentry = audit_wentry_fetch(nd.last.name, data);
+ if (!wentry) {
+ write_unlock(&data->lock);
+ goto audit_remove_watch_release;
+ }
+ audit_destroy_wentry(wentry);
+ audit_wentry_put(wentry);
+ write_unlock(&data->lock);
+
+ ret = 0;
+
+audit_remove_watch_release:
+ path_release(&nd);
+audit_remove_watch_exit:
+ return ret;
+}
+
+/* Eventually we'll want to have serialization routines to send audit_watch
+ * structures to and from the kernel. For the time being, we send back a
+ * watch in a formatted string to the userspace.
+ */
+static inline char *audit_watch_to_string(struct audit_watch *watch)
+{
+ int buflen;
+ char *buf = NULL;;
+
+ if (!watch)
+ goto audit_watch_to_string_exit;
+
+ buflen = watch->namelen + (watch->fklen ? watch->fklen : 6) + 22;
+ buf = kmalloc(buflen, GFP_KERNEL);
+ if (!buf)
+ goto audit_watch_to_string_exit;
+
+ snprintf(buf, buflen, "%s(filterkey=%s, perms=%d)",
+ watch->name, watch->filterkey, watch->perms);
+
+audit_watch_to_string_exit:
+ return buf;
+}
+
+/*
+ * Hook appears in fs/dcache.c:
+ * d_move(),
+ * d_delete(),
+ * d_instantiate(),
+ * d_splice_alias()
+ * __d_lookup()
+ */
+void audit_attach_watch(struct dentry *dentry, int remove)
+{
+ struct audit_wentry *wentry;
+ struct audit_data *data, *parent;
+
+ if (!dentry || !dentry->d_inode)
+ return;
+
+ if (!dentry->d_parent || !dentry->d_parent->d_inode)
+ return;
+
+ data = dentry->d_inode->i_audit;
+ parent = dentry->d_parent->d_inode->i_audit;
+
+ wentry = audit_wentry_fetch_lock(dentry->d_name.name, parent);
+
+ write_lock(&data->lock);
+ /* FIXME: long watchlist == too much spinning? */
+ if (remove) {
+ audit_drain_watchlist(data);
+ if (wentry && data->wentry) {
+ if (!strcmp(wentry->w_watch->name,
+ data->wentry->w_watch->name)) {
+ audit_wentry_put(data->wentry);
+ dentry->d_inode->i_audit->wentry = NULL;
+ }
+ }
+ } else if (!data->wentry || hlist_unhashed(&data->wentry->w_node)) {
+ audit_wentry_put(data->wentry);
+ dentry->d_inode->i_audit->wentry = audit_wentry_get(wentry);
+ }
+ audit_wentry_put(wentry);
+ write_unlock(&data->lock);
+}
+
+int audit_list_watches(int type, int pid, int uid, int seq, char *path)
+{
+ int ret;
+ char *watch = NULL;
+ struct nameidata nd;
+ struct hlist_node *pos;
+ struct audit_data *data;
+ struct audit_wentry *wentry;
+
+ ret = path_lookup(path, LOOKUP_FOLLOW, &nd);
+ if (ret < 0)
+ goto audit_list_watches_exit;
+
+ ret = -ENOMEM;
+ data = nd.dentry->d_inode->i_audit;
+ read_lock(&data->lock);
+ hlist_for_each_entry(wentry, pos, &data->watchlist, w_node) {
+ audit_wentry_get(wentry);
+ watch = audit_watch_to_string(wentry->w_watch);
+ if (!watch)
+ goto audit_list_watches_release;
+ audit_send_reply(pid, seq, AUDIT_WATCH_LIST, 0, 1,
+ watch, strlen(watch) + 1);
+ kfree(watch);
+ audit_wentry_put(wentry);
+ }
+ read_unlock(&data->lock);
+
+ audit_send_reply(pid, seq, AUDIT_WATCH_LIST, 1, 1, NULL, 0);
+ ret = 0;
+
+audit_list_watches_release:
+ path_release(&nd);
+audit_list_watches_exit:
+ return ret;
+}
+
+int audit_receive_watch(int type, int pid, int uid, int seq,
+ struct audit_watch *req)
+{
+ int ret = 0;
+ char *path = NULL;
+ char *filterkey = NULL;
+
+ ret = -EINVAL;
+ path = getname(req->name);
+ if (IS_ERR(path)) {
+ ret = PTR_ERR(path);
+ goto audit_receive_watch_exit;
+ }
+
+ if (!path || strlen(path) + 1 > PATH_MAX)
+ goto audit_receive_watch_exit;
+
+ /* Includes terminating '\0' */
+ if (req->fklen > AUDIT_FILTERKEY_MAX)
+ goto audit_receive_watch_exit;
+
+ if (req->perms > (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND))
+ goto audit_receive_watch_exit;
+
+ if (req->fklen) {
+ ret = -ENOMEM;
+ filterkey = kmalloc(req->fklen, GFP_KERNEL);
+ if (!filterkey)
+ goto audit_receive_watch_exit;
+
+ ret = strncpy_from_user(filterkey, req->filterkey, req->fklen);
+ if (ret < 0)
+ goto audit_receive_watch_exit;
+ }
+
+ switch (type) {
+ case AUDIT_WATCH_INS:
+ ret = audit_insert_watch(path, filterkey, req->perms);
+ break;
+ case AUDIT_WATCH_REM:
+ ret = audit_remove_watch(path);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+audit_receive_watch_exit:
+ putname(path);
+ kfree(filterkey);
+ return ret;
+}
+
+int audit_inode_alloc(struct inode *inode)
+{
+ if (inode) {
+ inode->i_audit = audit_data_alloc();
+ if (!inode->i_audit)
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+void audit_inode_free(struct inode *inode)
+{
+ if (inode)
+ audit_data_free(inode->i_audit);
+}
+
+int audit_filesystem_init()
+{
+ int ret = 0;
+
+ audit_watch_cache =
+ kmem_cache_create("audit_watch_cache",
+ sizeof(struct audit_watch), 0, 0, NULL, NULL);
+ if (!audit_watch_cache)
+ goto audit_filesystem_init_fail;
+
+ audit_wentry_cache =
+ kmem_cache_create("audit_wentry_cache",
+ sizeof(struct audit_wentry), 0, 0, NULL, NULL);
+ if (!audit_wentry_cache)
+ goto audit_filesystem_init_fail;
+
+ goto audit_filesystem_init_exit;
+
+audit_filesystem_init_fail:
+ ret = -ENOMEM;
+ kmem_cache_destroy(audit_watch_cache);
+ kmem_cache_destroy(audit_wentry_cache);
+audit_filesystem_init_exit:
+ return ret;
+
+}
diff -Nurp linux-2.6.11.5/kernel/auditsc.c linux-2.6.11.5~auditfs/kernel/auditsc.c
--- linux-2.6.11.5/kernel/auditsc.c 2005-03-19 00:35:01.000000000 -0600
+++ linux-2.6.11.5~auditfs/kernel/auditsc.c 2005-03-31 11:30:54.000000000 -0600
@@ -92,6 +92,24 @@ struct audit_names {
dev_t rdev;
};

+struct audit_aux_data {
+ struct audit_aux_data *next;
+ int type;
+};
+
+#define AUDIT_AUX_WATCH 1
+
+struct audit_aux_data_watched {
+ struct audit_aux_data link;
+ struct audit_wentry *wentry;
+ unsigned long ino;
+ int mask;
+ uid_t uid;
+ gid_t gid;
+ dev_t dev;
+ dev_t rdev;
+};
+
/* The per-task audit context. */
struct audit_context {
int in_syscall; /* 1 if task is in a syscall */
@@ -108,6 +126,7 @@ struct audit_context {
struct audit_names names[AUDIT_NAMES];
struct audit_context *previous; /* For nested syscalls */

+ struct audit_aux_data *aux;
/* Save things to print about task_struct */
pid_t pid;
uid_t uid, euid, suid, fsuid;
@@ -134,6 +153,16 @@ struct audit_entry {
struct audit_rule rule;
};

+static inline void audit_free_aux(struct audit_context *context)
+{
+ struct audit_aux_data *aux;
+
+ while ((aux = context->aux)) {
+ context->aux = aux->next;
+ kfree(aux);
+ }
+}
+
/* Check to see if two rules are identical. It is called from
* audit_del_rule during AUDIT_DEL. */
static int audit_compare_rule(struct audit_rule *a, struct audit_rule *b)
@@ -570,6 +599,7 @@ static inline void audit_free_context(st
context->name_count, count);
}
audit_free_names(context);
+ audit_free_aux(context);
kfree(context);
context = previous;
} while (context);
@@ -607,14 +637,49 @@ static void audit_log_exit(struct audit_
context->euid, context->suid, context->fsuid,
context->egid, context->sgid, context->fsgid);
audit_log_end(ab);
+
+ while (context->aux) {
+ struct audit_aux_data *aux;
+
+ ab = audit_log_start(context);
+ if (!ab)
+ continue; /* audit_panic has been called */
+
+ aux = context->aux;
+ context->aux = aux->next;
+
+ audit_log_format(ab, "auxitem=%d", aux->type);
+ switch (aux->type) {
+ case AUDIT_AUX_WATCH: {
+ struct audit_aux_data_watched *axi = (void *)aux;
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, axi->wentry->w_watch->name);
+ audit_log_format(ab,
+ " filterkey=%s perm=%u perm_mask=%d"
+ " inode=%lu inode_uid=%d inode_gid=%d"
+ " inode_dev=%02x:%02x inode_rdev=%02x:%02x",
+ axi->wentry->w_watch->filterkey,
+ axi->wentry->w_watch->perms,
+ axi->mask, axi->ino, axi->uid, axi->gid,
+ MAJOR(axi->dev), MINOR(axi->dev),
+ MAJOR(axi->rdev), MINOR(axi->rdev));
+ audit_wentry_put(axi->wentry);
+ }
+ }
+ audit_log_end(ab);
+ kfree(aux);
+ }
+
for (i = 0; i < context->name_count; i++) {
ab = audit_log_start(context);
if (!ab)
continue; /* audit_panic has been called */
audit_log_format(ab, "item=%d", i);
- if (context->names[i].name)
- audit_log_format(ab, " name=%s",
- context->names[i].name);
+ if (context->names[i].name) {
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, context->names[i].name);
+ }
+
if (context->names[i].ino != (unsigned long)-1)
audit_log_format(ab, " inode=%lu",
context->names[i].ino);
@@ -789,6 +854,7 @@ void audit_syscall_exit(struct task_stru
tsk->audit_context = new_context;
} else {
audit_free_names(context);
+ audit_free_aux(context);
audit_zero_context(context, context->state);
tsk->audit_context = context;
}
@@ -927,3 +993,52 @@ uid_t audit_get_loginuid(struct audit_co
{
return ctx ? ctx->loginuid : -1;
}
+
+int audit_notify_watch(struct inode *inode, int mask)
+{
+ int ret = 0;
+ struct audit_context *context = current->audit_context;
+ struct audit_aux_data_watched *ax;
+ struct audit_wentry *wentry = NULL;
+
+ if (likely(!context))
+ goto audit_notify_watch_fail;
+
+ if (!inode)
+ goto audit_notify_watch_fail;
+
+ wentry = audit_wentry_get(inode->i_audit->wentry);
+ if (!wentry)
+ goto audit_notify_watch_fail;
+
+ if (mask && (wentry->w_watch->perms && !(wentry->w_watch->perms&mask)))
+ goto audit_notify_watch_fail;
+
+ ret = -ENOMEM;
+ ax = kmalloc(sizeof(*ax), GFP_KERNEL);
+ if (!ax)
+ goto audit_notify_watch_fail;
+
+ ret = 0;
+ if (context->in_syscall && !context->auditable)
+ context->auditable = 1;
+
+ ax->wentry = wentry;
+ ax->mask = mask;
+ ax->ino = inode->i_ino;
+ ax->uid = inode->i_uid;
+ ax->gid = inode->i_gid;
+ ax->dev = inode->i_sb->s_dev;
+ ax->rdev = inode->i_rdev;
+
+ ax->link.type = AUDIT_AUX_WATCH;
+ ax->link.next = context->aux;
+ context->aux = (void *)ax;
+
+ goto audit_notify_watch_exit;
+
+audit_notify_watch_fail:
+ audit_wentry_put(wentry);
+audit_notify_watch_exit:
+ return ret;
+}
diff -Nurp linux-2.6.11.5/security/selinux/nlmsgtab.c linux-2.6.11.5~auditfs/security/selinux/nlmsgtab.c
--- linux-2.6.11.5/security/selinux/nlmsgtab.c 2005-03-19 00:35:01.000000000 -0600
+++ linux-2.6.11.5~auditfs/security/selinux/nlmsgtab.c 2005-03-31 11:36:52.000000000 -0600
@@ -98,6 +98,8 @@ static struct nlmsg_perm nlmsg_audit_per
{ AUDIT_DEL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
{ AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
{ AUDIT_LOGIN, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
+ { AUDIT_WATCH_INS, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
+ { AUDIT_WATCH_REM, NETLINK_AUDIT_SOCKET__NLMSG_WRITE },
};
David Woodhouse
2005-03-31 20:13:02 UTC
Permalink
+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);
When you submit your patch for testing it wants to be against the -mm
tree, which already has the above.
--
dwmw2
Stephen Smalley
2005-03-31 20:05:28 UTC
Permalink
Post by Timothy R. Chavez
Let's try this...
Ah, much better. Thanks.
--
Stephen Smalley <***@tycho.nsa.gov>
National Security Agency
Timothy R. Chavez
2005-03-31 18:22:50 UTC
Permalink
.:: Hooks ::.

1. Setup

Placement in fs/inode.c

audit_inode_alloc()/audit_inode_free():
These hooks are responsible for allocating and deallocating inode audit data.

2. Management

Management of "watches" starts at the dentry level.

Placement in fs/dcache.c

Initial setup:

d_instantiate()/d_splice_alias() :
These hooks cover initial audit setup for inodes that are newly created and
for existing inodes when they are first looked up, prior to becoming
accessible via the dcache.

Update:

__d_lookup():
This hook covers updating the inode audit data if necessary upon subsequent
lookups when the inode is already accessible via dcache, both to reflect
removal of "watches" and to handle changes in state since the initial setup.

d_move():
This hook covers resetting the inode audit data and updating it accordingly
post-move.

Deletion:

dentry_iput():
This hook covers resetting the inode audit data if the dentry being
deleted is at a "watch point".

3. Generation

Any time we access a "watched" object we are auditable. These hooks are used
to notify the audit subsystem of relevant access. Placement in fs/namei.c.

Permissions:

permission()/exec_permission_lite():
Notify the audit subsystem when a "watched" object is consulted about
permissions requirements.

Creation:

vfs_link()/symlink()/create()/mkdir()/mknod():
Notify the audit subsystem when an object has successfully manifested at a
"watch point".

Deletion:

may_delete() [vfs_unlink()/rmdir()]:
Notify the audit subsystem when an object successfully leaves a "watch point".
The hook appears in may_delete() after we have determined the victim dentry
has an inode.

Note: We are unable to use may_create() in a similar fashion to may_delete()
because the inode does/should not exist for the child dentry in question yet.

Rename:

may_delete(),vfs_rename_other()/rename_dir():
Notify the audit subsystem when an object has been moved out of a "watch
point" using the may_delete() hook. Notify the audit subsystem when a object
moves in to a "watch point" using the vfs_rename_other/rename_dir() hooks.

--
PART 2
This patch was diffed against linux-2.6.11.5 and introduces the file system
hooks.

diff -Nurp linux-2.6.11.5/fs/dcache.c linux-2.6.11.5~auditfs/fs/dcache.c
--- linux-2.6.11.5/fs/dcache.c 2005-03-19 00:34:52.000000000 -0600
+++ linux-2.6.11.5~auditfs/fs/dcache.c 2005-03-31 11:31:09.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 */

@@ -96,6 +97,7 @@ static inline void dentry_iput(struct de
{
struct inode *inode = dentry->d_inode;
if (inode) {
+ audit_attach_watch(dentry, 1);
dentry->d_inode = NULL;
list_del_init(&dentry->d_alias);
spin_unlock(&dentry->d_lock);
@@ -798,6 +800,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 +977,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 +987,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 +1091,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;
@@ -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~auditfs/fs/inode.c
--- linux-2.6.11.5/fs/inode.c 2005-03-19 00:35:04.000000000 -0600
+++ linux-2.6.11.5~auditfs/fs/inode.c 2005-03-31 11:31:17.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~auditfs/fs/namei.c
--- linux-2.6.11.5/fs/namei.c 2005-03-19 00:34:54.000000000 -0600
+++ linux-2.6.11.5~auditfs/fs/namei.c 2005-03-31 11:31:24.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);
inode_dir_notify(dir, DN_CREATE);
security_inode_post_create(dir, dentry, mode);
}
@@ -1557,6 +1564,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 +1638,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);
inode_dir_notify(dir, DN_CREATE);
security_inode_post_mkdir(dir,dentry, mode);
}
@@ -1874,6 +1883,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 +1957,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 +2081,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 +2110,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)

-tim
Timothy R. Chavez
2005-03-31 18:24:16 UTC
Permalink
:: Tools ::

To enable the kernel functionality one will need to download the audit-0.6.9
source tree (http://people.redhat.com/sgrubb/audit/) and apply the attached
patch. Read the README-install file for installation instructions. If the
audit daemon is not running (also included in this package), then audit
records will appear in /var/log/syslog rather then /var/log/audit.log. Use
the "auditctl" tool for inserting, removing, and listing watches.

Note: At the time of writing this e-mail the user space / kernel space
interaction is not yet complete. For instance, I'd eventually like to add
serialization routines to both spaces to pass "watch" structures more easily.
There is also talk about another type of watch listing feature that can list
all the watches present in memory.

++++

diff -Nurp audit-0.6.9/lib/libaudit.c audit-0.6.9~scratch/lib/libaudit.c
--- audit-0.6.9/lib/libaudit.c 2005-03-17 16:21:17.000000000 -0600
+++ audit-0.6.9~scratch/lib/libaudit.c 2005-03-29 23:33:51.000000000 -0600
@@ -220,6 +220,15 @@ int audit_remove_watch(int fd, struct au
return rc;
}

+int audit_list_watches(int fd, char *path)
+{
+ int rc = audit_send(fd, AUDIT_WATCH_LIST, path, strlen(path)+1);
+ if (rc < 0)
+ msg(LOG_WARNING, "Error sending watch list request (%s)",
+ strerror(-rc));
+ return rc;
+}
+
int audit_add_rule(int fd, struct audit_rule *rule, int flags, int action)
{
int rc;
diff -Nurp audit-0.6.9/lib/libaudit.h audit-0.6.9~scratch/lib/libaudit.h
--- audit-0.6.9/lib/libaudit.h 2005-03-17 16:20:26.000000000 -0600
+++ audit-0.6.9~scratch/lib/libaudit.h 2005-03-29 23:39:42.000000000 -0600
@@ -38,11 +38,12 @@
#ifndef AUDIT_WATCH_INS
#define AUDIT_WATCH_INS 1007 /* Insert file/dir watch entry */
#define AUDIT_WATCH_REM 1008 /* Remove file/dir watch entry */
+#define AUDIT_WATCH_LIST 1009 /* List watches on dir */
struct audit_watch {
- int namelen;
- int fklen;
- char *name;
- char *filterkey;
+ uint32_t namelen;
+ uint32_t fklen;
+ char *name;
+ char *filterkey;
uint32_t perms;
};
/* 32 byte max key size */
@@ -62,7 +63,7 @@ struct audit_reply {
struct audit_login *login;
const char *message;
struct nlmsgerr *error;
- int watch;
+ const char *watch;
};

struct auditd_reply_list {
@@ -119,6 +120,7 @@ extern int audit_request_list(int fd);
/* AUDIT_WATCH */
extern int audit_insert_watch(int fd, struct audit_watch *req);
extern int audit_remove_watch(int fd, struct audit_watch *req);
+extern int audit_list_watches(int fd, char *path);

/* AUDIT_ADD */
extern int audit_add_rule(int fd, struct audit_rule *rule,
diff -Nurp audit-0.6.9/lib/netlink.c audit-0.6.9~scratch/lib/netlink.c
--- audit-0.6.9/lib/netlink.c 2005-03-17 16:25:21.000000000 -0600
+++ audit-0.6.9~scratch/lib/netlink.c 2005-03-29 23:33:57.000000000 -0600
@@ -132,7 +132,7 @@ static int adjust_reply(struct audit_rep
rep->rule = NULL;
rep->message = NULL;
rep->error = NULL;
- rep->watch = 0;
+ rep->watch = NULL;
if (!NLMSG_OK(rep->nlh, (unsigned int)len))
return 0;
switch (rep->type) {
@@ -150,9 +150,8 @@ static int adjust_reply(struct audit_rep
case AUDIT_USER:
rep->message = NLMSG_DATA(rep->nlh);
break;
- case AUDIT_WATCH_INS:
- case AUDIT_WATCH_REM:
- memcpy(&rep->watch, NLMSG_DATA(rep->nlh), sizeof(int));
+ case AUDIT_WATCH_LIST:
+ rep->watch = NLMSG_DATA(rep->nlh);
break;
}
return len;
diff -Nurp audit-0.6.9/src/auditctl.c audit-0.6.9~scratch/src/auditctl.c
--- audit-0.6.9/src/auditctl.c 2005-03-17 16:24:50.000000000 -0600
+++ audit-0.6.9~scratch/src/auditctl.c 2005-03-29 23:44:10.000000000 -0600
@@ -71,7 +71,8 @@ static int fd = -1;
static int list_requested = 0;
static int syscalladded = 0;
static int add = 0, del = 0, action = 0;
-static int ins = 0, rem = 0;
+static int ins = 0, rem = 0, list = 0;
+static char *path;
static struct audit_rule rule;
static struct audit_watch watch;

@@ -122,10 +123,11 @@ static void usage(void)
" -S syscall Build rule: syscall name or number\n"
" -t <syscall> Translate syscall number to syscall name\n"
" -w <path> Insert watch at <path>\n"
- " -W <path> Remove watch at <path>\n"
- " -p [r|w|e|a] Set permissions filter on watch:\n"
+ " -p [rwea] Set permissions filter on watch:\n"
" r=read, w=write, e=execute, a=append\n"
" -k <key> Set filterkey on watch\n"
+ " -W <path> Remove watch at <path>\n"
+ " -L <path> List all watches at <path>\n"
);
}

@@ -295,7 +297,7 @@ static int setopt(int count, char *vars[
optind = 0;
opterr = 0;
while ((c = getopt(count, vars,
- "hslDe:f:r:b:a:A:d:S:F:m:t:R:w:W:k:p:")) != EOF &&
+ "hslDe:f:r:b:a:A:d:S:F:m:t:R:w:W:L:k:p:")) != EOF &&
retval != -1) {
switch (c) {
case 'h':
@@ -467,6 +469,16 @@ static int setopt(int count, char *vars[
retval = -1;
}
break;
+ case 'L':
+ if (optarg) {
+ list = 1;
+ path = optarg;
+ retval = audit_list_watches(fd, path);
+ } else {
+ fprintf(stderr, "watch option needs a path\n");
+ retval = -1;
+ }
+ break;
case 'k':
if (!ins) {
fprintf(stderr,
@@ -646,13 +658,13 @@ int main(int argc, char *argv[])
/*
* This function is called after setopt to handle the return code.
* status = 0 means just get the reply. Greater than 0 means we
- * are adding or deleting a rule. Less than 0 means an error occurred.
- * Even if there's an error, we need to call this routine to close up the
- * audit fd.
+ * are adding or deleting a rule or watch. Less than 0 means an error
+ * occurred. Even if there's an error, we need to call this routine
+ * to close up the audit fd.
*/
static int handle_request(int status)
{
- if (status == 0)
+ if (list || status == 0)
get_reply();
else if (status > 0) {
int rc;
@@ -669,20 +681,21 @@ static int handle_request(int status)
audit_close(fd);
exit(1);
}
- if (rc > 0) {
- if (audit_request_list(fd) > 0) {
- list_requested = 1;
- get_reply();
+ if (!ins && !rem) {
+ if (rc > 0) {
+ if (audit_request_list(fd) > 0) {
+ list_requested = 1;
+ get_reply();
+ } else {
+ fprintf(stderr, "Error requesting list\n");
+ status = -1;
+ }
} else {
- fprintf(stderr, "Error requesting list\n");
+ fprintf(stderr, "Error sending rule to kernel\n");
status = -1;
- }
- } else {
- fprintf(stderr, "Error sending rule to kernel\n");
- status = -1;
+ }
}
- }
- else {
+ } else {
status = -1;
}
audit_close(fd);
@@ -727,7 +740,7 @@ static int audit_print_reply(struct audi
case NLMSG_NOOP:
return 1;
case NLMSG_DONE:
- if (list_requested)
+ if (list_requested && (!rem && !ins))
printf("No rules\n");
return 0;
case NLMSG_ERROR:
@@ -788,18 +801,16 @@ static int audit_print_reply(struct audi
}
printf("\n");
return 1; /* get more messages, until NLMSG_DONE */
- case AUDIT_WATCH_INS:
- if (rep->watch < 0)
- printf("AUDIT_WATCH : INSERT : %s\n",
strerror(-(rep->watch)));
- else
- printf("AUDIT_WATCH : INSERT : SUCCESS\n");
- return 0;
- case AUDIT_WATCH_REM:
- if (rep->watch < 0)
- printf("AUDIT_WATCH : REMOVE : %s\n",
strerror(-(rep->watch)));
- else
- printf("AUDIT_WATCH : REMOVE : SUCCESS\n");
- return 0;
+ case AUDIT_WATCH_LIST: {
+ if (rep->watch)
+ if (path[strlen(path)-1] == '/')
+ printf("AUDIT_WATCH: LIST: path=%s%s\n",
+ path, rep->watch);
+ else
+ printf("AUDIT_WATCH: LIST: path=%s/%s\n",
+ path, rep->watch);
+ return 1;
+ }
default:
printf("Unknown: type=%d, len=%d\n", rep->type, rep->nlh->nlmsg_len);
return 0;


-tim
Stephen Smalley
2005-03-31 21:02:01 UTC
Permalink
Post by Timothy R. Chavez
To enable the kernel functionality one will need to download the audit-0.6.9
source tree (http://people.redhat.com/sgrubb/audit/) and apply the attached
patch. Read the README-install file for installation instructions. If the
audit daemon is not running (also included in this package), then audit
records will appear in /var/log/syslog rather then /var/log/audit.log. Use
the "auditctl" tool for inserting, removing, and listing watches.
Note: At the time of writing this e-mail the user space / kernel space
interaction is not yet complete. For instance, I'd eventually like to add
serialization routines to both spaces to pass "watch" structures more easily.
There is also talk about another type of watch listing feature that can list
all the watches present in memory.
This one was also line-wrapped, but I fixed it up by hand. After
rebuilding auditctl, I again tried my trusty test case:
auditctl -e 1
auditctl -w /etc/shadow -p w -k SHADOW
passwd

I'm not sure why yet, but I end up with three different inode numbers
involved in the resulting audit messages, two different ones for the two
auxitem records on the shadow watch (which both have name "shadow"), and
a third inode number listed for both /etc/nshadow and /etc/shadow on the
regular item list collected during pathname resolution. For the watch-
generated ones, I expected the same inode number (since it is a rename
and involves no change); for the regular items, I expected
the /etc/nshadow inode number to correspond with that same inode number
(since it is the file that is renamed to /etc/shadow), with
the /etc/shadow inode number being the original inode number of the old
file. Seems to bear investigation...
--
Stephen Smalley <***@tycho.nsa.gov>
National Security Agency
Timothy R. Chavez
2005-04-01 00:00:54 UTC
Permalink
Post by Stephen Smalley
I'm not sure why yet, but I end up with three different inode numbers
involved in the resulting audit messages, two different ones for the two
auxitem records on the shadow watch (which both have name "shadow"), and
a third inode number listed for both /etc/nshadow and /etc/shadow on the
regular item list collected during pathname resolution. For the watch-
generated ones, I expected the same inode number (since it is a rename
and involves no change); for the regular items, I expected
the /etc/nshadow inode number to correspond with that same inode number
(since it is the file that is renamed to /etc/shadow), with
the /etc/shadow inode number being the original inode number of the old
file. Seems to bear investigation...
Its clearer when you audit /etc/nshadow too:

audit(1112312855.601:11168432): syscall=38 exit=0
audit(1112312855.601:11168432): auxitem=1 name="shadow" filterkey=SHADOW
perm=2 perm_mask=2 inode=131125
audit(1112312855.601:11168432): auxitem=1 name="shadow" filterkey=SHADOW
perm=2 perm_mask=2 inode=130359
audit(1112312855.601:11168432): auxitem=1 name="nshadow" filterkey=NSHADOW
perm=2 perm_mask=2 inode=131125
audit(1112312855.601:11168432): item=0 name="/etc/nshadow" inode=130305
audit(1112312855.601:11168432): item=1 name="/etc/shadow" inode=130305

Order is tricky, the last aux record is the first thing that happened. We
move from /etc/nshadow (first may_delete() in vfs_rename()) to /etc/shadow
(second may_delete() in vfs_rename()), which overwrites it with /etc/shadow
(vfs_rename_other())

Also the inode reported in the item records is actually the parent
directory :)

ls -di /etc/
130305 /etc/
--
-tim
Timothy R. Chavez
2005-03-31 18:33:29 UTC
Permalink
Oops, some little mistakes :) I seem to pick up on those only AFTER I send it
out to the entire world. I'll fix it up a little.
--
-tim
Stephen Smalley
2005-03-31 19:02:21 UTC
Permalink
Post by Timothy R. Chavez
In its present state, the Linux audit subsystem cannot be used in a Common
Criteria (ISO/IEC 15408)[1] CAPP/EAL4+[2] evaluation.
Where specifically does CAPP say that you need the particular
functionality that is missing? While I don't think the target audience
will care too much about CC/CAPP per se, it would help to have a more
specific reference than just the entire document. And I'm not sure you
even want to start on this note (as CAPP isn't going to motivate them
much) vs. just starting with an explanation of the missing functionality
that is desired.
Post by Timothy R. Chavez
This is
insufficient for CAPP because (1) the object is not being audited by "name"
Is that truly an issue for CAPP? Where does it say that? I'm a bit
concerned that you are over-emphasizing the importance of the name here.
The issue really is that some locations in the filesystem have well-
defined and security-relevant meaning, right?
Post by Timothy R. Chavez
nor (2) will it remain auditable if the underlying inode changes.
This part is more likely to make sense to the target audience,
especially when coupled with the /etc/shadow example. Again, the point
being that you care about events on any object that is in that location,
regardless of the specific object there.
Post by Timothy R. Chavez
The administrator audits "/etc/shadow". To do so, she adds the filter rule
using /etc/shadow's current inode and device. Then, she runs 'passwd' to
change her password. She consults the audit log and sees that some records
have been generated, but when she runs 'passwd' again, she notices that no
longer are audit records being generated. She does an 'ls -i /etc/shadow'
and notices that the inode has changed. She then decides to consult the
audit log and comes to the realization that what's there is incomplete and
does not tell the complete story of /etc/shadow during the execuation of
'passwd'.
I think you can state this more briefly, i.e. /etc/shadow is a classic
example where we want to preserve auditing on it across transactions,
and each transaction re-creates the file; hence, a simple
(device,inode)-based filter is inadequate or at least would need to be
updated after every transaction.
Post by Timothy R. Chavez
The patch is broken into two parts.
Part 1: The actual implementation of the file system auditing piece
Part 2: The hooks
Hmmm...seems truncated.
--
Stephen Smalley <***@tycho.nsa.gov>
National Security Agency
Timothy R. Chavez
2005-03-31 18:53:30 UTC
Permalink
Post by Stephen Smalley
Post by Timothy R. Chavez
In its present state, the Linux audit subsystem cannot be used in a
Common Criteria (ISO/IEC 15408)[1] CAPP/EAL4+[2] evaluation.
Where specifically does CAPP say that you need the particular
functionality that is missing? While I don't think the target audience
will care too much about CC/CAPP per se, it would help to have a more
specific reference than just the entire document. And I'm not sure you
even want to start on this note (as CAPP isn't going to motivate them
much) vs. just starting with an explanation of the missing functionality
that is desired.
Alright, but the missing functionality is in terms of what CAPP requires. But
I will have another go at it. I do appreciate the comments. I can go ahead
and put at the end, the specific wording in the protection profile, in
addition to a link to the actual document?
Post by Stephen Smalley
Post by Timothy R. Chavez
This is
insufficient for CAPP because (1) the object is not being audited by "name"
Is that truly an issue for CAPP? Where does it say that? I'm a bit
concerned that you are over-emphasizing the importance of the name here.
Well CAPP describes file system objects as "named" objects, so it is my
understanding that auditing by "name" has a different implication then
auditing by (inode,device) and thus, to meet CAPP, the ability to audit by
"name" is important _and_ required.
Post by Stephen Smalley
The issue really is that some locations in the filesystem have well-
defined and security-relevant meaning, right?
Yes. I will work this in. Thanks.
Post by Stephen Smalley
Post by Timothy R. Chavez
nor (2) will it remain auditable if the underlying inode changes.
This part is more likely to make sense to the target audience,
especially when coupled with the /etc/shadow example. Again, the point
being that you care about events on any object that is in that location,
regardless of the specific object there.
Post by Timothy R. Chavez
The administrator audits "/etc/shadow". To do so, she adds the filter
rule using /etc/shadow's current inode and device. Then, she runs
'passwd' to change her password. She consults the audit log and sees
that some records have been generated, but when she runs 'passwd' again,
she notices that no longer are audit records being generated. She does
an 'ls -i /etc/shadow' and notices that the inode has changed. She then
decides to consult the audit log and comes to the realization that what's
there is incomplete and does not tell the complete story of /etc/shadow
during the execuation of 'passwd'.
I think you can state this more briefly, i.e. /etc/shadow is a classic
example where we want to preserve auditing on it across transactions,
and each transaction re-creates the file; hence, a simple
(device,inode)-based filter is inadequate or at least would need to be
updated after every transaction.
Right. Thanks.
Post by Stephen Smalley
Post by Timothy R. Chavez
The patch is broken into two parts.
Part 1: The actual implementation of the file system auditing piece
Part 2: The hooks
Hmmm...seems truncated.
Yeah, I suppose so. Last minute.. I'll provide some more detail. I tried to
split up the exposition logically based patch, but perhaps it certainly
won't hurt to give a more descriptive preview here.


Thanks for the feedback.

-tim
Timothy R. Chavez
2005-03-31 22:46:29 UTC
Permalink
.:: Introduction ::.

The audit subsystem is currently incapable of auditing a file system object
based on its location and name. This is critical for auditing well-defined
and security-relevant files such as /etc/shadow, where auditing on inode and
device is fallible. This patch adds the necessary functionality to the audit
subsystem and VFS to support file system auditing in which an object is
audited based on its location and name.

The patch is split in two.

The first patch is the implementation of file system auditing. The bulk of it
resides in kernel/auditfs.c. It is accompanied by a functional overview of
the design in the next message.

The second patch consists of file system hooks. I anticipate some discussion
with regards to them and wanted to provide some context around their
placements and purpose.

----

There... is that succinct enough?


-tim
Stephen Smalley
2005-04-01 13:55:26 UTC
Permalink
Post by Timothy R. Chavez
.:: Introduction ::.
I'd drop the titles. Just eats up space to no advantage.
Post by Timothy R. Chavez
The audit subsystem is currently incapable of auditing a file system object
based on its location and name. This is critical for auditing well-defined
and security-relevant files such as /etc/shadow, where auditing on inode and
device is fallible. This patch adds the necessary functionality to the audit
subsystem and VFS to support file system auditing in which an object is
audited based on its location and name.
This is much better. Now, I think it is still ok (and likely good) to
mention that this work is being done in order to meet CAPP requirements.
Just add a sentence at the end of this paragraph that notes this fact.
Post by Timothy R. Chavez
The second patch consists of file system hooks. I anticipate some discussion
with regards to them and wanted to provide some context around their
placements and purpose.
The last sentence sounds a little odd; it isn't clear that you are
providing that context in the third message/2nd patch.

BTW, I think I saw some discussion on lkml earlier that suggested that
the whole [0/n] style of submissions was viewed as a nuisance for real
submission, although it may be appropriate for RFC, because it
ultimately means that Andrew has to manually insert the introductory
text into the patches so that it isn't lost when they are merged.
--
Stephen Smalley <***@tycho.nsa.gov>
National Security Agency
Stephen Smalley
2005-04-01 14:05:13 UTC
Permalink
Post by Timothy R. Chavez
The audit subsystem is currently incapable of auditing a file system object
based on its location and name. This is critical for auditing well-defined
and security-relevant files such as /etc/shadow, where auditing on inode and
device is fallible.
You might want to elaborate slightly on what you mean by "fallible",
e.g. rewriting this sentence to:
This is critical for auditing well-defined and security-relevant
locations like /etc/shadow, where the file is re-created on each
transaction and thus (device, inode)-based filters will not ensure
persistence of auditing across transactions.
--
Stephen Smalley <***@tycho.nsa.gov>
National Security Agency
Timothy R. Chavez
2005-04-01 16:47:04 UTC
Permalink
Post by Stephen Smalley
Post by Timothy R. Chavez
The audit subsystem is currently incapable of auditing a file system
object based on its location and name. This is critical for auditing
well-defined and security-relevant files such as /etc/shadow, where
auditing on inode and device is fallible.
You might want to elaborate slightly on what you mean by "fallible",
This is critical for auditing well-defined and security-relevant
locations like /etc/shadow, where the file is re-created on each
transaction and thus (device, inode)-based filters will not ensure
persistence of auditing across transactions.
Hm. Ok...

So how about I do this all in one message, cut out the general overview and
hook explanations and save those for discussion? By the time this goes to
fsdevel there should be an audit package in-sync with the RFC patch.

-tim
Stephen Smalley
2005-04-01 16:51:54 UTC
Permalink
Post by Timothy R. Chavez
Hm. Ok...
So how about I do this all in one message, cut out the general overview and
hook explanations and save those for discussion? By the time this goes to
fsdevel there should be an audit package in-sync with the RFC patch.
Possibly you can do it as two messages, i.e.
[1/2] includes the intro text, hook explanations, and hook patch (which
is the most important piece to get accepted by the kernel developers)
[2/2] includes design/implementation description of the auditfs.c code
and the patch for it (which is mostly just so that they can go look at
it when they aren't sure how you are using a given hook).
--
Stephen Smalley <***@tycho.nsa.gov>
National Security Agency
Timothy R. Chavez
2005-04-01 17:07:35 UTC
Permalink
Post by Stephen Smalley
Post by Timothy R. Chavez
Hm. Ok...
So how about I do this all in one message, cut out the general overview
and hook explanations and save those for discussion? By the time this
goes to fsdevel there should be an audit package in-sync with the RFC
patch.
Possibly you can do it as two messages, i.e.
[1/2] includes the intro text, hook explanations, and hook patch (which
is the most important piece to get accepted by the kernel developers)
[2/2] includes design/implementation description of the auditfs.c code
and the patch for it (which is mostly just so that they can go look at
it when they aren't sure how you are using a given hook).
I'll be out for the rest of the day, unfortunately, but, I'll leave you with
the newest incarnation of the intro message to critique and the rest will be
done by tonight (or early this morning) and I suspect you'll be happily
sleeping by then :)

Hello,

The audit subsystem is currently incapable of auditing a file system object
based on its location and name. This is critical for auditing well-defined
and security-relevant locations such as /etc/shadow, where the inode is
mutable, and can not rely on the (device, inode)-based filters to ensure
persistence of auditing across transactions. This patch adds the necessary
functionality to the audit subsystem and VFS to support file system auditing
in which an object is audited based on its location and name. This work is
being done to make the audit subsystem compliant with Common Criteria's
Controlled Access Protection Profile (CAPP) specification.
--
-tim
Stephen Smalley
2005-04-01 17:38:05 UTC
Permalink
Post by Timothy R. Chavez
The audit subsystem is currently incapable of auditing a file system object
based on its location and name. This is critical for auditing well-defined
and security-relevant locations such as /etc/shadow, where the inode is
mutable,
I think "where the file is re-created on each transaction" is clearer
than "where the inode is mutable". YMMV. To me, the latter just says
that the inode's state can be changed (e.g. its mode, flags, etc), which
isn't quite the same as the issue of having an entirely new inode
created and associated with the /etc/shadow location on every
transaction.
Post by Timothy R. Chavez
and can not rely on the (device, inode)-based filters to ensure
persistence of auditing across transactions. This patch adds the necessary
functionality to the audit subsystem and VFS to support file system auditing
in which an object is audited based on its location and name. This work is
being done to make the audit subsystem compliant with Common Criteria's
Controlled Access Protection Profile (CAPP) specification.
Looks good otherwise.
--
Stephen Smalley <***@tycho.nsa.gov>
National Security Agency
Timothy R. Chavez
2005-03-31 18:42:49 UTC
Permalink
Hello,

.:: Introduction ::.

In its present state, the Linux audit subsystem cannot be used in a Common
Criteria (ISO/IEC 15408)[1] CAPP/EAL4+[2] evaluation.  This patch addresses a
blocking deficiency in the current implementation regarding the inability to
audit file system objects by "name".   Currently, one is limited to using a
(inode,device) filter rule to audit syscall access to the object.  This is
insufficient for CAPP because (1) the object is not being audited or
identified by "name" nor (2) will it remain auditable if the underlying inode
changes.  What follows from this requirement is the ability to better observe
the _behavior_ of the object.

Here is a relevant example show casing the deficiency:

The administrator audits "/etc/shadow".  To do so, she adds the filter rule
using "/etc/shadow"'s inode and device. She then runs 'passwd' and changes
her password. She gives a cusory glance at the audit log and sees that some
records have been generated. However, she runs 'passwd' again, checks the
log, and sees that no new records have been generated. She does an 'ls
-i /etc/shadow' and notices that the inode has changed. Upon closer
inspection of the audit log, she realizes that what's there is actually
incomplete; audit records were lost due to the inode change before 'passwd'
completed execution.

The patch is broken into two parts.

Part 1: The actual implementation of the file system auditing piece
Part 2: The hooks

+ + + +

[1] Common Criteria is an internationally recognized ISO centered around IT
security evaluations (http://csrc.nist.gov/cc/)
[2] CAPP/EAL4 (Controlled Access Protection Profile)/Evaluation Assurance
Level 4+ is for generalized environments with a moderate level of risk to the
assets.  For more information about CAPP requirements:
http://www.commoncriteriaportal.org/public/files/ppfiles/capp.pdf)

-tim
Mounir Bsaibes
2005-03-31 23:53:43 UTC
Permalink
Post by Timothy R. Chavez
.:: Introduction ::.
The audit subsystem is currently incapable of auditing a file system
object
Post by Timothy R. Chavez
based on its location and name. This is critical for auditing
well-defined
Post by Timothy R. Chavez
and security-relevant files such as /etc/shadow, where auditing on inode
and
Post by Timothy R. Chavez
device is fallible. This patch adds the necessary functionality to the
audit
Post by Timothy R. Chavez
subsystem and VFS to support file system auditing in which an object is
audited based on its location and name.
The patch is split in two.
The first patch is the implementation of file system auditing. The
bulk of it
resides in kernel/auditfs.c. It is accompanied by a functional overview
of
Post by Timothy R. Chavez
the design in the next message.
The second patch consists of file system hooks. I anticipate some
discussion
Post by Timothy R. Chavez
with regards to them and wanted to provide some context around their
placements and purpose.
----
There... is that succinct enough?
Yes, much much better. consice, clear and to the point.
You might say it is succinct.
Post by Timothy R. Chavez
-tim
--
Linux-audit mailing list
http://www.redhat.com/mailman/listinfo/linux-audit
Casey Schaufler
2005-04-02 04:17:43 UTC
Permalink
Post by Timothy R. Chavez
Due to the subjective nature of "name", the rules
for auditing a file system
object are fairly strict. In terms of CAPP, the
"name" is any identifier
that a user may specify to access an object in some
fashion.
It is entirely possible that there may be
viable alternatives to what I'm about to
suggest. I know that the argument below worked
for TSCEC B1, CC CAPP/EAL3 and CC LSPP/EAL3.
Use or ignore, as you chose.

An object is uniquely identified by its
device/inode pair. This is the name of the
object. Users of the system do not refer to
objects directly be their names. The interfaces
provided use pathnames and file descriptors
which are translated internally by the system,
based on the process context, into object names.

Thus, the pathname "/etc/shadow" is not the
name of an object, it is a handle that the
system translates on behalf of a process.
Similarly, an open file is accessed by a
file descriptor, which also identifies an
object but it not the object's name.

Consider the case where a file is open and
unlinked. It has no representation in the
file system name space, yet can still be
accessed. You can put the descriptor in
the audit record, but that's process relative.
When this file is closed, the object is
destroyed, having niether a descriptor nor
a pathname to reference it by.


Casey Schaufler
***@schaufler-ca.com



__________________________________
Yahoo! Messenger
Show us what our next emoticon should look like. Join the fun.
http://www.advision.webevents.yahoo.com/emoticontest

Loading...