Userspace audit component
This patch is against Steve Grubb's audit-1.1.13 audit release, plus a
patch from Amy Griffis on 2006-02-02 that adds a couple of key functions
needed to pass arbitrary length strings in audit rules to and from the
kernel. This patch can be found here:
https://www.redhat.com/archives/linux-audit/2006-February/msg00001.html
There has been discussion on this list and elsewhere with Steve
explaining his commitment to maintain compatibility between various
kernel and userspace audit versions. I expect that this subtle yet
powerful change in the data structures exchanged between kernel and
userspace audit will test our mettle on that point. Some amount of code
duplication will be required for a time to support both kernels using
the old audit_rule structure and the new audit_rule_data paradigm. I do
not disagree with this, and I understand that the patch that follows
violates these compatibility requirements.
However, this patch is one of the first consumers of the new
audit_rule_data structure, and as such I think it will be most easily
understood and reviewed without the excess code required for dual
compatibility. I invite suggestions on how we should avoid the "drop
in" replacement of audit_rule that this simpler patch provides. I think
Steve has some ideas on the matter, perhaps requiring a VERSION field in
the netlink messages between kernel and userspace audit. This should
probably be further hashed out in a thread of it's own.
Back to this patch... Some key points are called out:
New fields acceptable to the -F option have been added (se_user,
se_role, se_type, se_cat, se_sens). The se_ prefix (or something like)
is necessary to distinguish between some of the loaded terms, such as
"user" and "type".
Note that I left in this patch a debug function that I found useful,
print_audit_rule_data(). This function will present a pretty clean view
of the current state of an audit_rule_data structure. This was key to
me developing in the uncharted territory around populating and passing
this new structure to the kernel. Feel free to drop it at any point (or
augment it on the other hand if you see it as being useful).
I also extracted the chunk of code out of audit_rule_fieldpair() that
would determine which operator was used in the optarg argument. This
now lives in audit_get_operator() and returns the integer value of the
contained operator. It cleans up audit_rule_fieldpair() and makes it a
bit more readable.
Speaking of which, several functions (including audit_rule_fieldpair(),
audit_rule_syscall(), audit_rule_syscallbyname()) have been updated to
use audit_rule_data structure instead of audit_rule. The new structure
is a clean replacement for the former, able to provide all of it's
predecessor's functionality and more. However, the code that Steve
eventually merges into the audit tree will likely require either a void*
that can be cast appropriately (which I think he's spoken against), or a
duplicate function that continues to work with the older structure.
The audit_rule_fieldpair() switch statement is able to extend the
existing structure via realloc() to make room for the new string, which
is inserted at the appropriate offset and the buffer lengths are
recorded.
A couple of bits of code that will eventually be in the kernel headers
are temporarily stubbed into libaudit.h such that the code will compile.
This includes #defines for AUDIT_SE_* and the audit_rule_data structure.
I'm working on adding support for listing audit rules containing
strings. I should be able to put this forth shortly. In the interest
of getting this patches out for comment immediately, though, I'm posting
what I have working now.
I've been manually testing using test cases such as these, coupled with
a very small binary that performs a simple create/delete of an ipc
message queue):
# ./auditctl -a exit,always -S ipc -F "se_role=system_r"
# ./auditctl -a exit,always -S ipc -F "se_user=user_u"
# ./auditctl -a exit,always -S ipc -F "se_user!=user_u"
...and so on...
:-Dustin
---
diff -urpN audit-1.1.3/lib/fieldtab.h audit-1.1.3.dustin/lib/fieldtab.h
--- audit-1.1.3/lib/fieldtab.h 2006-01-05 14:30:40.000000000 -0600
+++ audit-1.1.3.dustin/lib/fieldtab.h 2006-01-10 13:40:05.000000000 -0600
@@ -34,6 +34,11 @@ _S(AUDIT_LOGINUID, "loginuid" )
_S(AUDIT_PERS, "pers" )
_S(AUDIT_ARCH, "arch" )
_S(AUDIT_MSGTYPE, "msgtype" )
+_S(AUDIT_SE_USER, "se_user" )
+_S(AUDIT_SE_ROLE, "se_role" )
+_S(AUDIT_SE_TYPE, "se_type" )
+_S(AUDIT_SE_CAT, "se_cat" )
+_S(AUDIT_SE_SENS, "se_sens" )
_S(AUDIT_DEVMAJOR, "devmajor" )
_S(AUDIT_DEVMINOR, "devminor" )
diff -urpN audit-1.1.3/lib/libaudit.c audit-1.1.3.dustin/lib/libaudit.c
--- audit-1.1.3/lib/libaudit.c 2006-02-02 11:02:12.000000000 -0600
+++ audit-1.1.3.dustin/lib/libaudit.c 2006-02-02 13:14:25.000000000 -0600
@@ -48,6 +48,7 @@ int audit_syscalladded = 0;
unsigned int audit_elf = 0U;
static int name_to_uid(const char *name, uid_t *uid);
static int name_to_gid(const char *name, gid_t *gid);
+static void print_audit_rule_data(struct audit_rule_data *rd);
int audit_request_status(int fd)
@@ -580,7 +581,7 @@ int audit_log_if_enabled(int fd, int typ
return rc;
}
-int audit_rule_syscall(struct audit_rule *rule, int scall)
+int audit_rule_syscall(struct audit_rule_data *rule, int scall)
{
int word = AUDIT_WORD(scall);
int bit = AUDIT_BIT(scall);
@@ -591,7 +592,7 @@ int audit_rule_syscall(struct audit_rule
return 0;
}
-int audit_rule_syscallbyname(struct audit_rule *rule,
+int audit_rule_syscallbyname(struct audit_rule_data *rule,
const char *scall)
{
int nr, i;
@@ -602,7 +603,10 @@ int audit_rule_syscallbyname(struct audi
rule->mask[i] = ~0;
return 0;
}
- machine = audit_elf_to_machine(audit_elf);
+ if (!audit_elf)
+ machine = audit_detect_machine();
+ else
+ machine = audit_elf_to_machine(audit_elf);
if (machine < 0)
return -2;
nr = audit_name_to_syscall(scall, machine);
@@ -626,44 +630,58 @@ int audit_rule_field(struct audit_rule *
return 0;
}
-int audit_rule_fieldpair(struct audit_rule *rule, const char *pair, int flags)
+int audit_get_operator(const char *pair, char **v)
+{
+ /* look for 2-char operators first
+ then look for 1-char operators afterwards
+ when found, null out the bytes under the operators to split
+ and set value pointer just past operator bytes
+ */
+ if ( (*v = strstr(pair, "!=")) ) {
+ *(*v)++ = '\0';
+ *(*v)++ = '\0';
+ return AUDIT_NOT_EQUAL;
+ }
+ if ( (*v = strstr(pair, ">=")) ) {
+ *(*v)++ = '\0';
+ *(*v)++ = '\0';
+ return AUDIT_GREATER_THAN_OR_EQUAL;
+ }
+ if ( (*v = strstr(pair, "<=")) ) {
+ *(*v)++ = '\0';
+ *(*v)++ = '\0';
+ return AUDIT_LESS_THAN_OR_EQUAL;
+ }
+ if ( (*v = strstr(pair, "=")) ) {
+ *(*v)++ = '\0';
+ return AUDIT_EQUAL;
+ }
+ if ( (*v = strstr(pair, ">")) ) {
+ *(*v)++ = '\0';
+ return AUDIT_GREATER_THAN;
+ }
+ if ( (*v = strstr(pair, "<")) ) {
+ *(*v)++ = '\0';
+ return AUDIT_LESS_THAN;
+ }
+ return 0;
+}
+
+int audit_rule_fieldpair(struct audit_rule_data *rule,
+ const char *pair, int flags)
{
const char *f = pair;
char *v;
int op;
int field;
int vlen;
+ int total_size;
+ int offset;
if (f == NULL)
return -1;
- /* look for 2-char operators first
- then look for 1-char operators afterwards
- when found, null out the bytes under the operators to split
- and set value pointer just past operator bytes
- */
- if ( (v = strstr(pair, "!=")) ) {
- *v++ = '\0';
- *v++ = '\0';
- op = AUDIT_NOT_EQUAL;
- } else if ( (v = strstr(pair, ">=")) ) {
- *v++ = '\0';
- *v++ = '\0';
- op = AUDIT_GREATER_THAN_OR_EQUAL;
- } else if ( (v = strstr(pair, "<=")) ) {
- *v++ = '\0';
- *v++ = '\0';
- op = AUDIT_LESS_THAN_OR_EQUAL;
- } else if ( (v = strstr(pair, "=")) ) {
- *v++ = '\0';
- op = AUDIT_EQUAL;
- } else if ( (v = strstr(pair, ">")) ) {
- *v++ = '\0';
- op = AUDIT_GREATER_THAN;
- } else if ( (v = strstr(pair, "<")) ) {
- *v++ = '\0';
- op = AUDIT_LESS_THAN;
- }
+ op = audit_get_operator(pair, &v);
if (v == NULL || f == v)
return -1;
@@ -673,7 +691,9 @@ int audit_rule_fieldpair(struct audit_ru
return -2;
audit_msg(LOG_DEBUG,"f%d%s%s\n", field, audit_operator_to_symbol(op),v);
- rule->fields[rule->field_count] = field | op;
+ rule->fields[rule->field_count] = field;
+ rule->fieldflags[rule->field_count] = op;
+
switch (field)
{
case AUDIT_UID:
@@ -819,6 +839,22 @@ int audit_rule_fieldpair(struct audit_ru
case AUDIT_DEVMAJOR...AUDIT_SUCCESS:
if (flags == AUDIT_FILTER_ENTRY)
return -7;
+ case AUDIT_SE_USER:
+ case AUDIT_SE_ROLE:
+ case AUDIT_SE_TYPE:
+ case AUDIT_SE_CAT:
+ case AUDIT_SE_SENS:
+ rule->values[rule->field_count] = strlen(v);
+ rule->buflen += strlen(v);
+ total_size = sizeof(*rule) + rule->buflen;
+ if (realloc(rule, total_size) == NULL) {
+ printf("Cannot realloc memory!\n");
+ return -3;
+ }
+ offset = total_size - sizeof(*rule) - strlen(v);
+ strncpy(&rule->buf[offset], v, strlen(v));
+ break;
+
/* fallthrough */
default:
rule->values[rule->field_count] = strtol(v, NULL, 0);
@@ -828,6 +864,30 @@ int audit_rule_fieldpair(struct audit_ru
return 0;
}
+/*
+ * Debug function useful to sanity check the contents of this new
+ * audit_rule_data structure, and in particular the variable length
+ * strings
+ */
+void print_audit_rule_data(struct audit_rule_data *rd) {
+ int i;
+ printf("====================================\n");
+ printf("flags = [0x%x]\n", rd->flags);
+ printf("action = [%d]\n", rd->action);
+ printf("field_count = [%d]\n", rd->field_count);
+ for (i=0; i<AUDIT_BITMASK_SIZE; i++)
+ if (rd->mask[i])
+ printf("mask[%d] = [0x%x]\n", i, rd->mask[i]);
+ for (i=0; i<AUDIT_MAX_FIELDS; i++) {
+ if (rd->fields[i] || rd->values[i] || rd->fieldflags[i])
+ printf("fields[%d] = [%d], values[%d] = [%d], fieldflags[%d] = [0x%x]\n", i,
rd->fields[i], i, rd->values[i], i, rd->fieldflags[i]);
+ }
+ printf("buflen = [%d]\n", rd->buflen);
+ printf("buf = [%s]\n", rd->buf);
+ printf("TOTAL_SIZE = [%d]\n", sizeof(*rd) + rd->buflen);
+ printf("====================================\n");
+}
+
void audit_rule_free(struct audit_rule *rule)
{
if (rule)
diff -urpN audit-1.1.3/lib/libaudit.h audit-1.1.3.dustin/lib/libaudit.h
--- audit-1.1.3/lib/libaudit.h 2006-02-02 11:02:12.000000000 -0600
+++ audit-1.1.3.dustin/lib/libaudit.h 2006-02-02 13:14:47.000000000 -0600
@@ -182,6 +182,13 @@ extern "C" {
#ifndef AUDIT_MSGTYPE
#define AUDIT_MSGTYPE 12
#endif
+#ifndef AUDIT_SE_USER
+#define AUDIT_SE_USER 13
+#define AUDIT_SE_ROLE 14
+#define AUDIT_SE_TYPE 15
+#define AUDIT_SE_CAT 16
+#define AUDIT_SE_SENS 17
+#endif
/* This is new list defines from audit.h */
#ifndef AUDIT_FILTER_USER
@@ -236,6 +243,30 @@ struct watch_transport {
};
#endif
+
+#ifndef AUDIT_ADD_RULE
+#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 */
+/* audit_rule_data 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_data {
+ __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 */
+};
+#endif
+
+
+
struct audit_watch {
uint32_t dev_major;
uint32_t dev_minor;
@@ -386,12 +417,12 @@ extern int audit_log_user_avc_message(in
const char *tty, uid_t uid);
/* Rule-building helper functions */
-extern int audit_rule_syscall(struct audit_rule *rule, int scall);
-extern int audit_rule_syscallbyname(struct audit_rule *rule,
+extern int audit_rule_syscall(struct audit_rule_data *rule, int scall);
+extern int audit_rule_syscallbyname(struct audit_rule_data *rule,
const char *scall);
extern int audit_rule_field(struct audit_rule *rule, int field,
unsigned int value);
-extern int audit_rule_fieldpair(struct audit_rule *rule, const char *pair,
+extern int audit_rule_fieldpair(struct audit_rule_data *rule, const char *pair,
int flags);
extern void audit_rule_free(struct audit_rule *rule);
diff -urpN audit-1.1.3/src/auditctl.c audit-1.1.3.dustin/src/auditctl.c
--- audit-1.1.3/src/auditctl.c 2006-01-04 16:19:55.000000000 -0600
+++ audit-1.1.3.dustin/src/auditctl.c 2006-02-02 13:15:09.000000000 -0600
@@ -70,6 +70,7 @@ static int list_requested = 0;
static int add = AUDIT_FILTER_UNSET, del = AUDIT_FILTER_UNSET, action = 0;
static int ins = 0, rem = 0, ignore = 0;
static struct audit_rule rule;
+static struct audit_rule_data *rule_data;
static struct audit_watch watch;
extern int audit_archadded;
@@ -92,6 +93,7 @@ static int reset_vars(void)
ins = 0;
rem = 0;
+ rule_data = calloc(1, sizeof(*rule_data));
memset(&rule, 0, sizeof(rule));
memset(&watch, 0, sizeof(watch));
if ((fd = audit_open()) < 0) {
@@ -158,6 +160,8 @@ static int audit_rule_setup(const char *
*act = AUDIT_ALWAYS;
else
return 1;
+ rule_data->flags = *flags;
+ rule_data->action = *act;
return 0;
}
@@ -498,7 +502,7 @@ static int setopt(int count, char *vars[
audit_elf = elf;
}
}
- switch (audit_rule_syscallbyname(&rule, optarg))
+ switch (audit_rule_syscallbyname(rule_data, optarg))
{
case 0:
audit_syscalladded = 1;
@@ -527,8 +531,8 @@ static int setopt(int count, char *vars[
fprintf(stderr, "List must be given before field\n");
retval = -1;
break;
- }
- switch (audit_rule_fieldpair(&rule, optarg, flags))
+ }
+ switch (audit_rule_fieldpair(rule_data, optarg, flags))
{
case 0:
break;
@@ -863,14 +867,14 @@ static int handle_request(int status)
// if !task add syscall any if not specified
if ((add & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK &&
audit_syscalladded != 1)
- audit_rule_syscallbyname(&rule, "all");
- rc = audit_add_rule(fd, &rule, add, action);
+ audit_rule_syscallbyname(rule_data, "all");
+ rc = audit_add_rule_data(fd, rule_data);
}
else if (del != AUDIT_FILTER_UNSET) {
if ((del & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK &&
audit_syscalladded != 1)
- audit_rule_syscallbyname(&rule, "all");
- rc = audit_delete_rule(fd, &rule, del, action);
+ audit_rule_syscallbyname(rule_data, "all");
+ rc = audit_del_rule_data(fd, rule_data);
}
else if (ins && !rem)
rc = audit_insert_watch(fd, &watch);