Commit f368c07d authored by Amy Griffis's avatar Amy Griffis Committed by Al Viro

[PATCH] audit: path-based rules

In this implementation, audit registers inotify watches on the parent
directories of paths specified in audit rules.  When audit's inotify
event handler is called, it updates any affected rules based on the
filesystem event.  If the parent directory is renamed, removed, or its
filesystem is unmounted, audit removes all rules referencing that
inotify watch.

To keep things simple, this implementation limits location-based
auditing to the directory entries in an existing directory.  Given
a path-based rule for /foo/bar/passwd, the following table applies:

    passwd modified -- audit event logged
    passwd replaced -- audit event logged, rules list updated
    bar renamed     -- rule removed
    foo renamed     -- untracked, meaning that the rule now applies to
		       the new location

Audit users typically want to have many rules referencing filesystem
objects, which can significantly impact filtering performance.  This
patch also adds an inode-number-based rule hash to mitigate this
situation.

The patch is relative to the audit git tree:
http://kernel.org/git/?p=linux/kernel/git/viro/audit-current.git;a=summary
and uses the inotify kernel API:
http://lkml.org/lkml/2006/6/1/145Signed-off-by: default avatarAmy Griffis <amy.griffis@hp.com>
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 20ca73bc
......@@ -165,6 +165,7 @@
#define AUDIT_INODE 102
#define AUDIT_EXIT 103
#define AUDIT_SUCCESS 104 /* exit >= 0; value ignored */
#define AUDIT_WATCH 105
#define AUDIT_ARG0 200
#define AUDIT_ARG1 (AUDIT_ARG0+1)
......
......@@ -182,7 +182,8 @@ config AUDITSYSCALL
help
Enable low-overhead system-call auditing infrastructure that
can be used independently or with another kernel subsystem,
such as SELinux.
such as SELinux. To use audit's filesystem watch feature, please
ensure that INOTIFY is configured.
config IKCONFIG
bool "Kernel .config support"
......
......@@ -56,6 +56,7 @@
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/selinux.h>
#include <linux/inotify.h>
#include "audit.h"
......@@ -103,6 +104,12 @@ static atomic_t audit_lost = ATOMIC_INIT(0);
/* The netlink socket. */
static struct sock *audit_sock;
/* Inotify handle. */
struct inotify_handle *audit_ih;
/* Hash for inode-based rules */
struct list_head audit_inode_hash[AUDIT_INODE_BUCKETS];
/* The audit_freelist is a list of pre-allocated audit buffers (if more
* than AUDIT_MAXFREE are in use, the audit buffer is freed instead of
* being placed on the freelist). */
......@@ -115,10 +122,8 @@ static struct task_struct *kauditd_task;
static DECLARE_WAIT_QUEUE_HEAD(kauditd_wait);
static DECLARE_WAIT_QUEUE_HEAD(audit_backlog_wait);
/* The netlink socket is only to be read by 1 CPU, which lets us assume
* that list additions and deletions never happen simultaneously in
* auditsc.c */
DEFINE_MUTEX(audit_netlink_mutex);
/* Serialize requests from userspace. */
static DEFINE_MUTEX(audit_cmd_mutex);
/* AUDIT_BUFSIZ is the size of the temporary buffer used for formatting
* audit records. Since printk uses a 1024 byte buffer, this buffer
......@@ -373,8 +378,8 @@ int audit_send_list(void *_dest)
struct sk_buff *skb;
/* wait for parent to finish and send an ACK */
mutex_lock(&audit_netlink_mutex);
mutex_unlock(&audit_netlink_mutex);
mutex_lock(&audit_cmd_mutex);
mutex_unlock(&audit_cmd_mutex);
while ((skb = __skb_dequeue(&dest->q)) != NULL)
netlink_unicast(audit_sock, skb, pid, 0);
......@@ -665,20 +670,30 @@ static void audit_receive(struct sock *sk, int length)
struct sk_buff *skb;
unsigned int qlen;
mutex_lock(&audit_netlink_mutex);
mutex_lock(&audit_cmd_mutex);
for (qlen = skb_queue_len(&sk->sk_receive_queue); qlen; qlen--) {
skb = skb_dequeue(&sk->sk_receive_queue);
audit_receive_skb(skb);
kfree_skb(skb);
}
mutex_unlock(&audit_netlink_mutex);
mutex_unlock(&audit_cmd_mutex);
}
#ifdef CONFIG_AUDITSYSCALL
static const struct inotify_operations audit_inotify_ops = {
.handle_event = audit_handle_ievent,
.destroy_watch = audit_free_parent,
};
#endif
/* Initialize audit support at boot time. */
static int __init audit_init(void)
{
#ifdef CONFIG_AUDITSYSCALL
int i;
#endif
printk(KERN_INFO "audit: initializing netlink socket (%s)\n",
audit_default ? "enabled" : "disabled");
audit_sock = netlink_kernel_create(NETLINK_AUDIT, 0, audit_receive,
......@@ -697,6 +712,16 @@ static int __init audit_init(void)
selinux_audit_set_callback(&selinux_audit_rule_update);
audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL, "initialized");
#ifdef CONFIG_AUDITSYSCALL
audit_ih = inotify_init(&audit_inotify_ops);
if (IS_ERR(audit_ih))
audit_panic("cannot initialize inotify handle");
for (i = 0; i < AUDIT_INODE_BUCKETS; i++)
INIT_LIST_HEAD(&audit_inode_hash[i]);
#endif
return 0;
}
__initcall(audit_init);
......
......@@ -19,7 +19,6 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/mutex.h>
#include <linux/fs.h>
#include <linux/audit.h>
#include <linux/skbuff.h>
......@@ -54,6 +53,18 @@ enum audit_state {
};
/* Rule lists */
struct audit_parent;
struct audit_watch {
atomic_t count; /* reference count */
char *path; /* insertion path */
dev_t dev; /* associated superblock device */
unsigned long ino; /* associated inode number */
struct audit_parent *parent; /* associated parent */
struct list_head wlist; /* entry in parent->watches list */
struct list_head rules; /* associated rules */
};
struct audit_field {
u32 type;
u32 val;
......@@ -71,6 +82,9 @@ struct audit_krule {
u32 buflen; /* for data alloc on list rules */
u32 field_count;
struct audit_field *fields;
struct audit_field *inode_f; /* quick access to an inode field */
struct audit_watch *watch; /* associated watch */
struct list_head rlist; /* entry in audit_watch.rules list */
};
struct audit_entry {
......@@ -79,10 +93,18 @@ struct audit_entry {
struct audit_krule rule;
};
extern int audit_pid;
extern int audit_comparator(const u32 left, const u32 op, const u32 right);
#define AUDIT_INODE_BUCKETS 32
extern struct list_head audit_inode_hash[AUDIT_INODE_BUCKETS];
static inline int audit_hash_ino(u32 ino)
{
return (ino & (AUDIT_INODE_BUCKETS-1));
}
extern int audit_comparator(const u32 left, const u32 op, const u32 right);
extern int audit_compare_dname_path(const char *dname, const char *path);
extern struct sk_buff * audit_make_reply(int pid, int seq, int type,
int done, int multi,
void *payload, int size);
......@@ -91,7 +113,6 @@ extern void audit_send_reply(int pid, int seq, int type,
void *payload, int size);
extern void audit_log_lost(const char *message);
extern void audit_panic(const char *message);
extern struct mutex audit_netlink_mutex;
struct audit_netlink_list {
int pid;
......@@ -100,6 +121,10 @@ struct audit_netlink_list {
int audit_send_list(void *);
struct inotify_watch;
extern void audit_free_parent(struct inotify_watch *);
extern void audit_handle_ievent(struct inotify_watch *, u32, u32, u32,
const char *, struct inode *);
extern int selinux_audit_rule_update(void);
#ifdef CONFIG_AUDITSYSCALL
......@@ -109,6 +134,11 @@ static inline void audit_signal_info(int sig, struct task_struct *t)
if (unlikely(audit_pid && t->tgid == audit_pid))
__audit_signal_info(sig, t);
}
extern enum audit_state audit_filter_inodes(struct task_struct *,
struct audit_context *);
extern void audit_set_auditable(struct audit_context *);
#else
#define audit_signal_info(s,t)
#define audit_filter_inodes(t,c) AUDIT_DISABLED
#define audit_set_auditable(c)
#endif
This diff is collapsed.
......@@ -200,12 +200,13 @@ struct audit_context {
#endif
};
/* Determine if any context name data matches a rule's watch data */
/* Compare a task_struct with an audit_rule. Return 1 on match, 0
* otherwise. */
static int audit_filter_rules(struct task_struct *tsk,
struct audit_krule *rule,
struct audit_context *ctx,
struct audit_names *name,
enum audit_state *state)
{
int i, j, need_sid = 1;
......@@ -268,7 +269,10 @@ static int audit_filter_rules(struct task_struct *tsk,
}
break;
case AUDIT_DEVMAJOR:
if (ctx) {
if (name)
result = audit_comparator(MAJOR(name->dev),
f->op, f->val);
else if (ctx) {
for (j = 0; j < ctx->name_count; j++) {
if (audit_comparator(MAJOR(ctx->names[j].dev), f->op, f->val)) {
++result;
......@@ -278,7 +282,10 @@ static int audit_filter_rules(struct task_struct *tsk,
}
break;
case AUDIT_DEVMINOR:
if (ctx) {
if (name)
result = audit_comparator(MINOR(name->dev),
f->op, f->val);
else if (ctx) {
for (j = 0; j < ctx->name_count; j++) {
if (audit_comparator(MINOR(ctx->names[j].dev), f->op, f->val)) {
++result;
......@@ -288,7 +295,10 @@ static int audit_filter_rules(struct task_struct *tsk,
}
break;
case AUDIT_INODE:
if (ctx) {
if (name)
result = (name->ino == f->val ||
name->pino == f->val);
else if (ctx) {
for (j = 0; j < ctx->name_count; j++) {
if (audit_comparator(ctx->names[j].ino, f->op, f->val) ||
audit_comparator(ctx->names[j].pino, f->op, f->val)) {
......@@ -298,6 +308,12 @@ static int audit_filter_rules(struct task_struct *tsk,
}
}
break;
case AUDIT_WATCH:
if (name && rule->watch->ino != (unsigned long)-1)
result = (name->dev == rule->watch->dev &&
(name->ino == rule->watch->ino ||
name->pino == rule->watch->ino));
break;
case AUDIT_LOGINUID:
result = 0;
if (ctx)
......@@ -354,7 +370,7 @@ static enum audit_state audit_filter_task(struct task_struct *tsk)
rcu_read_lock();
list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK], list) {
if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {
if (audit_filter_rules(tsk, &e->rule, NULL, NULL, &state)) {
rcu_read_unlock();
return state;
}
......@@ -384,8 +400,9 @@ static enum audit_state audit_filter_syscall(struct task_struct *tsk,
int bit = AUDIT_BIT(ctx->major);
list_for_each_entry_rcu(e, list, list) {
if ((e->rule.mask[word] & bit) == bit
&& audit_filter_rules(tsk, &e->rule, ctx, &state)) {
if ((e->rule.mask[word] & bit) == bit &&
audit_filter_rules(tsk, &e->rule, ctx, NULL,
&state)) {
rcu_read_unlock();
return state;
}
......@@ -395,6 +412,49 @@ static enum audit_state audit_filter_syscall(struct task_struct *tsk,
return AUDIT_BUILD_CONTEXT;
}
/* At syscall exit time, this filter is called if any audit_names[] have been
* collected during syscall processing. We only check rules in sublists at hash
* buckets applicable to the inode numbers in audit_names[].
* Regarding audit_state, same rules apply as for audit_filter_syscall().
*/
enum audit_state audit_filter_inodes(struct task_struct *tsk,
struct audit_context *ctx)
{
int i;
struct audit_entry *e;
enum audit_state state;
if (audit_pid && tsk->tgid == audit_pid)
return AUDIT_DISABLED;
rcu_read_lock();
for (i = 0; i < ctx->name_count; i++) {
int word = AUDIT_WORD(ctx->major);
int bit = AUDIT_BIT(ctx->major);
struct audit_names *n = &ctx->names[i];
int h = audit_hash_ino((u32)n->ino);
struct list_head *list = &audit_inode_hash[h];
if (list_empty(list))
continue;
list_for_each_entry_rcu(e, list, list) {
if ((e->rule.mask[word] & bit) == bit &&
audit_filter_rules(tsk, &e->rule, ctx, n, &state)) {
rcu_read_unlock();
return state;
}
}
}
rcu_read_unlock();
return AUDIT_BUILD_CONTEXT;
}
void audit_set_auditable(struct audit_context *ctx)
{
ctx->auditable = 1;
}
static inline struct audit_context *audit_get_context(struct task_struct *tsk,
int return_valid,
int return_code)
......@@ -408,11 +468,20 @@ static inline struct audit_context *audit_get_context(struct task_struct *tsk,
if (context->in_syscall && !context->auditable) {
enum audit_state state;
state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_EXIT]);
if (state == AUDIT_RECORD_CONTEXT) {
context->auditable = 1;
goto get_context;
}
state = audit_filter_inodes(tsk, context);
if (state == AUDIT_RECORD_CONTEXT)
context->auditable = 1;
}
get_context:
context->pid = tsk->pid;
context->ppid = sys_getppid(); /* sic. tsk == current in all cases */
context->uid = tsk->uid;
......@@ -1142,37 +1211,20 @@ void __audit_inode_child(const char *dname, const struct inode *inode,
return;
/* determine matching parent */
if (dname)
for (idx = 0; idx < context->name_count; idx++)
if (context->names[idx].pino == pino) {
const char *n;
const char *name = context->names[idx].name;
int dlen = strlen(dname);
int nlen = name ? strlen(name) : 0;
if (nlen < dlen)
continue;
/* disregard trailing slashes */
n = name + nlen - 1;
while ((*n == '/') && (n > name))
n--;
/* find last path component */
n = n - dlen + 1;
if (n < name)
continue;
else if (n > name) {
if (*--n != '/')
continue;
else
n++;
}
if (!dname)
goto no_match;
for (idx = 0; idx < context->name_count; idx++)
if (context->names[idx].pino == pino) {
const char *name = context->names[idx].name;
if (strncmp(n, dname, dlen) == 0)
goto update_context;
}
if (!name)
continue;
if (audit_compare_dname_path(dname, name) == 0)
goto update_context;
}
no_match:
/* catch-all in case match not found */
idx = context->name_count++;
context->names[idx].name = NULL;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment