Kernel audit component
This patch is against David Woodhouse's last update of his audit-2.6 git
tree, plus a patch submitted by Amy Griffis on 2006-01-17 that adds
support for strings in audit rules. This patch can be found here:
https://www.redhat.com/archives/linux-audit/2006-January/msg00064.html
Let me highlight and explain a few key points:
I've added another function similar to audit_comparator():
audit_str_comparator(). This function takes a left string, an operator,
and a right string and returns true or false based on the comparison.
NULL is considered absolute zero, such that any valid string is greater
than NULL. Additionally, two NULL strings are considered equal. These
assumptions are made such that ugly NULL pointer dereferences are easily
avoided.
Please double check my memory management. I need to kmalloc() space to
memcpy() the string from the netlink message into a kernel rule
structure. Obviously, this needs to be cleaned up properly upon rule
destruction, which I think I'm doing properly.
I also split the function audit_log_task_context() into two parts. The
first part allocates the memory necessary and returns the a context
(aka, label) as a string pointer--this new function is called
audit_get_task_label(). In turn, audit_log_task_context() simply calls
audit_get_task_label() and inserts that string into the audit record.
And audit_get_task_label() is called just before the filter code such
that it has a label to potentially filter upon. Again, proper kfree()'s
are required as audit_get_task_label() does the memory allocation and
the consumer must free.
Note that this code actually only provides enough functionality to
filter on _task_ labels. I'm looking for input or acknowledgment from
the SELinux guys (cc'd) on the validity of the approach herein.
Additionally, I'm open to suggestions on how I might similarly collect
object and user labels for the same filtering mechanism (if required).
I hope to easily extend this patch to handle those as well, though I
wanted to put this much forth immediately to incorporate suggestions.
Comments appreciated...
:-Dustin
---
diff --git a/include/linux/audit.h b/include/linux/audit.h
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -140,6 +140,11 @@
#define AUDIT_PERS 10
#define AUDIT_ARCH 11
#define AUDIT_MSGTYPE 12
+#define AUDIT_SE_USER 13 /* security label user */
+#define AUDIT_SE_ROLE 14 /* security label role */
+#define AUDIT_SE_TYPE 15 /* security label type */
+#define AUDIT_SE_CAT 16 /* security label category */
+#define AUDIT_SE_SENS 17 /* security label sensitivity */
/* These are ONLY useful when checking
* at syscall exit time (AUDIT_AT_EXIT). */
diff --git a/kernel/audit.c b/kernel/audit.c
diff --git a/kernel/audit.h b/kernel/audit.h
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -56,6 +56,7 @@ struct audit_field {
u32 type;
u32 val;
u32 op;
+ char *buf;
};
struct audit_krule {
@@ -78,6 +79,7 @@ struct audit_entry {
extern int audit_pid;
extern int audit_comparator(const u32 left, const u32 op, const u32 right);
+extern int audit_str_comparator(const char *left, const u32 op, const char *right);
extern void audit_send_reply(int pid, int seq, int type,
int done, int multi,
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -160,15 +160,14 @@ static struct audit_entry *audit_data_to
{
int err = 0;
struct audit_entry *entry;
- void *bufp;
/* size_t remain = datasz - sizeof(struct audit_rule_data); */
int i;
+ int offset = 0;
entry = audit_to_entry_common((struct audit_rule *)data);
if (IS_ERR(entry))
goto exit_nofree;
- bufp = data->buf;
entry->rule.vers_ops = 2;
for (i = 0; i < data->field_count; i++) {
struct audit_field *f = &entry->rule.fields[i];
@@ -180,10 +179,26 @@ static struct audit_entry *audit_data_to
f->op = data->fieldflags[i] & AUDIT_OPERATORS;
f->type = data->fields[i];
+ f->val = data->values[i];
switch(f->type) {
/* call type-specific conversion routines here */
+ case AUDIT_SE_USER:
+ case AUDIT_SE_ROLE:
+ case AUDIT_SE_TYPE:
+ case AUDIT_SE_CAT:
+ case AUDIT_SE_SENS:
+ f->buf = kmalloc(f->val + 1, GFP_KERNEL);
+ if (!f->buf) {
+ err = -ENOMEM;
+ goto exit_free;
+ }
+ memcpy(f->buf, data->buf + offset, f->val);
+ f->buf[f->val] = 0;
+ offset += f->val;
+ break;
default:
- f->val = data->values[i];
+ f->buf = NULL;
+ break;
}
}
@@ -326,7 +341,13 @@ static inline int audit_add_rule(struct
static inline void audit_free_rule(struct rcu_head *head)
{
+ int i;
struct audit_entry *e = container_of(head, struct audit_entry, rcu);
+ for (i=0; i<e->rule.field_count; i++) {
+ struct audit_field *f = &e->rule.fields[i];
+ if (f->buf)
+ kfree(f->buf);
+ }
kfree(e);
}
@@ -521,6 +542,39 @@ int audit_comparator(const u32 left, con
}
}
+int audit_str_comparator(const char *left, const u32 op, const char *right)
+{
+ u32 i = 0;
+
+ if ( (left == NULL) || (right == NULL) ) {
+ /* first handle cases where left or right are NULL */
+ if ( (left == NULL) && (right == NULL) )
+ i = 0;
+ else if ( (left == NULL) && (right != NULL) )
+ i = -1;
+ else if ( (left != NULL) && (right == NULL))
+ i = 1;
+ } else
+ /* ok, not NULL, so compare strings */
+ i = strcmp(left, right);
+
+ switch(op) {
+ case AUDIT_EQUAL:
+ return (i == 0);
+ case AUDIT_NOT_EQUAL:
+ return (i != 0);
+ case AUDIT_LESS_THAN:
+ return (i < 0);
+ case AUDIT_LESS_THAN_OR_EQUAL:
+ return (i <= 0);
+ case AUDIT_GREATER_THAN:
+ return (i > 0);
+ case AUDIT_GREATER_THAN_OR_EQUAL:
+ return (i >= 0);
+ default:
+ return -EINVAL;
+ }
+}
static int audit_filter_user_rules(struct netlink_skb_parms *cb,
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -157,15 +157,23 @@ struct audit_context {
#endif
};
+static char *audit_get_task_label(void);
/* Compare a task_struct with an audit_rule. Return 1 on match, 0
* otherwise. */
static int audit_filter_rules(struct task_struct *tsk,
struct audit_krule *rule,
struct audit_context *ctx,
- enum audit_state *state)
+ enum audit_state *state,
+ char *label)
{
int i, j;
+ char *user, *role, *type, *cat, *sens;
+ user = strsep(&label, ":");
+ role = strsep(&label, ":");
+ type = strsep(&label, ":");
+ cat = strsep(&label, ":");
+ sens = strsep(&label, ":");
for (i = 0; i < rule->field_count; i++) {
struct audit_field *f = &rule->fields[i];
@@ -206,6 +214,21 @@ static int audit_filter_rules(struct tas
if (ctx)
result = audit_comparator(ctx->arch, f->op, f->val);
break;
+ case AUDIT_SE_USER:
+ result = audit_str_comparator(user, f->op, f->buf);
+ break;
+ case AUDIT_SE_ROLE:
+ result = audit_str_comparator(role, f->op, f->buf);
+ break;
+ case AUDIT_SE_TYPE:
+ result = audit_str_comparator(type, f->op, f->buf);
+ break;
+ case AUDIT_SE_CAT:
+ result = audit_str_comparator(cat, f->op, f->buf);
+ break;
+ case AUDIT_SE_SENS:
+ result = audit_str_comparator(sens, f->op, f->buf);
+ break;
case AUDIT_EXIT:
if (ctx && ctx->return_valid)
@@ -283,15 +306,18 @@ static enum audit_state audit_filter_tas
{
struct audit_entry *e;
enum audit_state state;
+ char *label = audit_get_task_label();
rcu_read_lock();
list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK], list) {
- if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {
+ if (audit_filter_rules(tsk, &e->rule, NULL, &state, label)) {
rcu_read_unlock();
return state;
}
}
rcu_read_unlock();
+ if (label)
+ kfree(label);
return AUDIT_BUILD_CONTEXT;
}
@@ -306,6 +332,7 @@ static enum audit_state audit_filter_sys
{
struct audit_entry *e;
enum audit_state state;
+ char *label = audit_get_task_label();
if (audit_pid && tsk->tgid == audit_pid)
return AUDIT_DISABLED;
@@ -317,13 +344,15 @@ 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, label)) {
rcu_read_unlock();
return state;
}
}
}
rcu_read_unlock();
+ if (label)
+ kfree(label);
return AUDIT_BUILD_CONTEXT;
}
@@ -503,32 +532,44 @@ static inline void audit_free_context(st
static void audit_log_task_context(struct audit_buffer *ab)
{
- char *ctx = NULL;
+ char *label = audit_get_task_label();
+
+ if (label == NULL)
+ audit_panic("error in audit_log_task_context");
+ else {
+ audit_log_format(ab, " subj=%s", label);
+ kfree(label);
+ }
+ return;
+}
+
+static char *audit_get_task_label(void)
+{
+ char *label = NULL;
ssize_t len = 0;
len = security_getprocattr(current, "current", NULL, 0);
if (len < 0) {
if (len != -EINVAL)
goto error_path;
- return;
+ return NULL;
}
- ctx = kmalloc(len, GFP_KERNEL);
- if (!ctx)
+ label = kmalloc(len, GFP_KERNEL);
+ if (!label)
goto error_path;
- len = security_getprocattr(current, "current", ctx, len);
+ len = security_getprocattr(current, "current", label, len);
if (len < 0 )
goto error_path;
- audit_log_format(ab, " subj=%s", ctx);
- return;
+ return label;
error_path:
- if (ctx)
- kfree(ctx);
- audit_panic("error in audit_log_task_context");
- return;
+ if (label)
+ kfree(label);
+ audit_panic("error in audit_get_task_label");
+ return NULL;
}
static void audit_log_task_info(struct audit_buffer *ab)