[RFC PATCH 4/9] audit: add filtering for io_uring records

Paul Moore
2021-05-21T21:50:10Z

WARNING - This is a work in progress and should not be merged
anywhere important. It is almost surely not complete, and while it
probably compiles it likely hasn't been booted and will do terrible
things. You have been warned.

This patch adds basic audit io_uring filtering, using as much of the
existing audit filtering infrastructure as possible. In order to do
this we reuse the audit filter rule's syscall mask for the io_uring
operation and we create a new filter for io_uring operations as
AUDIT_FILTER_URING_EXIT/audit_filter_list[7].

<TODO - provide some additional guidance for the userspace tools>

Signed-off-by: Paul Moore <paul@paul-moore.com>
---
include/uapi/linux/audit.h | 3 +-
kernel/auditfilter.c | 4 ++-
kernel/auditsc.c | 65 ++++++++++++++++++++++++++++++++++----------
3 files changed, 55 insertions(+), 17 deletions(-)

diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index b26e0c435e8b..621eb3c1076e 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -167,8 +167,9 @@
#define AUDIT_FILTER_EXCLUDE 0x05 /* Apply rule before record creation */
#define AUDIT_FILTER_TYPE AUDIT_FILTER_EXCLUDE /* obsolete misleading naming */
#define AUDIT_FILTER_FS 0x06 /* Apply rule at __audit_inode_child */
+#define AUDIT_FILTER_URING_EXIT 0x07 /* Apply rule at io_uring op exit */

-#define AUDIT_NR_FILTERS 7
+#define AUDIT_NR_FILTERS 8

#define AUDIT_FILTER_PREPEND 0x10 /* Prepend to front of list */

diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index db2c6b59dfc3..c21119c00504 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -44,7 +44,8 @@ struct list_head audit_filter_list[AUDIT_NR_FILTERS] = {
LIST_HEAD_INIT(audit_filter_list[4]),
LIST_HEAD_INIT(audit_filter_list[5]),
LIST_HEAD_INIT(audit_filter_list[6]),
-#if AUDIT_NR_FILTERS != 7
+ LIST_HEAD_INIT(audit_filter_list[7]),
+#if AUDIT_NR_FILTERS != 8
#error Fix audit_filter_list initialiser
#endif
};
@@ -56,6 +57,7 @@ static struct list_head audit_rules_list[AUDIT_NR_FILTERS] = {
LIST_HEAD_INIT(audit_rules_list[4]),
LIST_HEAD_INIT(audit_rules_list[5]),
LIST_HEAD_INIT(audit_rules_list[6]),
+ LIST_HEAD_INIT(audit_rules_list[7]),
};

DEFINE_MUTEX(audit_filter_mutex);
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index d8aa2c690bf9..4f6ab34020fb 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -799,6 +799,35 @@ static int audit_in_mask(const struct audit_krule *rule, unsigned long val)
return rule->mask[word] & bit;
}

+/**
+ * audit_filter_uring - apply filters to an io_uring operation
+ * @tsk: associated task
+ * @ctx: audit context
+ */
+static void audit_filter_uring(struct task_struct *tsk,
+ struct audit_context *ctx)
+{
+ struct audit_entry *e;
+ enum audit_state state;
+
+ if (auditd_test_task(tsk))
+ return;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_URING_EXIT],
+ list) {
+ if (audit_in_mask(&e->rule, ctx->uring_op) &&
+ audit_filter_rules(tsk, &e->rule, ctx, NULL, &state,
+ false)) {
+ rcu_read_unlock();
+ ctx->current_state = state;
+ return;
+ }
+ }
+ rcu_read_unlock();
+ return;
+}
+
/* At syscall 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
@@ -1754,7 +1783,7 @@ static void audit_log_exit(void)
* __audit_free - free a per-task audit context
* @tsk: task whose audit context block to free
*
- * Called from copy_process and do_exit
+ * Called from copy_process, do_exit, and the io_uring code
*/
void __audit_free(struct task_struct *tsk)
{
@@ -1772,15 +1801,21 @@ void __audit_free(struct task_struct *tsk)
* random task_struct that doesn't doesn't have any meaningful data we
* need to log via audit_log_exit().
*/
- if (tsk == current && !context->dummy &&
- context->context == AUDIT_CTX_SYSCALL) {
+ if (tsk == current && !context->dummy) {
context->return_valid = AUDITSC_INVALID;
context->return_code = 0;
-
- audit_filter_syscall(tsk, context);
- audit_filter_inodes(tsk, context);
- if (context->current_state == AUDIT_RECORD_CONTEXT)
- audit_log_exit();
+ if (context->context == AUDIT_CTX_SYSCALL) {
+ audit_filter_syscall(tsk, context);
+ audit_filter_inodes(tsk, context);
+ if (context->current_state == AUDIT_RECORD_CONTEXT)
+ audit_log_exit();
+ } else if (context->context == AUDIT_CTX_URING) {
+ /* TODO: verify this case is real and valid */
+ audit_filter_uring(tsk, context);
+ audit_filter_inodes(tsk, context);
+ if (context->current_state == AUDIT_RECORD_CONTEXT)
+ audit_log_uring(context);
+ }
}

audit_set_context(tsk, NULL);
@@ -1861,12 +1896,6 @@ void __audit_uring_exit(int success, long code)
{
struct audit_context *ctx = audit_context();

- /*
- * TODO: At some point we will likely want to filter on io_uring ops
- * and other things similar to what we do for syscalls, but that
- * is something for another day; just record what we can here.
- */
-
if (!ctx || ctx->dummy)
goto out;
if (ctx->context == AUDIT_CTX_SYSCALL) {
@@ -1891,6 +1920,8 @@ void __audit_uring_exit(int success, long code)
* the behavior here.
*/
audit_filter_syscall(current, ctx);
+ if (ctx->current_state != AUDIT_RECORD_CONTEXT)
+ audit_filter_uring(current, ctx);
audit_filter_inodes(current, ctx);
if (ctx->current_state != AUDIT_RECORD_CONTEXT)
return;
@@ -1899,7 +1930,9 @@ void __audit_uring_exit(int success, long code)
return;
}
#if 1
- /* XXX - temporary hack to force record generation */
+ /* XXX - temporary hack to force record generation, we are leaving this
+ * enabled, but if you want to actually test the filtering you
+ * need to disable this #if/#endif block */
ctx->current_state = AUDIT_RECORD_CONTEXT;
#endif

@@ -1907,6 +1940,8 @@ void __audit_uring_exit(int success, long code)
if (!list_empty(&ctx->killed_trees))
audit_kill_trees(ctx);

+ /* run through both filters to ensure we set the filterkey properly */
+ audit_filter_uring(current, ctx);
audit_filter_inodes(current, ctx);
if (ctx->current_state != AUDIT_RECORD_CONTEXT)
goto out;