[PATCH] new audit rule interface
by Amy Griffis
Hello,
Follwing is an updated patch which provides an interface for specifing
audit rules with string fields. The code related to filesystem paths
has been removed, and the interface has been generalized to support
string fields for more than one field type.
To summarize the interface, a new struct audit_rule_xprt and
corresponding netlink message types have been added. The new struct
allows userspace to supply one or more string fields packed in a
variable length buffer. The kernel expects the buffer to be neither
null-delimited nor null-terminated.
The length of string data for a given field is provided as its value
element in the array. The kernel provides the buflen element for
convenience on rule listing, to allow userspace to allocate memory for
the buffer without walking the array to tabulate lengths. Buflen is
ignored coming from userspace. Instead, the kernel ensures that the
total of the lengths specified in the value elements do not exceed the
length of the message payload minus sizeof(struct audit_rule_xprt).
Several routines have been added to auditfilter.c to translate between
the kernel's rule representation and the two userspace rule
representations.
This patch resolves the following issues present in the previous
patch:
- splits string fields interface patch from filesystem audit patch.
- struct audit_krule uses a separate field for operator bits
- struct audit_krule tracks which type of operator bits were used,
and sends rule back to userspace as it was supplied
- contents of struct audit_rule_xprt reviewed and solidified
Several of us talked on IRC and decided to split the field flags
away from the field type in the new structure. We also decided
against adding empty fields and versioning the structure.
- documentation related to modified routines has been updated
Signed-off-by: Amy Griffis <amy.griffis(a)hp.com>
diff --git a/include/linux/audit.h b/include/linux/audit.h
index 79d8271..b5da475 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -50,15 +50,18 @@
*/
#define AUDIT_GET 1000 /* Get status */
#define AUDIT_SET 1001 /* Set status (enable/disable/auditd) */
-#define AUDIT_LIST 1002 /* List syscall filtering rules */
-#define AUDIT_ADD 1003 /* Add syscall filtering rule */
-#define AUDIT_DEL 1004 /* Delete syscall filtering rule */
+#define AUDIT_LIST 1002 /* List syscall rules -- deprecated */
+#define AUDIT_ADD 1003 /* Add syscall rule -- deprecated */
+#define AUDIT_DEL 1004 /* Delete syscall rule -- deprecated */
#define AUDIT_USER 1005 /* Message from userspace -- deprecated */
#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 file/dir watches */
#define AUDIT_SIGNAL_INFO 1010 /* Get info about sender of signal to auditd */
+#define AUDIT_ADD_RULE 1011 /* Add syscall filtering rule */
+#define AUDIT_DEL_RULE 1012 /* Delete syscall filtering rule */
+#define AUDIT_LIST_RULES 1013 /* List syscall filtering rules */
#define AUDIT_FIRST_USER_MSG 1100 /* Userspace messages mostly uninteresting to kernel */
#define AUDIT_USER_AVC 1107 /* We filter this differently */
@@ -226,6 +229,26 @@ struct audit_status {
__u32 backlog; /* messages waiting in queue */
};
+/* audit_rule_xprt supports filter rules with both integer and string
+ * fields. It corresponds with AUDIT_ADD_RULE, AUDIT_DEL_RULE and
+ * AUDIT_LIST_RULES requests.
+ */
+struct audit_rule_xprt {
+ __u32 flags; /* AUDIT_PER_{TASK,CALL}, AUDIT_PREPEND */
+ __u32 action; /* AUDIT_NEVER, AUDIT_POSSIBLE, AUDIT_ALWAYS */
+ __u32 field_count;
+ __u32 mask[AUDIT_BITMASK_SIZE];
+ __u32 fields[AUDIT_MAX_FIELDS];
+ __u32 values[AUDIT_MAX_FIELDS];
+ __u32 fieldflags[AUDIT_MAX_FIELDS];
+ __u32 buflen; /* total length of string fields */
+ char buf[0]; /* string fields buffer */
+};
+
+/* audit_rule is supported to maintain backward compatibility with
+ * userspace. It supports integer fields only and corresponds to
+ * AUDIT_ADD, AUDIT_DEL and AUDIT_LIST requests.
+ */
struct audit_rule { /* for AUDIT_LIST, AUDIT_ADD, and AUDIT_DEL */
__u32 flags; /* AUDIT_PER_{TASK,CALL}, AUDIT_PREPEND */
__u32 action; /* AUDIT_NEVER, AUDIT_POSSIBLE, AUDIT_ALWAYS */
@@ -337,7 +360,7 @@ extern void audit_log_d_path(struct
extern int audit_filter_user(struct netlink_skb_parms *cb, int type);
extern int audit_filter_type(int type);
extern int audit_receive_filter(int type, int pid, int uid, int seq,
- void *data, uid_t loginuid);
+ void *data, int dlen, uid_t loginuid);
#else
#define audit_log(c,g,t,f,...) do { ; } while (0)
#define audit_log_start(c,g,t) ({ NULL; })
diff --git a/kernel/audit.c b/kernel/audit.c
index d95efd6..d55e8c5 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -52,6 +52,7 @@
#include <linux/audit.h>
#include <net/sock.h>
+#include <net/netlink.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
@@ -361,9 +362,12 @@ static int audit_netlink_ok(kernel_cap_t
switch (msg_type) {
case AUDIT_GET:
case AUDIT_LIST:
+ case AUDIT_LIST_RULES:
case AUDIT_SET:
case AUDIT_ADD:
+ case AUDIT_ADD_RULE:
case AUDIT_DEL:
+ case AUDIT_DEL_RULE:
case AUDIT_SIGNAL_INFO:
if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
err = -EPERM;
@@ -470,12 +474,23 @@ static int audit_receive_msg(struct sk_b
break;
case AUDIT_ADD:
case AUDIT_DEL:
- if (nlh->nlmsg_len < sizeof(struct audit_rule))
+ if (nlmsg_len(nlh) < sizeof(struct audit_rule))
return -EINVAL;
/* fallthrough */
case AUDIT_LIST:
err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid,
- uid, seq, data, loginuid);
+ uid, seq, data, nlmsg_len(nlh),
+ loginuid);
+ break;
+ case AUDIT_ADD_RULE:
+ case AUDIT_DEL_RULE:
+ if (nlmsg_len(nlh) < sizeof(struct audit_rule_xprt))
+ return -EINVAL;
+ /* fallthrough */
+ case AUDIT_LIST_RULES:
+ err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid,
+ uid, seq, data, nlmsg_len(nlh),
+ loginuid);
break;
case AUDIT_SIGNAL_INFO:
sig_data.uid = audit_sig_uid;
diff --git a/kernel/audit.h b/kernel/audit.h
index 7643e46..3e69ab3 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -52,12 +52,28 @@ enum audit_state {
};
/* Rule lists */
-struct audit_entry {
- struct list_head list;
- struct rcu_head rcu;
- struct audit_rule rule;
+struct audit_field {
+ u32 type;
+ u32 val;
+ u32 op;
+};
+
+struct audit_krule {
+ int vers_ops;
+ u32 flags;
+ u32 listnr;
+ u32 action;
+ u32 mask[AUDIT_BITMASK_SIZE];
+ u32 buflen; /* for data alloc on list rules */
+ u32 field_count;
+ struct audit_field fields[AUDIT_MAX_FIELDS];
};
+struct audit_entry {
+ struct list_head list;
+ struct rcu_head rcu;
+ struct audit_krule rule;
+};
extern int audit_pid;
extern int audit_comparator(const u32 left, const u32 op, const u32 right);
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index a3a3275..1fd4b2a 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -40,52 +40,253 @@ struct list_head audit_filter_list[AUDIT
#endif
};
-/* Copy rule from user-space to kernel-space. Called from
- * audit_add_rule during AUDIT_ADD. */
-static inline int audit_copy_rule(struct audit_rule *d, struct audit_rule *s)
+/* Unpack a filter field's string representation from user-space
+ * buffer. */
+static inline char *audit_unpack_string(void **bufp, int *remaining, int len)
{
+ char *str;
+
+ if (!*bufp || (len > *remaining))
+ return ERR_PTR(-EINVAL);
+
+ str = kmalloc(len + 1, GFP_KERNEL);
+ if (!str)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(str, *bufp, len);
+ str[len] = 0;
+ *bufp += len;
+ *remaining -= len;
+
+ return str;
+}
+
+/* Common user-space to kernel rule translation. */
+static struct audit_entry *audit_to_entry_common(struct audit_rule *rule)
+{
+ unsigned listnr;
+ struct audit_entry *entry;
+ int i, err;
+
+ err = -EINVAL;
+ listnr = rule->flags & ~AUDIT_FILTER_PREPEND;
+ switch(listnr) {
+ default:
+ goto exit_err;
+ case AUDIT_FILTER_USER:
+ case AUDIT_FILTER_TYPE:
+#ifdef CONFIG_AUDITSYSCALL
+ case AUDIT_FILTER_ENTRY:
+ case AUDIT_FILTER_EXIT:
+ case AUDIT_FILTER_TASK:
+#endif
+ ;
+ }
+ if (rule->action != AUDIT_NEVER && rule->action != AUDIT_POSSIBLE &&
+ rule->action != AUDIT_ALWAYS)
+ goto exit_err;
+ if (rule->field_count < 0 || rule->field_count > AUDIT_MAX_FIELDS)
+ goto exit_err;
+
+ err = -ENOMEM;
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ goto exit_err;
+ memset(&entry->rule, 0, sizeof(struct audit_krule));
+
+ entry->rule.flags = rule->flags & AUDIT_FILTER_PREPEND;
+ entry->rule.listnr = listnr;
+ entry->rule.action = rule->action;
+ entry->rule.field_count = rule->field_count;
+
+ for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
+ entry->rule.mask[i] = rule->mask[i];
+
+ return entry;
+
+exit_err:
+ return ERR_PTR(err);
+}
+
+/* Translate struct audit_rule to kernel's rule respresentation.
+ * Exists for backward compatibility with userspace. */
+static struct audit_entry *audit_rule_to_entry(struct audit_rule *rule)
+{
+ struct audit_entry *entry;
+ int err = 0;
int i;
- if (s->action != AUDIT_NEVER
- && s->action != AUDIT_POSSIBLE
- && s->action != AUDIT_ALWAYS)
- return -1;
- if (s->field_count < 0 || s->field_count > AUDIT_MAX_FIELDS)
- return -1;
- if ((s->flags & ~AUDIT_FILTER_PREPEND) >= AUDIT_NR_FILTERS)
- return -1;
-
- d->flags = s->flags;
- d->action = s->action;
- d->field_count = s->field_count;
- for (i = 0; i < d->field_count; i++) {
- d->fields[i] = s->fields[i];
- d->values[i] = s->values[i];
+ entry = audit_to_entry_common(rule);
+ if (IS_ERR(entry))
+ goto exit_nofree;
+
+ for (i = 0; i < rule->field_count; i++) {
+ struct audit_field *f = &entry->rule.fields[i];
+
+ if (rule->fields[i] & AUDIT_UNUSED_BITS) {
+ err = -EINVAL;
+ goto exit_free;
+ }
+
+ f->op = rule->fields[i] & (AUDIT_NEGATE|AUDIT_OPERATORS);
+ f->type = rule->fields[i] & (~AUDIT_NEGATE|AUDIT_OPERATORS);
+ f->val = rule->values[i];
+
+ entry->rule.vers_ops = (f->op & AUDIT_OPERATORS) ? 2 : 1;
+ if (f->op & AUDIT_NEGATE)
+ f->op |= AUDIT_NOT_EQUAL;
+ else if (!(f->op & AUDIT_OPERATORS))
+ f->op |= AUDIT_EQUAL;
+ f->op &= ~AUDIT_NEGATE;
}
- for (i = 0; i < AUDIT_BITMASK_SIZE; i++) d->mask[i] = s->mask[i];
- return 0;
+
+exit_nofree:
+ return entry;
+
+exit_free:
+ kfree(entry);
+ return ERR_PTR(err);
}
-/* Check to see if two rules are identical. It is called from
- * audit_add_rule during AUDIT_ADD and
- * audit_del_rule during AUDIT_DEL. */
-static int audit_compare_rule(struct audit_rule *a, struct audit_rule *b)
+/* Translate struct audit_rule_xprt to kernel's rule respresentation. */
+static struct audit_entry *audit_xprt_to_entry(struct audit_rule_xprt *xprt,
+ int dlen)
{
+ int err = 0;
+ struct audit_entry *entry;
+ void *bufp;
+ /* int remaining = dlen - sizeof(struct audit_rule_xprt); */
int i;
- if (a->flags != b->flags)
- return 1;
+ entry = audit_to_entry_common((struct audit_rule *)xprt);
+ if (IS_ERR(entry))
+ goto exit_nofree;
+
+ bufp = xprt->buf;
+ entry->rule.vers_ops = 2;
+ for (i = 0; i < xprt->field_count; i++) {
+ struct audit_field *f = &entry->rule.fields[i];
+
+ if (!(xprt->fieldflags[i] & AUDIT_OPERATORS) ||
+ xprt->fieldflags[i] & ~AUDIT_OPERATORS) {
+ err = -EINVAL;
+ goto exit_free;
+ }
- if (a->action != b->action)
- return 1;
+ f->op = xprt->fieldflags[i] & AUDIT_OPERATORS;
+ f->type = xprt->fields[i];
+ switch(f->type) {
+ /* call type-specific conversion routines here */
+ default:
+ f->val = xprt->values[i];
+ }
+ }
+
+exit_nofree:
+ return entry;
+
+exit_free:
+ kfree(entry);
+ return ERR_PTR(err);
+}
+
+/* Pack a filter field's string representation into data block. */
+static inline int audit_pack_string(void **bufp, char *str)
+{
+ int len = strlen(str);
+
+ memcpy(*bufp, str, len);
+ *bufp += len;
+
+ return len;
+}
+
+/* Translate kernel rule respresentation to struct audit_rule.
+ * Exists for backward compatibility with userspace. */
+static struct audit_rule *audit_krule_to_rule(struct audit_krule *krule)
+{
+ struct audit_rule *rule;
+ int i;
+
+ rule = kmalloc(sizeof(*rule), GFP_KERNEL);
+ if (!rule)
+ return ERR_PTR(-ENOMEM);
+ memset(rule, 0, sizeof(*rule));
+
+ rule->flags = krule->flags | krule->listnr;
+ rule->action = krule->action;
+ rule->field_count = krule->field_count;
+ for (i = 0; i < rule->field_count; i++) {
+ rule->values[i] = krule->fields[i].val;
+ rule->fields[i] = krule->fields[i].type;
+
+ if (krule->vers_ops == 1) {
+ if (krule->fields[i].op & AUDIT_NOT_EQUAL)
+ rule->fields[i] |= AUDIT_NEGATE;
+ } else {
+ rule->fields[i] |= krule->fields[i].op;
+ }
+ }
+ for (i = 0; i < AUDIT_BITMASK_SIZE; i++) rule->mask[i] = krule->mask[i];
+
+ return rule;
+}
+
+/* Translate kernel rule respresentation to struct audit_rule_xprt. */
+static struct audit_rule_xprt *audit_krule_to_xprt(struct audit_krule *krule)
+{
+ struct audit_rule_xprt *xprt;
+ void *bufp;
+ int i;
+
+ xprt = kmalloc(sizeof(*xprt) + krule->buflen, GFP_KERNEL);
+ if (!xprt)
+ return ERR_PTR(-ENOMEM);
+ memset(xprt, 0, sizeof(*xprt));
+
+ xprt->flags = krule->flags | krule->listnr;
+ xprt->action = krule->action;
+ xprt->field_count = krule->field_count;
+ bufp = xprt->buf;
+ for (i = 0; i < xprt->field_count; i++) {
+ struct audit_field *f = &krule->fields[i];
+
+ xprt->fields[i] = f->type;
+ xprt->fieldflags[i] = f->op;
+ switch(f->type) {
+ /* call type-specific conversion routines here */
+ default:
+ xprt->values[i] = f->val;
+ }
+ }
+ for (i = 0; i < AUDIT_BITMASK_SIZE; i++) xprt->mask[i] = krule->mask[i];
+
+ return xprt;
+}
- if (a->field_count != b->field_count)
+/* Compare two rules in kernel format. Considered success if rules
+ * don't match. */
+static int audit_compare_rule(struct audit_krule *a, struct audit_krule *b)
+{
+ int i;
+
+ if (a->flags != b->flags ||
+ a->listnr != b->listnr ||
+ a->action != b->action ||
+ a->field_count != b->field_count)
return 1;
for (i = 0; i < a->field_count; i++) {
- if (a->fields[i] != b->fields[i]
- || a->values[i] != b->values[i])
+ if (a->fields[i].type != b->fields[i].type ||
+ a->fields[i].op != b->fields[i].op)
return 1;
+
+ switch(a->fields[i].type) {
+ /* call type-specific comparison routines here */
+ default:
+ if (a->fields[i].val != b->fields[i].val)
+ return 1;
+ }
}
for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
@@ -95,41 +296,21 @@ static int audit_compare_rule(struct aud
return 0;
}
-/* Note that audit_add_rule and audit_del_rule are called via
- * audit_receive() in audit.c, and are protected by
+/* Add rule to given filterlist if not a duplicate. Protected by
* audit_netlink_sem. */
-static inline int audit_add_rule(struct audit_rule *rule,
- struct list_head *list)
+static inline int audit_add_rule(struct audit_entry *entry,
+ struct list_head *list)
{
- struct audit_entry *entry;
- int i;
+ struct audit_entry *e;
/* Do not use the _rcu iterator here, since this is the only
* addition routine. */
- list_for_each_entry(entry, list, list) {
- if (!audit_compare_rule(rule, &entry->rule))
+ list_for_each_entry(e, list, list) {
+ if (!audit_compare_rule(&entry->rule, &e->rule))
return -EEXIST;
}
- for (i = 0; i < rule->field_count; i++) {
- if (rule->fields[i] & AUDIT_UNUSED_BITS)
- return -EINVAL;
- if ( rule->fields[i] & AUDIT_NEGATE)
- rule->fields[i] |= AUDIT_NOT_EQUAL;
- else if ( (rule->fields[i] & AUDIT_OPERATORS) == 0 )
- rule->fields[i] |= AUDIT_EQUAL;
- rule->fields[i] &= ~AUDIT_NEGATE;
- }
-
- if (!(entry = kmalloc(sizeof(*entry), GFP_KERNEL)))
- return -ENOMEM;
- if (audit_copy_rule(&entry->rule, rule)) {
- kfree(entry);
- return -EINVAL;
- }
-
if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
- entry->rule.flags &= ~AUDIT_FILTER_PREPEND;
list_add_rcu(&entry->list, list);
} else {
list_add_tail_rcu(&entry->list, list);
@@ -144,10 +325,9 @@ static inline void audit_free_rule(struc
kfree(e);
}
-/* Note that audit_add_rule and audit_del_rule are called via
- * audit_receive() in audit.c, and are protected by
+/* Remove an existing rule from filterlist. Protected by
* audit_netlink_sem. */
-static inline int audit_del_rule(struct audit_rule *rule,
+static inline int audit_del_rule(struct audit_entry *entry,
struct list_head *list)
{
struct audit_entry *e;
@@ -155,7 +335,7 @@ static inline int audit_del_rule(struct
/* Do not use the _rcu iterator here, since this is the only
* deletion routine. */
list_for_each_entry(e, list, list) {
- if (!audit_compare_rule(rule, &e->rule)) {
+ if (!audit_compare_rule(&entry->rule, &e->rule)) {
list_del_rcu(&e->list);
call_rcu(&e->rcu, audit_free_rule);
return 0;
@@ -164,7 +344,9 @@ static inline int audit_del_rule(struct
return -ENOENT; /* No matching rule */
}
-static int audit_list_rules(void *_dest)
+/* List rules using struct audit_rule. Exists for backward
+ * compatibility with userspace. */
+static int audit_list(void *_dest)
{
int pid, seq;
int *dest = _dest;
@@ -180,9 +362,16 @@ static int audit_list_rules(void *_dest)
/* The *_rcu iterators not needed here because we are
always called with audit_netlink_sem held. */
for (i=0; i<AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(entry, &audit_filter_list[i], list)
+ list_for_each_entry(entry, &audit_filter_list[i], list) {
+ struct audit_rule *rule;
+
+ rule = audit_krule_to_rule(&entry->rule);
+ if (!rule)
+ break;
audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
- &entry->rule, sizeof(entry->rule));
+ rule, sizeof(*rule));
+ kfree(rule);
+ }
}
audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
@@ -190,6 +379,40 @@ static int audit_list_rules(void *_dest)
return 0;
}
+/* List rules using struct audit_rule_xprt. */
+static int audit_list_rules(void *_dest)
+{
+ int pid, seq;
+ int *dest = _dest;
+ struct audit_entry *e;
+ int i;
+
+ pid = dest[0];
+ seq = dest[1];
+ kfree(dest);
+
+ down(&audit_netlink_sem);
+
+ /* The *_rcu iterators not needed here because we are
+ always called with audit_netlink_sem held. */
+ for (i=0; i<AUDIT_NR_FILTERS; i++) {
+ list_for_each_entry(e, &audit_filter_list[i], list) {
+ struct audit_rule_xprt *xprt;
+
+ xprt = audit_krule_to_xprt(&e->rule);
+ if (!xprt)
+ break;
+ audit_send_reply(pid, seq, AUDIT_LIST_RULES, 0, 1,
+ xprt, sizeof(*xprt));
+ kfree(xprt);
+ }
+ }
+ audit_send_reply(pid, seq, AUDIT_LIST_RULES, 1, 1, NULL, 0);
+
+ up(&audit_netlink_sem);
+ return 0;
+}
+
/**
* audit_receive_filter - apply all rules to the specified message type
* @type: audit message type
@@ -197,18 +420,20 @@ static int audit_list_rules(void *_dest)
* @uid: target uid for netlink audit messages
* @seq: netlink audit message sequence (serial) number
* @data: payload data
+ * @dlen: length of payload data
* @loginuid: loginuid of sender
*/
int audit_receive_filter(int type, int pid, int uid, int seq, void *data,
- uid_t loginuid)
+ int dlen, uid_t loginuid)
{
struct task_struct *tsk;
int *dest;
- int err = 0;
- unsigned listnr;
+ int err = 0;
+ struct audit_entry *entry;
switch (type) {
case AUDIT_LIST:
+ case AUDIT_LIST_RULES:
/* We can't just spew out the rules here because we might fill
* the available socket buffer space and deadlock waiting for
* auditctl to read from it... which isn't ever going to
@@ -221,41 +446,48 @@ int audit_receive_filter(int type, int p
dest[0] = pid;
dest[1] = seq;
- tsk = kthread_run(audit_list_rules, dest, "audit_list_rules");
+ if (type == AUDIT_LIST)
+ tsk = kthread_run(audit_list, dest, "audit_list");
+ else
+ tsk = kthread_run(audit_list_rules, dest,
+ "audit_list_rules");
if (IS_ERR(tsk)) {
kfree(dest);
err = PTR_ERR(tsk);
}
break;
case AUDIT_ADD:
- listnr = ((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
- switch(listnr) {
- default:
- return -EINVAL;
+ case AUDIT_ADD_RULE:
+ if (type == AUDIT_ADD)
+ entry = audit_rule_to_entry(data);
+ else
+ entry = audit_xprt_to_entry(data, dlen);
+ if (IS_ERR(entry))
+ return PTR_ERR(entry);
- case AUDIT_FILTER_USER:
- case AUDIT_FILTER_TYPE:
-#ifdef CONFIG_AUDITSYSCALL
- case AUDIT_FILTER_ENTRY:
- case AUDIT_FILTER_EXIT:
- case AUDIT_FILTER_TASK:
-#endif
- ;
- }
- err = audit_add_rule(data, &audit_filter_list[listnr]);
+ err = audit_add_rule(entry,
+ &audit_filter_list[entry->rule.listnr]);
if (!err)
audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
"auid=%u added an audit rule\n", loginuid);
+ else
+ kfree(entry);
break;
case AUDIT_DEL:
- listnr =((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
- if (listnr >= AUDIT_NR_FILTERS)
- return -EINVAL;
+ case AUDIT_DEL_RULE:
+ if (type == AUDIT_DEL)
+ entry = audit_rule_to_entry(data);
+ else
+ entry = audit_xprt_to_entry(data, dlen);
+ if (IS_ERR(entry))
+ return PTR_ERR(entry);
- err = audit_del_rule(data, &audit_filter_list[listnr]);
+ err = audit_del_rule(entry,
+ &audit_filter_list[entry->rule.listnr]);
if (!err)
audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
"auid=%u removed an audit rule\n", loginuid);
+ kfree(entry);
break;
default:
return -EINVAL;
@@ -287,29 +519,27 @@ int audit_comparator(const u32 left, con
static int audit_filter_user_rules(struct netlink_skb_parms *cb,
- struct audit_rule *rule,
+ struct audit_krule *rule,
enum audit_state *state)
{
int i;
for (i = 0; i < rule->field_count; i++) {
- u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
- u32 op = rule->fields[i] & AUDIT_OPERATORS;
- u32 value = rule->values[i];
+ struct audit_field *f = &rule->fields[i];
int result = 0;
- switch (field) {
+ switch (f->type) {
case AUDIT_PID:
- result = audit_comparator(cb->creds.pid, op, value);
+ result = audit_comparator(cb->creds.pid, f->op, f->val);
break;
case AUDIT_UID:
- result = audit_comparator(cb->creds.uid, op, value);
+ result = audit_comparator(cb->creds.uid, f->op, f->val);
break;
case AUDIT_GID:
- result = audit_comparator(cb->creds.gid, op, value);
+ result = audit_comparator(cb->creds.gid, f->op, f->val);
break;
case AUDIT_LOGINUID:
- result = audit_comparator(cb->loginuid, op, value);
+ result = audit_comparator(cb->loginuid, f->op, f->val);
break;
}
@@ -354,14 +584,11 @@ int audit_filter_type(int type)
list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TYPE],
list) {
- struct audit_rule *rule = &e->rule;
int i;
- for (i = 0; i < rule->field_count; i++) {
- u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
- u32 op = rule->fields[i] & AUDIT_OPERATORS;
- u32 value = rule->values[i];
- if ( field == AUDIT_MSGTYPE ) {
- result = audit_comparator(type, op, value);
+ for (i = 0; i < e->rule.field_count; i++) {
+ struct audit_field *f = &e->rule.fields[i];
+ if (f->type == AUDIT_MSGTYPE) {
+ result = audit_comparator(type, f->op, f->val);
if (!result)
break;
}
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 8f0a61c..e4f7096 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -161,70 +161,68 @@ struct audit_context {
/* 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_rule *rule,
+ struct audit_krule *rule,
struct audit_context *ctx,
enum audit_state *state)
{
int i, j;
for (i = 0; i < rule->field_count; i++) {
- u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
- u32 op = rule->fields[i] & AUDIT_OPERATORS;
- u32 value = rule->values[i];
+ struct audit_field *f = &rule->fields[i];
int result = 0;
- switch (field) {
+ switch (f->type) {
case AUDIT_PID:
- result = audit_comparator(tsk->pid, op, value);
+ result = audit_comparator(tsk->pid, f->op, f->val);
break;
case AUDIT_UID:
- result = audit_comparator(tsk->uid, op, value);
+ result = audit_comparator(tsk->uid, f->op, f->val);
break;
case AUDIT_EUID:
- result = audit_comparator(tsk->euid, op, value);
+ result = audit_comparator(tsk->euid, f->op, f->val);
break;
case AUDIT_SUID:
- result = audit_comparator(tsk->suid, op, value);
+ result = audit_comparator(tsk->suid, f->op, f->val);
break;
case AUDIT_FSUID:
- result = audit_comparator(tsk->fsuid, op, value);
+ result = audit_comparator(tsk->fsuid, f->op, f->val);
break;
case AUDIT_GID:
- result = audit_comparator(tsk->gid, op, value);
+ result = audit_comparator(tsk->gid, f->op, f->val);
break;
case AUDIT_EGID:
- result = audit_comparator(tsk->egid, op, value);
+ result = audit_comparator(tsk->egid, f->op, f->val);
break;
case AUDIT_SGID:
- result = audit_comparator(tsk->sgid, op, value);
+ result = audit_comparator(tsk->sgid, f->op, f->val);
break;
case AUDIT_FSGID:
- result = audit_comparator(tsk->fsgid, op, value);
+ result = audit_comparator(tsk->fsgid, f->op, f->val);
break;
case AUDIT_PERS:
- result = audit_comparator(tsk->personality, op, value);
+ result = audit_comparator(tsk->personality, f->op, f->val);
break;
case AUDIT_ARCH:
if (ctx)
- result = audit_comparator(ctx->arch, op, value);
+ result = audit_comparator(ctx->arch, f->op, f->val);
break;
case AUDIT_EXIT:
if (ctx && ctx->return_valid)
- result = audit_comparator(ctx->return_code, op, value);
+ result = audit_comparator(ctx->return_code, f->op, f->val);
break;
case AUDIT_SUCCESS:
if (ctx && ctx->return_valid) {
- if (value)
- result = audit_comparator(ctx->return_valid, op, AUDITSC_SUCCESS);
+ if (f->val)
+ result = audit_comparator(ctx->return_valid, f->op, AUDITSC_SUCCESS);
else
- result = audit_comparator(ctx->return_valid, op, AUDITSC_FAILURE);
+ result = audit_comparator(ctx->return_valid, f->op, AUDITSC_FAILURE);
}
break;
case AUDIT_DEVMAJOR:
if (ctx) {
for (j = 0; j < ctx->name_count; j++) {
- if (audit_comparator(MAJOR(ctx->names[j].dev), op, value)) {
+ if (audit_comparator(MAJOR(ctx->names[j].dev), f->op, f->val)) {
++result;
break;
}
@@ -234,7 +232,7 @@ static int audit_filter_rules(struct tas
case AUDIT_DEVMINOR:
if (ctx) {
for (j = 0; j < ctx->name_count; j++) {
- if (audit_comparator(MINOR(ctx->names[j].dev), op, value)) {
+ if (audit_comparator(MINOR(ctx->names[j].dev), f->op, f->val)) {
++result;
break;
}
@@ -244,8 +242,8 @@ static int audit_filter_rules(struct tas
case AUDIT_INODE:
if (ctx) {
for (j = 0; j < ctx->name_count; j++) {
- if (audit_comparator(ctx->names[j].ino, op, value) ||
- audit_comparator(ctx->names[j].pino, op, value)) {
+ if (audit_comparator(ctx->names[j].ino, f->op, f->val) ||
+ audit_comparator(ctx->names[j].pino, f->op, f->val)) {
++result;
break;
}
@@ -255,14 +253,14 @@ static int audit_filter_rules(struct tas
case AUDIT_LOGINUID:
result = 0;
if (ctx)
- result = audit_comparator(ctx->loginuid, op, value);
+ result = audit_comparator(ctx->loginuid, f->op, f->val);
break;
case AUDIT_ARG0:
case AUDIT_ARG1:
case AUDIT_ARG2:
case AUDIT_ARG3:
if (ctx)
- result = audit_comparator(ctx->argv[field-AUDIT_ARG0], op, value);
+ result = audit_comparator(ctx->argv[f->type-AUDIT_ARG0], f->op, f->val);
break;
}
18 years, 10 months
[PATCH] audit files reorg
by Timothy R. Chavez
Hello,
I thought that this reorg might make things cleaner and more presentable.
I was particularly bothered by the kernel/audit.h for some reason. I'm posting
this patch as my suggestion as to how things should be organized. Feel free to
disagree :)
Basically I,
mkdir kernel/audit/
mv kernel/audit.c kernel/audit/audit.c
mv kernel/auditsc.c kernel/audit/syscall.c
mv kernel/auditfilter.c kernel/audit/filter.c
mv kernel/audit.h kernel/audit/audit.h
* Added a new kernel/audit/Makefile
* Modified the kernel/Makefile
* Updated each file to reflect move / rename
This was diffed against the latest audit-2.6.git tree
-tim
diff --git a/kernel/Makefile b/kernel/Makefile
index 56f6189..f76458f 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -9,6 +9,7 @@ obj-y = sched.o fork.o exec_domain.o
rcupdate.o intermodule.o extable.o params.o posix-timers.o \
kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o
+obj-$(CONFIG_AUDIT) += audit/
obj-$(CONFIG_FUTEX) += futex.o
obj-$(CONFIG_GENERIC_ISA_DMA) += dma.o
obj-$(CONFIG_SMP) += cpu.o spinlock.o
@@ -23,8 +24,6 @@ obj-$(CONFIG_COMPAT) += compat.o
obj-$(CONFIG_CPUSETS) += cpuset.o
obj-$(CONFIG_IKCONFIG) += configs.o
obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
-obj-$(CONFIG_AUDIT) += audit.o auditfilter.o
-obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_SYSFS) += ksysfs.o
obj-$(CONFIG_DETECT_SOFTLOCKUP) += softlockup.o
diff --git a/kernel/audit.c b/kernel/audit.c
deleted file mode 100644
index d95efd6..0000000
--- a/kernel/audit.c
+++ /dev/null
@@ -1,981 +0,0 @@
-/* audit.c -- Auditing support
- * Gateway between the kernel (e.g., selinux) and the user-space audit daemon.
- * System-call specific features have moved to auditsc.c
- *
- * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina.
- * 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 Rickard E. (Rik) Faith <faith(a)redhat.com>
- *
- * Goals: 1) Integrate fully with SELinux.
- * 2) Minimal run-time overhead:
- * a) Minimal when syscall auditing is disabled (audit_enable=0).
- * b) Small when syscall auditing is enabled and no audit record
- * is generated (defer as much work as possible to record
- * generation time):
- * i) context is allocated,
- * ii) names from getname are stored without a copy, and
- * iii) inode information stored from path_lookup.
- * 3) Ability to disable syscall auditing at boot time (audit=0).
- * 4) Usable by other parts of the kernel (if audit_log* is called,
- * then a syscall record will be generated automatically for the
- * current syscall).
- * 5) Netlink interface to user-space.
- * 6) Support low-overhead kernel-based filtering to minimize the
- * information that must be passed to user-space.
- *
- * Example user-space utilities: http://people.redhat.com/sgrubb/audit/
- */
-
-#include <linux/init.h>
-#include <asm/atomic.h>
-#include <asm/types.h>
-#include <linux/mm.h>
-#include <linux/module.h>
-#include <linux/err.h>
-#include <linux/kthread.h>
-
-#include <linux/audit.h>
-
-#include <net/sock.h>
-#include <linux/skbuff.h>
-#include <linux/netlink.h>
-
-/* No auditing will take place until audit_initialized != 0.
- * (Initialization happens after skb_init is called.) */
-static int audit_initialized;
-
-/* No syscall auditing will take place unless audit_enabled != 0. */
-int audit_enabled;
-
-/* Default state when kernel boots without any parameters. */
-static int audit_default;
-
-/* If auditing cannot proceed, audit_failure selects what happens. */
-static int audit_failure = AUDIT_FAIL_PRINTK;
-
-/* If audit records are to be written to the netlink socket, audit_pid
- * contains the (non-zero) pid. */
-int audit_pid;
-
-/* If audit_rate_limit is non-zero, limit the rate of sending audit records
- * to that number per second. This prevents DoS attacks, but results in
- * audit records being dropped. */
-static int audit_rate_limit;
-
-/* Number of outstanding audit_buffers allowed. */
-static int audit_backlog_limit = 64;
-static int audit_backlog_wait_time = 60 * HZ;
-static int audit_backlog_wait_overflow = 0;
-
-/* The identity of the user shutting down the audit system. */
-uid_t audit_sig_uid = -1;
-pid_t audit_sig_pid = -1;
-
-/* Records can be lost in several ways:
- 0) [suppressed in audit_alloc]
- 1) out of memory in audit_log_start [kmalloc of struct audit_buffer]
- 2) out of memory in audit_log_move [alloc_skb]
- 3) suppressed due to audit_rate_limit
- 4) suppressed due to audit_backlog_limit
-*/
-static atomic_t audit_lost = ATOMIC_INIT(0);
-
-/* The netlink socket. */
-static struct sock *audit_sock;
-
-/* 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). */
-static DEFINE_SPINLOCK(audit_freelist_lock);
-static int audit_freelist_count;
-static LIST_HEAD(audit_freelist);
-
-static struct sk_buff_head audit_skb_queue;
-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 */
-DECLARE_MUTEX(audit_netlink_sem);
-
-/* AUDIT_BUFSIZ is the size of the temporary buffer used for formatting
- * audit records. Since printk uses a 1024 byte buffer, this buffer
- * should be at least that large. */
-#define AUDIT_BUFSIZ 1024
-
-/* AUDIT_MAXFREE is the number of empty audit_buffers we keep on the
- * audit_freelist. Doing so eliminates many kmalloc/kfree calls. */
-#define AUDIT_MAXFREE (2*NR_CPUS)
-
-/* The audit_buffer is used when formatting an audit record. The caller
- * locks briefly to get the record off the freelist or to allocate the
- * buffer, and locks briefly to send the buffer to the netlink layer or
- * to place it on a transmit queue. Multiple audit_buffers can be in
- * use simultaneously. */
-struct audit_buffer {
- struct list_head list;
- struct sk_buff *skb; /* formatted skb ready to send */
- struct audit_context *ctx; /* NULL or associated context */
- gfp_t gfp_mask;
-};
-
-static void audit_set_pid(struct audit_buffer *ab, pid_t pid)
-{
- struct nlmsghdr *nlh = (struct nlmsghdr *)ab->skb->data;
- nlh->nlmsg_pid = pid;
-}
-
-void audit_panic(const char *message)
-{
- switch (audit_failure)
- {
- case AUDIT_FAIL_SILENT:
- break;
- case AUDIT_FAIL_PRINTK:
- printk(KERN_ERR "audit: %s\n", message);
- break;
- case AUDIT_FAIL_PANIC:
- panic("audit: %s\n", message);
- break;
- }
-}
-
-static inline int audit_rate_check(void)
-{
- static unsigned long last_check = 0;
- static int messages = 0;
- static DEFINE_SPINLOCK(lock);
- unsigned long flags;
- unsigned long now;
- unsigned long elapsed;
- int retval = 0;
-
- if (!audit_rate_limit) return 1;
-
- spin_lock_irqsave(&lock, flags);
- if (++messages < audit_rate_limit) {
- retval = 1;
- } else {
- now = jiffies;
- elapsed = now - last_check;
- if (elapsed > HZ) {
- last_check = now;
- messages = 0;
- retval = 1;
- }
- }
- spin_unlock_irqrestore(&lock, flags);
-
- return retval;
-}
-
-/**
- * audit_log_lost - conditionally log lost audit message event
- * @message: the message stating reason for lost audit message
- *
- * Emit at least 1 message per second, even if audit_rate_check is
- * throttling.
- * Always increment the lost messages counter.
-*/
-void audit_log_lost(const char *message)
-{
- static unsigned long last_msg = 0;
- static DEFINE_SPINLOCK(lock);
- unsigned long flags;
- unsigned long now;
- int print;
-
- atomic_inc(&audit_lost);
-
- print = (audit_failure == AUDIT_FAIL_PANIC || !audit_rate_limit);
-
- if (!print) {
- spin_lock_irqsave(&lock, flags);
- now = jiffies;
- if (now - last_msg > HZ) {
- print = 1;
- last_msg = now;
- }
- spin_unlock_irqrestore(&lock, flags);
- }
-
- if (print) {
- printk(KERN_WARNING
- "audit: audit_lost=%d audit_rate_limit=%d audit_backlog_limit=%d\n",
- atomic_read(&audit_lost),
- audit_rate_limit,
- audit_backlog_limit);
- audit_panic(message);
- }
-}
-
-static int audit_set_rate_limit(int limit, uid_t loginuid)
-{
- int old = audit_rate_limit;
- audit_rate_limit = limit;
- audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
- "audit_rate_limit=%d old=%d by auid=%u",
- audit_rate_limit, old, loginuid);
- return old;
-}
-
-static int audit_set_backlog_limit(int limit, uid_t loginuid)
-{
- int old = audit_backlog_limit;
- audit_backlog_limit = limit;
- audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
- "audit_backlog_limit=%d old=%d by auid=%u",
- audit_backlog_limit, old, loginuid);
- return old;
-}
-
-static int audit_set_enabled(int state, uid_t loginuid)
-{
- int old = audit_enabled;
- if (state != 0 && state != 1)
- return -EINVAL;
- audit_enabled = state;
- audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
- "audit_enabled=%d old=%d by auid=%u",
- audit_enabled, old, loginuid);
- return old;
-}
-
-static int audit_set_failure(int state, uid_t loginuid)
-{
- int old = audit_failure;
- if (state != AUDIT_FAIL_SILENT
- && state != AUDIT_FAIL_PRINTK
- && state != AUDIT_FAIL_PANIC)
- return -EINVAL;
- audit_failure = state;
- audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
- "audit_failure=%d old=%d by auid=%u",
- audit_failure, old, loginuid);
- return old;
-}
-
-int kauditd_thread(void *dummy)
-{
- struct sk_buff *skb;
-
- while (1) {
- skb = skb_dequeue(&audit_skb_queue);
- wake_up(&audit_backlog_wait);
- if (skb) {
- if (audit_pid) {
- int err = netlink_unicast(audit_sock, skb, audit_pid, 0);
- if (err < 0) {
- BUG_ON(err != -ECONNREFUSED); /* Shoudn't happen */
- printk(KERN_ERR "audit: *NO* daemon at audit_pid=%d\n", audit_pid);
- audit_pid = 0;
- }
- } else {
- printk(KERN_NOTICE "%s\n", skb->data + NLMSG_SPACE(0));
- kfree_skb(skb);
- }
- } else {
- DECLARE_WAITQUEUE(wait, current);
- set_current_state(TASK_INTERRUPTIBLE);
- add_wait_queue(&kauditd_wait, &wait);
-
- if (!skb_queue_len(&audit_skb_queue)) {
- try_to_freeze();
- schedule();
- }
-
- __set_current_state(TASK_RUNNING);
- remove_wait_queue(&kauditd_wait, &wait);
- }
- }
- return 0;
-}
-
-/**
- * audit_send_reply - send an audit reply message via netlink
- * @pid: process id to send reply to
- * @seq: sequence number
- * @type: audit message type
- * @done: done (last) flag
- * @multi: multi-part message flag
- * @payload: payload data
- * @size: payload size
- *
- * Allocates an skb, builds the netlink message, and sends it to the pid.
- * No failure notifications.
- */
-void audit_send_reply(int pid, int seq, int type, int done, int multi,
- void *payload, int size)
-{
- struct sk_buff *skb;
- struct nlmsghdr *nlh;
- int len = NLMSG_SPACE(size);
- void *data;
- int flags = multi ? NLM_F_MULTI : 0;
- int t = done ? NLMSG_DONE : type;
-
- skb = alloc_skb(len, GFP_KERNEL);
- if (!skb)
- return;
-
- nlh = NLMSG_PUT(skb, pid, seq, t, size);
- nlh->nlmsg_flags = flags;
- data = NLMSG_DATA(nlh);
- memcpy(data, payload, size);
-
- /* Ignore failure. It'll only happen if the sender goes away,
- because our timeout is set to infinite. */
- netlink_unicast(audit_sock, skb, pid, 0);
- return;
-
-nlmsg_failure: /* Used by NLMSG_PUT */
- if (skb)
- kfree_skb(skb);
-}
-
-/*
- * Check for appropriate CAP_AUDIT_ capabilities on incoming audit
- * control messages.
- */
-static int audit_netlink_ok(kernel_cap_t eff_cap, u16 msg_type)
-{
- int err = 0;
-
- switch (msg_type) {
- case AUDIT_GET:
- case AUDIT_LIST:
- case AUDIT_SET:
- case AUDIT_ADD:
- case AUDIT_DEL:
- case AUDIT_SIGNAL_INFO:
- if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
- err = -EPERM;
- break;
- case AUDIT_USER:
- case AUDIT_FIRST_USER_MSG...AUDIT_LAST_USER_MSG:
- case AUDIT_FIRST_USER_MSG2...AUDIT_LAST_USER_MSG2:
- if (!cap_raised(eff_cap, CAP_AUDIT_WRITE))
- err = -EPERM;
- break;
- default: /* bad msg */
- err = -EINVAL;
- }
-
- return err;
-}
-
-static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
-{
- u32 uid, pid, seq;
- void *data;
- struct audit_status *status_get, status_set;
- int err;
- struct audit_buffer *ab;
- u16 msg_type = nlh->nlmsg_type;
- uid_t loginuid; /* loginuid of sender */
- struct audit_sig_info sig_data;
-
- err = audit_netlink_ok(NETLINK_CB(skb).eff_cap, msg_type);
- if (err)
- return err;
-
- /* As soon as there's any sign of userspace auditd,
- * start kauditd to talk to it */
- if (!kauditd_task)
- kauditd_task = kthread_run(kauditd_thread, NULL, "kauditd");
- if (IS_ERR(kauditd_task)) {
- err = PTR_ERR(kauditd_task);
- kauditd_task = NULL;
- return err;
- }
-
- pid = NETLINK_CREDS(skb)->pid;
- uid = NETLINK_CREDS(skb)->uid;
- loginuid = NETLINK_CB(skb).loginuid;
- seq = nlh->nlmsg_seq;
- data = NLMSG_DATA(nlh);
-
- switch (msg_type) {
- case AUDIT_GET:
- status_set.enabled = audit_enabled;
- status_set.failure = audit_failure;
- status_set.pid = audit_pid;
- status_set.rate_limit = audit_rate_limit;
- status_set.backlog_limit = audit_backlog_limit;
- status_set.lost = atomic_read(&audit_lost);
- status_set.backlog = skb_queue_len(&audit_skb_queue);
- audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_GET, 0, 0,
- &status_set, sizeof(status_set));
- break;
- case AUDIT_SET:
- if (nlh->nlmsg_len < sizeof(struct audit_status))
- return -EINVAL;
- status_get = (struct audit_status *)data;
- if (status_get->mask & AUDIT_STATUS_ENABLED) {
- err = audit_set_enabled(status_get->enabled, loginuid);
- if (err < 0) return err;
- }
- if (status_get->mask & AUDIT_STATUS_FAILURE) {
- err = audit_set_failure(status_get->failure, loginuid);
- if (err < 0) return err;
- }
- if (status_get->mask & AUDIT_STATUS_PID) {
- int old = audit_pid;
- audit_pid = status_get->pid;
- audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
- "audit_pid=%d old=%d by auid=%u",
- audit_pid, old, loginuid);
- }
- if (status_get->mask & AUDIT_STATUS_RATE_LIMIT)
- audit_set_rate_limit(status_get->rate_limit, loginuid);
- if (status_get->mask & AUDIT_STATUS_BACKLOG_LIMIT)
- audit_set_backlog_limit(status_get->backlog_limit,
- loginuid);
- break;
- case AUDIT_USER:
- case AUDIT_FIRST_USER_MSG...AUDIT_LAST_USER_MSG:
- case AUDIT_FIRST_USER_MSG2...AUDIT_LAST_USER_MSG2:
- if (!audit_enabled && msg_type != AUDIT_USER_AVC)
- return 0;
-
- err = audit_filter_user(&NETLINK_CB(skb), msg_type);
- if (err == 1) {
- err = 0;
- ab = audit_log_start(NULL, GFP_KERNEL, msg_type);
- if (ab) {
- audit_log_format(ab,
- "user pid=%d uid=%u auid=%u msg='%.1024s'",
- pid, uid, loginuid, (char *)data);
- audit_set_pid(ab, pid);
- audit_log_end(ab);
- }
- }
- break;
- case AUDIT_ADD:
- case AUDIT_DEL:
- if (nlh->nlmsg_len < sizeof(struct audit_rule))
- return -EINVAL;
- /* fallthrough */
- case AUDIT_LIST:
- err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid,
- uid, seq, data, loginuid);
- break;
- case AUDIT_SIGNAL_INFO:
- sig_data.uid = audit_sig_uid;
- sig_data.pid = audit_sig_pid;
- audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_SIGNAL_INFO,
- 0, 0, &sig_data, sizeof(sig_data));
- break;
- default:
- err = -EINVAL;
- break;
- }
-
- return err < 0 ? err : 0;
-}
-
-/*
- * Get message from skb (based on rtnetlink_rcv_skb). Each message is
- * processed by audit_receive_msg. Malformed skbs with wrong length are
- * discarded silently.
- */
-static void audit_receive_skb(struct sk_buff *skb)
-{
- int err;
- struct nlmsghdr *nlh;
- u32 rlen;
-
- while (skb->len >= NLMSG_SPACE(0)) {
- nlh = (struct nlmsghdr *)skb->data;
- if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
- return;
- rlen = NLMSG_ALIGN(nlh->nlmsg_len);
- if (rlen > skb->len)
- rlen = skb->len;
- if ((err = audit_receive_msg(skb, nlh))) {
- netlink_ack(skb, nlh, err);
- } else if (nlh->nlmsg_flags & NLM_F_ACK)
- netlink_ack(skb, nlh, 0);
- skb_pull(skb, rlen);
- }
-}
-
-/* Receive messages from netlink socket. */
-static void audit_receive(struct sock *sk, int length)
-{
- struct sk_buff *skb;
- unsigned int qlen;
-
- down(&audit_netlink_sem);
-
- 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);
- }
- up(&audit_netlink_sem);
-}
-
-
-/* Initialize audit support at boot time. */
-static int __init audit_init(void)
-{
- printk(KERN_INFO "audit: initializing netlink socket (%s)\n",
- audit_default ? "enabled" : "disabled");
- audit_sock = netlink_kernel_create(NETLINK_AUDIT, 0, audit_receive,
- THIS_MODULE);
- if (!audit_sock)
- audit_panic("cannot initialize netlink socket");
-
- audit_sock->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT;
- skb_queue_head_init(&audit_skb_queue);
- audit_initialized = 1;
- audit_enabled = audit_default;
- audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL, "initialized");
- return 0;
-}
-__initcall(audit_init);
-
-/* Process kernel command-line parameter at boot time. audit=0 or audit=1. */
-static int __init audit_enable(char *str)
-{
- audit_default = !!simple_strtol(str, NULL, 0);
- printk(KERN_INFO "audit: %s%s\n",
- audit_default ? "enabled" : "disabled",
- audit_initialized ? "" : " (after initialization)");
- if (audit_initialized)
- audit_enabled = audit_default;
- return 0;
-}
-
-__setup("audit=", audit_enable);
-
-static void audit_buffer_free(struct audit_buffer *ab)
-{
- unsigned long flags;
-
- if (!ab)
- return;
-
- if (ab->skb)
- kfree_skb(ab->skb);
-
- spin_lock_irqsave(&audit_freelist_lock, flags);
- if (++audit_freelist_count > AUDIT_MAXFREE)
- kfree(ab);
- else
- list_add(&ab->list, &audit_freelist);
- spin_unlock_irqrestore(&audit_freelist_lock, flags);
-}
-
-static struct audit_buffer * audit_buffer_alloc(struct audit_context *ctx,
- gfp_t gfp_mask, int type)
-{
- unsigned long flags;
- struct audit_buffer *ab = NULL;
- struct nlmsghdr *nlh;
-
- spin_lock_irqsave(&audit_freelist_lock, flags);
- if (!list_empty(&audit_freelist)) {
- ab = list_entry(audit_freelist.next,
- struct audit_buffer, list);
- list_del(&ab->list);
- --audit_freelist_count;
- }
- spin_unlock_irqrestore(&audit_freelist_lock, flags);
-
- if (!ab) {
- ab = kmalloc(sizeof(*ab), gfp_mask);
- if (!ab)
- goto err;
- }
-
- ab->skb = alloc_skb(AUDIT_BUFSIZ, gfp_mask);
- if (!ab->skb)
- goto err;
-
- ab->ctx = ctx;
- ab->gfp_mask = gfp_mask;
- nlh = (struct nlmsghdr *)skb_put(ab->skb, NLMSG_SPACE(0));
- nlh->nlmsg_type = type;
- nlh->nlmsg_flags = 0;
- nlh->nlmsg_pid = 0;
- nlh->nlmsg_seq = 0;
- return ab;
-err:
- audit_buffer_free(ab);
- return NULL;
-}
-
-/**
- * audit_serial - compute a serial number for the audit record
- *
- * Compute a serial number for the audit record. Audit records are
- * written to user-space as soon as they are generated, so a complete
- * audit record may be written in several pieces. The timestamp of the
- * record and this serial number are used by the user-space tools to
- * determine which pieces belong to the same audit record. The
- * (timestamp,serial) tuple is unique for each syscall and is live from
- * syscall entry to syscall exit.
- *
- * NOTE: Another possibility is to store the formatted records off the
- * audit context (for those records that have a context), and emit them
- * all at syscall exit. However, this could delay the reporting of
- * significant errors until syscall exit (or never, if the system
- * halts).
- */
-unsigned int audit_serial(void)
-{
- static spinlock_t serial_lock = SPIN_LOCK_UNLOCKED;
- static unsigned int serial = 0;
-
- unsigned long flags;
- unsigned int ret;
-
- spin_lock_irqsave(&serial_lock, flags);
- do {
- ret = ++serial;
- } while (unlikely(!ret));
- spin_unlock_irqrestore(&serial_lock, flags);
-
- return ret;
-}
-
-static inline void audit_get_stamp(struct audit_context *ctx,
- struct timespec *t, unsigned int *serial)
-{
- if (ctx)
- auditsc_get_stamp(ctx, t, serial);
- else {
- *t = CURRENT_TIME;
- *serial = audit_serial();
- }
-}
-
-/* Obtain an audit buffer. This routine does locking to obtain the
- * audit buffer, but then no locking is required for calls to
- * audit_log_*format. If the tsk is a task that is currently in a
- * syscall, then the syscall is marked as auditable and an audit record
- * will be written at syscall exit. If there is no associated task, tsk
- * should be NULL. */
-
-/**
- * audit_log_start - obtain an audit buffer
- * @ctx: audit_context (may be NULL)
- * @gfp_mask: type of allocation
- * @type: audit message type
- *
- * Returns audit_buffer pointer on success or NULL on error.
- *
- * Obtain an audit buffer. This routine does locking to obtain the
- * audit buffer, but then no locking is required for calls to
- * audit_log_*format. If the task (ctx) is a task that is currently in a
- * syscall, then the syscall is marked as auditable and an audit record
- * will be written at syscall exit. If there is no associated task, then
- * task context (ctx) should be NULL.
- */
-struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask,
- int type)
-{
- struct audit_buffer *ab = NULL;
- struct timespec t;
- unsigned int serial;
- int reserve;
- unsigned long timeout_start = jiffies;
-
- if (!audit_initialized)
- return NULL;
-
- if (unlikely(audit_filter_type(type)))
- return NULL;
-
- if (gfp_mask & __GFP_WAIT)
- reserve = 0;
- else
- reserve = 5; /* Allow atomic callers to go up to five
- entries over the normal backlog limit */
-
- while (audit_backlog_limit
- && skb_queue_len(&audit_skb_queue) > audit_backlog_limit + reserve) {
- if (gfp_mask & __GFP_WAIT && audit_backlog_wait_time
- && time_before(jiffies, timeout_start + audit_backlog_wait_time)) {
-
- /* Wait for auditd to drain the queue a little */
- DECLARE_WAITQUEUE(wait, current);
- set_current_state(TASK_INTERRUPTIBLE);
- add_wait_queue(&audit_backlog_wait, &wait);
-
- if (audit_backlog_limit &&
- skb_queue_len(&audit_skb_queue) > audit_backlog_limit)
- schedule_timeout(timeout_start + audit_backlog_wait_time - jiffies);
-
- __set_current_state(TASK_RUNNING);
- remove_wait_queue(&audit_backlog_wait, &wait);
- continue;
- }
- if (audit_rate_check())
- printk(KERN_WARNING
- "audit: audit_backlog=%d > "
- "audit_backlog_limit=%d\n",
- skb_queue_len(&audit_skb_queue),
- audit_backlog_limit);
- audit_log_lost("backlog limit exceeded");
- audit_backlog_wait_time = audit_backlog_wait_overflow;
- wake_up(&audit_backlog_wait);
- return NULL;
- }
-
- ab = audit_buffer_alloc(ctx, gfp_mask, type);
- if (!ab) {
- audit_log_lost("out of memory in audit_log_start");
- return NULL;
- }
-
- audit_get_stamp(ab->ctx, &t, &serial);
-
- audit_log_format(ab, "audit(%lu.%03lu:%u): ",
- t.tv_sec, t.tv_nsec/1000000, serial);
- return ab;
-}
-
-/**
- * audit_expand - expand skb in the audit buffer
- * @ab: audit_buffer
- * @extra: space to add at tail of the skb
- *
- * Returns 0 (no space) on failed expansion, or available space if
- * successful.
- */
-static inline int audit_expand(struct audit_buffer *ab, int extra)
-{
- struct sk_buff *skb = ab->skb;
- int ret = pskb_expand_head(skb, skb_headroom(skb), extra,
- ab->gfp_mask);
- if (ret < 0) {
- audit_log_lost("out of memory in audit_expand");
- return 0;
- }
- return skb_tailroom(skb);
-}
-
-/*
- * Format an audit message into the audit buffer. If there isn't enough
- * room in the audit buffer, more room will be allocated and vsnprint
- * will be called a second time. Currently, we assume that a printk
- * can't format message larger than 1024 bytes, so we don't either.
- */
-static void audit_log_vformat(struct audit_buffer *ab, const char *fmt,
- va_list args)
-{
- int len, avail;
- struct sk_buff *skb;
- va_list args2;
-
- if (!ab)
- return;
-
- BUG_ON(!ab->skb);
- skb = ab->skb;
- avail = skb_tailroom(skb);
- if (avail == 0) {
- avail = audit_expand(ab, AUDIT_BUFSIZ);
- if (!avail)
- goto out;
- }
- va_copy(args2, args);
- len = vsnprintf(skb->tail, avail, fmt, args);
- if (len >= avail) {
- /* The printk buffer is 1024 bytes long, so if we get
- * here and AUDIT_BUFSIZ is at least 1024, then we can
- * log everything that printk could have logged. */
- avail = audit_expand(ab,
- max_t(unsigned, AUDIT_BUFSIZ, 1+len-avail));
- if (!avail)
- goto out;
- len = vsnprintf(skb->tail, avail, fmt, args2);
- }
- if (len > 0)
- skb_put(skb, len);
-out:
- return;
-}
-
-/**
- * audit_log_format - format a message into the audit buffer.
- * @ab: audit_buffer
- * @fmt: format string
- * @...: optional parameters matching @fmt string
- *
- * All the work is done in audit_log_vformat.
- */
-void audit_log_format(struct audit_buffer *ab, const char *fmt, ...)
-{
- va_list args;
-
- if (!ab)
- return;
- va_start(args, fmt);
- audit_log_vformat(ab, fmt, args);
- va_end(args);
-}
-
-/**
- * audit_log_hex - convert a buffer to hex and append it to the audit skb
- * @ab: the audit_buffer
- * @buf: buffer to convert to hex
- * @len: length of @buf to be converted
- *
- * No return value; failure to expand is silently ignored.
- *
- * This function will take the passed buf and convert it into a string of
- * ascii hex digits. The new string is placed onto the skb.
- */
-void audit_log_hex(struct audit_buffer *ab, const unsigned char *buf,
- size_t len)
-{
- int i, avail, new_len;
- unsigned char *ptr;
- struct sk_buff *skb;
- static const unsigned char *hex = "0123456789ABCDEF";
-
- BUG_ON(!ab->skb);
- skb = ab->skb;
- avail = skb_tailroom(skb);
- new_len = len<<1;
- if (new_len >= avail) {
- /* Round the buffer request up to the next multiple */
- new_len = AUDIT_BUFSIZ*(((new_len-avail)/AUDIT_BUFSIZ) + 1);
- avail = audit_expand(ab, new_len);
- if (!avail)
- return;
- }
-
- ptr = skb->tail;
- for (i=0; i<len; i++) {
- *ptr++ = hex[(buf[i] & 0xF0)>>4]; /* Upper nibble */
- *ptr++ = hex[buf[i] & 0x0F]; /* Lower nibble */
- }
- *ptr = 0;
- skb_put(skb, len << 1); /* new string is twice the old string */
-}
-
-/**
- * audit_log_unstrustedstring - log a string that may contain random characters
- * @ab: audit_buffer
- * @string: string to be logged
- *
- * This code will escape a string that is passed to it if the string
- * contains a control character, unprintable character, double quote mark,
- * or a space. Unescaped strings will start and end with a double quote mark.
- * Strings that are escaped are printed in hex (2 digits per char).
- */
-void audit_log_untrustedstring(struct audit_buffer *ab, const char *string)
-{
- const unsigned char *p = string;
-
- while (*p) {
- if (*p == '"' || *p < 0x21 || *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 escaped d_path */
-void audit_log_d_path(struct audit_buffer *ab, const char *prefix,
- struct dentry *dentry, struct vfsmount *vfsmnt)
-{
- char *p, *path;
-
- if (prefix)
- audit_log_format(ab, " %s", prefix);
-
- /* We will allow 11 spaces for ' (deleted)' to be appended */
- path = kmalloc(PATH_MAX+11, ab->gfp_mask);
- if (!path) {
- audit_log_format(ab, "<no memory>");
- return;
- }
- p = d_path(dentry, vfsmnt, path, PATH_MAX+11);
- if (IS_ERR(p)) { /* Should never happen since we send PATH_MAX */
- /* FIXME: can we save some information here? */
- audit_log_format(ab, "<too long>");
- } else
- audit_log_untrustedstring(ab, p);
- kfree(path);
-}
-
-/**
- * audit_log_end - end one audit record
- * @ab: the audit_buffer
- *
- * The netlink_* functions cannot be called inside an irq context, so
- * the audit buffer is placed on a queue and a tasklet is scheduled to
- * remove them from the queue outside the irq context. May be called in
- * any context.
- */
-void audit_log_end(struct audit_buffer *ab)
-{
- if (!ab)
- return;
- if (!audit_rate_check()) {
- audit_log_lost("rate limit exceeded");
- } else {
- if (audit_pid) {
- struct nlmsghdr *nlh = (struct nlmsghdr *)ab->skb->data;
- nlh->nlmsg_len = ab->skb->len - NLMSG_SPACE(0);
- skb_queue_tail(&audit_skb_queue, ab->skb);
- ab->skb = NULL;
- wake_up_interruptible(&kauditd_wait);
- } else {
- printk(KERN_NOTICE "%s\n", ab->skb->data + NLMSG_SPACE(0));
- }
- }
- audit_buffer_free(ab);
-}
-
-/**
- * audit_log - Log an audit record
- * @ctx: audit context
- * @gfp_mask: type of allocation
- * @type: audit message type
- * @fmt: format string to use
- * @...: variable parameters matching the format string
- *
- * This is a convenience function that calls audit_log_start,
- * audit_log_vformat, and audit_log_end. It may be called
- * in any context.
- */
-void audit_log(struct audit_context *ctx, gfp_t gfp_mask, int type,
- const char *fmt, ...)
-{
- struct audit_buffer *ab;
- va_list args;
-
- ab = audit_log_start(ctx, gfp_mask, type);
- if (ab) {
- va_start(args, fmt);
- audit_log_vformat(ab, fmt, args);
- va_end(args);
- audit_log_end(ab);
- }
-}
diff --git a/kernel/audit.h b/kernel/audit.h
deleted file mode 100644
index 7643e46..0000000
--- a/kernel/audit.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/* audit -- definition of audit_context structure and supporting types
- *
- * Copyright 2003-2004 Red Hat, Inc.
- * Copyright 2005 Hewlett-Packard Development Company, L.P.
- * Copyright 2005 IBM Corporation
- *
- * 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
- */
-
-#include <linux/fs.h>
-#include <linux/audit.h>
-
-/* 0 = no checking
- 1 = put_count checking
- 2 = verbose put_count checking
-*/
-#define AUDIT_DEBUG 0
-
-/* At task start time, the audit_state is set in the audit_context using
- a per-task filter. At syscall entry, the audit_state is augmented by
- the syscall filter. */
-enum audit_state {
- AUDIT_DISABLED, /* Do not create per-task audit_context.
- * No syscall-specific audit records can
- * be generated. */
- AUDIT_SETUP_CONTEXT, /* Create the per-task audit_context,
- * but don't necessarily fill it in at
- * syscall entry time (i.e., filter
- * instead). */
- AUDIT_BUILD_CONTEXT, /* Create the per-task audit_context,
- * and always fill it in at syscall
- * entry time. This makes a full
- * syscall record available if some
- * other part of the kernel decides it
- * should be recorded. */
- AUDIT_RECORD_CONTEXT /* Create the per-task audit_context,
- * always fill it in at syscall entry
- * time, and always write out the audit
- * record at syscall exit time. */
-};
-
-/* Rule lists */
-struct audit_entry {
- struct list_head list;
- struct rcu_head rcu;
- struct audit_rule rule;
-};
-
-
-extern int audit_pid;
-extern int audit_comparator(const u32 left, const u32 op, const u32 right);
-
-extern void audit_send_reply(int pid, int seq, int type,
- int done, int multi,
- void *payload, int size);
-extern void audit_log_lost(const char *message);
-extern void audit_panic(const char *message);
-extern struct semaphore audit_netlink_sem;
diff --git a/kernel/audit/Makefile b/kernel/audit/Makefile
new file mode 100644
index 0000000..69234c5
--- /dev/null
+++ b/kernel/audit/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the audit subsystem.
+#
+obj-$(CONFIG_AUDIT) += audit.o filter.o
+obj-$(CONFIG_AUDITSYSCALL) += syscall.o
diff --git a/kernel/audit/audit.c b/kernel/audit/audit.c
new file mode 100644
index 0000000..6a85eec
--- /dev/null
+++ b/kernel/audit/audit.c
@@ -0,0 +1,981 @@
+/* audit/audit.c -- Auditing support
+ * Gateway between the kernel (e.g., selinux) and the user-space audit daemon.
+ * System-call specific features have moved to auditsc.c
+ *
+ * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina.
+ * 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 Rickard E. (Rik) Faith <faith(a)redhat.com>
+ *
+ * Goals: 1) Integrate fully with SELinux.
+ * 2) Minimal run-time overhead:
+ * a) Minimal when syscall auditing is disabled (audit_enable=0).
+ * b) Small when syscall auditing is enabled and no audit record
+ * is generated (defer as much work as possible to record
+ * generation time):
+ * i) context is allocated,
+ * ii) names from getname are stored without a copy, and
+ * iii) inode information stored from path_lookup.
+ * 3) Ability to disable syscall auditing at boot time (audit=0).
+ * 4) Usable by other parts of the kernel (if audit_log* is called,
+ * then a syscall record will be generated automatically for the
+ * current syscall).
+ * 5) Netlink interface to user-space.
+ * 6) Support low-overhead kernel-based filtering to minimize the
+ * information that must be passed to user-space.
+ *
+ * Example user-space utilities: http://people.redhat.com/sgrubb/audit/
+ */
+
+#include <linux/init.h>
+#include <asm/atomic.h>
+#include <asm/types.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/kthread.h>
+
+#include <linux/audit.h>
+
+#include <net/sock.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+
+/* No auditing will take place until audit_initialized != 0.
+ * (Initialization happens after skb_init is called.) */
+static int audit_initialized;
+
+/* No syscall auditing will take place unless audit_enabled != 0. */
+int audit_enabled;
+
+/* Default state when kernel boots without any parameters. */
+static int audit_default;
+
+/* If auditing cannot proceed, audit_failure selects what happens. */
+static int audit_failure = AUDIT_FAIL_PRINTK;
+
+/* If audit records are to be written to the netlink socket, audit_pid
+ * contains the (non-zero) pid. */
+int audit_pid;
+
+/* If audit_rate_limit is non-zero, limit the rate of sending audit records
+ * to that number per second. This prevents DoS attacks, but results in
+ * audit records being dropped. */
+static int audit_rate_limit;
+
+/* Number of outstanding audit_buffers allowed. */
+static int audit_backlog_limit = 64;
+static int audit_backlog_wait_time = 60 * HZ;
+static int audit_backlog_wait_overflow = 0;
+
+/* The identity of the user shutting down the audit system. */
+uid_t audit_sig_uid = -1;
+pid_t audit_sig_pid = -1;
+
+/* Records can be lost in several ways:
+ 0) [suppressed in audit_alloc]
+ 1) out of memory in audit_log_start [kmalloc of struct audit_buffer]
+ 2) out of memory in audit_log_move [alloc_skb]
+ 3) suppressed due to audit_rate_limit
+ 4) suppressed due to audit_backlog_limit
+*/
+static atomic_t audit_lost = ATOMIC_INIT(0);
+
+/* The netlink socket. */
+static struct sock *audit_sock;
+
+/* 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). */
+static DEFINE_SPINLOCK(audit_freelist_lock);
+static int audit_freelist_count;
+static LIST_HEAD(audit_freelist);
+
+static struct sk_buff_head audit_skb_queue;
+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 */
+DECLARE_MUTEX(audit_netlink_sem);
+
+/* AUDIT_BUFSIZ is the size of the temporary buffer used for formatting
+ * audit records. Since printk uses a 1024 byte buffer, this buffer
+ * should be at least that large. */
+#define AUDIT_BUFSIZ 1024
+
+/* AUDIT_MAXFREE is the number of empty audit_buffers we keep on the
+ * audit_freelist. Doing so eliminates many kmalloc/kfree calls. */
+#define AUDIT_MAXFREE (2*NR_CPUS)
+
+/* The audit_buffer is used when formatting an audit record. The caller
+ * locks briefly to get the record off the freelist or to allocate the
+ * buffer, and locks briefly to send the buffer to the netlink layer or
+ * to place it on a transmit queue. Multiple audit_buffers can be in
+ * use simultaneously. */
+struct audit_buffer {
+ struct list_head list;
+ struct sk_buff *skb; /* formatted skb ready to send */
+ struct audit_context *ctx; /* NULL or associated context */
+ gfp_t gfp_mask;
+};
+
+static void audit_set_pid(struct audit_buffer *ab, pid_t pid)
+{
+ struct nlmsghdr *nlh = (struct nlmsghdr *)ab->skb->data;
+ nlh->nlmsg_pid = pid;
+}
+
+void audit_panic(const char *message)
+{
+ switch (audit_failure)
+ {
+ case AUDIT_FAIL_SILENT:
+ break;
+ case AUDIT_FAIL_PRINTK:
+ printk(KERN_ERR "audit: %s\n", message);
+ break;
+ case AUDIT_FAIL_PANIC:
+ panic("audit: %s\n", message);
+ break;
+ }
+}
+
+static inline int audit_rate_check(void)
+{
+ static unsigned long last_check = 0;
+ static int messages = 0;
+ static DEFINE_SPINLOCK(lock);
+ unsigned long flags;
+ unsigned long now;
+ unsigned long elapsed;
+ int retval = 0;
+
+ if (!audit_rate_limit) return 1;
+
+ spin_lock_irqsave(&lock, flags);
+ if (++messages < audit_rate_limit) {
+ retval = 1;
+ } else {
+ now = jiffies;
+ elapsed = now - last_check;
+ if (elapsed > HZ) {
+ last_check = now;
+ messages = 0;
+ retval = 1;
+ }
+ }
+ spin_unlock_irqrestore(&lock, flags);
+
+ return retval;
+}
+
+/**
+ * audit_log_lost - conditionally log lost audit message event
+ * @message: the message stating reason for lost audit message
+ *
+ * Emit at least 1 message per second, even if audit_rate_check is
+ * throttling.
+ * Always increment the lost messages counter.
+*/
+void audit_log_lost(const char *message)
+{
+ static unsigned long last_msg = 0;
+ static DEFINE_SPINLOCK(lock);
+ unsigned long flags;
+ unsigned long now;
+ int print;
+
+ atomic_inc(&audit_lost);
+
+ print = (audit_failure == AUDIT_FAIL_PANIC || !audit_rate_limit);
+
+ if (!print) {
+ spin_lock_irqsave(&lock, flags);
+ now = jiffies;
+ if (now - last_msg > HZ) {
+ print = 1;
+ last_msg = now;
+ }
+ spin_unlock_irqrestore(&lock, flags);
+ }
+
+ if (print) {
+ printk(KERN_WARNING
+ "audit: audit_lost=%d audit_rate_limit=%d audit_backlog_limit=%d\n",
+ atomic_read(&audit_lost),
+ audit_rate_limit,
+ audit_backlog_limit);
+ audit_panic(message);
+ }
+}
+
+static int audit_set_rate_limit(int limit, uid_t loginuid)
+{
+ int old = audit_rate_limit;
+ audit_rate_limit = limit;
+ audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+ "audit_rate_limit=%d old=%d by auid=%u",
+ audit_rate_limit, old, loginuid);
+ return old;
+}
+
+static int audit_set_backlog_limit(int limit, uid_t loginuid)
+{
+ int old = audit_backlog_limit;
+ audit_backlog_limit = limit;
+ audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+ "audit_backlog_limit=%d old=%d by auid=%u",
+ audit_backlog_limit, old, loginuid);
+ return old;
+}
+
+static int audit_set_enabled(int state, uid_t loginuid)
+{
+ int old = audit_enabled;
+ if (state != 0 && state != 1)
+ return -EINVAL;
+ audit_enabled = state;
+ audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+ "audit_enabled=%d old=%d by auid=%u",
+ audit_enabled, old, loginuid);
+ return old;
+}
+
+static int audit_set_failure(int state, uid_t loginuid)
+{
+ int old = audit_failure;
+ if (state != AUDIT_FAIL_SILENT
+ && state != AUDIT_FAIL_PRINTK
+ && state != AUDIT_FAIL_PANIC)
+ return -EINVAL;
+ audit_failure = state;
+ audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+ "audit_failure=%d old=%d by auid=%u",
+ audit_failure, old, loginuid);
+ return old;
+}
+
+int kauditd_thread(void *dummy)
+{
+ struct sk_buff *skb;
+
+ while (1) {
+ skb = skb_dequeue(&audit_skb_queue);
+ wake_up(&audit_backlog_wait);
+ if (skb) {
+ if (audit_pid) {
+ int err = netlink_unicast(audit_sock, skb, audit_pid, 0);
+ if (err < 0) {
+ BUG_ON(err != -ECONNREFUSED); /* Shoudn't happen */
+ printk(KERN_ERR "audit: *NO* daemon at audit_pid=%d\n", audit_pid);
+ audit_pid = 0;
+ }
+ } else {
+ printk(KERN_NOTICE "%s\n", skb->data + NLMSG_SPACE(0));
+ kfree_skb(skb);
+ }
+ } else {
+ DECLARE_WAITQUEUE(wait, current);
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&kauditd_wait, &wait);
+
+ if (!skb_queue_len(&audit_skb_queue)) {
+ try_to_freeze();
+ schedule();
+ }
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&kauditd_wait, &wait);
+ }
+ }
+ return 0;
+}
+
+/**
+ * audit_send_reply - send an audit reply message via netlink
+ * @pid: process id to send reply to
+ * @seq: sequence number
+ * @type: audit message type
+ * @done: done (last) flag
+ * @multi: multi-part message flag
+ * @payload: payload data
+ * @size: payload size
+ *
+ * Allocates an skb, builds the netlink message, and sends it to the pid.
+ * No failure notifications.
+ */
+void audit_send_reply(int pid, int seq, int type, int done, int multi,
+ void *payload, int size)
+{
+ struct sk_buff *skb;
+ struct nlmsghdr *nlh;
+ int len = NLMSG_SPACE(size);
+ void *data;
+ int flags = multi ? NLM_F_MULTI : 0;
+ int t = done ? NLMSG_DONE : type;
+
+ skb = alloc_skb(len, GFP_KERNEL);
+ if (!skb)
+ return;
+
+ nlh = NLMSG_PUT(skb, pid, seq, t, size);
+ nlh->nlmsg_flags = flags;
+ data = NLMSG_DATA(nlh);
+ memcpy(data, payload, size);
+
+ /* Ignore failure. It'll only happen if the sender goes away,
+ because our timeout is set to infinite. */
+ netlink_unicast(audit_sock, skb, pid, 0);
+ return;
+
+nlmsg_failure: /* Used by NLMSG_PUT */
+ if (skb)
+ kfree_skb(skb);
+}
+
+/*
+ * Check for appropriate CAP_AUDIT_ capabilities on incoming audit
+ * control messages.
+ */
+static int audit_netlink_ok(kernel_cap_t eff_cap, u16 msg_type)
+{
+ int err = 0;
+
+ switch (msg_type) {
+ case AUDIT_GET:
+ case AUDIT_LIST:
+ case AUDIT_SET:
+ case AUDIT_ADD:
+ case AUDIT_DEL:
+ case AUDIT_SIGNAL_INFO:
+ if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
+ err = -EPERM;
+ break;
+ case AUDIT_USER:
+ case AUDIT_FIRST_USER_MSG...AUDIT_LAST_USER_MSG:
+ case AUDIT_FIRST_USER_MSG2...AUDIT_LAST_USER_MSG2:
+ if (!cap_raised(eff_cap, CAP_AUDIT_WRITE))
+ err = -EPERM;
+ break;
+ default: /* bad msg */
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+ u32 uid, pid, seq;
+ void *data;
+ struct audit_status *status_get, status_set;
+ int err;
+ struct audit_buffer *ab;
+ u16 msg_type = nlh->nlmsg_type;
+ uid_t loginuid; /* loginuid of sender */
+ struct audit_sig_info sig_data;
+
+ err = audit_netlink_ok(NETLINK_CB(skb).eff_cap, msg_type);
+ if (err)
+ return err;
+
+ /* As soon as there's any sign of userspace auditd,
+ * start kauditd to talk to it */
+ if (!kauditd_task)
+ kauditd_task = kthread_run(kauditd_thread, NULL, "kauditd");
+ if (IS_ERR(kauditd_task)) {
+ err = PTR_ERR(kauditd_task);
+ kauditd_task = NULL;
+ return err;
+ }
+
+ pid = NETLINK_CREDS(skb)->pid;
+ uid = NETLINK_CREDS(skb)->uid;
+ loginuid = NETLINK_CB(skb).loginuid;
+ seq = nlh->nlmsg_seq;
+ data = NLMSG_DATA(nlh);
+
+ switch (msg_type) {
+ case AUDIT_GET:
+ status_set.enabled = audit_enabled;
+ status_set.failure = audit_failure;
+ status_set.pid = audit_pid;
+ status_set.rate_limit = audit_rate_limit;
+ status_set.backlog_limit = audit_backlog_limit;
+ status_set.lost = atomic_read(&audit_lost);
+ status_set.backlog = skb_queue_len(&audit_skb_queue);
+ audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_GET, 0, 0,
+ &status_set, sizeof(status_set));
+ break;
+ case AUDIT_SET:
+ if (nlh->nlmsg_len < sizeof(struct audit_status))
+ return -EINVAL;
+ status_get = (struct audit_status *)data;
+ if (status_get->mask & AUDIT_STATUS_ENABLED) {
+ err = audit_set_enabled(status_get->enabled, loginuid);
+ if (err < 0) return err;
+ }
+ if (status_get->mask & AUDIT_STATUS_FAILURE) {
+ err = audit_set_failure(status_get->failure, loginuid);
+ if (err < 0) return err;
+ }
+ if (status_get->mask & AUDIT_STATUS_PID) {
+ int old = audit_pid;
+ audit_pid = status_get->pid;
+ audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+ "audit_pid=%d old=%d by auid=%u",
+ audit_pid, old, loginuid);
+ }
+ if (status_get->mask & AUDIT_STATUS_RATE_LIMIT)
+ audit_set_rate_limit(status_get->rate_limit, loginuid);
+ if (status_get->mask & AUDIT_STATUS_BACKLOG_LIMIT)
+ audit_set_backlog_limit(status_get->backlog_limit,
+ loginuid);
+ break;
+ case AUDIT_USER:
+ case AUDIT_FIRST_USER_MSG...AUDIT_LAST_USER_MSG:
+ case AUDIT_FIRST_USER_MSG2...AUDIT_LAST_USER_MSG2:
+ if (!audit_enabled && msg_type != AUDIT_USER_AVC)
+ return 0;
+
+ err = audit_filter_user(&NETLINK_CB(skb), msg_type);
+ if (err == 1) {
+ err = 0;
+ ab = audit_log_start(NULL, GFP_KERNEL, msg_type);
+ if (ab) {
+ audit_log_format(ab,
+ "user pid=%d uid=%u auid=%u msg='%.1024s'",
+ pid, uid, loginuid, (char *)data);
+ audit_set_pid(ab, pid);
+ audit_log_end(ab);
+ }
+ }
+ break;
+ case AUDIT_ADD:
+ case AUDIT_DEL:
+ if (nlh->nlmsg_len < sizeof(struct audit_rule))
+ return -EINVAL;
+ /* fallthrough */
+ case AUDIT_LIST:
+ err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid,
+ uid, seq, data, loginuid);
+ break;
+ case AUDIT_SIGNAL_INFO:
+ sig_data.uid = audit_sig_uid;
+ sig_data.pid = audit_sig_pid;
+ audit_send_reply(NETLINK_CB(skb).pid, seq, AUDIT_SIGNAL_INFO,
+ 0, 0, &sig_data, sizeof(sig_data));
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ return err < 0 ? err : 0;
+}
+
+/*
+ * Get message from skb (based on rtnetlink_rcv_skb). Each message is
+ * processed by audit_receive_msg. Malformed skbs with wrong length are
+ * discarded silently.
+ */
+static void audit_receive_skb(struct sk_buff *skb)
+{
+ int err;
+ struct nlmsghdr *nlh;
+ u32 rlen;
+
+ while (skb->len >= NLMSG_SPACE(0)) {
+ nlh = (struct nlmsghdr *)skb->data;
+ if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
+ return;
+ rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+ if (rlen > skb->len)
+ rlen = skb->len;
+ if ((err = audit_receive_msg(skb, nlh))) {
+ netlink_ack(skb, nlh, err);
+ } else if (nlh->nlmsg_flags & NLM_F_ACK)
+ netlink_ack(skb, nlh, 0);
+ skb_pull(skb, rlen);
+ }
+}
+
+/* Receive messages from netlink socket. */
+static void audit_receive(struct sock *sk, int length)
+{
+ struct sk_buff *skb;
+ unsigned int qlen;
+
+ down(&audit_netlink_sem);
+
+ 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);
+ }
+ up(&audit_netlink_sem);
+}
+
+
+/* Initialize audit support at boot time. */
+static int __init audit_init(void)
+{
+ printk(KERN_INFO "audit: initializing netlink socket (%s)\n",
+ audit_default ? "enabled" : "disabled");
+ audit_sock = netlink_kernel_create(NETLINK_AUDIT, 0, audit_receive,
+ THIS_MODULE);
+ if (!audit_sock)
+ audit_panic("cannot initialize netlink socket");
+
+ audit_sock->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT;
+ skb_queue_head_init(&audit_skb_queue);
+ audit_initialized = 1;
+ audit_enabled = audit_default;
+ audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL, "initialized");
+ return 0;
+}
+__initcall(audit_init);
+
+/* Process kernel command-line parameter at boot time. audit=0 or audit=1. */
+static int __init audit_enable(char *str)
+{
+ audit_default = !!simple_strtol(str, NULL, 0);
+ printk(KERN_INFO "audit: %s%s\n",
+ audit_default ? "enabled" : "disabled",
+ audit_initialized ? "" : " (after initialization)");
+ if (audit_initialized)
+ audit_enabled = audit_default;
+ return 0;
+}
+
+__setup("audit=", audit_enable);
+
+static void audit_buffer_free(struct audit_buffer *ab)
+{
+ unsigned long flags;
+
+ if (!ab)
+ return;
+
+ if (ab->skb)
+ kfree_skb(ab->skb);
+
+ spin_lock_irqsave(&audit_freelist_lock, flags);
+ if (++audit_freelist_count > AUDIT_MAXFREE)
+ kfree(ab);
+ else
+ list_add(&ab->list, &audit_freelist);
+ spin_unlock_irqrestore(&audit_freelist_lock, flags);
+}
+
+static struct audit_buffer * audit_buffer_alloc(struct audit_context *ctx,
+ gfp_t gfp_mask, int type)
+{
+ unsigned long flags;
+ struct audit_buffer *ab = NULL;
+ struct nlmsghdr *nlh;
+
+ spin_lock_irqsave(&audit_freelist_lock, flags);
+ if (!list_empty(&audit_freelist)) {
+ ab = list_entry(audit_freelist.next,
+ struct audit_buffer, list);
+ list_del(&ab->list);
+ --audit_freelist_count;
+ }
+ spin_unlock_irqrestore(&audit_freelist_lock, flags);
+
+ if (!ab) {
+ ab = kmalloc(sizeof(*ab), gfp_mask);
+ if (!ab)
+ goto err;
+ }
+
+ ab->skb = alloc_skb(AUDIT_BUFSIZ, gfp_mask);
+ if (!ab->skb)
+ goto err;
+
+ ab->ctx = ctx;
+ ab->gfp_mask = gfp_mask;
+ nlh = (struct nlmsghdr *)skb_put(ab->skb, NLMSG_SPACE(0));
+ nlh->nlmsg_type = type;
+ nlh->nlmsg_flags = 0;
+ nlh->nlmsg_pid = 0;
+ nlh->nlmsg_seq = 0;
+ return ab;
+err:
+ audit_buffer_free(ab);
+ return NULL;
+}
+
+/**
+ * audit_serial - compute a serial number for the audit record
+ *
+ * Compute a serial number for the audit record. Audit records are
+ * written to user-space as soon as they are generated, so a complete
+ * audit record may be written in several pieces. The timestamp of the
+ * record and this serial number are used by the user-space tools to
+ * determine which pieces belong to the same audit record. The
+ * (timestamp,serial) tuple is unique for each syscall and is live from
+ * syscall entry to syscall exit.
+ *
+ * NOTE: Another possibility is to store the formatted records off the
+ * audit context (for those records that have a context), and emit them
+ * all at syscall exit. However, this could delay the reporting of
+ * significant errors until syscall exit (or never, if the system
+ * halts).
+ */
+unsigned int audit_serial(void)
+{
+ static spinlock_t serial_lock = SPIN_LOCK_UNLOCKED;
+ static unsigned int serial = 0;
+
+ unsigned long flags;
+ unsigned int ret;
+
+ spin_lock_irqsave(&serial_lock, flags);
+ do {
+ ret = ++serial;
+ } while (unlikely(!ret));
+ spin_unlock_irqrestore(&serial_lock, flags);
+
+ return ret;
+}
+
+static inline void audit_get_stamp(struct audit_context *ctx,
+ struct timespec *t, unsigned int *serial)
+{
+ if (ctx)
+ auditsc_get_stamp(ctx, t, serial);
+ else {
+ *t = CURRENT_TIME;
+ *serial = audit_serial();
+ }
+}
+
+/* Obtain an audit buffer. This routine does locking to obtain the
+ * audit buffer, but then no locking is required for calls to
+ * audit_log_*format. If the tsk is a task that is currently in a
+ * syscall, then the syscall is marked as auditable and an audit record
+ * will be written at syscall exit. If there is no associated task, tsk
+ * should be NULL. */
+
+/**
+ * audit_log_start - obtain an audit buffer
+ * @ctx: audit_context (may be NULL)
+ * @gfp_mask: type of allocation
+ * @type: audit message type
+ *
+ * Returns audit_buffer pointer on success or NULL on error.
+ *
+ * Obtain an audit buffer. This routine does locking to obtain the
+ * audit buffer, but then no locking is required for calls to
+ * audit_log_*format. If the task (ctx) is a task that is currently in a
+ * syscall, then the syscall is marked as auditable and an audit record
+ * will be written at syscall exit. If there is no associated task, then
+ * task context (ctx) should be NULL.
+ */
+struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask,
+ int type)
+{
+ struct audit_buffer *ab = NULL;
+ struct timespec t;
+ unsigned int serial;
+ int reserve;
+ unsigned long timeout_start = jiffies;
+
+ if (!audit_initialized)
+ return NULL;
+
+ if (unlikely(audit_filter_type(type)))
+ return NULL;
+
+ if (gfp_mask & __GFP_WAIT)
+ reserve = 0;
+ else
+ reserve = 5; /* Allow atomic callers to go up to five
+ entries over the normal backlog limit */
+
+ while (audit_backlog_limit
+ && skb_queue_len(&audit_skb_queue) > audit_backlog_limit + reserve) {
+ if (gfp_mask & __GFP_WAIT && audit_backlog_wait_time
+ && time_before(jiffies, timeout_start + audit_backlog_wait_time)) {
+
+ /* Wait for auditd to drain the queue a little */
+ DECLARE_WAITQUEUE(wait, current);
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&audit_backlog_wait, &wait);
+
+ if (audit_backlog_limit &&
+ skb_queue_len(&audit_skb_queue) > audit_backlog_limit)
+ schedule_timeout(timeout_start + audit_backlog_wait_time - jiffies);
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&audit_backlog_wait, &wait);
+ continue;
+ }
+ if (audit_rate_check())
+ printk(KERN_WARNING
+ "audit: audit_backlog=%d > "
+ "audit_backlog_limit=%d\n",
+ skb_queue_len(&audit_skb_queue),
+ audit_backlog_limit);
+ audit_log_lost("backlog limit exceeded");
+ audit_backlog_wait_time = audit_backlog_wait_overflow;
+ wake_up(&audit_backlog_wait);
+ return NULL;
+ }
+
+ ab = audit_buffer_alloc(ctx, gfp_mask, type);
+ if (!ab) {
+ audit_log_lost("out of memory in audit_log_start");
+ return NULL;
+ }
+
+ audit_get_stamp(ab->ctx, &t, &serial);
+
+ audit_log_format(ab, "audit(%lu.%03lu:%u): ",
+ t.tv_sec, t.tv_nsec/1000000, serial);
+ return ab;
+}
+
+/**
+ * audit_expand - expand skb in the audit buffer
+ * @ab: audit_buffer
+ * @extra: space to add at tail of the skb
+ *
+ * Returns 0 (no space) on failed expansion, or available space if
+ * successful.
+ */
+static inline int audit_expand(struct audit_buffer *ab, int extra)
+{
+ struct sk_buff *skb = ab->skb;
+ int ret = pskb_expand_head(skb, skb_headroom(skb), extra,
+ ab->gfp_mask);
+ if (ret < 0) {
+ audit_log_lost("out of memory in audit_expand");
+ return 0;
+ }
+ return skb_tailroom(skb);
+}
+
+/*
+ * Format an audit message into the audit buffer. If there isn't enough
+ * room in the audit buffer, more room will be allocated and vsnprint
+ * will be called a second time. Currently, we assume that a printk
+ * can't format message larger than 1024 bytes, so we don't either.
+ */
+static void audit_log_vformat(struct audit_buffer *ab, const char *fmt,
+ va_list args)
+{
+ int len, avail;
+ struct sk_buff *skb;
+ va_list args2;
+
+ if (!ab)
+ return;
+
+ BUG_ON(!ab->skb);
+ skb = ab->skb;
+ avail = skb_tailroom(skb);
+ if (avail == 0) {
+ avail = audit_expand(ab, AUDIT_BUFSIZ);
+ if (!avail)
+ goto out;
+ }
+ va_copy(args2, args);
+ len = vsnprintf(skb->tail, avail, fmt, args);
+ if (len >= avail) {
+ /* The printk buffer is 1024 bytes long, so if we get
+ * here and AUDIT_BUFSIZ is at least 1024, then we can
+ * log everything that printk could have logged. */
+ avail = audit_expand(ab,
+ max_t(unsigned, AUDIT_BUFSIZ, 1+len-avail));
+ if (!avail)
+ goto out;
+ len = vsnprintf(skb->tail, avail, fmt, args2);
+ }
+ if (len > 0)
+ skb_put(skb, len);
+out:
+ return;
+}
+
+/**
+ * audit_log_format - format a message into the audit buffer.
+ * @ab: audit_buffer
+ * @fmt: format string
+ * @...: optional parameters matching @fmt string
+ *
+ * All the work is done in audit_log_vformat.
+ */
+void audit_log_format(struct audit_buffer *ab, const char *fmt, ...)
+{
+ va_list args;
+
+ if (!ab)
+ return;
+ va_start(args, fmt);
+ audit_log_vformat(ab, fmt, args);
+ va_end(args);
+}
+
+/**
+ * audit_log_hex - convert a buffer to hex and append it to the audit skb
+ * @ab: the audit_buffer
+ * @buf: buffer to convert to hex
+ * @len: length of @buf to be converted
+ *
+ * No return value; failure to expand is silently ignored.
+ *
+ * This function will take the passed buf and convert it into a string of
+ * ascii hex digits. The new string is placed onto the skb.
+ */
+void audit_log_hex(struct audit_buffer *ab, const unsigned char *buf,
+ size_t len)
+{
+ int i, avail, new_len;
+ unsigned char *ptr;
+ struct sk_buff *skb;
+ static const unsigned char *hex = "0123456789ABCDEF";
+
+ BUG_ON(!ab->skb);
+ skb = ab->skb;
+ avail = skb_tailroom(skb);
+ new_len = len<<1;
+ if (new_len >= avail) {
+ /* Round the buffer request up to the next multiple */
+ new_len = AUDIT_BUFSIZ*(((new_len-avail)/AUDIT_BUFSIZ) + 1);
+ avail = audit_expand(ab, new_len);
+ if (!avail)
+ return;
+ }
+
+ ptr = skb->tail;
+ for (i=0; i<len; i++) {
+ *ptr++ = hex[(buf[i] & 0xF0)>>4]; /* Upper nibble */
+ *ptr++ = hex[buf[i] & 0x0F]; /* Lower nibble */
+ }
+ *ptr = 0;
+ skb_put(skb, len << 1); /* new string is twice the old string */
+}
+
+/**
+ * audit_log_unstrustedstring - log a string that may contain random characters
+ * @ab: audit_buffer
+ * @string: string to be logged
+ *
+ * This code will escape a string that is passed to it if the string
+ * contains a control character, unprintable character, double quote mark,
+ * or a space. Unescaped strings will start and end with a double quote mark.
+ * Strings that are escaped are printed in hex (2 digits per char).
+ */
+void audit_log_untrustedstring(struct audit_buffer *ab, const char *string)
+{
+ const unsigned char *p = string;
+
+ while (*p) {
+ if (*p == '"' || *p < 0x21 || *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 escaped d_path */
+void audit_log_d_path(struct audit_buffer *ab, const char *prefix,
+ struct dentry *dentry, struct vfsmount *vfsmnt)
+{
+ char *p, *path;
+
+ if (prefix)
+ audit_log_format(ab, " %s", prefix);
+
+ /* We will allow 11 spaces for ' (deleted)' to be appended */
+ path = kmalloc(PATH_MAX+11, ab->gfp_mask);
+ if (!path) {
+ audit_log_format(ab, "<no memory>");
+ return;
+ }
+ p = d_path(dentry, vfsmnt, path, PATH_MAX+11);
+ if (IS_ERR(p)) { /* Should never happen since we send PATH_MAX */
+ /* FIXME: can we save some information here? */
+ audit_log_format(ab, "<too long>");
+ } else
+ audit_log_untrustedstring(ab, p);
+ kfree(path);
+}
+
+/**
+ * audit_log_end - end one audit record
+ * @ab: the audit_buffer
+ *
+ * The netlink_* functions cannot be called inside an irq context, so
+ * the audit buffer is placed on a queue and a tasklet is scheduled to
+ * remove them from the queue outside the irq context. May be called in
+ * any context.
+ */
+void audit_log_end(struct audit_buffer *ab)
+{
+ if (!ab)
+ return;
+ if (!audit_rate_check()) {
+ audit_log_lost("rate limit exceeded");
+ } else {
+ if (audit_pid) {
+ struct nlmsghdr *nlh = (struct nlmsghdr *)ab->skb->data;
+ nlh->nlmsg_len = ab->skb->len - NLMSG_SPACE(0);
+ skb_queue_tail(&audit_skb_queue, ab->skb);
+ ab->skb = NULL;
+ wake_up_interruptible(&kauditd_wait);
+ } else {
+ printk(KERN_NOTICE "%s\n", ab->skb->data + NLMSG_SPACE(0));
+ }
+ }
+ audit_buffer_free(ab);
+}
+
+/**
+ * audit_log - Log an audit record
+ * @ctx: audit context
+ * @gfp_mask: type of allocation
+ * @type: audit message type
+ * @fmt: format string to use
+ * @...: variable parameters matching the format string
+ *
+ * This is a convenience function that calls audit_log_start,
+ * audit_log_vformat, and audit_log_end. It may be called
+ * in any context.
+ */
+void audit_log(struct audit_context *ctx, gfp_t gfp_mask, int type,
+ const char *fmt, ...)
+{
+ struct audit_buffer *ab;
+ va_list args;
+
+ ab = audit_log_start(ctx, gfp_mask, type);
+ if (ab) {
+ va_start(args, fmt);
+ audit_log_vformat(ab, fmt, args);
+ va_end(args);
+ audit_log_end(ab);
+ }
+}
diff --git a/kernel/audit/audit.h b/kernel/audit/audit.h
new file mode 100644
index 0000000..3ac9f6c
--- /dev/null
+++ b/kernel/audit/audit.h
@@ -0,0 +1,70 @@
+/* audit/audit.h -- definition of audit_context structure and supporting types
+ *
+ * Copyright 2003-2004 Red Hat, Inc.
+ * Copyright 2005 Hewlett-Packard Development Company, L.P.
+ * Copyright 2005 IBM Corporation
+ *
+ * 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
+ */
+
+#include <linux/fs.h>
+#include <linux/audit.h>
+
+/* 0 = no checking
+ 1 = put_count checking
+ 2 = verbose put_count checking
+*/
+#define AUDIT_DEBUG 0
+
+/* At task start time, the audit_state is set in the audit_context using
+ a per-task filter. At syscall entry, the audit_state is augmented by
+ the syscall filter. */
+enum audit_state {
+ AUDIT_DISABLED, /* Do not create per-task audit_context.
+ * No syscall-specific audit records can
+ * be generated. */
+ AUDIT_SETUP_CONTEXT, /* Create the per-task audit_context,
+ * but don't necessarily fill it in at
+ * syscall entry time (i.e., filter
+ * instead). */
+ AUDIT_BUILD_CONTEXT, /* Create the per-task audit_context,
+ * and always fill it in at syscall
+ * entry time. This makes a full
+ * syscall record available if some
+ * other part of the kernel decides it
+ * should be recorded. */
+ AUDIT_RECORD_CONTEXT /* Create the per-task audit_context,
+ * always fill it in at syscall entry
+ * time, and always write out the audit
+ * record at syscall exit time. */
+};
+
+/* Rule lists */
+struct audit_entry {
+ struct list_head list;
+ struct rcu_head rcu;
+ struct audit_rule rule;
+};
+
+
+extern int audit_pid;
+extern int audit_comparator(const u32 left, const u32 op, const u32 right);
+
+extern void audit_send_reply(int pid, int seq, int type,
+ int done, int multi,
+ void *payload, int size);
+extern void audit_log_lost(const char *message);
+extern void audit_panic(const char *message);
+extern struct semaphore audit_netlink_sem;
diff --git a/kernel/audit/filter.c b/kernel/audit/filter.c
new file mode 100644
index 0000000..20dea91
--- /dev/null
+++ b/kernel/audit/filter.c
@@ -0,0 +1,375 @@
+/* audit/filter.c -- filtering of audit events
+ *
+ * Copyright 2003-2004 Red Hat, Inc.
+ * Copyright 2005 Hewlett-Packard Development Company, L.P.
+ * Copyright 2005 IBM Corporation
+ *
+ * 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
+ */
+
+#include <linux/kernel.h>
+#include <linux/audit.h>
+#include <linux/kthread.h>
+#include <linux/netlink.h>
+#include "audit.h"
+
+/* There are three lists of rules -- one to search at task creation
+ * time, one to search at syscall entry time, and another to search at
+ * syscall exit time. */
+struct list_head audit_filter_list[AUDIT_NR_FILTERS] = {
+ LIST_HEAD_INIT(audit_filter_list[0]),
+ LIST_HEAD_INIT(audit_filter_list[1]),
+ LIST_HEAD_INIT(audit_filter_list[2]),
+ LIST_HEAD_INIT(audit_filter_list[3]),
+ LIST_HEAD_INIT(audit_filter_list[4]),
+ LIST_HEAD_INIT(audit_filter_list[5]),
+#if AUDIT_NR_FILTERS != 6
+#error Fix audit_filter_list initialiser
+#endif
+};
+
+/* Copy rule from user-space to kernel-space. Called from
+ * audit_add_rule during AUDIT_ADD. */
+static inline int audit_copy_rule(struct audit_rule *d, struct audit_rule *s)
+{
+ int i;
+
+ if (s->action != AUDIT_NEVER
+ && s->action != AUDIT_POSSIBLE
+ && s->action != AUDIT_ALWAYS)
+ return -1;
+ if (s->field_count < 0 || s->field_count > AUDIT_MAX_FIELDS)
+ return -1;
+ if ((s->flags & ~AUDIT_FILTER_PREPEND) >= AUDIT_NR_FILTERS)
+ return -1;
+
+ d->flags = s->flags;
+ d->action = s->action;
+ d->field_count = s->field_count;
+ for (i = 0; i < d->field_count; i++) {
+ d->fields[i] = s->fields[i];
+ d->values[i] = s->values[i];
+ }
+ for (i = 0; i < AUDIT_BITMASK_SIZE; i++) d->mask[i] = s->mask[i];
+ return 0;
+}
+
+/* Check to see if two rules are identical. It is called from
+ * audit_add_rule during AUDIT_ADD and
+ * audit_del_rule during AUDIT_DEL. */
+static int audit_compare_rule(struct audit_rule *a, struct audit_rule *b)
+{
+ int i;
+
+ if (a->flags != b->flags)
+ return 1;
+
+ if (a->action != b->action)
+ return 1;
+
+ if (a->field_count != b->field_count)
+ return 1;
+
+ for (i = 0; i < a->field_count; i++) {
+ if (a->fields[i] != b->fields[i]
+ || a->values[i] != b->values[i])
+ return 1;
+ }
+
+ for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
+ if (a->mask[i] != b->mask[i])
+ return 1;
+
+ return 0;
+}
+
+/* Note that audit_add_rule and audit_del_rule are called via
+ * audit_receive() in audit.c, and are protected by
+ * audit_netlink_sem. */
+static inline int audit_add_rule(struct audit_rule *rule,
+ struct list_head *list)
+{
+ struct audit_entry *entry;
+ int i;
+
+ /* Do not use the _rcu iterator here, since this is the only
+ * addition routine. */
+ list_for_each_entry(entry, list, list) {
+ if (!audit_compare_rule(rule, &entry->rule))
+ return -EEXIST;
+ }
+
+ for (i = 0; i < rule->field_count; i++) {
+ if (rule->fields[i] & AUDIT_UNUSED_BITS)
+ return -EINVAL;
+ if ( rule->fields[i] & AUDIT_NEGATE)
+ rule->fields[i] |= AUDIT_NOT_EQUAL;
+ else if ( (rule->fields[i] & AUDIT_OPERATORS) == 0 )
+ rule->fields[i] |= AUDIT_EQUAL;
+ rule->fields[i] &= ~AUDIT_NEGATE;
+ }
+
+ if (!(entry = kmalloc(sizeof(*entry), GFP_KERNEL)))
+ return -ENOMEM;
+ if (audit_copy_rule(&entry->rule, rule)) {
+ kfree(entry);
+ return -EINVAL;
+ }
+
+ if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
+ entry->rule.flags &= ~AUDIT_FILTER_PREPEND;
+ list_add_rcu(&entry->list, list);
+ } else {
+ list_add_tail_rcu(&entry->list, list);
+ }
+
+ return 0;
+}
+
+static inline void audit_free_rule(struct rcu_head *head)
+{
+ struct audit_entry *e = container_of(head, struct audit_entry, rcu);
+ kfree(e);
+}
+
+/* Note that audit_add_rule and audit_del_rule are called via
+ * audit_receive() in audit.c, and are protected by
+ * audit_netlink_sem. */
+static inline int audit_del_rule(struct audit_rule *rule,
+ struct list_head *list)
+{
+ struct audit_entry *e;
+
+ /* Do not use the _rcu iterator here, since this is the only
+ * deletion routine. */
+ list_for_each_entry(e, list, list) {
+ if (!audit_compare_rule(rule, &e->rule)) {
+ list_del_rcu(&e->list);
+ call_rcu(&e->rcu, audit_free_rule);
+ return 0;
+ }
+ }
+ return -ENOENT; /* No matching rule */
+}
+
+static int audit_list_rules(void *_dest)
+{
+ int pid, seq;
+ int *dest = _dest;
+ struct audit_entry *entry;
+ int i;
+
+ pid = dest[0];
+ seq = dest[1];
+ kfree(dest);
+
+ down(&audit_netlink_sem);
+
+ /* The *_rcu iterators not needed here because we are
+ always called with audit_netlink_sem held. */
+ for (i=0; i<AUDIT_NR_FILTERS; i++) {
+ list_for_each_entry(entry, &audit_filter_list[i], list)
+ audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
+ &entry->rule, sizeof(entry->rule));
+ }
+ audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
+
+ up(&audit_netlink_sem);
+ return 0;
+}
+
+/**
+ * audit_receive_filter - apply all rules to the specified message type
+ * @type: audit message type
+ * @pid: target pid for netlink audit messages
+ * @uid: target uid for netlink audit messages
+ * @seq: netlink audit message sequence (serial) number
+ * @data: payload data
+ * @loginuid: loginuid of sender
+ */
+int audit_receive_filter(int type, int pid, int uid, int seq, void *data,
+ uid_t loginuid)
+{
+ struct task_struct *tsk;
+ int *dest;
+ int err = 0;
+ unsigned listnr;
+
+ switch (type) {
+ case AUDIT_LIST:
+ /* We can't just spew out the rules here because we might fill
+ * the available socket buffer space and deadlock waiting for
+ * auditctl to read from it... which isn't ever going to
+ * happen if we're actually running in the context of auditctl
+ * trying to _send_ the stuff */
+
+ dest = kmalloc(2 * sizeof(int), GFP_KERNEL);
+ if (!dest)
+ return -ENOMEM;
+ dest[0] = pid;
+ dest[1] = seq;
+
+ tsk = kthread_run(audit_list_rules, dest, "audit_list_rules");
+ if (IS_ERR(tsk)) {
+ kfree(dest);
+ err = PTR_ERR(tsk);
+ }
+ break;
+ case AUDIT_ADD:
+ listnr = ((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
+ switch(listnr) {
+ default:
+ return -EINVAL;
+
+ case AUDIT_FILTER_USER:
+ case AUDIT_FILTER_TYPE:
+#ifdef CONFIG_AUDITSYSCALL
+ case AUDIT_FILTER_ENTRY:
+ case AUDIT_FILTER_EXIT:
+ case AUDIT_FILTER_TASK:
+#endif
+ ;
+ }
+ err = audit_add_rule(data, &audit_filter_list[listnr]);
+ if (!err)
+ audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+ "auid=%u added an audit rule\n", loginuid);
+ break;
+ case AUDIT_DEL:
+ listnr =((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
+ if (listnr >= AUDIT_NR_FILTERS)
+ return -EINVAL;
+
+ err = audit_del_rule(data, &audit_filter_list[listnr]);
+ if (!err)
+ audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+ "auid=%u removed an audit rule\n", loginuid);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return err;
+}
+
+int audit_comparator(const u32 left, const u32 op, const u32 right)
+{
+ switch (op) {
+ case AUDIT_EQUAL:
+ return (left == right);
+ case AUDIT_NOT_EQUAL:
+ return (left != right);
+ case AUDIT_LESS_THAN:
+ return (left < right);
+ case AUDIT_LESS_THAN_OR_EQUAL:
+ return (left <= right);
+ case AUDIT_GREATER_THAN:
+ return (left > right);
+ case AUDIT_GREATER_THAN_OR_EQUAL:
+ return (left >= right);
+ default:
+ return -EINVAL;
+ }
+}
+
+
+
+static int audit_filter_user_rules(struct netlink_skb_parms *cb,
+ struct audit_rule *rule,
+ enum audit_state *state)
+{
+ int i;
+
+ for (i = 0; i < rule->field_count; i++) {
+ u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
+ u32 op = rule->fields[i] & AUDIT_OPERATORS;
+ u32 value = rule->values[i];
+ int result = 0;
+
+ switch (field) {
+ case AUDIT_PID:
+ result = audit_comparator(cb->creds.pid, op, value);
+ break;
+ case AUDIT_UID:
+ result = audit_comparator(cb->creds.uid, op, value);
+ break;
+ case AUDIT_GID:
+ result = audit_comparator(cb->creds.gid, op, value);
+ break;
+ case AUDIT_LOGINUID:
+ result = audit_comparator(cb->loginuid, op, value);
+ break;
+ }
+
+ if (!result)
+ return 0;
+ }
+ switch (rule->action) {
+ case AUDIT_NEVER: *state = AUDIT_DISABLED; break;
+ case AUDIT_POSSIBLE: *state = AUDIT_BUILD_CONTEXT; break;
+ case AUDIT_ALWAYS: *state = AUDIT_RECORD_CONTEXT; break;
+ }
+ return 1;
+}
+
+int audit_filter_user(struct netlink_skb_parms *cb, int type)
+{
+ struct audit_entry *e;
+ enum audit_state state;
+ int ret = 1;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_USER], list) {
+ if (audit_filter_user_rules(cb, &e->rule, &state)) {
+ if (state == AUDIT_DISABLED)
+ ret = 0;
+ break;
+ }
+ }
+ rcu_read_unlock();
+
+ return ret; /* Audit by default */
+}
+
+int audit_filter_type(int type)
+{
+ struct audit_entry *e;
+ int result = 0;
+
+ rcu_read_lock();
+ if (list_empty(&audit_filter_list[AUDIT_FILTER_TYPE]))
+ goto unlock_and_return;
+
+ list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TYPE],
+ list) {
+ struct audit_rule *rule = &e->rule;
+ int i;
+ for (i = 0; i < rule->field_count; i++) {
+ u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
+ u32 op = rule->fields[i] & AUDIT_OPERATORS;
+ u32 value = rule->values[i];
+ if ( field == AUDIT_MSGTYPE ) {
+ result = audit_comparator(type, op, value);
+ if (!result)
+ break;
+ }
+ }
+ if (result)
+ goto unlock_and_return;
+ }
+unlock_and_return:
+ rcu_read_unlock();
+ return result;
+}
diff --git a/kernel/audit/syscall.c b/kernel/audit/syscall.c
new file mode 100644
index 0000000..4d7ec87
--- /dev/null
+++ b/kernel/audit/syscall.c
@@ -0,0 +1,1320 @@
+/* audit/syscall.c -- System-call auditing support
+ * Handles all system-call specific auditing features.
+ *
+ * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina.
+ * Copyright 2005 Hewlett-Packard Development Company, L.P.
+ * Copyright (C) 2005 IBM Corporation
+ * 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 Rickard E. (Rik) Faith <faith(a)redhat.com>
+ *
+ * Many of the ideas implemented here are from Stephen C. Tweedie,
+ * especially the idea of avoiding a copy by using getname.
+ *
+ * The method for actual interception of syscall entry and exit (not in
+ * this file -- see entry.S) is based on a GPL'd patch written by
+ * okir(a)suse.de and Copyright 2003 SuSE Linux AG.
+ *
+ * The support of additional filter rules compares (>, <, >=, <=) was
+ * added by Dustin Kirkland <dustin.kirkland(a)us.ibm.com>, 2005.
+ *
+ * Modified by Amy Griffis <amy.griffis(a)hp.com> to collect additional
+ * filesystem information.
+ *
+ * Subject and object context labeling support added by <danjones(a)us.ibm.com>
+ * and <dustin.kirkland(a)us.ibm.com> for LSPP certification compliance.
+ */
+
+#include <linux/init.h>
+#include <asm/atomic.h>
+#include <asm/types.h>
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mount.h>
+#include <linux/socket.h>
+#include <linux/audit.h>
+#include <linux/personality.h>
+#include <linux/time.h>
+#include <linux/netlink.h>
+#include <linux/compiler.h>
+#include <asm/unistd.h>
+#include <linux/security.h>
+#include <linux/list.h>
+
+#include "audit.h"
+
+extern struct list_head audit_filter_list[];
+
+/* No syscall auditing will take place unless audit_enabled != 0. */
+extern int audit_enabled;
+
+/* AUDIT_NAMES is the number of slots we reserve in the audit_context
+ * for saving names from getname(). */
+#define AUDIT_NAMES 20
+
+/* AUDIT_NAMES_RESERVED is the number of slots we reserve in the
+ * audit_context from being used for nameless inodes from
+ * path_lookup. */
+#define AUDIT_NAMES_RESERVED 7
+
+/* When fs/namei.c:getname() is called, we store the pointer in name and
+ * we don't let putname() free it (instead we free all of the saved
+ * pointers at syscall exit time).
+ *
+ * Further, in fs/namei.c:path_lookup() we store the inode and device. */
+struct audit_names {
+ const char *name;
+ unsigned long ino;
+ unsigned long pino;
+ dev_t dev;
+ umode_t mode;
+ uid_t uid;
+ gid_t gid;
+ dev_t rdev;
+ char *ctx;
+};
+
+struct audit_aux_data {
+ struct audit_aux_data *next;
+ int type;
+};
+
+#define AUDIT_AUX_IPCPERM 0
+
+struct audit_aux_data_ipcctl {
+ struct audit_aux_data d;
+ struct ipc_perm p;
+ unsigned long qbytes;
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+ char *ctx;
+};
+
+struct audit_aux_data_socketcall {
+ struct audit_aux_data d;
+ int nargs;
+ unsigned long args[0];
+};
+
+struct audit_aux_data_sockaddr {
+ struct audit_aux_data d;
+ int len;
+ char a[0];
+};
+
+struct audit_aux_data_path {
+ struct audit_aux_data d;
+ struct dentry *dentry;
+ struct vfsmount *mnt;
+};
+
+/* The per-task audit context. */
+struct audit_context {
+ int in_syscall; /* 1 if task is in a syscall */
+ enum audit_state state;
+ unsigned int serial; /* serial number for record */
+ struct timespec ctime; /* time of syscall entry */
+ uid_t loginuid; /* login uid (identity) */
+ int major; /* syscall number */
+ unsigned long argv[4]; /* syscall arguments */
+ int return_valid; /* return code is valid */
+ long return_code;/* syscall return code */
+ int auditable; /* 1 if record should be written */
+ int name_count;
+ struct audit_names names[AUDIT_NAMES];
+ struct dentry * pwd;
+ struct vfsmount * pwdmnt;
+ 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;
+ gid_t gid, egid, sgid, fsgid;
+ unsigned long personality;
+ int arch;
+
+#if AUDIT_DEBUG
+ int put_count;
+ int ino_count;
+#endif
+};
+
+
+/* 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_rule *rule,
+ struct audit_context *ctx,
+ enum audit_state *state)
+{
+ int i, j;
+
+ for (i = 0; i < rule->field_count; i++) {
+ u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
+ u32 op = rule->fields[i] & AUDIT_OPERATORS;
+ u32 value = rule->values[i];
+ int result = 0;
+
+ switch (field) {
+ case AUDIT_PID:
+ result = audit_comparator(tsk->pid, op, value);
+ break;
+ case AUDIT_UID:
+ result = audit_comparator(tsk->uid, op, value);
+ break;
+ case AUDIT_EUID:
+ result = audit_comparator(tsk->euid, op, value);
+ break;
+ case AUDIT_SUID:
+ result = audit_comparator(tsk->suid, op, value);
+ break;
+ case AUDIT_FSUID:
+ result = audit_comparator(tsk->fsuid, op, value);
+ break;
+ case AUDIT_GID:
+ result = audit_comparator(tsk->gid, op, value);
+ break;
+ case AUDIT_EGID:
+ result = audit_comparator(tsk->egid, op, value);
+ break;
+ case AUDIT_SGID:
+ result = audit_comparator(tsk->sgid, op, value);
+ break;
+ case AUDIT_FSGID:
+ result = audit_comparator(tsk->fsgid, op, value);
+ break;
+ case AUDIT_PERS:
+ result = audit_comparator(tsk->personality, op, value);
+ break;
+ case AUDIT_ARCH:
+ if (ctx)
+ result = audit_comparator(ctx->arch, op, value);
+ break;
+
+ case AUDIT_EXIT:
+ if (ctx && ctx->return_valid)
+ result = audit_comparator(ctx->return_code, op, value);
+ break;
+ case AUDIT_SUCCESS:
+ if (ctx && ctx->return_valid) {
+ if (value)
+ result = audit_comparator(ctx->return_valid, op, AUDITSC_SUCCESS);
+ else
+ result = audit_comparator(ctx->return_valid, op, AUDITSC_FAILURE);
+ }
+ break;
+ case AUDIT_DEVMAJOR:
+ if (ctx) {
+ for (j = 0; j < ctx->name_count; j++) {
+ if (audit_comparator(MAJOR(ctx->names[j].dev), op, value)) {
+ ++result;
+ break;
+ }
+ }
+ }
+ break;
+ case AUDIT_DEVMINOR:
+ if (ctx) {
+ for (j = 0; j < ctx->name_count; j++) {
+ if (audit_comparator(MINOR(ctx->names[j].dev), op, value)) {
+ ++result;
+ break;
+ }
+ }
+ }
+ break;
+ case AUDIT_INODE:
+ if (ctx) {
+ for (j = 0; j < ctx->name_count; j++) {
+ if (audit_comparator(ctx->names[j].ino, op, value) ||
+ audit_comparator(ctx->names[j].pino, op, value)) {
+ ++result;
+ break;
+ }
+ }
+ }
+ break;
+ case AUDIT_LOGINUID:
+ result = 0;
+ if (ctx)
+ result = audit_comparator(ctx->loginuid, op, value);
+ break;
+ case AUDIT_ARG0:
+ case AUDIT_ARG1:
+ case AUDIT_ARG2:
+ case AUDIT_ARG3:
+ if (ctx)
+ result = audit_comparator(ctx->argv[field-AUDIT_ARG0], op, value);
+ break;
+ }
+
+ if (!result)
+ return 0;
+ }
+ switch (rule->action) {
+ case AUDIT_NEVER: *state = AUDIT_DISABLED; break;
+ case AUDIT_POSSIBLE: *state = AUDIT_BUILD_CONTEXT; break;
+ case AUDIT_ALWAYS: *state = AUDIT_RECORD_CONTEXT; break;
+ }
+ return 1;
+}
+
+/* At process creation time, we can determine if system-call auditing is
+ * completely disabled for this task. Since we only have the task
+ * structure at this point, we can only check uid and gid.
+ */
+static enum audit_state audit_filter_task(struct task_struct *tsk)
+{
+ struct audit_entry *e;
+ enum audit_state state;
+
+ 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)) {
+ rcu_read_unlock();
+ return state;
+ }
+ }
+ rcu_read_unlock();
+ return AUDIT_BUILD_CONTEXT;
+}
+
+/* At syscall entry and exit time, this filter is called if the
+ * audit_state is not low enough that auditing cannot take place, but is
+ * also not high enough that we already know we have to write an audit
+ * record (i.e., the state is AUDIT_SETUP_CONTEXT or AUDIT_BUILD_CONTEXT).
+ */
+static enum audit_state audit_filter_syscall(struct task_struct *tsk,
+ struct audit_context *ctx,
+ struct list_head *list)
+{
+ struct audit_entry *e;
+ enum audit_state state;
+
+ if (audit_pid && tsk->tgid == audit_pid)
+ return AUDIT_DISABLED;
+
+ rcu_read_lock();
+ if (!list_empty(list)) {
+ int word = AUDIT_WORD(ctx->major);
+ 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)) {
+ rcu_read_unlock();
+ return state;
+ }
+ }
+ }
+ rcu_read_unlock();
+ return AUDIT_BUILD_CONTEXT;
+}
+
+/* This should be called with task_lock() held. */
+static inline struct audit_context *audit_get_context(struct task_struct *tsk,
+ int return_valid,
+ int return_code)
+{
+ struct audit_context *context = tsk->audit_context;
+
+ if (likely(!context))
+ return NULL;
+ context->return_valid = return_valid;
+ context->return_code = return_code;
+
+ 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;
+ }
+
+ context->pid = tsk->pid;
+ context->uid = tsk->uid;
+ context->gid = tsk->gid;
+ context->euid = tsk->euid;
+ context->suid = tsk->suid;
+ context->fsuid = tsk->fsuid;
+ context->egid = tsk->egid;
+ context->sgid = tsk->sgid;
+ context->fsgid = tsk->fsgid;
+ context->personality = tsk->personality;
+ tsk->audit_context = NULL;
+ return context;
+}
+
+static inline void audit_free_names(struct audit_context *context)
+{
+ int i;
+
+#if AUDIT_DEBUG == 2
+ if (context->auditable
+ ||context->put_count + context->ino_count != context->name_count) {
+ printk(KERN_ERR "%s:%d(:%d): major=%d in_syscall=%d"
+ " name_count=%d put_count=%d"
+ " ino_count=%d [NOT freeing]\n",
+ __FILE__, __LINE__,
+ context->serial, context->major, context->in_syscall,
+ context->name_count, context->put_count,
+ context->ino_count);
+ for (i = 0; i < context->name_count; i++) {
+ printk(KERN_ERR "names[%d] = %p = %s\n", i,
+ context->names[i].name,
+ context->names[i].name ?: "(null)");
+ kfree(context->names[i].ctx);
+ }
+ dump_stack();
+ return;
+ }
+#endif
+#if AUDIT_DEBUG
+ context->put_count = 0;
+ context->ino_count = 0;
+#endif
+
+ for (i = 0; i < context->name_count; i++)
+ if (context->names[i].name)
+ __putname(context->names[i].name);
+ context->name_count = 0;
+ if (context->pwd)
+ dput(context->pwd);
+ if (context->pwdmnt)
+ mntput(context->pwdmnt);
+ context->pwd = NULL;
+ context->pwdmnt = NULL;
+}
+
+static inline void audit_free_aux(struct audit_context *context)
+{
+ struct audit_aux_data *aux;
+
+ while ((aux = context->aux)) {
+ if (aux->type == AUDIT_AVC_PATH) {
+ struct audit_aux_data_path *axi = (void *)aux;
+ dput(axi->dentry);
+ mntput(axi->mnt);
+ }
+ if ( aux->type == AUDIT_IPC ) {
+ struct audit_aux_data_ipcctl *axi = (void *)aux;
+ if (axi->ctx)
+ kfree(axi->ctx);
+ }
+
+ context->aux = aux->next;
+ kfree(aux);
+ }
+}
+
+static inline void audit_zero_context(struct audit_context *context,
+ enum audit_state state)
+{
+ uid_t loginuid = context->loginuid;
+
+ memset(context, 0, sizeof(*context));
+ context->state = state;
+ context->loginuid = loginuid;
+}
+
+static inline struct audit_context *audit_alloc_context(enum audit_state state)
+{
+ struct audit_context *context;
+
+ if (!(context = kmalloc(sizeof(*context), GFP_KERNEL)))
+ return NULL;
+ audit_zero_context(context, state);
+ return context;
+}
+
+/**
+ * audit_alloc - allocate an audit context block for a task
+ * @tsk: task
+ *
+ * Filter on the task information and allocate a per-task audit context
+ * if necessary. Doing so turns on system call auditing for the
+ * specified task. This is called from copy_process, so no lock is
+ * needed.
+ */
+int audit_alloc(struct task_struct *tsk)
+{
+ struct audit_context *context;
+ enum audit_state state;
+
+ if (likely(!audit_enabled))
+ return 0; /* Return if not auditing. */
+
+ state = audit_filter_task(tsk);
+ if (likely(state == AUDIT_DISABLED))
+ return 0;
+
+ if (!(context = audit_alloc_context(state))) {
+ audit_log_lost("out of memory in audit_alloc");
+ return -ENOMEM;
+ }
+
+ /* Preserve login uid */
+ context->loginuid = -1;
+ if (current->audit_context)
+ context->loginuid = current->audit_context->loginuid;
+
+ tsk->audit_context = context;
+ set_tsk_thread_flag(tsk, TIF_SYSCALL_AUDIT);
+ return 0;
+}
+
+static inline void audit_free_context(struct audit_context *context)
+{
+ struct audit_context *previous;
+ int count = 0;
+
+ do {
+ previous = context->previous;
+ if (previous || (count && count < 10)) {
+ ++count;
+ printk(KERN_ERR "audit(:%d): major=%d name_count=%d:"
+ " freeing multiple contexts (%d)\n",
+ context->serial, context->major,
+ context->name_count, count);
+ }
+ audit_free_names(context);
+ audit_free_aux(context);
+ kfree(context);
+ context = previous;
+ } while (context);
+ if (count >= 10)
+ printk(KERN_ERR "audit: freed %d contexts\n", count);
+}
+
+static void audit_log_task_context(struct audit_buffer *ab)
+{
+ char *ctx = NULL;
+ ssize_t len = 0;
+
+ len = security_getprocattr(current, "current", NULL, 0);
+ if (len < 0) {
+ if (len != -EINVAL)
+ goto error_path;
+ return;
+ }
+
+ ctx = kmalloc(len, GFP_KERNEL);
+ if (!ctx)
+ goto error_path;
+
+ len = security_getprocattr(current, "current", ctx, len);
+ if (len < 0 )
+ goto error_path;
+
+ audit_log_format(ab, " subj=%s", ctx);
+ return;
+
+error_path:
+ if (ctx)
+ kfree(ctx);
+ audit_panic("error in audit_log_task_context");
+ return;
+}
+
+static void audit_log_task_info(struct audit_buffer *ab)
+{
+ char name[sizeof(current->comm)];
+ struct mm_struct *mm = current->mm;
+ struct vm_area_struct *vma;
+
+ get_task_comm(name, current);
+ audit_log_format(ab, " comm=");
+ audit_log_untrustedstring(ab, name);
+
+ if (!mm)
+ return;
+
+ down_read(&mm->mmap_sem);
+ vma = mm->mmap;
+ while (vma) {
+ if ((vma->vm_flags & VM_EXECUTABLE) &&
+ vma->vm_file) {
+ audit_log_d_path(ab, "exe=",
+ vma->vm_file->f_dentry,
+ vma->vm_file->f_vfsmnt);
+ break;
+ }
+ vma = vma->vm_next;
+ }
+ up_read(&mm->mmap_sem);
+ audit_log_task_context(ab);
+}
+
+static void audit_log_exit(struct audit_context *context, gfp_t gfp_mask)
+{
+ int i;
+ struct audit_buffer *ab;
+ struct audit_aux_data *aux;
+
+ ab = audit_log_start(context, gfp_mask, AUDIT_SYSCALL);
+ if (!ab)
+ return; /* audit_panic has been called */
+ audit_log_format(ab, "arch=%x syscall=%d",
+ context->arch, context->major);
+ if (context->personality != PER_LINUX)
+ audit_log_format(ab, " per=%lx", context->personality);
+ if (context->return_valid)
+ audit_log_format(ab, " success=%s exit=%ld",
+ (context->return_valid==AUDITSC_SUCCESS)?"yes":"no",
+ context->return_code);
+ audit_log_format(ab,
+ " a0=%lx a1=%lx a2=%lx a3=%lx items=%d"
+ " pid=%d auid=%u uid=%u gid=%u"
+ " euid=%u suid=%u fsuid=%u"
+ " egid=%u sgid=%u fsgid=%u",
+ context->argv[0],
+ context->argv[1],
+ context->argv[2],
+ context->argv[3],
+ context->name_count,
+ context->pid,
+ context->loginuid,
+ context->uid,
+ context->gid,
+ context->euid, context->suid, context->fsuid,
+ context->egid, context->sgid, context->fsgid);
+ audit_log_task_info(ab);
+ audit_log_end(ab);
+
+ for (aux = context->aux; aux; aux = aux->next) {
+
+ ab = audit_log_start(context, GFP_KERNEL, aux->type);
+ if (!ab)
+ continue; /* audit_panic has been called */
+
+ switch (aux->type) {
+ case AUDIT_IPC: {
+ struct audit_aux_data_ipcctl *axi = (void *)aux;
+ audit_log_format(ab,
+ " qbytes=%lx iuid=%u igid=%u mode=%x obj=%s",
+ axi->qbytes, axi->uid, axi->gid, axi->mode, axi->ctx);
+ break; }
+
+ case AUDIT_SOCKETCALL: {
+ int i;
+ struct audit_aux_data_socketcall *axs = (void *)aux;
+ audit_log_format(ab, "nargs=%d", axs->nargs);
+ for (i=0; i<axs->nargs; i++)
+ audit_log_format(ab, " a%d=%lx", i, axs->args[i]);
+ break; }
+
+ case AUDIT_SOCKADDR: {
+ struct audit_aux_data_sockaddr *axs = (void *)aux;
+
+ audit_log_format(ab, "saddr=");
+ audit_log_hex(ab, axs->a, axs->len);
+ break; }
+
+ case AUDIT_AVC_PATH: {
+ struct audit_aux_data_path *axi = (void *)aux;
+ audit_log_d_path(ab, "path=", axi->dentry, axi->mnt);
+ break; }
+
+ }
+ audit_log_end(ab);
+ }
+
+ if (context->pwd && context->pwdmnt) {
+ ab = audit_log_start(context, GFP_KERNEL, AUDIT_CWD);
+ if (ab) {
+ audit_log_d_path(ab, "cwd=", context->pwd, context->pwdmnt);
+ audit_log_end(ab);
+ }
+ }
+ for (i = 0; i < context->name_count; i++) {
+ unsigned long ino = context->names[i].ino;
+ unsigned long pino = context->names[i].pino;
+
+ ab = audit_log_start(context, GFP_KERNEL, AUDIT_PATH);
+ if (!ab)
+ continue; /* audit_panic has been called */
+
+ audit_log_format(ab, "item=%d", i);
+
+ audit_log_format(ab, " name=");
+ if (context->names[i].name)
+ audit_log_untrustedstring(ab, context->names[i].name);
+ else
+ audit_log_format(ab, "(null)");
+
+ if (pino != (unsigned long)-1)
+ audit_log_format(ab, " parent=%lu", pino);
+ if (ino != (unsigned long)-1)
+ audit_log_format(ab, " inode=%lu", ino);
+ if ((pino != (unsigned long)-1) || (ino != (unsigned long)-1))
+ audit_log_format(ab, " dev=%02x:%02x mode=%#o"
+ " ouid=%u ogid=%u rdev=%02x:%02x",
+ MAJOR(context->names[i].dev),
+ MINOR(context->names[i].dev),
+ context->names[i].mode,
+ context->names[i].uid,
+ context->names[i].gid,
+ MAJOR(context->names[i].rdev),
+ MINOR(context->names[i].rdev));
+ if (context->names[i].ctx) {
+ audit_log_format(ab, " obj=%s",
+ context->names[i].ctx);
+ }
+
+ audit_log_end(ab);
+ }
+}
+
+/**
+ * audit_free - free a per-task audit context
+ * @tsk: task whose audit context block to free
+ *
+ * Called from copy_process and __put_task_struct.
+ */
+void audit_free(struct task_struct *tsk)
+{
+ struct audit_context *context;
+
+ task_lock(tsk);
+ context = audit_get_context(tsk, 0, 0);
+ task_unlock(tsk);
+
+ if (likely(!context))
+ return;
+
+ /* Check for system calls that do not go through the exit
+ * function (e.g., exit_group), then free context block.
+ * We use GFP_ATOMIC here because we might be doing this
+ * in the context of the idle thread */
+ if (context->in_syscall && context->auditable)
+ audit_log_exit(context, GFP_ATOMIC);
+
+ audit_free_context(context);
+}
+
+/**
+ * audit_syscall_entry - fill in an audit record at syscall entry
+ * @tsk: task being audited
+ * @arch: architecture type
+ * @major: major syscall type (function)
+ * @a1: additional syscall register 1
+ * @a2: additional syscall register 2
+ * @a3: additional syscall register 3
+ * @a4: additional syscall register 4
+ *
+ * Fill in audit context at syscall entry. This only happens if the
+ * audit context was created when the task was created and the state or
+ * filters demand the audit context be built. If the state from the
+ * per-task filter or from the per-syscall filter is AUDIT_RECORD_CONTEXT,
+ * then the record will be written at syscall exit time (otherwise, it
+ * will only be written if another part of the kernel requests that it
+ * be written).
+ */
+void audit_syscall_entry(struct task_struct *tsk, int arch, int major,
+ unsigned long a1, unsigned long a2,
+ unsigned long a3, unsigned long a4)
+{
+ struct audit_context *context = tsk->audit_context;
+ enum audit_state state;
+
+ BUG_ON(!context);
+
+ /*
+ * This happens only on certain architectures that make system
+ * calls in kernel_thread via the entry.S interface, instead of
+ * with direct calls. (If you are porting to a new
+ * architecture, hitting this condition can indicate that you
+ * got the _exit/_leave calls backward in entry.S.)
+ *
+ * i386 no
+ * x86_64 no
+ * ppc64 yes (see arch/ppc64/kernel/misc.S)
+ *
+ * This also happens with vm86 emulation in a non-nested manner
+ * (entries without exits), so this case must be caught.
+ */
+ if (context->in_syscall) {
+ struct audit_context *newctx;
+
+#if defined(__NR_vm86) && defined(__NR_vm86old)
+ /* vm86 mode should only be entered once */
+ if (major == __NR_vm86 || major == __NR_vm86old)
+ return;
+#endif
+#if AUDIT_DEBUG
+ printk(KERN_ERR
+ "audit(:%d) pid=%d in syscall=%d;"
+ " entering syscall=%d\n",
+ context->serial, tsk->pid, context->major, major);
+#endif
+ newctx = audit_alloc_context(context->state);
+ if (newctx) {
+ newctx->previous = context;
+ context = newctx;
+ tsk->audit_context = newctx;
+ } else {
+ /* If we can't alloc a new context, the best we
+ * can do is to leak memory (any pending putname
+ * will be lost). The only other alternative is
+ * to abandon auditing. */
+ audit_zero_context(context, context->state);
+ }
+ }
+ BUG_ON(context->in_syscall || context->name_count);
+
+ if (!audit_enabled)
+ return;
+
+ context->arch = arch;
+ context->major = major;
+ context->argv[0] = a1;
+ context->argv[1] = a2;
+ context->argv[2] = a3;
+ context->argv[3] = a4;
+
+ state = context->state;
+ if (state == AUDIT_SETUP_CONTEXT || state == AUDIT_BUILD_CONTEXT)
+ state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_ENTRY]);
+ if (likely(state == AUDIT_DISABLED))
+ return;
+
+ context->serial = 0;
+ context->ctime = CURRENT_TIME;
+ context->in_syscall = 1;
+ context->auditable = !!(state == AUDIT_RECORD_CONTEXT);
+}
+
+/**
+ * audit_syscall_exit - deallocate audit context after a system call
+ * @tsk: task being audited
+ * @valid: success/failure flag
+ * @return_code: syscall return value
+ *
+ * Tear down after system call. If the audit context has been marked as
+ * auditable (either because of the AUDIT_RECORD_CONTEXT state from
+ * filtering, or because some other part of the kernel write an audit
+ * message), then write out the syscall information. In call cases,
+ * free the names stored from getname().
+ */
+void audit_syscall_exit(struct task_struct *tsk, int valid, long return_code)
+{
+ struct audit_context *context;
+
+ get_task_struct(tsk);
+ task_lock(tsk);
+ context = audit_get_context(tsk, valid, return_code);
+ task_unlock(tsk);
+
+ /* Not having a context here is ok, since the parent may have
+ * called __put_task_struct. */
+ if (likely(!context))
+ goto out;
+
+ if (context->in_syscall && context->auditable)
+ audit_log_exit(context, GFP_KERNEL);
+
+ context->in_syscall = 0;
+ context->auditable = 0;
+
+ if (context->previous) {
+ struct audit_context *new_context = context->previous;
+ context->previous = NULL;
+ audit_free_context(context);
+ tsk->audit_context = new_context;
+ } else {
+ audit_free_names(context);
+ audit_free_aux(context);
+ tsk->audit_context = context;
+ }
+ out:
+ put_task_struct(tsk);
+}
+
+/**
+ * audit_getname - add a name to the list
+ * @name: name to add
+ *
+ * Add a name to the list of audit names for this context.
+ * Called from fs/namei.c:getname().
+ */
+void audit_getname(const char *name)
+{
+ struct audit_context *context = current->audit_context;
+
+ if (!context || IS_ERR(name) || !name)
+ return;
+
+ if (!context->in_syscall) {
+#if AUDIT_DEBUG == 2
+ printk(KERN_ERR "%s:%d(:%d): ignoring getname(%p)\n",
+ __FILE__, __LINE__, context->serial, name);
+ dump_stack();
+#endif
+ return;
+ }
+ BUG_ON(context->name_count >= AUDIT_NAMES);
+ context->names[context->name_count].name = name;
+ context->names[context->name_count].ino = (unsigned long)-1;
+ ++context->name_count;
+ if (!context->pwd) {
+ read_lock(¤t->fs->lock);
+ context->pwd = dget(current->fs->pwd);
+ context->pwdmnt = mntget(current->fs->pwdmnt);
+ read_unlock(¤t->fs->lock);
+ }
+
+}
+
+/* audit_putname - intercept a putname request
+ * @name: name to intercept and delay for putname
+ *
+ * If we have stored the name from getname in the audit context,
+ * then we delay the putname until syscall exit.
+ * Called from include/linux/fs.h:putname().
+ */
+void audit_putname(const char *name)
+{
+ struct audit_context *context = current->audit_context;
+
+ BUG_ON(!context);
+ if (!context->in_syscall) {
+#if AUDIT_DEBUG == 2
+ printk(KERN_ERR "%s:%d(:%d): __putname(%p)\n",
+ __FILE__, __LINE__, context->serial, name);
+ if (context->name_count) {
+ int i;
+ for (i = 0; i < context->name_count; i++)
+ printk(KERN_ERR "name[%d] = %p = %s\n", i,
+ context->names[i].name,
+ context->names[i].name ?: "(null)");
+ }
+#endif
+ __putname(name);
+ }
+#if AUDIT_DEBUG
+ else {
+ ++context->put_count;
+ if (context->put_count > context->name_count) {
+ printk(KERN_ERR "%s:%d(:%d): major=%d"
+ " in_syscall=%d putname(%p) name_count=%d"
+ " put_count=%d\n",
+ __FILE__, __LINE__,
+ context->serial, context->major,
+ context->in_syscall, name, context->name_count,
+ context->put_count);
+ dump_stack();
+ }
+ }
+#endif
+}
+
+void audit_inode_context(int idx, const struct inode *inode)
+{
+ struct audit_context *context = current->audit_context;
+ const char *suffix = security_inode_xattr_getsuffix();
+ char *ctx = NULL;
+ int len = 0;
+
+ if (!suffix)
+ goto ret;
+
+ len = security_inode_getsecurity(inode, suffix, NULL, 0, 0);
+ if (len == -EOPNOTSUPP)
+ goto ret;
+ if (len < 0)
+ goto error_path;
+
+ ctx = kmalloc(len, GFP_KERNEL);
+ if (!ctx)
+ goto error_path;
+
+ len = security_inode_getsecurity(inode, suffix, ctx, len, 0);
+ if (len < 0)
+ goto error_path;
+
+ context->names[idx].ctx = ctx;
+ goto ret;
+
+error_path:
+ if (ctx)
+ kfree(ctx);
+ audit_panic("error in audit_inode_context");
+ret:
+ return;
+}
+
+
+/**
+ * audit_inode - store the inode and device from a lookup
+ * @name: name being audited
+ * @inode: inode being audited
+ * @flags: lookup flags (as used in path_lookup())
+ *
+ * Called from fs/namei.c:path_lookup().
+ */
+void __audit_inode(const char *name, const struct inode *inode, unsigned flags)
+{
+ int idx;
+ struct audit_context *context = current->audit_context;
+
+ if (!context->in_syscall)
+ return;
+ if (context->name_count
+ && context->names[context->name_count-1].name
+ && context->names[context->name_count-1].name == name)
+ idx = context->name_count - 1;
+ else if (context->name_count > 1
+ && context->names[context->name_count-2].name
+ && context->names[context->name_count-2].name == name)
+ idx = context->name_count - 2;
+ else {
+ /* FIXME: how much do we care about inodes that have no
+ * associated name? */
+ if (context->name_count >= AUDIT_NAMES - AUDIT_NAMES_RESERVED)
+ return;
+ idx = context->name_count++;
+ context->names[idx].name = NULL;
+#if AUDIT_DEBUG
+ ++context->ino_count;
+#endif
+ }
+ context->names[idx].dev = inode->i_sb->s_dev;
+ context->names[idx].mode = inode->i_mode;
+ context->names[idx].uid = inode->i_uid;
+ context->names[idx].gid = inode->i_gid;
+ context->names[idx].rdev = inode->i_rdev;
+ audit_inode_context(idx, inode);
+ if ((flags & LOOKUP_PARENT) && (strcmp(name, "/") != 0) &&
+ (strcmp(name, ".") != 0)) {
+ context->names[idx].ino = (unsigned long)-1;
+ context->names[idx].pino = inode->i_ino;
+ } else {
+ context->names[idx].ino = inode->i_ino;
+ context->names[idx].pino = (unsigned long)-1;
+ }
+}
+
+/**
+ * audit_inode_child - collect inode info for created/removed objects
+ * @dname: inode's dentry name
+ * @inode: inode being audited
+ * @pino: inode number of dentry parent
+ *
+ * For syscalls that create or remove filesystem objects, audit_inode
+ * can only collect information for the filesystem object's parent.
+ * This call updates the audit context with the child's information.
+ * Syscalls that create a new filesystem object must be hooked after
+ * the object is created. Syscalls that remove a filesystem object
+ * must be hooked prior, in order to capture the target inode during
+ * unsuccessful attempts.
+ */
+void __audit_inode_child(const char *dname, const struct inode *inode,
+ unsigned long pino)
+{
+ int idx;
+ struct audit_context *context = current->audit_context;
+
+ if (!context->in_syscall)
+ 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 (strncmp(n, dname, dlen) == 0)
+ goto update_context;
+ }
+
+ /* catch-all in case match not found */
+ idx = context->name_count++;
+ context->names[idx].name = NULL;
+ context->names[idx].pino = pino;
+#if AUDIT_DEBUG
+ context->ino_count++;
+#endif
+
+update_context:
+ if (inode) {
+ context->names[idx].ino = inode->i_ino;
+ context->names[idx].dev = inode->i_sb->s_dev;
+ context->names[idx].mode = inode->i_mode;
+ context->names[idx].uid = inode->i_uid;
+ context->names[idx].gid = inode->i_gid;
+ context->names[idx].rdev = inode->i_rdev;
+ audit_inode_context(idx, inode);
+ }
+}
+
+/**
+ * auditsc_get_stamp - get local copies of audit_context values
+ * @ctx: audit_context for the task
+ * @t: timespec to store time recorded in the audit_context
+ * @serial: serial value that is recorded in the audit_context
+ *
+ * Also sets the context as auditable.
+ */
+void auditsc_get_stamp(struct audit_context *ctx,
+ struct timespec *t, unsigned int *serial)
+{
+ if (!ctx->serial)
+ ctx->serial = audit_serial();
+ t->tv_sec = ctx->ctime.tv_sec;
+ t->tv_nsec = ctx->ctime.tv_nsec;
+ *serial = ctx->serial;
+ ctx->auditable = 1;
+}
+
+/**
+ * audit_set_loginuid - set a task's audit_context loginuid
+ * @task: task whose audit context is being modified
+ * @loginuid: loginuid value
+ *
+ * Returns 0.
+ *
+ * Called (set) from fs/proc/base.c::proc_loginuid_write().
+ */
+int audit_set_loginuid(struct task_struct *task, uid_t loginuid)
+{
+ if (task->audit_context) {
+ struct audit_buffer *ab;
+
+ ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_LOGIN);
+ if (ab) {
+ audit_log_format(ab, "login pid=%d uid=%u "
+ "old auid=%u new auid=%u",
+ task->pid, task->uid,
+ task->audit_context->loginuid, loginuid);
+ audit_log_end(ab);
+ }
+ task->audit_context->loginuid = loginuid;
+ }
+ return 0;
+}
+
+/**
+ * audit_get_loginuid - get the loginuid for an audit_context
+ * @ctx: the audit_context
+ *
+ * Returns the context's loginuid or -1 if @ctx is NULL.
+ */
+uid_t audit_get_loginuid(struct audit_context *ctx)
+{
+ return ctx ? ctx->loginuid : -1;
+}
+
+/**
+ * audit_ipc_perms - record audit data for ipc
+ * @qbytes: msgq bytes
+ * @uid: msgq user id
+ * @gid: msgq group id
+ * @mode: msgq mode (permissions)
+ *
+ * Returns 0 for success or NULL context or < 0 on error.
+ */
+int audit_ipc_perms(unsigned long qbytes, uid_t uid, gid_t gid, mode_t mode, struct kern_ipc_perm *ipcp)
+{
+ struct audit_aux_data_ipcctl *ax;
+ struct audit_context *context = current->audit_context;
+
+ if (likely(!context))
+ return 0;
+
+ ax = kmalloc(sizeof(*ax), GFP_ATOMIC);
+ if (!ax)
+ return -ENOMEM;
+
+ ax->qbytes = qbytes;
+ ax->uid = uid;
+ ax->gid = gid;
+ ax->mode = mode;
+ ax->ctx = audit_ipc_context(ipcp);
+
+ ax->d.type = AUDIT_IPC;
+ ax->d.next = context->aux;
+ context->aux = (void *)ax;
+ return 0;
+}
+
+char *audit_ipc_context(struct kern_ipc_perm *ipcp)
+{
+ struct audit_context *context = current->audit_context;
+ char *ctx = NULL;
+ int len = 0;
+
+ if (likely(!context))
+ return NULL;
+
+ len = security_ipc_getsecurity(ipcp, NULL, 0);
+ if (len == -EOPNOTSUPP)
+ goto ret;
+ if (len < 0)
+ goto error_path;
+
+ ctx = kmalloc(len, GFP_ATOMIC);
+ if (!ctx)
+ goto error_path;
+
+ len = security_ipc_getsecurity(ipcp, ctx, len);
+ if (len < 0)
+ goto error_path;
+
+ return ctx;
+
+error_path:
+ kfree(ctx);
+ audit_panic("error in audit_ipc_context");
+ret:
+ return NULL;
+}
+
+/**
+ * audit_socketcall - record audit data for sys_socketcall
+ * @nargs: number of args
+ * @args: args array
+ *
+ * Returns 0 for success or NULL context or < 0 on error.
+ */
+int audit_socketcall(int nargs, unsigned long *args)
+{
+ struct audit_aux_data_socketcall *ax;
+ struct audit_context *context = current->audit_context;
+
+ if (likely(!context))
+ return 0;
+
+ ax = kmalloc(sizeof(*ax) + nargs * sizeof(unsigned long), GFP_KERNEL);
+ if (!ax)
+ return -ENOMEM;
+
+ ax->nargs = nargs;
+ memcpy(ax->args, args, nargs * sizeof(unsigned long));
+
+ ax->d.type = AUDIT_SOCKETCALL;
+ ax->d.next = context->aux;
+ context->aux = (void *)ax;
+ return 0;
+}
+
+/**
+ * audit_sockaddr - record audit data for sys_bind, sys_connect, sys_sendto
+ * @len: data length in user space
+ * @a: data address in kernel space
+ *
+ * Returns 0 for success or NULL context or < 0 on error.
+ */
+int audit_sockaddr(int len, void *a)
+{
+ struct audit_aux_data_sockaddr *ax;
+ struct audit_context *context = current->audit_context;
+
+ if (likely(!context))
+ return 0;
+
+ ax = kmalloc(sizeof(*ax) + len, GFP_KERNEL);
+ if (!ax)
+ return -ENOMEM;
+
+ ax->len = len;
+ memcpy(ax->a, a, len);
+
+ ax->d.type = AUDIT_SOCKADDR;
+ ax->d.next = context->aux;
+ context->aux = (void *)ax;
+ return 0;
+}
+
+/**
+ * audit_avc_path - record the granting or denial of permissions
+ * @dentry: dentry to record
+ * @mnt: mnt to record
+ *
+ * Returns 0 for success or NULL context or < 0 on error.
+ *
+ * Called from security/selinux/avc.c::avc_audit()
+ */
+int audit_avc_path(struct dentry *dentry, struct vfsmount *mnt)
+{
+ struct audit_aux_data_path *ax;
+ struct audit_context *context = current->audit_context;
+
+ if (likely(!context))
+ return 0;
+
+ ax = kmalloc(sizeof(*ax), GFP_ATOMIC);
+ if (!ax)
+ return -ENOMEM;
+
+ ax->dentry = dget(dentry);
+ ax->mnt = mntget(mnt);
+
+ ax->d.type = AUDIT_AVC_PATH;
+ ax->d.next = context->aux;
+ context->aux = (void *)ax;
+ return 0;
+}
+
+/**
+ * audit_signal_info - record signal info for shutting down audit subsystem
+ * @sig: signal value
+ * @t: task being signaled
+ *
+ * If the audit subsystem is being terminated, record the task (pid)
+ * and uid that is doing that.
+ */
+void audit_signal_info(int sig, struct task_struct *t)
+{
+ extern pid_t audit_sig_pid;
+ extern uid_t audit_sig_uid;
+
+ if (unlikely(audit_pid && t->tgid == audit_pid)) {
+ if (sig == SIGTERM || sig == SIGHUP) {
+ struct audit_context *ctx = current->audit_context;
+ audit_sig_pid = current->pid;
+ if (ctx)
+ audit_sig_uid = ctx->loginuid;
+ else
+ audit_sig_uid = current->uid;
+ }
+ }
+}
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
deleted file mode 100644
index a3a3275..0000000
--- a/kernel/auditfilter.c
+++ /dev/null
@@ -1,375 +0,0 @@
-/* auditfilter.c -- filtering of audit events
- *
- * Copyright 2003-2004 Red Hat, Inc.
- * Copyright 2005 Hewlett-Packard Development Company, L.P.
- * Copyright 2005 IBM Corporation
- *
- * 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
- */
-
-#include <linux/kernel.h>
-#include <linux/audit.h>
-#include <linux/kthread.h>
-#include <linux/netlink.h>
-#include "audit.h"
-
-/* There are three lists of rules -- one to search at task creation
- * time, one to search at syscall entry time, and another to search at
- * syscall exit time. */
-struct list_head audit_filter_list[AUDIT_NR_FILTERS] = {
- LIST_HEAD_INIT(audit_filter_list[0]),
- LIST_HEAD_INIT(audit_filter_list[1]),
- LIST_HEAD_INIT(audit_filter_list[2]),
- LIST_HEAD_INIT(audit_filter_list[3]),
- LIST_HEAD_INIT(audit_filter_list[4]),
- LIST_HEAD_INIT(audit_filter_list[5]),
-#if AUDIT_NR_FILTERS != 6
-#error Fix audit_filter_list initialiser
-#endif
-};
-
-/* Copy rule from user-space to kernel-space. Called from
- * audit_add_rule during AUDIT_ADD. */
-static inline int audit_copy_rule(struct audit_rule *d, struct audit_rule *s)
-{
- int i;
-
- if (s->action != AUDIT_NEVER
- && s->action != AUDIT_POSSIBLE
- && s->action != AUDIT_ALWAYS)
- return -1;
- if (s->field_count < 0 || s->field_count > AUDIT_MAX_FIELDS)
- return -1;
- if ((s->flags & ~AUDIT_FILTER_PREPEND) >= AUDIT_NR_FILTERS)
- return -1;
-
- d->flags = s->flags;
- d->action = s->action;
- d->field_count = s->field_count;
- for (i = 0; i < d->field_count; i++) {
- d->fields[i] = s->fields[i];
- d->values[i] = s->values[i];
- }
- for (i = 0; i < AUDIT_BITMASK_SIZE; i++) d->mask[i] = s->mask[i];
- return 0;
-}
-
-/* Check to see if two rules are identical. It is called from
- * audit_add_rule during AUDIT_ADD and
- * audit_del_rule during AUDIT_DEL. */
-static int audit_compare_rule(struct audit_rule *a, struct audit_rule *b)
-{
- int i;
-
- if (a->flags != b->flags)
- return 1;
-
- if (a->action != b->action)
- return 1;
-
- if (a->field_count != b->field_count)
- return 1;
-
- for (i = 0; i < a->field_count; i++) {
- if (a->fields[i] != b->fields[i]
- || a->values[i] != b->values[i])
- return 1;
- }
-
- for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
- if (a->mask[i] != b->mask[i])
- return 1;
-
- return 0;
-}
-
-/* Note that audit_add_rule and audit_del_rule are called via
- * audit_receive() in audit.c, and are protected by
- * audit_netlink_sem. */
-static inline int audit_add_rule(struct audit_rule *rule,
- struct list_head *list)
-{
- struct audit_entry *entry;
- int i;
-
- /* Do not use the _rcu iterator here, since this is the only
- * addition routine. */
- list_for_each_entry(entry, list, list) {
- if (!audit_compare_rule(rule, &entry->rule))
- return -EEXIST;
- }
-
- for (i = 0; i < rule->field_count; i++) {
- if (rule->fields[i] & AUDIT_UNUSED_BITS)
- return -EINVAL;
- if ( rule->fields[i] & AUDIT_NEGATE)
- rule->fields[i] |= AUDIT_NOT_EQUAL;
- else if ( (rule->fields[i] & AUDIT_OPERATORS) == 0 )
- rule->fields[i] |= AUDIT_EQUAL;
- rule->fields[i] &= ~AUDIT_NEGATE;
- }
-
- if (!(entry = kmalloc(sizeof(*entry), GFP_KERNEL)))
- return -ENOMEM;
- if (audit_copy_rule(&entry->rule, rule)) {
- kfree(entry);
- return -EINVAL;
- }
-
- if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
- entry->rule.flags &= ~AUDIT_FILTER_PREPEND;
- list_add_rcu(&entry->list, list);
- } else {
- list_add_tail_rcu(&entry->list, list);
- }
-
- return 0;
-}
-
-static inline void audit_free_rule(struct rcu_head *head)
-{
- struct audit_entry *e = container_of(head, struct audit_entry, rcu);
- kfree(e);
-}
-
-/* Note that audit_add_rule and audit_del_rule are called via
- * audit_receive() in audit.c, and are protected by
- * audit_netlink_sem. */
-static inline int audit_del_rule(struct audit_rule *rule,
- struct list_head *list)
-{
- struct audit_entry *e;
-
- /* Do not use the _rcu iterator here, since this is the only
- * deletion routine. */
- list_for_each_entry(e, list, list) {
- if (!audit_compare_rule(rule, &e->rule)) {
- list_del_rcu(&e->list);
- call_rcu(&e->rcu, audit_free_rule);
- return 0;
- }
- }
- return -ENOENT; /* No matching rule */
-}
-
-static int audit_list_rules(void *_dest)
-{
- int pid, seq;
- int *dest = _dest;
- struct audit_entry *entry;
- int i;
-
- pid = dest[0];
- seq = dest[1];
- kfree(dest);
-
- down(&audit_netlink_sem);
-
- /* The *_rcu iterators not needed here because we are
- always called with audit_netlink_sem held. */
- for (i=0; i<AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(entry, &audit_filter_list[i], list)
- audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
- &entry->rule, sizeof(entry->rule));
- }
- audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
-
- up(&audit_netlink_sem);
- return 0;
-}
-
-/**
- * audit_receive_filter - apply all rules to the specified message type
- * @type: audit message type
- * @pid: target pid for netlink audit messages
- * @uid: target uid for netlink audit messages
- * @seq: netlink audit message sequence (serial) number
- * @data: payload data
- * @loginuid: loginuid of sender
- */
-int audit_receive_filter(int type, int pid, int uid, int seq, void *data,
- uid_t loginuid)
-{
- struct task_struct *tsk;
- int *dest;
- int err = 0;
- unsigned listnr;
-
- switch (type) {
- case AUDIT_LIST:
- /* We can't just spew out the rules here because we might fill
- * the available socket buffer space and deadlock waiting for
- * auditctl to read from it... which isn't ever going to
- * happen if we're actually running in the context of auditctl
- * trying to _send_ the stuff */
-
- dest = kmalloc(2 * sizeof(int), GFP_KERNEL);
- if (!dest)
- return -ENOMEM;
- dest[0] = pid;
- dest[1] = seq;
-
- tsk = kthread_run(audit_list_rules, dest, "audit_list_rules");
- if (IS_ERR(tsk)) {
- kfree(dest);
- err = PTR_ERR(tsk);
- }
- break;
- case AUDIT_ADD:
- listnr = ((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
- switch(listnr) {
- default:
- return -EINVAL;
-
- case AUDIT_FILTER_USER:
- case AUDIT_FILTER_TYPE:
-#ifdef CONFIG_AUDITSYSCALL
- case AUDIT_FILTER_ENTRY:
- case AUDIT_FILTER_EXIT:
- case AUDIT_FILTER_TASK:
-#endif
- ;
- }
- err = audit_add_rule(data, &audit_filter_list[listnr]);
- if (!err)
- audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
- "auid=%u added an audit rule\n", loginuid);
- break;
- case AUDIT_DEL:
- listnr =((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
- if (listnr >= AUDIT_NR_FILTERS)
- return -EINVAL;
-
- err = audit_del_rule(data, &audit_filter_list[listnr]);
- if (!err)
- audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
- "auid=%u removed an audit rule\n", loginuid);
- break;
- default:
- return -EINVAL;
- }
-
- return err;
-}
-
-int audit_comparator(const u32 left, const u32 op, const u32 right)
-{
- switch (op) {
- case AUDIT_EQUAL:
- return (left == right);
- case AUDIT_NOT_EQUAL:
- return (left != right);
- case AUDIT_LESS_THAN:
- return (left < right);
- case AUDIT_LESS_THAN_OR_EQUAL:
- return (left <= right);
- case AUDIT_GREATER_THAN:
- return (left > right);
- case AUDIT_GREATER_THAN_OR_EQUAL:
- return (left >= right);
- default:
- return -EINVAL;
- }
-}
-
-
-
-static int audit_filter_user_rules(struct netlink_skb_parms *cb,
- struct audit_rule *rule,
- enum audit_state *state)
-{
- int i;
-
- for (i = 0; i < rule->field_count; i++) {
- u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
- u32 op = rule->fields[i] & AUDIT_OPERATORS;
- u32 value = rule->values[i];
- int result = 0;
-
- switch (field) {
- case AUDIT_PID:
- result = audit_comparator(cb->creds.pid, op, value);
- break;
- case AUDIT_UID:
- result = audit_comparator(cb->creds.uid, op, value);
- break;
- case AUDIT_GID:
- result = audit_comparator(cb->creds.gid, op, value);
- break;
- case AUDIT_LOGINUID:
- result = audit_comparator(cb->loginuid, op, value);
- break;
- }
-
- if (!result)
- return 0;
- }
- switch (rule->action) {
- case AUDIT_NEVER: *state = AUDIT_DISABLED; break;
- case AUDIT_POSSIBLE: *state = AUDIT_BUILD_CONTEXT; break;
- case AUDIT_ALWAYS: *state = AUDIT_RECORD_CONTEXT; break;
- }
- return 1;
-}
-
-int audit_filter_user(struct netlink_skb_parms *cb, int type)
-{
- struct audit_entry *e;
- enum audit_state state;
- int ret = 1;
-
- rcu_read_lock();
- list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_USER], list) {
- if (audit_filter_user_rules(cb, &e->rule, &state)) {
- if (state == AUDIT_DISABLED)
- ret = 0;
- break;
- }
- }
- rcu_read_unlock();
-
- return ret; /* Audit by default */
-}
-
-int audit_filter_type(int type)
-{
- struct audit_entry *e;
- int result = 0;
-
- rcu_read_lock();
- if (list_empty(&audit_filter_list[AUDIT_FILTER_TYPE]))
- goto unlock_and_return;
-
- list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TYPE],
- list) {
- struct audit_rule *rule = &e->rule;
- int i;
- for (i = 0; i < rule->field_count; i++) {
- u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
- u32 op = rule->fields[i] & AUDIT_OPERATORS;
- u32 value = rule->values[i];
- if ( field == AUDIT_MSGTYPE ) {
- result = audit_comparator(type, op, value);
- if (!result)
- break;
- }
- }
- if (result)
- goto unlock_and_return;
- }
-unlock_and_return:
- rcu_read_unlock();
- return result;
-}
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
deleted file mode 100644
index 8f0a61c..0000000
--- a/kernel/auditsc.c
+++ /dev/null
@@ -1,1320 +0,0 @@
-/* auditsc.c -- System-call auditing support
- * Handles all system-call specific auditing features.
- *
- * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina.
- * Copyright 2005 Hewlett-Packard Development Company, L.P.
- * Copyright (C) 2005 IBM Corporation
- * 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 Rickard E. (Rik) Faith <faith(a)redhat.com>
- *
- * Many of the ideas implemented here are from Stephen C. Tweedie,
- * especially the idea of avoiding a copy by using getname.
- *
- * The method for actual interception of syscall entry and exit (not in
- * this file -- see entry.S) is based on a GPL'd patch written by
- * okir(a)suse.de and Copyright 2003 SuSE Linux AG.
- *
- * The support of additional filter rules compares (>, <, >=, <=) was
- * added by Dustin Kirkland <dustin.kirkland(a)us.ibm.com>, 2005.
- *
- * Modified by Amy Griffis <amy.griffis(a)hp.com> to collect additional
- * filesystem information.
- *
- * Subject and object context labeling support added by <danjones(a)us.ibm.com>
- * and <dustin.kirkland(a)us.ibm.com> for LSPP certification compliance.
- */
-
-#include <linux/init.h>
-#include <asm/atomic.h>
-#include <asm/types.h>
-#include <linux/fs.h>
-#include <linux/namei.h>
-#include <linux/mm.h>
-#include <linux/module.h>
-#include <linux/mount.h>
-#include <linux/socket.h>
-#include <linux/audit.h>
-#include <linux/personality.h>
-#include <linux/time.h>
-#include <linux/netlink.h>
-#include <linux/compiler.h>
-#include <asm/unistd.h>
-#include <linux/security.h>
-#include <linux/list.h>
-
-#include "audit.h"
-
-extern struct list_head audit_filter_list[];
-
-/* No syscall auditing will take place unless audit_enabled != 0. */
-extern int audit_enabled;
-
-/* AUDIT_NAMES is the number of slots we reserve in the audit_context
- * for saving names from getname(). */
-#define AUDIT_NAMES 20
-
-/* AUDIT_NAMES_RESERVED is the number of slots we reserve in the
- * audit_context from being used for nameless inodes from
- * path_lookup. */
-#define AUDIT_NAMES_RESERVED 7
-
-/* When fs/namei.c:getname() is called, we store the pointer in name and
- * we don't let putname() free it (instead we free all of the saved
- * pointers at syscall exit time).
- *
- * Further, in fs/namei.c:path_lookup() we store the inode and device. */
-struct audit_names {
- const char *name;
- unsigned long ino;
- unsigned long pino;
- dev_t dev;
- umode_t mode;
- uid_t uid;
- gid_t gid;
- dev_t rdev;
- char *ctx;
-};
-
-struct audit_aux_data {
- struct audit_aux_data *next;
- int type;
-};
-
-#define AUDIT_AUX_IPCPERM 0
-
-struct audit_aux_data_ipcctl {
- struct audit_aux_data d;
- struct ipc_perm p;
- unsigned long qbytes;
- uid_t uid;
- gid_t gid;
- mode_t mode;
- char *ctx;
-};
-
-struct audit_aux_data_socketcall {
- struct audit_aux_data d;
- int nargs;
- unsigned long args[0];
-};
-
-struct audit_aux_data_sockaddr {
- struct audit_aux_data d;
- int len;
- char a[0];
-};
-
-struct audit_aux_data_path {
- struct audit_aux_data d;
- struct dentry *dentry;
- struct vfsmount *mnt;
-};
-
-/* The per-task audit context. */
-struct audit_context {
- int in_syscall; /* 1 if task is in a syscall */
- enum audit_state state;
- unsigned int serial; /* serial number for record */
- struct timespec ctime; /* time of syscall entry */
- uid_t loginuid; /* login uid (identity) */
- int major; /* syscall number */
- unsigned long argv[4]; /* syscall arguments */
- int return_valid; /* return code is valid */
- long return_code;/* syscall return code */
- int auditable; /* 1 if record should be written */
- int name_count;
- struct audit_names names[AUDIT_NAMES];
- struct dentry * pwd;
- struct vfsmount * pwdmnt;
- 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;
- gid_t gid, egid, sgid, fsgid;
- unsigned long personality;
- int arch;
-
-#if AUDIT_DEBUG
- int put_count;
- int ino_count;
-#endif
-};
-
-
-/* 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_rule *rule,
- struct audit_context *ctx,
- enum audit_state *state)
-{
- int i, j;
-
- for (i = 0; i < rule->field_count; i++) {
- u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
- u32 op = rule->fields[i] & AUDIT_OPERATORS;
- u32 value = rule->values[i];
- int result = 0;
-
- switch (field) {
- case AUDIT_PID:
- result = audit_comparator(tsk->pid, op, value);
- break;
- case AUDIT_UID:
- result = audit_comparator(tsk->uid, op, value);
- break;
- case AUDIT_EUID:
- result = audit_comparator(tsk->euid, op, value);
- break;
- case AUDIT_SUID:
- result = audit_comparator(tsk->suid, op, value);
- break;
- case AUDIT_FSUID:
- result = audit_comparator(tsk->fsuid, op, value);
- break;
- case AUDIT_GID:
- result = audit_comparator(tsk->gid, op, value);
- break;
- case AUDIT_EGID:
- result = audit_comparator(tsk->egid, op, value);
- break;
- case AUDIT_SGID:
- result = audit_comparator(tsk->sgid, op, value);
- break;
- case AUDIT_FSGID:
- result = audit_comparator(tsk->fsgid, op, value);
- break;
- case AUDIT_PERS:
- result = audit_comparator(tsk->personality, op, value);
- break;
- case AUDIT_ARCH:
- if (ctx)
- result = audit_comparator(ctx->arch, op, value);
- break;
-
- case AUDIT_EXIT:
- if (ctx && ctx->return_valid)
- result = audit_comparator(ctx->return_code, op, value);
- break;
- case AUDIT_SUCCESS:
- if (ctx && ctx->return_valid) {
- if (value)
- result = audit_comparator(ctx->return_valid, op, AUDITSC_SUCCESS);
- else
- result = audit_comparator(ctx->return_valid, op, AUDITSC_FAILURE);
- }
- break;
- case AUDIT_DEVMAJOR:
- if (ctx) {
- for (j = 0; j < ctx->name_count; j++) {
- if (audit_comparator(MAJOR(ctx->names[j].dev), op, value)) {
- ++result;
- break;
- }
- }
- }
- break;
- case AUDIT_DEVMINOR:
- if (ctx) {
- for (j = 0; j < ctx->name_count; j++) {
- if (audit_comparator(MINOR(ctx->names[j].dev), op, value)) {
- ++result;
- break;
- }
- }
- }
- break;
- case AUDIT_INODE:
- if (ctx) {
- for (j = 0; j < ctx->name_count; j++) {
- if (audit_comparator(ctx->names[j].ino, op, value) ||
- audit_comparator(ctx->names[j].pino, op, value)) {
- ++result;
- break;
- }
- }
- }
- break;
- case AUDIT_LOGINUID:
- result = 0;
- if (ctx)
- result = audit_comparator(ctx->loginuid, op, value);
- break;
- case AUDIT_ARG0:
- case AUDIT_ARG1:
- case AUDIT_ARG2:
- case AUDIT_ARG3:
- if (ctx)
- result = audit_comparator(ctx->argv[field-AUDIT_ARG0], op, value);
- break;
- }
-
- if (!result)
- return 0;
- }
- switch (rule->action) {
- case AUDIT_NEVER: *state = AUDIT_DISABLED; break;
- case AUDIT_POSSIBLE: *state = AUDIT_BUILD_CONTEXT; break;
- case AUDIT_ALWAYS: *state = AUDIT_RECORD_CONTEXT; break;
- }
- return 1;
-}
-
-/* At process creation time, we can determine if system-call auditing is
- * completely disabled for this task. Since we only have the task
- * structure at this point, we can only check uid and gid.
- */
-static enum audit_state audit_filter_task(struct task_struct *tsk)
-{
- struct audit_entry *e;
- enum audit_state state;
-
- 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)) {
- rcu_read_unlock();
- return state;
- }
- }
- rcu_read_unlock();
- return AUDIT_BUILD_CONTEXT;
-}
-
-/* At syscall entry and exit time, this filter is called if the
- * audit_state is not low enough that auditing cannot take place, but is
- * also not high enough that we already know we have to write an audit
- * record (i.e., the state is AUDIT_SETUP_CONTEXT or AUDIT_BUILD_CONTEXT).
- */
-static enum audit_state audit_filter_syscall(struct task_struct *tsk,
- struct audit_context *ctx,
- struct list_head *list)
-{
- struct audit_entry *e;
- enum audit_state state;
-
- if (audit_pid && tsk->tgid == audit_pid)
- return AUDIT_DISABLED;
-
- rcu_read_lock();
- if (!list_empty(list)) {
- int word = AUDIT_WORD(ctx->major);
- 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)) {
- rcu_read_unlock();
- return state;
- }
- }
- }
- rcu_read_unlock();
- return AUDIT_BUILD_CONTEXT;
-}
-
-/* This should be called with task_lock() held. */
-static inline struct audit_context *audit_get_context(struct task_struct *tsk,
- int return_valid,
- int return_code)
-{
- struct audit_context *context = tsk->audit_context;
-
- if (likely(!context))
- return NULL;
- context->return_valid = return_valid;
- context->return_code = return_code;
-
- 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;
- }
-
- context->pid = tsk->pid;
- context->uid = tsk->uid;
- context->gid = tsk->gid;
- context->euid = tsk->euid;
- context->suid = tsk->suid;
- context->fsuid = tsk->fsuid;
- context->egid = tsk->egid;
- context->sgid = tsk->sgid;
- context->fsgid = tsk->fsgid;
- context->personality = tsk->personality;
- tsk->audit_context = NULL;
- return context;
-}
-
-static inline void audit_free_names(struct audit_context *context)
-{
- int i;
-
-#if AUDIT_DEBUG == 2
- if (context->auditable
- ||context->put_count + context->ino_count != context->name_count) {
- printk(KERN_ERR "%s:%d(:%d): major=%d in_syscall=%d"
- " name_count=%d put_count=%d"
- " ino_count=%d [NOT freeing]\n",
- __FILE__, __LINE__,
- context->serial, context->major, context->in_syscall,
- context->name_count, context->put_count,
- context->ino_count);
- for (i = 0; i < context->name_count; i++) {
- printk(KERN_ERR "names[%d] = %p = %s\n", i,
- context->names[i].name,
- context->names[i].name ?: "(null)");
- kfree(context->names[i].ctx);
- }
- dump_stack();
- return;
- }
-#endif
-#if AUDIT_DEBUG
- context->put_count = 0;
- context->ino_count = 0;
-#endif
-
- for (i = 0; i < context->name_count; i++)
- if (context->names[i].name)
- __putname(context->names[i].name);
- context->name_count = 0;
- if (context->pwd)
- dput(context->pwd);
- if (context->pwdmnt)
- mntput(context->pwdmnt);
- context->pwd = NULL;
- context->pwdmnt = NULL;
-}
-
-static inline void audit_free_aux(struct audit_context *context)
-{
- struct audit_aux_data *aux;
-
- while ((aux = context->aux)) {
- if (aux->type == AUDIT_AVC_PATH) {
- struct audit_aux_data_path *axi = (void *)aux;
- dput(axi->dentry);
- mntput(axi->mnt);
- }
- if ( aux->type == AUDIT_IPC ) {
- struct audit_aux_data_ipcctl *axi = (void *)aux;
- if (axi->ctx)
- kfree(axi->ctx);
- }
-
- context->aux = aux->next;
- kfree(aux);
- }
-}
-
-static inline void audit_zero_context(struct audit_context *context,
- enum audit_state state)
-{
- uid_t loginuid = context->loginuid;
-
- memset(context, 0, sizeof(*context));
- context->state = state;
- context->loginuid = loginuid;
-}
-
-static inline struct audit_context *audit_alloc_context(enum audit_state state)
-{
- struct audit_context *context;
-
- if (!(context = kmalloc(sizeof(*context), GFP_KERNEL)))
- return NULL;
- audit_zero_context(context, state);
- return context;
-}
-
-/**
- * audit_alloc - allocate an audit context block for a task
- * @tsk: task
- *
- * Filter on the task information and allocate a per-task audit context
- * if necessary. Doing so turns on system call auditing for the
- * specified task. This is called from copy_process, so no lock is
- * needed.
- */
-int audit_alloc(struct task_struct *tsk)
-{
- struct audit_context *context;
- enum audit_state state;
-
- if (likely(!audit_enabled))
- return 0; /* Return if not auditing. */
-
- state = audit_filter_task(tsk);
- if (likely(state == AUDIT_DISABLED))
- return 0;
-
- if (!(context = audit_alloc_context(state))) {
- audit_log_lost("out of memory in audit_alloc");
- return -ENOMEM;
- }
-
- /* Preserve login uid */
- context->loginuid = -1;
- if (current->audit_context)
- context->loginuid = current->audit_context->loginuid;
-
- tsk->audit_context = context;
- set_tsk_thread_flag(tsk, TIF_SYSCALL_AUDIT);
- return 0;
-}
-
-static inline void audit_free_context(struct audit_context *context)
-{
- struct audit_context *previous;
- int count = 0;
-
- do {
- previous = context->previous;
- if (previous || (count && count < 10)) {
- ++count;
- printk(KERN_ERR "audit(:%d): major=%d name_count=%d:"
- " freeing multiple contexts (%d)\n",
- context->serial, context->major,
- context->name_count, count);
- }
- audit_free_names(context);
- audit_free_aux(context);
- kfree(context);
- context = previous;
- } while (context);
- if (count >= 10)
- printk(KERN_ERR "audit: freed %d contexts\n", count);
-}
-
-static void audit_log_task_context(struct audit_buffer *ab)
-{
- char *ctx = NULL;
- ssize_t len = 0;
-
- len = security_getprocattr(current, "current", NULL, 0);
- if (len < 0) {
- if (len != -EINVAL)
- goto error_path;
- return;
- }
-
- ctx = kmalloc(len, GFP_KERNEL);
- if (!ctx)
- goto error_path;
-
- len = security_getprocattr(current, "current", ctx, len);
- if (len < 0 )
- goto error_path;
-
- audit_log_format(ab, " subj=%s", ctx);
- return;
-
-error_path:
- if (ctx)
- kfree(ctx);
- audit_panic("error in audit_log_task_context");
- return;
-}
-
-static void audit_log_task_info(struct audit_buffer *ab)
-{
- char name[sizeof(current->comm)];
- struct mm_struct *mm = current->mm;
- struct vm_area_struct *vma;
-
- get_task_comm(name, current);
- audit_log_format(ab, " comm=");
- audit_log_untrustedstring(ab, name);
-
- if (!mm)
- return;
-
- down_read(&mm->mmap_sem);
- vma = mm->mmap;
- while (vma) {
- if ((vma->vm_flags & VM_EXECUTABLE) &&
- vma->vm_file) {
- audit_log_d_path(ab, "exe=",
- vma->vm_file->f_dentry,
- vma->vm_file->f_vfsmnt);
- break;
- }
- vma = vma->vm_next;
- }
- up_read(&mm->mmap_sem);
- audit_log_task_context(ab);
-}
-
-static void audit_log_exit(struct audit_context *context, gfp_t gfp_mask)
-{
- int i;
- struct audit_buffer *ab;
- struct audit_aux_data *aux;
-
- ab = audit_log_start(context, gfp_mask, AUDIT_SYSCALL);
- if (!ab)
- return; /* audit_panic has been called */
- audit_log_format(ab, "arch=%x syscall=%d",
- context->arch, context->major);
- if (context->personality != PER_LINUX)
- audit_log_format(ab, " per=%lx", context->personality);
- if (context->return_valid)
- audit_log_format(ab, " success=%s exit=%ld",
- (context->return_valid==AUDITSC_SUCCESS)?"yes":"no",
- context->return_code);
- audit_log_format(ab,
- " a0=%lx a1=%lx a2=%lx a3=%lx items=%d"
- " pid=%d auid=%u uid=%u gid=%u"
- " euid=%u suid=%u fsuid=%u"
- " egid=%u sgid=%u fsgid=%u",
- context->argv[0],
- context->argv[1],
- context->argv[2],
- context->argv[3],
- context->name_count,
- context->pid,
- context->loginuid,
- context->uid,
- context->gid,
- context->euid, context->suid, context->fsuid,
- context->egid, context->sgid, context->fsgid);
- audit_log_task_info(ab);
- audit_log_end(ab);
-
- for (aux = context->aux; aux; aux = aux->next) {
-
- ab = audit_log_start(context, GFP_KERNEL, aux->type);
- if (!ab)
- continue; /* audit_panic has been called */
-
- switch (aux->type) {
- case AUDIT_IPC: {
- struct audit_aux_data_ipcctl *axi = (void *)aux;
- audit_log_format(ab,
- " qbytes=%lx iuid=%u igid=%u mode=%x obj=%s",
- axi->qbytes, axi->uid, axi->gid, axi->mode, axi->ctx);
- break; }
-
- case AUDIT_SOCKETCALL: {
- int i;
- struct audit_aux_data_socketcall *axs = (void *)aux;
- audit_log_format(ab, "nargs=%d", axs->nargs);
- for (i=0; i<axs->nargs; i++)
- audit_log_format(ab, " a%d=%lx", i, axs->args[i]);
- break; }
-
- case AUDIT_SOCKADDR: {
- struct audit_aux_data_sockaddr *axs = (void *)aux;
-
- audit_log_format(ab, "saddr=");
- audit_log_hex(ab, axs->a, axs->len);
- break; }
-
- case AUDIT_AVC_PATH: {
- struct audit_aux_data_path *axi = (void *)aux;
- audit_log_d_path(ab, "path=", axi->dentry, axi->mnt);
- break; }
-
- }
- audit_log_end(ab);
- }
-
- if (context->pwd && context->pwdmnt) {
- ab = audit_log_start(context, GFP_KERNEL, AUDIT_CWD);
- if (ab) {
- audit_log_d_path(ab, "cwd=", context->pwd, context->pwdmnt);
- audit_log_end(ab);
- }
- }
- for (i = 0; i < context->name_count; i++) {
- unsigned long ino = context->names[i].ino;
- unsigned long pino = context->names[i].pino;
-
- ab = audit_log_start(context, GFP_KERNEL, AUDIT_PATH);
- if (!ab)
- continue; /* audit_panic has been called */
-
- audit_log_format(ab, "item=%d", i);
-
- audit_log_format(ab, " name=");
- if (context->names[i].name)
- audit_log_untrustedstring(ab, context->names[i].name);
- else
- audit_log_format(ab, "(null)");
-
- if (pino != (unsigned long)-1)
- audit_log_format(ab, " parent=%lu", pino);
- if (ino != (unsigned long)-1)
- audit_log_format(ab, " inode=%lu", ino);
- if ((pino != (unsigned long)-1) || (ino != (unsigned long)-1))
- audit_log_format(ab, " dev=%02x:%02x mode=%#o"
- " ouid=%u ogid=%u rdev=%02x:%02x",
- MAJOR(context->names[i].dev),
- MINOR(context->names[i].dev),
- context->names[i].mode,
- context->names[i].uid,
- context->names[i].gid,
- MAJOR(context->names[i].rdev),
- MINOR(context->names[i].rdev));
- if (context->names[i].ctx) {
- audit_log_format(ab, " obj=%s",
- context->names[i].ctx);
- }
-
- audit_log_end(ab);
- }
-}
-
-/**
- * audit_free - free a per-task audit context
- * @tsk: task whose audit context block to free
- *
- * Called from copy_process and __put_task_struct.
- */
-void audit_free(struct task_struct *tsk)
-{
- struct audit_context *context;
-
- task_lock(tsk);
- context = audit_get_context(tsk, 0, 0);
- task_unlock(tsk);
-
- if (likely(!context))
- return;
-
- /* Check for system calls that do not go through the exit
- * function (e.g., exit_group), then free context block.
- * We use GFP_ATOMIC here because we might be doing this
- * in the context of the idle thread */
- if (context->in_syscall && context->auditable)
- audit_log_exit(context, GFP_ATOMIC);
-
- audit_free_context(context);
-}
-
-/**
- * audit_syscall_entry - fill in an audit record at syscall entry
- * @tsk: task being audited
- * @arch: architecture type
- * @major: major syscall type (function)
- * @a1: additional syscall register 1
- * @a2: additional syscall register 2
- * @a3: additional syscall register 3
- * @a4: additional syscall register 4
- *
- * Fill in audit context at syscall entry. This only happens if the
- * audit context was created when the task was created and the state or
- * filters demand the audit context be built. If the state from the
- * per-task filter or from the per-syscall filter is AUDIT_RECORD_CONTEXT,
- * then the record will be written at syscall exit time (otherwise, it
- * will only be written if another part of the kernel requests that it
- * be written).
- */
-void audit_syscall_entry(struct task_struct *tsk, int arch, int major,
- unsigned long a1, unsigned long a2,
- unsigned long a3, unsigned long a4)
-{
- struct audit_context *context = tsk->audit_context;
- enum audit_state state;
-
- BUG_ON(!context);
-
- /*
- * This happens only on certain architectures that make system
- * calls in kernel_thread via the entry.S interface, instead of
- * with direct calls. (If you are porting to a new
- * architecture, hitting this condition can indicate that you
- * got the _exit/_leave calls backward in entry.S.)
- *
- * i386 no
- * x86_64 no
- * ppc64 yes (see arch/ppc64/kernel/misc.S)
- *
- * This also happens with vm86 emulation in a non-nested manner
- * (entries without exits), so this case must be caught.
- */
- if (context->in_syscall) {
- struct audit_context *newctx;
-
-#if defined(__NR_vm86) && defined(__NR_vm86old)
- /* vm86 mode should only be entered once */
- if (major == __NR_vm86 || major == __NR_vm86old)
- return;
-#endif
-#if AUDIT_DEBUG
- printk(KERN_ERR
- "audit(:%d) pid=%d in syscall=%d;"
- " entering syscall=%d\n",
- context->serial, tsk->pid, context->major, major);
-#endif
- newctx = audit_alloc_context(context->state);
- if (newctx) {
- newctx->previous = context;
- context = newctx;
- tsk->audit_context = newctx;
- } else {
- /* If we can't alloc a new context, the best we
- * can do is to leak memory (any pending putname
- * will be lost). The only other alternative is
- * to abandon auditing. */
- audit_zero_context(context, context->state);
- }
- }
- BUG_ON(context->in_syscall || context->name_count);
-
- if (!audit_enabled)
- return;
-
- context->arch = arch;
- context->major = major;
- context->argv[0] = a1;
- context->argv[1] = a2;
- context->argv[2] = a3;
- context->argv[3] = a4;
-
- state = context->state;
- if (state == AUDIT_SETUP_CONTEXT || state == AUDIT_BUILD_CONTEXT)
- state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_ENTRY]);
- if (likely(state == AUDIT_DISABLED))
- return;
-
- context->serial = 0;
- context->ctime = CURRENT_TIME;
- context->in_syscall = 1;
- context->auditable = !!(state == AUDIT_RECORD_CONTEXT);
-}
-
-/**
- * audit_syscall_exit - deallocate audit context after a system call
- * @tsk: task being audited
- * @valid: success/failure flag
- * @return_code: syscall return value
- *
- * Tear down after system call. If the audit context has been marked as
- * auditable (either because of the AUDIT_RECORD_CONTEXT state from
- * filtering, or because some other part of the kernel write an audit
- * message), then write out the syscall information. In call cases,
- * free the names stored from getname().
- */
-void audit_syscall_exit(struct task_struct *tsk, int valid, long return_code)
-{
- struct audit_context *context;
-
- get_task_struct(tsk);
- task_lock(tsk);
- context = audit_get_context(tsk, valid, return_code);
- task_unlock(tsk);
-
- /* Not having a context here is ok, since the parent may have
- * called __put_task_struct. */
- if (likely(!context))
- goto out;
-
- if (context->in_syscall && context->auditable)
- audit_log_exit(context, GFP_KERNEL);
-
- context->in_syscall = 0;
- context->auditable = 0;
-
- if (context->previous) {
- struct audit_context *new_context = context->previous;
- context->previous = NULL;
- audit_free_context(context);
- tsk->audit_context = new_context;
- } else {
- audit_free_names(context);
- audit_free_aux(context);
- tsk->audit_context = context;
- }
- out:
- put_task_struct(tsk);
-}
-
-/**
- * audit_getname - add a name to the list
- * @name: name to add
- *
- * Add a name to the list of audit names for this context.
- * Called from fs/namei.c:getname().
- */
-void audit_getname(const char *name)
-{
- struct audit_context *context = current->audit_context;
-
- if (!context || IS_ERR(name) || !name)
- return;
-
- if (!context->in_syscall) {
-#if AUDIT_DEBUG == 2
- printk(KERN_ERR "%s:%d(:%d): ignoring getname(%p)\n",
- __FILE__, __LINE__, context->serial, name);
- dump_stack();
-#endif
- return;
- }
- BUG_ON(context->name_count >= AUDIT_NAMES);
- context->names[context->name_count].name = name;
- context->names[context->name_count].ino = (unsigned long)-1;
- ++context->name_count;
- if (!context->pwd) {
- read_lock(¤t->fs->lock);
- context->pwd = dget(current->fs->pwd);
- context->pwdmnt = mntget(current->fs->pwdmnt);
- read_unlock(¤t->fs->lock);
- }
-
-}
-
-/* audit_putname - intercept a putname request
- * @name: name to intercept and delay for putname
- *
- * If we have stored the name from getname in the audit context,
- * then we delay the putname until syscall exit.
- * Called from include/linux/fs.h:putname().
- */
-void audit_putname(const char *name)
-{
- struct audit_context *context = current->audit_context;
-
- BUG_ON(!context);
- if (!context->in_syscall) {
-#if AUDIT_DEBUG == 2
- printk(KERN_ERR "%s:%d(:%d): __putname(%p)\n",
- __FILE__, __LINE__, context->serial, name);
- if (context->name_count) {
- int i;
- for (i = 0; i < context->name_count; i++)
- printk(KERN_ERR "name[%d] = %p = %s\n", i,
- context->names[i].name,
- context->names[i].name ?: "(null)");
- }
-#endif
- __putname(name);
- }
-#if AUDIT_DEBUG
- else {
- ++context->put_count;
- if (context->put_count > context->name_count) {
- printk(KERN_ERR "%s:%d(:%d): major=%d"
- " in_syscall=%d putname(%p) name_count=%d"
- " put_count=%d\n",
- __FILE__, __LINE__,
- context->serial, context->major,
- context->in_syscall, name, context->name_count,
- context->put_count);
- dump_stack();
- }
- }
-#endif
-}
-
-void audit_inode_context(int idx, const struct inode *inode)
-{
- struct audit_context *context = current->audit_context;
- const char *suffix = security_inode_xattr_getsuffix();
- char *ctx = NULL;
- int len = 0;
-
- if (!suffix)
- goto ret;
-
- len = security_inode_getsecurity(inode, suffix, NULL, 0, 0);
- if (len == -EOPNOTSUPP)
- goto ret;
- if (len < 0)
- goto error_path;
-
- ctx = kmalloc(len, GFP_KERNEL);
- if (!ctx)
- goto error_path;
-
- len = security_inode_getsecurity(inode, suffix, ctx, len, 0);
- if (len < 0)
- goto error_path;
-
- context->names[idx].ctx = ctx;
- goto ret;
-
-error_path:
- if (ctx)
- kfree(ctx);
- audit_panic("error in audit_inode_context");
-ret:
- return;
-}
-
-
-/**
- * audit_inode - store the inode and device from a lookup
- * @name: name being audited
- * @inode: inode being audited
- * @flags: lookup flags (as used in path_lookup())
- *
- * Called from fs/namei.c:path_lookup().
- */
-void __audit_inode(const char *name, const struct inode *inode, unsigned flags)
-{
- int idx;
- struct audit_context *context = current->audit_context;
-
- if (!context->in_syscall)
- return;
- if (context->name_count
- && context->names[context->name_count-1].name
- && context->names[context->name_count-1].name == name)
- idx = context->name_count - 1;
- else if (context->name_count > 1
- && context->names[context->name_count-2].name
- && context->names[context->name_count-2].name == name)
- idx = context->name_count - 2;
- else {
- /* FIXME: how much do we care about inodes that have no
- * associated name? */
- if (context->name_count >= AUDIT_NAMES - AUDIT_NAMES_RESERVED)
- return;
- idx = context->name_count++;
- context->names[idx].name = NULL;
-#if AUDIT_DEBUG
- ++context->ino_count;
-#endif
- }
- context->names[idx].dev = inode->i_sb->s_dev;
- context->names[idx].mode = inode->i_mode;
- context->names[idx].uid = inode->i_uid;
- context->names[idx].gid = inode->i_gid;
- context->names[idx].rdev = inode->i_rdev;
- audit_inode_context(idx, inode);
- if ((flags & LOOKUP_PARENT) && (strcmp(name, "/") != 0) &&
- (strcmp(name, ".") != 0)) {
- context->names[idx].ino = (unsigned long)-1;
- context->names[idx].pino = inode->i_ino;
- } else {
- context->names[idx].ino = inode->i_ino;
- context->names[idx].pino = (unsigned long)-1;
- }
-}
-
-/**
- * audit_inode_child - collect inode info for created/removed objects
- * @dname: inode's dentry name
- * @inode: inode being audited
- * @pino: inode number of dentry parent
- *
- * For syscalls that create or remove filesystem objects, audit_inode
- * can only collect information for the filesystem object's parent.
- * This call updates the audit context with the child's information.
- * Syscalls that create a new filesystem object must be hooked after
- * the object is created. Syscalls that remove a filesystem object
- * must be hooked prior, in order to capture the target inode during
- * unsuccessful attempts.
- */
-void __audit_inode_child(const char *dname, const struct inode *inode,
- unsigned long pino)
-{
- int idx;
- struct audit_context *context = current->audit_context;
-
- if (!context->in_syscall)
- 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 (strncmp(n, dname, dlen) == 0)
- goto update_context;
- }
-
- /* catch-all in case match not found */
- idx = context->name_count++;
- context->names[idx].name = NULL;
- context->names[idx].pino = pino;
-#if AUDIT_DEBUG
- context->ino_count++;
-#endif
-
-update_context:
- if (inode) {
- context->names[idx].ino = inode->i_ino;
- context->names[idx].dev = inode->i_sb->s_dev;
- context->names[idx].mode = inode->i_mode;
- context->names[idx].uid = inode->i_uid;
- context->names[idx].gid = inode->i_gid;
- context->names[idx].rdev = inode->i_rdev;
- audit_inode_context(idx, inode);
- }
-}
-
-/**
- * auditsc_get_stamp - get local copies of audit_context values
- * @ctx: audit_context for the task
- * @t: timespec to store time recorded in the audit_context
- * @serial: serial value that is recorded in the audit_context
- *
- * Also sets the context as auditable.
- */
-void auditsc_get_stamp(struct audit_context *ctx,
- struct timespec *t, unsigned int *serial)
-{
- if (!ctx->serial)
- ctx->serial = audit_serial();
- t->tv_sec = ctx->ctime.tv_sec;
- t->tv_nsec = ctx->ctime.tv_nsec;
- *serial = ctx->serial;
- ctx->auditable = 1;
-}
-
-/**
- * audit_set_loginuid - set a task's audit_context loginuid
- * @task: task whose audit context is being modified
- * @loginuid: loginuid value
- *
- * Returns 0.
- *
- * Called (set) from fs/proc/base.c::proc_loginuid_write().
- */
-int audit_set_loginuid(struct task_struct *task, uid_t loginuid)
-{
- if (task->audit_context) {
- struct audit_buffer *ab;
-
- ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_LOGIN);
- if (ab) {
- audit_log_format(ab, "login pid=%d uid=%u "
- "old auid=%u new auid=%u",
- task->pid, task->uid,
- task->audit_context->loginuid, loginuid);
- audit_log_end(ab);
- }
- task->audit_context->loginuid = loginuid;
- }
- return 0;
-}
-
-/**
- * audit_get_loginuid - get the loginuid for an audit_context
- * @ctx: the audit_context
- *
- * Returns the context's loginuid or -1 if @ctx is NULL.
- */
-uid_t audit_get_loginuid(struct audit_context *ctx)
-{
- return ctx ? ctx->loginuid : -1;
-}
-
-/**
- * audit_ipc_perms - record audit data for ipc
- * @qbytes: msgq bytes
- * @uid: msgq user id
- * @gid: msgq group id
- * @mode: msgq mode (permissions)
- *
- * Returns 0 for success or NULL context or < 0 on error.
- */
-int audit_ipc_perms(unsigned long qbytes, uid_t uid, gid_t gid, mode_t mode, struct kern_ipc_perm *ipcp)
-{
- struct audit_aux_data_ipcctl *ax;
- struct audit_context *context = current->audit_context;
-
- if (likely(!context))
- return 0;
-
- ax = kmalloc(sizeof(*ax), GFP_ATOMIC);
- if (!ax)
- return -ENOMEM;
-
- ax->qbytes = qbytes;
- ax->uid = uid;
- ax->gid = gid;
- ax->mode = mode;
- ax->ctx = audit_ipc_context(ipcp);
-
- ax->d.type = AUDIT_IPC;
- ax->d.next = context->aux;
- context->aux = (void *)ax;
- return 0;
-}
-
-char *audit_ipc_context(struct kern_ipc_perm *ipcp)
-{
- struct audit_context *context = current->audit_context;
- char *ctx = NULL;
- int len = 0;
-
- if (likely(!context))
- return NULL;
-
- len = security_ipc_getsecurity(ipcp, NULL, 0);
- if (len == -EOPNOTSUPP)
- goto ret;
- if (len < 0)
- goto error_path;
-
- ctx = kmalloc(len, GFP_ATOMIC);
- if (!ctx)
- goto error_path;
-
- len = security_ipc_getsecurity(ipcp, ctx, len);
- if (len < 0)
- goto error_path;
-
- return ctx;
-
-error_path:
- kfree(ctx);
- audit_panic("error in audit_ipc_context");
-ret:
- return NULL;
-}
-
-/**
- * audit_socketcall - record audit data for sys_socketcall
- * @nargs: number of args
- * @args: args array
- *
- * Returns 0 for success or NULL context or < 0 on error.
- */
-int audit_socketcall(int nargs, unsigned long *args)
-{
- struct audit_aux_data_socketcall *ax;
- struct audit_context *context = current->audit_context;
-
- if (likely(!context))
- return 0;
-
- ax = kmalloc(sizeof(*ax) + nargs * sizeof(unsigned long), GFP_KERNEL);
- if (!ax)
- return -ENOMEM;
-
- ax->nargs = nargs;
- memcpy(ax->args, args, nargs * sizeof(unsigned long));
-
- ax->d.type = AUDIT_SOCKETCALL;
- ax->d.next = context->aux;
- context->aux = (void *)ax;
- return 0;
-}
-
-/**
- * audit_sockaddr - record audit data for sys_bind, sys_connect, sys_sendto
- * @len: data length in user space
- * @a: data address in kernel space
- *
- * Returns 0 for success or NULL context or < 0 on error.
- */
-int audit_sockaddr(int len, void *a)
-{
- struct audit_aux_data_sockaddr *ax;
- struct audit_context *context = current->audit_context;
-
- if (likely(!context))
- return 0;
-
- ax = kmalloc(sizeof(*ax) + len, GFP_KERNEL);
- if (!ax)
- return -ENOMEM;
-
- ax->len = len;
- memcpy(ax->a, a, len);
-
- ax->d.type = AUDIT_SOCKADDR;
- ax->d.next = context->aux;
- context->aux = (void *)ax;
- return 0;
-}
-
-/**
- * audit_avc_path - record the granting or denial of permissions
- * @dentry: dentry to record
- * @mnt: mnt to record
- *
- * Returns 0 for success or NULL context or < 0 on error.
- *
- * Called from security/selinux/avc.c::avc_audit()
- */
-int audit_avc_path(struct dentry *dentry, struct vfsmount *mnt)
-{
- struct audit_aux_data_path *ax;
- struct audit_context *context = current->audit_context;
-
- if (likely(!context))
- return 0;
-
- ax = kmalloc(sizeof(*ax), GFP_ATOMIC);
- if (!ax)
- return -ENOMEM;
-
- ax->dentry = dget(dentry);
- ax->mnt = mntget(mnt);
-
- ax->d.type = AUDIT_AVC_PATH;
- ax->d.next = context->aux;
- context->aux = (void *)ax;
- return 0;
-}
-
-/**
- * audit_signal_info - record signal info for shutting down audit subsystem
- * @sig: signal value
- * @t: task being signaled
- *
- * If the audit subsystem is being terminated, record the task (pid)
- * and uid that is doing that.
- */
-void audit_signal_info(int sig, struct task_struct *t)
-{
- extern pid_t audit_sig_pid;
- extern uid_t audit_sig_uid;
-
- if (unlikely(audit_pid && t->tgid == audit_pid)) {
- if (sig == SIGTERM || sig == SIGHUP) {
- struct audit_context *ctx = current->audit_context;
- audit_sig_pid = current->pid;
- if (ctx)
- audit_sig_uid = ctx->loginuid;
- else
- audit_sig_uid = current->uid;
- }
- }
-}
18 years, 10 months
[RFC][PATCH] new audit rule interface
by Amy Griffis
Hello,
Following is a first cut at a patch which implements an interface for
specifying filesystem paths in audit rules. I've identified some
issues, and would like to offer the patch up for discussion before
attempting to resolve them. If you see issues that I haven't
identified below, please mention them as well.
Overview
--------
The kernel-userspace interface changes required to specify audit rules
with string fields were implemented based on the proposal:
https://www.redhat.com/archives/linux-audit/2005-November/msg00015.html
Support for specifying filesystem paths in audit rules was included as
a proof-of-concept.
With this patch, userspace may specify a path filter, akin to
specifying an inode filter. If a filename exists at the specified
path, audit records will be generated as they are for inode filters.
Watched paths are tracked via a master_watchlist. The
master_watchlist is composed of one or more audit_watch structs, each
of which is associated with one or more struct audit_krule's in the
syscall exit filter list (AUDIT_FILTER_EXIT). Audit's filtering data
structures are still updated exclusively through explicit rule
addition and removal, so this patch did not require any locking
changes.
To reduce the complexity of the patch, I omitted the functionality
necessary to provide persistence for path-based filters. At present,
audit is not aware when a file is created or removed from watched path
locations. I plan to submit this functionality in a separate patch.
Issues
------
I have identified some issues with this patch that I'll list here.
1) struct audit_rule_xprt
Introducing a new data structure for specifying audit rules via
netlink provides a good opportunity to revisit the data structure
design and determine if we want to make any other changes, e.g.
adding a structure version field, reserving fields, etc. At
present, I've only added the empty buf[] array.
How much we deviate from the current structure should be a factor
of how long we expect to continue using netlink for all
userspace-kernel communication.
2) struct audit_krule
The kernel structure defining an audit rule isn't keeping enough
information about what it received from userspace.
- the kernel currently converts the upper bits of a field[]
element to its own representation; when the rule is listed back
to userspace, the bits are not converted back to the way in
which they were originally specified
- the difference between an inode-based and a path-based filter is
not always discernible, as it needs to be (e.g. kernel currently
won't allow a rule to be added for an inode # that can be
resolved from an existing path-based filter)
- might not be an issue now, but should probably track which type
or version of structure was used to add a given rule
- the operator bits consistently need to be masked out; they
should just be a separate field in kernel
3) audit_rule_in()
This function is long and somewhat specific to specifying paths in
audit rules. This should be more generalized, or the
path-specific code should be split out to another function.
4) in general
Perhaps most importantly, this patch mixes the interface changes
required to specify audit rules with string fields with some of
the initial filesystem audit functionality. I think these pieces
should be split into separate patches to allow a more generalized
approach.
I plan to update this patch to resolve these issues, along with any
other identified issues.
Thanks for your review.
Amy
diff --git a/include/linux/audit.h b/include/linux/audit.h
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -59,6 +59,9 @@
#define AUDIT_WATCH_REM 1008 /* Remove file/dir watch entry */
#define AUDIT_WATCH_LIST 1009 /* List all file/dir watches */
#define AUDIT_SIGNAL_INFO 1010 /* Get info about sender of signal to auditd */
+#define AUDIT_ADD_RULE 1011 /* Add syscall filter (string supp) */
+#define AUDIT_DEL_RULE 1012 /* Delete syscall filter (string supp)*/
+#define AUDIT_LIST_RULES 1013 /* List syscall filters (string supp) */
#define AUDIT_FIRST_USER_MSG 1100 /* Userspace messages mostly uninteresting to kernel */
#define AUDIT_USER_AVC 1107 /* We filter this differently */
@@ -142,6 +145,7 @@
#define AUDIT_INODE 102
#define AUDIT_EXIT 103
#define AUDIT_SUCCESS 104 /* exit >= 0; value ignored */
+#define AUDIT_PATHNAME 105 /* location in filesystem (attached path len */
#define AUDIT_ARG0 200
#define AUDIT_ARG1 (AUDIT_ARG0+1)
@@ -226,7 +230,26 @@ struct audit_status {
__u32 backlog; /* messages waiting in queue */
};
-struct audit_rule { /* for AUDIT_LIST, AUDIT_ADD, and AUDIT_DEL */
+/* audit_rule_xprt supports filter rules with both integer and string
+ * fields. It corresponds with AUDIT_ADD_RULE, AUDIT_DEL_RULE and
+ * AUDIT_LIST_RULES requests.
+ */
+struct audit_rule_xprt {
+ __u32 flags; /* AUDIT_PER_{TASK,CALL}, AUDIT_PREPEND */
+ __u32 action; /* AUDIT_NEVER, AUDIT_POSSIBLE, AUDIT_ALWAYS */
+ __u32 field_count;
+ __u32 mask[AUDIT_BITMASK_SIZE];
+ __u32 fields[AUDIT_MAX_FIELDS];
+ __u32 values[AUDIT_MAX_FIELDS];
+ __u32 buflen; /* total length of string fields */
+ char buf[0]; /* string fields buffer */
+};
+
+/* audit_rule is supported to maintain backward compatibility with
+ * userspace. It supports integer fields only and corresponds to
+ * AUDIT_ADD, AUDIT_DEL and AUDIT_LIST requests.
+ */
+struct audit_rule {
__u32 flags; /* AUDIT_PER_{TASK,CALL}, AUDIT_PREPEND */
__u32 action; /* AUDIT_NEVER, AUDIT_POSSIBLE, AUDIT_ALWAYS */
__u32 field_count;
diff --git a/kernel/audit.c b/kernel/audit.c
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -358,9 +358,12 @@ static int audit_netlink_ok(kernel_cap_t
switch (msg_type) {
case AUDIT_GET:
case AUDIT_LIST:
+ case AUDIT_LIST_RULES:
case AUDIT_SET:
case AUDIT_ADD:
+ case AUDIT_ADD_RULE:
case AUDIT_DEL:
+ case AUDIT_DEL_RULE:
case AUDIT_SIGNAL_INFO:
if (!cap_raised(eff_cap, CAP_AUDIT_CONTROL))
err = -EPERM;
@@ -474,6 +477,15 @@ static int audit_receive_msg(struct sk_b
err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid,
uid, seq, data, loginuid);
break;
+ case AUDIT_ADD_RULE:
+ case AUDIT_DEL_RULE:
+ if (nlh->nlmsg_len < sizeof(struct audit_rule_xprt))
+ return -EINVAL;
+ /* fallthrough */
+ case AUDIT_LIST_RULES:
+ err = audit_receive_filter(nlh->nlmsg_type, NETLINK_CB(skb).pid,
+ uid, seq, data, loginuid);
+ break;
case AUDIT_SIGNAL_INFO:
sig_data.uid = audit_sig_uid;
sig_data.pid = audit_sig_pid;
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -182,7 +182,33 @@ struct audit_context {
#endif
};
- /* Public API */
+/* Audit Filters */
+
+/* kernel's representation of a watched filesystem location */
+struct audit_watch {
+ char *path; /* watch insertion path */
+ struct list_head mlist; /* entry in master_watchlist */
+ struct list_head rules; /* associated rules*/
+};
+
+/* kernel's internal filter rule representation */
+struct audit_krule {
+ u32 flags;
+ u32 action;
+ u32 field_count;
+ u32 mask[AUDIT_BITMASK_SIZE];
+ u32 fields[AUDIT_MAX_FIELDS];
+ u32 values[AUDIT_MAX_FIELDS];
+ struct audit_watch *watch; /* associated watch */
+ struct list_head rlist; /* entry in audit_watch.rules list */
+};
+
+struct audit_entry {
+ struct list_head list; /* entry in audit_filter_list */
+ struct rcu_head rcu;
+ struct audit_krule rule;
+};
+
/* There are three lists of rules -- one to search at task creation
* time, one to search at syscall entry time, and another to search at
* syscall exit time. */
@@ -198,101 +224,290 @@ static struct list_head audit_filter_lis
#endif
};
-struct audit_entry {
- struct list_head list;
- struct rcu_head rcu;
- struct audit_rule rule;
-};
+static LIST_HEAD(master_watchlist);
extern int audit_pid;
-/* Copy rule from user-space to kernel-space. Called from
- * audit_add_rule during AUDIT_ADD. */
-static inline int audit_copy_rule(struct audit_rule *d, struct audit_rule *s)
+/* Check to see if two rules are identical. */
+static inline int audit_compare_rule(struct audit_krule *a,
+ struct audit_krule *b,
+ unsigned skip_ino)
{
int i;
- if (s->action != AUDIT_NEVER
- && s->action != AUDIT_POSSIBLE
- && s->action != AUDIT_ALWAYS)
- return -1;
- if (s->field_count < 0 || s->field_count > AUDIT_MAX_FIELDS)
- return -1;
- if ((s->flags & ~AUDIT_FILTER_PREPEND) >= AUDIT_NR_FILTERS)
- return -1;
-
- d->flags = s->flags;
- d->action = s->action;
- d->field_count = s->field_count;
- for (i = 0; i < d->field_count; i++) {
- d->fields[i] = s->fields[i];
- d->values[i] = s->values[i];
+ if (a->flags != b->flags ||
+ a->action != b->action ||
+ a->field_count != b->field_count)
+ return 1;
+
+ /* rules must have same field ordering to match */
+ for (i = 0; i < a->field_count; i++) {
+ /* skip inode comparison with matching paths */
+ if (skip_ino &&
+ (a->fields[i] & ~AUDIT_OPERATORS) == AUDIT_INODE &&
+ (b->fields[i] & ~AUDIT_OPERATORS) == AUDIT_INODE)
+ continue;
+ if (a->fields[i] != b->fields[i] ||
+ a->values[i] != b->values[i])
+ return 1;
}
- for (i = 0; i < AUDIT_BITMASK_SIZE; i++) d->mask[i] = s->mask[i];
+
+ for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
+ if (a->mask[i] != b->mask[i])
+ return 1;
+
return 0;
}
-/* Check to see if two rules are identical. It is called from
- * audit_add_rule during AUDIT_ADD and
- * audit_del_rule during AUDIT_DEL. */
-static inline int audit_compare_rule(struct audit_rule *a, struct audit_rule *b)
+static inline void audit_destroy_rule(struct rcu_head *head)
+{
+ struct audit_entry *e = container_of(head, struct audit_entry, rcu);
+ kfree(e);
+}
+
+static inline void audit_remove_watch(struct audit_krule *rule)
+{
+ struct audit_watch *watch;
+
+ watch = rule->watch;
+ list_del(&rule->rlist);
+ rule->watch = NULL;
+
+ if (list_empty(&watch->rules)) {
+ list_del(&watch->mlist);
+ kfree(watch->path);
+ kfree(watch);
+ }
+}
+
+static int audit_add_watch(struct audit_krule *rule, char *path)
{
+ int rc = 0;
+ struct audit_watch *w, *watch;
+ struct audit_entry *e;
+ struct nameidata nd;
int i;
- if (a->flags != b->flags)
- return 1;
+ list_for_each_entry(w, &master_watchlist, mlist) {
+ if (strcmp(path, w->path) != 0)
+ continue;
+
+ list_for_each_entry(e, &audit_filter_list[AUDIT_FILTER_EXIT],
+ list)
+ if (!audit_compare_rule(rule, &e->rule, 1)) {
+ rc = -EEXIST;
+ goto exit;
+ }
+ watch = w;
+ goto attach_rule;
+ }
- if (a->action != b->action)
- return 1;
+ watch = kmalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch) {
+ rc = -ENOMEM;
+ goto exit;
+ }
+ watch->path = path;
+ list_add(&watch->mlist, &master_watchlist);
+ INIT_LIST_HEAD(&watch->rules);
- if (a->field_count != b->field_count)
- return 1;
+attach_rule:
+ list_add(&rule->rlist, &watch->rules);
+ rule->watch = watch;
- for (i = 0; i < a->field_count; i++) {
- if (a->fields[i] != b->fields[i]
- || a->values[i] != b->values[i])
- return 1;
+ /* update inode filter field if possible */
+ if (path_lookup(path, 0, &nd) == 0)
+ for (i = 0; i < rule->field_count; i++)
+ if ((rule->fields[i] & ~AUDIT_OPERATORS) == AUDIT_INODE)
+ rule->values[i] = nd.dentry->d_inode->i_ino;
+ path_release(&nd);
+
+exit:
+ return rc;
+}
+
+static inline char *audit_path_in(char *buf, int buflen)
+{
+ char *path;
+
+ if (buflen > PATH_MAX)
+ return ERR_PTR(-ENAMETOOLONG);
+
+ if (buflen <= 1 || !buf || buf[0] != '/')
+ return ERR_PTR(-EINVAL);
+
+ path = kmalloc(buflen + 1, GFP_KERNEL);
+ if (!path)
+ return ERR_PTR(-ENOMEM);
+ memcpy(path, buf, buflen);
+ path[buflen] = 0;
+
+ return path;
+}
+
+/* Copy rule from user-space to kernel-space. */
+static struct audit_entry *audit_rule_in(struct audit_rule_xprt *rulex,
+ char **path, int type)
+{
+ int i, ret;
+ struct audit_entry *entry;
+ void *buf = NULL;
+ char *tmp = NULL;
+ unsigned listnr;
+ unsigned int offset = 0;
+
+ if (!(entry = kmalloc(sizeof(*entry), GFP_KERNEL)))
+ return ERR_PTR(-ENOMEM);
+
+ ret = -EINVAL;
+ listnr = rulex->flags & ~AUDIT_FILTER_PREPEND;
+ if (listnr >= AUDIT_NR_FILTERS)
+ goto exit_err;
+ if (rulex->action != AUDIT_NEVER && rulex->action != AUDIT_POSSIBLE &&
+ rulex->action != AUDIT_ALWAYS)
+ goto exit_err;
+ if (rulex->field_count < 0 || rulex->field_count > AUDIT_MAX_FIELDS)
+ goto exit_err;
+
+ entry->rule.flags = rulex->flags;
+ entry->rule.action = rulex->action;
+ entry->rule.field_count = rulex->field_count;
+ entry->rule.watch = NULL;
+
+ for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
+ entry->rule.mask[i] = rulex->mask[i];
+
+ if (type == AUDIT_ADD_RULE || type == AUDIT_DEL_RULE)
+ buf = rulex->buf;
+
+ for (i = 0; i < rulex->field_count; i++) {
+ u32 rfield = rulex->fields[i] & (~AUDIT_NEGATE|AUDIT_OPERATORS);
+ u32 rvalue = rulex->values[i];
+
+ ret = -EINVAL;
+ if (rulex->fields[i] & AUDIT_UNUSED_BITS)
+ goto exit_err;
+
+ if (rfield == AUDIT_PATHNAME) {
+ if (listnr != AUDIT_FILTER_EXIT)
+ goto exit_err;
+ if (offset + rvalue > rulex->buflen)
+ goto exit_err;
+
+ tmp = audit_path_in(buf, rvalue);
+ if (IS_ERR(tmp)) {
+ ret = PTR_ERR(tmp);
+ tmp = NULL;
+ goto exit_err;
+ }
+ *path = tmp;
+
+ offset += rvalue;
+ buf = rulex->buf + offset;
+
+ entry->rule.fields[i] = AUDIT_INODE|AUDIT_EQUAL;
+ entry->rule.values[i] = -1;
+ } else {
+ entry->rule.fields[i] = rulex->fields[i];
+ entry->rule.values[i] = rvalue;
+
+ if (entry->rule.fields[i] & AUDIT_NEGATE) {
+ entry->rule.fields[i] &= ~AUDIT_NEGATE;
+ entry->rule.fields[i] |= AUDIT_NOT_EQUAL;
+ } else if ((entry->rule.fields[i] &
+ AUDIT_OPERATORS) == 0)
+ entry->rule.fields[i] |= AUDIT_EQUAL;
+ }
}
+ return entry;
+
+exit_err:
+ kfree(tmp);
+ kfree(entry);
+ return ERR_PTR(ret);
+}
+
+/* Copy rule into user-space compatible struct */
+static struct audit_rule_xprt *audit_rule_out(struct audit_krule *rule)
+{
+ int i, len;
+ int pathlen = 0;
+ int watched = 0; /* flag rules with attached watches */
+ struct audit_rule_xprt *rulex;
+ void *buf;
+
+ if (rule->watch) {
+ pathlen = strlen(rule->watch->path) + 1;
+ watched = 1;
+ }
+
+ len = sizeof(struct audit_rule_xprt) + pathlen;
+ rulex = kmalloc(len, GFP_KERNEL);
+ if (!rulex)
+ return ERR_PTR(-ENOMEM);
+ memset(rulex, 0, len);
+
+ rulex->flags = rule->flags;
+ rulex->action = rule->action;
+ rulex->field_count = rule->field_count;
for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
- if (a->mask[i] != b->mask[i])
- return 1;
+ rulex->mask[i] = rule->mask[i];
- return 0;
+ buf = rulex->buf;
+ for (i = 0; i < rule->field_count; i++) {
+ u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
+ if (watched && field == AUDIT_INODE) {
+ memcpy(buf, rule->watch->path, pathlen);
+ rulex->buflen += pathlen;
+ buf = rulex->buf + rulex->buflen;
+
+ rulex->fields[i] = AUDIT_PATHNAME|AUDIT_EQUAL;
+ rulex->values[i] = pathlen;
+ } else {
+ rulex->fields[i] = rule->fields[i];
+ rulex->values[i] = rule->values[i];
+ }
+ }
+ return rulex;
}
/* Note that audit_add_rule and audit_del_rule are called via
* audit_receive() in audit.c, and are protected by
* audit_netlink_sem. */
-static inline int audit_add_rule(struct audit_rule *rule,
- struct list_head *list)
+static inline int audit_add_rule(void *data, int type)
{
- struct audit_entry *entry;
- int i;
+ int ret = 0;
+ struct audit_entry *e, *entry;
+ char *path = NULL;
+ unsigned listnr;
+ struct list_head *list;
- /* Do not use the _rcu iterator here, since this is the only
- * addition routine. */
- list_for_each_entry(entry, list, list) {
- if (!audit_compare_rule(rule, &entry->rule)) {
- return -EEXIST;
- }
+ entry = audit_rule_in(data, &path, type);
+ if (IS_ERR(entry)) {
+ ret = PTR_ERR(entry);
+ goto out;
}
- for (i = 0; i < rule->field_count; i++) {
- if (rule->fields[i] & AUDIT_UNUSED_BITS)
- return -EINVAL;
- if ( rule->fields[i] & AUDIT_NEGATE )
- rule->fields[i] |= AUDIT_NOT_EQUAL;
- else if ( (rule->fields[i] & AUDIT_OPERATORS) == 0 )
- rule->fields[i] |= AUDIT_EQUAL;
- rule->fields[i] &= (~AUDIT_NEGATE);
+ if (path && (ret = audit_add_watch(&entry->rule, path))) {
+ kfree(path);
+ kfree(entry);
+ goto out;
}
- if (!(entry = kmalloc(sizeof(*entry), GFP_KERNEL)))
- return -ENOMEM;
- if (audit_copy_rule(&entry->rule, rule)) {
- kfree(entry);
- return -EINVAL;
+ listnr = entry->rule.flags & ~AUDIT_FILTER_PREPEND;
+ list = &audit_filter_list[listnr];
+
+ /* Do not use the _rcu iterator here, since this is the only
+ * addition routine. */
+ if (!path) {
+ list_for_each_entry(e, list, list) {
+ if (!audit_compare_rule(&entry->rule, &e->rule, 0)) {
+ ret = -EEXIST;
+ kfree(entry);
+ goto out;
+ }
+ }
}
if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
@@ -302,56 +517,91 @@ static inline int audit_add_rule(struct
list_add_tail_rcu(&entry->list, list);
}
- return 0;
-}
-
-static inline void audit_free_rule(struct rcu_head *head)
-{
- struct audit_entry *e = container_of(head, struct audit_entry, rcu);
- kfree(e);
+out:
+ return ret;
}
/* Note that audit_add_rule and audit_del_rule are called via
* audit_receive() in audit.c, and are protected by
* audit_netlink_sem. */
-static inline int audit_del_rule(struct audit_rule *rule,
- struct list_head *list)
+static inline int audit_del_rule(void *data, int type)
{
- struct audit_entry *e;
+ int ret = 0;
+ struct audit_entry *e, *entry;
+ char *path = NULL;
+ struct list_head *list;
+ unsigned listnr;
+
+ entry = audit_rule_in(data, &path, type);
+ if (IS_ERR(entry)) {
+ ret = PTR_ERR(entry);
+ goto out;
+ }
+
+ listnr = entry->rule.flags & ~AUDIT_FILTER_PREPEND;
+ list = &audit_filter_list[listnr];
/* Do not use the _rcu iterator here, since this is the only
* deletion routine. */
list_for_each_entry(e, list, list) {
- if (!audit_compare_rule(rule, &e->rule)) {
+ unsigned skip_ino = 0;
+
+ if (path) {
+ if (!e->rule.watch ||
+ strcmp(path, e->rule.watch->path))
+ continue;
+ skip_ino = 1; /* watch paths match */
+ } else if (e->rule.watch)
+ continue;
+
+ if (!audit_compare_rule(&entry->rule, &e->rule, skip_ino)) {
+ if (path)
+ audit_remove_watch(&e->rule);
list_del_rcu(&e->list);
- call_rcu(&e->rcu, audit_free_rule);
- return 0;
+ call_rcu(&e->rcu, audit_destroy_rule);
+ kfree(path);
+ kfree(entry);
+ goto out;
}
}
- return -ENOENT; /* No matching rule */
+ ret = -ENOENT; /* No matching rule */
+out:
+ return ret;
}
-static int audit_list_rules(void *_dest)
+static int audit_list_rules(void *_args)
{
- int pid, seq;
- int *dest = _dest;
+ int pid, seq, type;
+ int *args = _args;
struct audit_entry *entry;
int i;
- pid = dest[0];
- seq = dest[1];
- kfree(dest);
+ pid = args[0];
+ seq = args[1];
+ type = args[2];
+ kfree(args);
down(&audit_netlink_sem);
/* The *_rcu iterators not needed here because we are
always called with audit_netlink_sem held. */
for (i=0; i<AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(entry, &audit_filter_list[i], list)
- audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
- &entry->rule, sizeof(entry->rule));
+ list_for_each_entry(entry, &audit_filter_list[i], list) {
+ struct audit_rule_xprt *rule;
+ int len;
+
+ rule = audit_rule_out(&entry->rule);
+ if (type == AUDIT_LIST)
+ len = sizeof(struct audit_rule);
+ else
+ len = sizeof(struct audit_rule_xprt) +
+ rule->buflen;
+ audit_send_reply(pid, seq, type, 0, 1, rule, len);
+
+ kfree(rule);
+ }
}
- audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
+ audit_send_reply(pid, seq, type, 1, 1, NULL, 0);
up(&audit_netlink_sem);
return 0;
@@ -370,46 +620,42 @@ int audit_receive_filter(int type, int p
uid_t loginuid)
{
struct task_struct *tsk;
- int *dest;
- int err = 0;
- unsigned listnr;
+ int *args;
+ int err = 0;
switch (type) {
case AUDIT_LIST:
+ case AUDIT_LIST_RULES:
/* We can't just spew out the rules here because we might fill
* the available socket buffer space and deadlock waiting for
* auditctl to read from it... which isn't ever going to
* happen if we're actually running in the context of auditctl
* trying to _send_ the stuff */
-
- dest = kmalloc(2 * sizeof(int), GFP_KERNEL);
- if (!dest)
+
+ args = kmalloc(3 * sizeof(int), GFP_KERNEL);
+ if (!args)
return -ENOMEM;
- dest[0] = pid;
- dest[1] = seq;
+ args[0] = pid;
+ args[1] = seq;
+ args[2] = type;
+
+ tsk = kthread_run(audit_list_rules, args, "audit_list_rules");
- tsk = kthread_run(audit_list_rules, dest, "audit_list_rules");
if (IS_ERR(tsk)) {
- kfree(dest);
+ kfree(args);
err = PTR_ERR(tsk);
}
break;
case AUDIT_ADD:
- listnr =((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
- if (listnr >= AUDIT_NR_FILTERS)
- return -EINVAL;
-
- err = audit_add_rule(data, &audit_filter_list[listnr]);
+ case AUDIT_ADD_RULE:
+ err = audit_add_rule(data, type);
if (!err)
audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
"auid=%u added an audit rule\n", loginuid);
break;
case AUDIT_DEL:
- listnr =((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
- if (listnr >= AUDIT_NR_FILTERS)
- return -EINVAL;
-
- err = audit_del_rule(data, &audit_filter_list[listnr]);
+ case AUDIT_DEL_RULE:
+ err = audit_del_rule(data, type);
if (!err)
audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
"auid=%u removed an audit rule\n", loginuid);
@@ -444,7 +690,7 @@ static int audit_comparator(const u32 le
/* 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_rule *rule,
+ struct audit_krule *rule,
struct audit_context *ctx,
enum audit_state *state)
{
@@ -525,9 +771,9 @@ static int audit_filter_rules(struct tas
}
break;
case AUDIT_INODE:
- if (ctx) {
+ if (ctx && value != (unsigned int)-1) {
for (j = 0; j < ctx->name_count; j++) {
- if (audit_comparator(ctx->names[j].pino, op, value) ||
+ if (audit_comparator(ctx->names[j].ino, op, value) ||
audit_comparator(ctx->names[j].pino, op, value)) {
++result;
break;
@@ -602,7 +848,8 @@ static enum audit_state audit_filter_sys
list_for_each_entry_rcu(e, list, list) {
if ((e->rule.mask[word] & bit) == bit
- && audit_filter_rules(tsk, &e->rule, ctx, &state)) {
+ && audit_filter_rules(tsk, &e->rule,
+ ctx, &state)) {
rcu_read_unlock();
return state;
}
@@ -613,7 +860,7 @@ static enum audit_state audit_filter_sys
}
static int audit_filter_user_rules(struct netlink_skb_parms *cb,
- struct audit_rule *rule,
+ struct audit_krule *rule,
enum audit_state *state)
{
int i;
@@ -680,7 +927,7 @@ int audit_filter_exclude(int type)
list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_EXCLUDE],
list) {
- struct audit_rule *rule = &e->rule;
+ struct audit_krule *rule = &e->rule;
int i;
for (i = 0; i < rule->field_count; i++) {
u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
18 years, 10 months
[PATCH] Split audit filtering into auditfilter.c
by David Woodhouse
I've committed this to my tree locally and checked that it builds with
all three possible combinations of CONFIG_AUDIT, CONFIG_AUDITSYSCALL.
My DSL line is down at the moment so I'll push it to the git tree later
or tomorrow. This mail will be delayed too, but it'll probably get out
as soon as the line comes back, and long before I notice and respond by
pushing to the git tree.
[AUDIT] Fix audit record filtering with !CONFIG_AUDITSYSCALL
This fixes the per-user and per-message-type filtering when syscall
auditing isn't enabled.
Signed-off-by: David Woodhouse <dwmw2(a)infradead.org>
diff --git a/include/linux/audit.h b/include/linux/audit.h
index 36e5090..8817141 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -278,8 +278,6 @@ static inline void audit_inode_child(con
}
/* Private API (for audit.c only) */
-extern int audit_receive_filter(int type, int pid, int uid, int seq,
- void *data, uid_t loginuid);
extern unsigned int audit_serial(void);
extern void auditsc_get_stamp(struct audit_context *ctx,
struct timespec *t, unsigned int *serial);
@@ -290,8 +288,6 @@ extern int audit_socketcall(int nargs, u
extern int audit_sockaddr(int len, void *addr);
extern int audit_avc_path(struct dentry *dentry, struct vfsmount *mnt);
extern void audit_signal_info(int sig, struct task_struct *t);
-extern int audit_filter_user(struct netlink_skb_parms *cb, int type);
-extern int audit_filter_exclude(int type);
extern char *audit_ipc_context(struct kern_ipc_perm *ipcp);
extern int audit_set_macxattr(const char *name);
#else
@@ -305,7 +301,6 @@ extern int audit_set_macxattr(const char
#define __audit_inode_child(d,i,p) do { ; } while (0)
#define audit_inode(n,i,f) do { ; } while (0)
#define audit_inode_child(d,i,p) do { ; } while (0)
-#define audit_receive_filter(t,p,u,s,d,l) ({ -EOPNOTSUPP; })
#define auditsc_get_stamp(c,t,s) do { BUG(); } while (0)
#define audit_get_loginuid(c) ({ -1; })
#define audit_ipc_perms(q,u,g,m,i) ({ 0; })
@@ -313,7 +308,6 @@ extern int audit_set_macxattr(const char
#define audit_sockaddr(len, addr) ({ 0; })
#define audit_avc_path(dentry, mnt) ({ 0; })
#define audit_signal_info(s,t) do { ; } while (0)
-#define audit_filter_user(cb,t) ({ 1; })
#define audit_ipc_context(i) do { ; } while (0)
#define audit_set_macxattr(n) do { ; } while (0)
#endif
@@ -339,13 +333,11 @@ extern void audit_log_d_path(struct
const char *prefix,
struct dentry *dentry,
struct vfsmount *vfsmnt);
- /* Private API (for auditsc.c only) */
-extern void audit_send_reply(int pid, int seq, int type,
- int done, int multi,
- void *payload, int size);
-extern void audit_log_lost(const char *message);
-extern void audit_panic(const char *message);
-extern struct semaphore audit_netlink_sem;
+ /* Private API (for audit.c only) */
+extern int audit_filter_user(struct netlink_skb_parms *cb, int type);
+extern int audit_filter_exclude(int type);
+extern int audit_receive_filter(int type, int pid, int uid, int seq,
+ void *data, uid_t loginuid);
#else
#define audit_log(c,g,t,f,...) do { ; } while (0)
#define audit_log_start(c,g,t) ({ NULL; })
diff --git a/kernel/Makefile b/kernel/Makefile
index 4f5a145..56f6189 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -23,7 +23,7 @@ obj-$(CONFIG_COMPAT) += compat.o
obj-$(CONFIG_CPUSETS) += cpuset.o
obj-$(CONFIG_IKCONFIG) += configs.o
obj-$(CONFIG_STOP_MACHINE) += stop_machine.o
-obj-$(CONFIG_AUDIT) += audit.o
+obj-$(CONFIG_AUDIT) += audit.o auditfilter.o
obj-$(CONFIG_AUDITSYSCALL) += auditsc.o
obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_SYSFS) += ksysfs.o
diff --git a/kernel/audit.c b/kernel/audit.c
index 9c13bf6..5941a07 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -305,6 +305,7 @@ int kauditd_thread(void *dummy)
remove_wait_queue(&kauditd_wait, &wait);
}
}
+ return 0;
}
/**
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 3e46e1c..8f0a61c 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -51,17 +51,15 @@
#include <linux/audit.h>
#include <linux/personality.h>
#include <linux/time.h>
-#include <linux/kthread.h>
#include <linux/netlink.h>
#include <linux/compiler.h>
#include <asm/unistd.h>
#include <linux/security.h>
+#include <linux/list.h>
-/* 0 = no checking
- 1 = put_count checking
- 2 = verbose put_count checking
-*/
-#define AUDIT_DEBUG 0
+#include "audit.h"
+
+extern struct list_head audit_filter_list[];
/* No syscall auditing will take place unless audit_enabled != 0. */
extern int audit_enabled;
@@ -75,29 +73,6 @@ extern int audit_enabled;
* path_lookup. */
#define AUDIT_NAMES_RESERVED 7
-/* At task start time, the audit_state is set in the audit_context using
- a per-task filter. At syscall entry, the audit_state is augmented by
- the syscall filter. */
-enum audit_state {
- AUDIT_DISABLED, /* Do not create per-task audit_context.
- * No syscall-specific audit records can
- * be generated. */
- AUDIT_SETUP_CONTEXT, /* Create the per-task audit_context,
- * but don't necessarily fill it in at
- * syscall entry time (i.e., filter
- * instead). */
- AUDIT_BUILD_CONTEXT, /* Create the per-task audit_context,
- * and always fill it in at syscall
- * entry time. This makes a full
- * syscall record available if some
- * other part of the kernel decides it
- * should be recorded. */
- AUDIT_RECORD_CONTEXT /* Create the per-task audit_context,
- * always fill it in at syscall entry
- * time, and always write out the audit
- * record at syscall exit time. */
-};
-
/* When fs/namei.c:getname() is called, we store the pointer in name and
* we don't let putname() free it (instead we free all of the saved
* pointers at syscall exit time).
@@ -182,264 +157,6 @@ struct audit_context {
#endif
};
- /* Public API */
-/* There are three lists of rules -- one to search at task creation
- * time, one to search at syscall entry time, and another to search at
- * syscall exit time. */
-static struct list_head audit_filter_list[AUDIT_NR_FILTERS] = {
- LIST_HEAD_INIT(audit_filter_list[0]),
- LIST_HEAD_INIT(audit_filter_list[1]),
- LIST_HEAD_INIT(audit_filter_list[2]),
- LIST_HEAD_INIT(audit_filter_list[3]),
- LIST_HEAD_INIT(audit_filter_list[4]),
- LIST_HEAD_INIT(audit_filter_list[5]),
-#if AUDIT_NR_FILTERS != 6
-#error Fix audit_filter_list initialiser
-#endif
-};
-
-struct audit_entry {
- struct list_head list;
- struct rcu_head rcu;
- struct audit_rule rule;
-};
-
-extern int audit_pid;
-
-/* Copy rule from user-space to kernel-space. Called from
- * audit_add_rule during AUDIT_ADD. */
-static inline int audit_copy_rule(struct audit_rule *d, struct audit_rule *s)
-{
- int i;
-
- if (s->action != AUDIT_NEVER
- && s->action != AUDIT_POSSIBLE
- && s->action != AUDIT_ALWAYS)
- return -1;
- if (s->field_count < 0 || s->field_count > AUDIT_MAX_FIELDS)
- return -1;
- if ((s->flags & ~AUDIT_FILTER_PREPEND) >= AUDIT_NR_FILTERS)
- return -1;
-
- d->flags = s->flags;
- d->action = s->action;
- d->field_count = s->field_count;
- for (i = 0; i < d->field_count; i++) {
- d->fields[i] = s->fields[i];
- d->values[i] = s->values[i];
- }
- for (i = 0; i < AUDIT_BITMASK_SIZE; i++) d->mask[i] = s->mask[i];
- return 0;
-}
-
-/* Check to see if two rules are identical. It is called from
- * audit_add_rule during AUDIT_ADD and
- * audit_del_rule during AUDIT_DEL. */
-static inline int audit_compare_rule(struct audit_rule *a, struct audit_rule *b)
-{
- int i;
-
- if (a->flags != b->flags)
- return 1;
-
- if (a->action != b->action)
- return 1;
-
- if (a->field_count != b->field_count)
- return 1;
-
- for (i = 0; i < a->field_count; i++) {
- if (a->fields[i] != b->fields[i]
- || a->values[i] != b->values[i])
- return 1;
- }
-
- for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
- if (a->mask[i] != b->mask[i])
- return 1;
-
- return 0;
-}
-
-/* Note that audit_add_rule and audit_del_rule are called via
- * audit_receive() in audit.c, and are protected by
- * audit_netlink_sem. */
-static inline int audit_add_rule(struct audit_rule *rule,
- struct list_head *list)
-{
- struct audit_entry *entry;
- int i;
-
- /* Do not use the _rcu iterator here, since this is the only
- * addition routine. */
- list_for_each_entry(entry, list, list) {
- if (!audit_compare_rule(rule, &entry->rule)) {
- return -EEXIST;
- }
- }
-
- for (i = 0; i < rule->field_count; i++) {
- if (rule->fields[i] & AUDIT_UNUSED_BITS)
- return -EINVAL;
- if ( rule->fields[i] & AUDIT_NEGATE )
- rule->fields[i] |= AUDIT_NOT_EQUAL;
- else if ( (rule->fields[i] & AUDIT_OPERATORS) == 0 )
- rule->fields[i] |= AUDIT_EQUAL;
- rule->fields[i] &= (~AUDIT_NEGATE);
- }
-
- if (!(entry = kmalloc(sizeof(*entry), GFP_KERNEL)))
- return -ENOMEM;
- if (audit_copy_rule(&entry->rule, rule)) {
- kfree(entry);
- return -EINVAL;
- }
-
- if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
- entry->rule.flags &= ~AUDIT_FILTER_PREPEND;
- list_add_rcu(&entry->list, list);
- } else {
- list_add_tail_rcu(&entry->list, list);
- }
-
- return 0;
-}
-
-static inline void audit_free_rule(struct rcu_head *head)
-{
- struct audit_entry *e = container_of(head, struct audit_entry, rcu);
- kfree(e);
-}
-
-/* Note that audit_add_rule and audit_del_rule are called via
- * audit_receive() in audit.c, and are protected by
- * audit_netlink_sem. */
-static inline int audit_del_rule(struct audit_rule *rule,
- struct list_head *list)
-{
- struct audit_entry *e;
-
- /* Do not use the _rcu iterator here, since this is the only
- * deletion routine. */
- list_for_each_entry(e, list, list) {
- if (!audit_compare_rule(rule, &e->rule)) {
- list_del_rcu(&e->list);
- call_rcu(&e->rcu, audit_free_rule);
- return 0;
- }
- }
- return -ENOENT; /* No matching rule */
-}
-
-static int audit_list_rules(void *_dest)
-{
- int pid, seq;
- int *dest = _dest;
- struct audit_entry *entry;
- int i;
-
- pid = dest[0];
- seq = dest[1];
- kfree(dest);
-
- down(&audit_netlink_sem);
-
- /* The *_rcu iterators not needed here because we are
- always called with audit_netlink_sem held. */
- for (i=0; i<AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(entry, &audit_filter_list[i], list)
- audit_send_reply(pid, seq, AUDIT_LIST, 0, 1,
- &entry->rule, sizeof(entry->rule));
- }
- audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
-
- up(&audit_netlink_sem);
- return 0;
-}
-
-/**
- * audit_receive_filter - apply all rules to the specified message type
- * @type: audit message type
- * @pid: target pid for netlink audit messages
- * @uid: target uid for netlink audit messages
- * @seq: netlink audit message sequence (serial) number
- * @data: payload data
- * @loginuid: loginuid of sender
- */
-int audit_receive_filter(int type, int pid, int uid, int seq, void *data,
- uid_t loginuid)
-{
- struct task_struct *tsk;
- int *dest;
- int err = 0;
- unsigned listnr;
-
- switch (type) {
- case AUDIT_LIST:
- /* We can't just spew out the rules here because we might fill
- * the available socket buffer space and deadlock waiting for
- * auditctl to read from it... which isn't ever going to
- * happen if we're actually running in the context of auditctl
- * trying to _send_ the stuff */
-
- dest = kmalloc(2 * sizeof(int), GFP_KERNEL);
- if (!dest)
- return -ENOMEM;
- dest[0] = pid;
- dest[1] = seq;
-
- tsk = kthread_run(audit_list_rules, dest, "audit_list_rules");
- if (IS_ERR(tsk)) {
- kfree(dest);
- err = PTR_ERR(tsk);
- }
- break;
- case AUDIT_ADD:
- listnr =((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
- if (listnr >= AUDIT_NR_FILTERS)
- return -EINVAL;
-
- err = audit_add_rule(data, &audit_filter_list[listnr]);
- if (!err)
- audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
- "auid=%u added an audit rule\n", loginuid);
- break;
- case AUDIT_DEL:
- listnr =((struct audit_rule *)data)->flags & ~AUDIT_FILTER_PREPEND;
- if (listnr >= AUDIT_NR_FILTERS)
- return -EINVAL;
-
- err = audit_del_rule(data, &audit_filter_list[listnr]);
- if (!err)
- audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
- "auid=%u removed an audit rule\n", loginuid);
- break;
- default:
- return -EINVAL;
- }
-
- return err;
-}
-
-static int audit_comparator(const u32 left, const u32 op, const u32 right)
-{
- switch (op) {
- case AUDIT_EQUAL:
- return (left == right);
- case AUDIT_NOT_EQUAL:
- return (left != right);
- case AUDIT_LESS_THAN:
- return (left < right);
- case AUDIT_LESS_THAN_OR_EQUAL:
- return (left <= right);
- case AUDIT_GREATER_THAN:
- return (left > right);
- case AUDIT_GREATER_THAN_OR_EQUAL:
- return (left >= right);
- default:
- return -EINVAL;
- }
-}
/* Compare a task_struct with an audit_rule. Return 1 on match, 0
* otherwise. */
@@ -612,95 +329,6 @@ static enum audit_state audit_filter_sys
return AUDIT_BUILD_CONTEXT;
}
-static int audit_filter_user_rules(struct netlink_skb_parms *cb,
- struct audit_rule *rule,
- enum audit_state *state)
-{
- int i;
-
- for (i = 0; i < rule->field_count; i++) {
- u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
- u32 op = rule->fields[i] & AUDIT_OPERATORS;
- u32 value = rule->values[i];
- int result = 0;
-
- switch (field) {
- case AUDIT_PID:
- result = audit_comparator(cb->creds.pid, op, value);
- break;
- case AUDIT_UID:
- result = audit_comparator(cb->creds.uid, op, value);
- break;
- case AUDIT_GID:
- result = audit_comparator(cb->creds.gid, op, value);
- break;
- case AUDIT_LOGINUID:
- result = audit_comparator(cb->loginuid, op, value);
- break;
- }
-
- if (!result)
- return 0;
- }
- switch (rule->action) {
- case AUDIT_NEVER: *state = AUDIT_DISABLED; break;
- case AUDIT_POSSIBLE: *state = AUDIT_BUILD_CONTEXT; break;
- case AUDIT_ALWAYS: *state = AUDIT_RECORD_CONTEXT; break;
- }
- return 1;
-}
-
-int audit_filter_user(struct netlink_skb_parms *cb, int type)
-{
- struct audit_entry *e;
- enum audit_state state;
- int ret = 1;
-
- rcu_read_lock();
- list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_USER], list) {
- if (audit_filter_user_rules(cb, &e->rule, &state)) {
- if (state == AUDIT_DISABLED)
- ret = 0;
- break;
- }
- }
- rcu_read_unlock();
-
- return ret; /* Audit by default */
-}
-
-int audit_filter_exclude(int type)
-{
- struct audit_entry *e;
- int result = 0;
-
- rcu_read_lock();
- if (list_empty(&audit_filter_list[AUDIT_FILTER_EXCLUDE]))
- goto unlock_and_return;
-
- list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_EXCLUDE],
- list) {
- struct audit_rule *rule = &e->rule;
- int i;
- for (i = 0; i < rule->field_count; i++) {
- u32 field = rule->fields[i] & ~AUDIT_OPERATORS;
- u32 op = rule->fields[i] & AUDIT_OPERATORS;
- u32 value = rule->values[i];
- if ( field == AUDIT_MSGTYPE ) {
- result = audit_comparator(type, op, value);
- if (!result)
- break;
- }
- }
- if (result)
- goto unlock_and_return;
- }
-unlock_and_return:
- rcu_read_unlock();
- return result;
-}
-
-
/* This should be called with task_lock() held. */
static inline struct audit_context *audit_get_context(struct task_struct *tsk,
int return_valid,
--
dwmw2
18 years, 10 months
[PATCH] fix inode filtering
by Amy Griffis
Inode filter needs to check for .ino, instead of checking for .pino
twice.
(Must have been a merge error somewhere.)
Signed-off-by: Amy Griffis <amy.griffis(a)hp.com>
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -527,7 +527,7 @@ static int audit_filter_rules(struct tas
case AUDIT_INODE:
if (ctx) {
for (j = 0; j < ctx->name_count; j++) {
- if (audit_comparator(ctx->names[j].pino, op, value) ||
+ if (audit_comparator(ctx->names[j].ino, op, value) ||
audit_comparator(ctx->names[j].pino, op, value)) {
++result;
break;
18 years, 10 months
problem with current kernel tree
by Dustin Kirkland
David-
I just synced to your current git-tree, built, and installed the kernel.
Kernel boots and seems to run ok, but I'm not able to start
auditd. /var/log/messages shows:
Dec 8 16:31:18 localhost auditd[3093]: Error sending failure mode
request (Connection refused)
Dec 8 16:31:18 localhost auditd[3093]: Unable to set audit pid, exiting
Dec 8 16:31:18 localhost auditd[3093]: The audit daemon is exiting.
Dec 8 16:31:18 localhost auditd[3093]: Error sending failure mode
request (Connection refused)
On the other hand, I rebuilt the latest kernel you posted to your site
from source rpm's and it's able to start auditd just fine. Any ideas?
This machine is running FC4 with audit-1.0.12-2.fc4.
:-Dustin
18 years, 10 months
[PATCH] promiscuous mode
by Steve Grubb
Hi,
When a network interface goes into promiscuous mode, its an important security
issue. The attached patch is intended to capture that action and send an
event to the audit system.
The patch carves out a new block of numbers for kernel detected anomalies.
These are events that may indicate suspicious activity. Other examples of
potential kernel anomalies would be: exceeding disk quota, rlimit violations,
changes to syscall entry table.
Signed-off-by: Steve Grubb <sgrubb(a)redhat.com>
18 years, 10 months
Changes to SELinux in git-audit tree
by Stephen Smalley
Hi,
In reviewing the audit-related patches in -mm originating from the
git-audit tree, I noticed two unexpected changes in SELinux behavior as
a result of these patches. These changes in behavior naturally need to
be corrected before these patches proceed any further, but more
generally, I think that the SELinux maintainers (James and I) should be
cc'd on every patch that touches the SELinux code when it is first
submitted to these lists and every time it has to be updated to deal
with changes upstream (one of the changes in behavior seems to have been
due to an attempt to re-base the patch against more recent upstream code
without re-submitting the revised patch for review), and none of the
SELinux modifications should go to -mm without at least an Acked-by
response from a SELinux maintainer. On our end, we can try to be more
responsive (but having an explicit cc for all patches that touch SELinux
will help significantly).
--
Stephen Smalley
National Security Agency
18 years, 10 months
Re: [redhat-lspp] Re: [Patch] SE Linux audit events
by Steve Grubb
On Tuesday 06 December 2005 14:59, Timothy R. Chavez wrote:
> I believe it's common practice to place linux before asm, so:
details, details,... :)
Attached is a patch that hardwires important SE Linux events to the audit
system. Please Apply.
Signed-off-by: Steve Grubb <sgrubb(a)redhat.com>
diff -urp linux-2.6.14.orig/include/linux/audit.h
linux-2.6.14/include/linux/audit.h
--- linux-2.6.14.orig/include/linux/audit.h 2005-12-06 15:18:10.000000000
-0500
+++ linux-2.6.14/include/linux/audit.h 2005-12-06 15:23:33.000000000 -0500
@@ -83,6 +83,9 @@
#define AUDIT_AVC 1400 /* SE Linux avc denial or grant */
#define AUDIT_SELINUX_ERR 1401 /* Internal SE Linux Errors */
#define AUDIT_AVC_PATH 1402 /* dentry, vfsmount pair from avc */
+#define AUDIT_MAC_POLICY_LOAD 1403 /* Policy file load */
+#define AUDIT_MAC_STATUS 1404 /* Changed enforcing,permissive,off */
+#define AUDIT_MAC_CONFIG_CHANGE 1405 /* Changes to booleans */
#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A
REQUEST. */
diff -urp linux-2.6.14.orig/security/selinux/selinuxfs.c
linux-2.6.14/security/selinux/selinuxfs.c
--- linux-2.6.14.orig/security/selinux/selinuxfs.c 2005-12-06
15:18:20.000000000 -0500
+++ linux-2.6.14/security/selinux/selinuxfs.c 2005-12-06 15:23:55.000000000
-0500
@@ -21,6 +21,7 @@
#include <linux/major.h>
#include <linux/seq_file.h>
#include <linux/percpu.h>
+#include <linux/audit.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>
@@ -126,6 +127,10 @@ static ssize_t sel_write_enforce(struct
length = task_has_security(current, SECURITY__SETENFORCE);
if (length)
goto out;
+ audit_log(current->audit_context, GFP_KERNEL,
AUDIT_MAC_STATUS,
+ "enforcing=%d old_enforcing=%d auid=%u", new_value,
+ selinux_enforcing,
+ audit_get_loginuid(current->audit_context));
selinux_enforcing = new_value;
if (selinux_enforcing)
avc_ss_reset(0);
@@ -176,6 +181,9 @@ static ssize_t sel_write_disable(struct
length = selinux_disable();
if (length < 0)
goto out;
+ audit_log(current->audit_context, GFP_KERNEL,
AUDIT_MAC_STATUS,
+ "selinux=0 auid=%u",
+ audit_get_loginuid(current->audit_context));
}
length = count;
@@ -261,6 +269,9 @@ static ssize_t sel_write_load(struct fil
length = ret;
else
length = count;
+ audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_POLICY_LOAD,
+ "policy loaded auid=%u",
+ audit_get_loginuid(current->audit_context));
out:
up(&sel_sem);
vfree(data);
diff -urp linux-2.6.14.orig/security/selinux/ss/services.c
linux-2.6.14/security/selinux/ss/services.c
--- linux-2.6.14.orig/security/selinux/ss/services.c 2005-12-06
15:18:20.000000000 -0500
+++ linux-2.6.14/security/selinux/ss/services.c 2005-12-06 15:23:33.000000000
-0500
@@ -1758,19 +1758,22 @@ int security_set_bools(int len, int *val
goto out;
}
- printk(KERN_INFO "security: committed booleans { ");
for (i = 0; i < len; i++) {
+ if (!!values[i] != policydb.bool_val_to_struct[i]->state) {
+ audit_log(current->audit_context, GFP_ATOMIC,
+ AUDIT_MAC_CONFIG_CHANGE,
+ "bool=%s val=%d old_val=%d auid=%u",
+ policydb.p_bool_val_to_name[i],
+ !!values[i],
+ policydb.bool_val_to_struct[i]->state,
+ audit_get_loginuid(current->audit_context));
+ }
if (values[i]) {
policydb.bool_val_to_struct[i]->state = 1;
} else {
policydb.bool_val_to_struct[i]->state = 0;
}
- if (i != 0)
- printk(", ");
- printk("%s:%d", policydb.p_bool_val_to_name[i],
- policydb.bool_val_to_struct[i]->state);
}
- printk(" }\n");
for (cur = policydb.cond_list; cur != NULL; cur = cur->next) {
rc = evaluate_cond_node(&policydb, cur);
18 years, 11 months
role based audit filtering
by Dustin Kirkland
This question is primarily directed to the SELinux gurus, Stephen
Smalley and James Morris, but I'm open to input from anyone.
I sat down today and took a first stab at (b):
FAU_SEL.1 Selective Audit
The TSF shall be able to include or exclude auditable events from the
set of audited events based on the following attributes:
(a) Object identity, user identity, subject identity, host identity, and
event type
(b) Users belonging to a specified Role and Access types (e.g. delete,
insert) on a particular object
The userspace interface might look something like the following, to
filter out messages related to the sysadmin_r role:
auditctl -a entry,always -S open -F role=sysadmin_r
My questions revolve around translating a role string such as
"sysadmin_r" to something that can be easily matched in the kernel.
- What are going to be the best practices here?
- Is it possible for either userspace, or the kernel upon adding the
rule to translate a valid role to some string based id that can be
compared later?
- And speaking of "later", when is going to be the best time to do this
comparison? I'm thinking perhaps of adding the necessary fields to
struct audit_context such that audit_filter_rules() only another case is
needed to do the comparison and match the role. Is that a good place
for this?
- I would expect to be crucified if I attempted to do strcmp() or
strstr() string comparisons/matches in the kernel at the oft-called
filters, so I'm really hoping to keep this to integer comparisons. For
that, I think I might need an api into SELinux to get some sort of
integer looking value to compare. Am I approaching this correctly?
Probably enough questions for now. Thanks!
:-Dustin
18 years, 11 months