Kernel-mediated spawn executes file setup and exec from initial child task work, not from a second syscall entry. Representing that work as another AUDIT_SYSCALL record duplicates the source pidfd_spawn_run transaction and invents a syscall that the child never entered. Add a dedicated audit context and AUDIT_PIDFD_SPAWN record. Carry the originating syscall number and arguments. Existing syscall exit filters can then select the child transaction. CWD, PATH, and EXECVE records remain correlated with it. Let the child close the transaction with an explicit result instead of rewriting an architecture syscall-return frame. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Li Chen --- include/linux/audit.h | 31 +++++++++++ include/uapi/linux/audit.h | 1 + kernel/audit.h | 1 + kernel/auditsc.c | 105 ++++++++++++++++++++++++++++++++++++- 4 files changed, 136 insertions(+), 2 deletions(-) diff --git a/include/linux/audit.h b/include/linux/audit.h index 45abb3722d304..872624a9fba08 100644 --- a/include/linux/audit.h +++ b/include/linux/audit.h @@ -323,6 +323,9 @@ extern int audit_alloc(struct task_struct *task); extern void __audit_free(struct task_struct *task); extern void __audit_uring_entry(u8 op); extern void __audit_uring_exit(int success, long code); +void __audit_pidfd_spawn_entry(int major, unsigned long a0, unsigned long a1, + unsigned long a2, unsigned long a3); +void __audit_pidfd_spawn_exit(int success, long code); extern void __audit_syscall_entry(int major, unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3); extern void __audit_syscall_exit(int ret_success, long ret_value); @@ -373,6 +376,22 @@ static inline void audit_uring_exit(int success, long code) if (unlikely(audit_context())) __audit_uring_exit(success, code); } + +static inline void audit_pidfd_spawn_entry(int major, unsigned long a0, + unsigned long a1, + unsigned long a2, + unsigned long a3) +{ + if (unlikely(audit_context())) + __audit_pidfd_spawn_entry(major, a0, a1, a2, a3); +} + +static inline void audit_pidfd_spawn_exit(int success, long code) +{ + if (unlikely(audit_context())) + __audit_pidfd_spawn_exit(success, code); +} + static inline void audit_syscall_entry(int major, unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3) @@ -380,6 +399,7 @@ static inline void audit_syscall_entry(int major, unsigned long a0, if (unlikely(audit_context())) __audit_syscall_entry(major, a0, a1, a2, a3); } + static inline void audit_syscall_exit(void *pt_regs) { if (unlikely(audit_context())) { @@ -611,10 +631,21 @@ static inline void audit_uring_entry(u8 op) { } static inline void audit_uring_exit(int success, long code) { } + +static inline void audit_pidfd_spawn_entry(int major, unsigned long a0, + unsigned long a1, + unsigned long a2, + unsigned long a3) +{ } + +static inline void audit_pidfd_spawn_exit(int success, long code) +{ } + static inline void audit_syscall_entry(int major, unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3) { } + static inline void audit_syscall_exit(void *pt_regs) { } static inline bool audit_dummy_context(void) diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h index e8f5ce677df73..e09fb5222293a 100644 --- a/include/uapi/linux/audit.h +++ b/include/uapi/linux/audit.h @@ -122,6 +122,7 @@ #define AUDIT_OPENAT2 1337 /* Record showing openat2 how args */ #define AUDIT_DM_CTRL 1338 /* Device Mapper target control */ #define AUDIT_DM_EVENT 1339 /* Device Mapper events */ +#define AUDIT_PIDFD_SPAWN 1340 /* pidfd spawn child operation */ #define AUDIT_AVC 1400 /* SE Linux avc denial or grant */ #define AUDIT_SELINUX_ERR 1401 /* Internal SE Linux Errors */ diff --git a/kernel/audit.h b/kernel/audit.h index 92d5e723d570b..2f740df7beecf 100644 --- a/kernel/audit.h +++ b/kernel/audit.h @@ -112,6 +112,7 @@ struct audit_context { AUDIT_CTX_UNUSED, /* audit_context is currently unused */ AUDIT_CTX_SYSCALL, /* in use by syscall */ AUDIT_CTX_URING, /* in use by io_uring */ + AUDIT_CTX_PIDFD_SPAWN, /* pidfd spawn child operation */ } context; enum audit_state state, current_state; struct audit_stamp stamp; /* event identifier */ diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 6610e667c728a..8f6d84a902239 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -1649,6 +1649,30 @@ static void audit_log_uring(struct audit_context *ctx) audit_log_end(ab); } +/** + * audit_log_pidfd_spawn - generate an AUDIT_PIDFD_SPAWN record + * @ctx: the audit context + */ +static void audit_log_pidfd_spawn(struct audit_context *ctx) +{ + struct audit_buffer *ab; + + ab = audit_log_start(ctx, GFP_KERNEL, AUDIT_PIDFD_SPAWN); + if (!ab) + return; + audit_log_format(ab, "arch=%x syscall=%d", ctx->arch, ctx->major); + if (ctx->return_valid != AUDITSC_INVALID) + audit_log_format(ab, " success=%s exit=%ld", + str_yes_no(ctx->return_valid == AUDITSC_SUCCESS), + ctx->return_code); + audit_log_format(ab, " a0=%lx a1=%lx a2=%lx a3=%lx items=%d", + ctx->argv[0], ctx->argv[1], ctx->argv[2], + ctx->argv[3], ctx->name_count); + audit_log_task_info(ab); + audit_log_key(ab, ctx->filterkey); + audit_log_end(ab); +} + static void audit_log_exit(void) { int i, call_panic = 0; @@ -1687,6 +1711,9 @@ static void audit_log_exit(void) case AUDIT_CTX_URING: audit_log_uring(context); break; + case AUDIT_CTX_PIDFD_SPAWN: + audit_log_pidfd_spawn(context); + break; default: BUG(); break; @@ -1782,7 +1809,8 @@ static void audit_log_exit(void) audit_log_name(context, n, NULL, i++, &call_panic); } - if (context->context == AUDIT_CTX_SYSCALL) + if (context->context == AUDIT_CTX_SYSCALL || + context->context == AUDIT_CTX_PIDFD_SPAWN) audit_log_proctitle(); /* Send end of event record to help user space know we are finished */ @@ -1818,7 +1846,8 @@ void __audit_free(struct task_struct *tsk) if (tsk == current && !context->dummy) { context->return_valid = AUDITSC_INVALID; context->return_code = 0; - if (context->context == AUDIT_CTX_SYSCALL) { + if (context->context == AUDIT_CTX_SYSCALL || + context->context == AUDIT_CTX_PIDFD_SPAWN) { audit_filter_syscall(tsk, context); audit_filter_inodes(tsk, context); if (context->current_state == AUDIT_STATE_RECORD) @@ -1967,6 +1996,78 @@ void __audit_uring_exit(int success, long code) audit_reset_context(ctx); } +/** + * __audit_pidfd_spawn_entry - start a pidfd spawn child audit context + * @major: originating pidfd_spawn_run syscall number + * @a0: originating syscall argument 0 + * @a1: originating syscall argument 1 + * @a2: originating syscall argument 2 + * @a3: originating syscall argument 3 + */ +void __audit_pidfd_spawn_entry(int major, unsigned long a0, unsigned long a1, + unsigned long a2, unsigned long a3) +{ + struct audit_context *context = audit_context(); + enum audit_state state; + + if (!audit_enabled || !context) + return; + + WARN_ON(context->context != AUDIT_CTX_UNUSED); + WARN_ON(context->name_count); + if (context->context != AUDIT_CTX_UNUSED || context->name_count) { + audit_panic("unrecoverable error in audit_pidfd_spawn_entry()"); + return; + } + + state = context->state; + if (state == AUDIT_STATE_DISABLED) + return; + + context->dummy = !audit_n_rules; + if (!context->dummy && state == AUDIT_STATE_BUILD) { + context->prio = 0; + if (auditd_test_task(current)) + return; + } + + context->arch = syscall_get_arch(current); + context->major = major; + context->argv[0] = a0; + context->argv[1] = a1; + context->argv[2] = a2; + context->argv[3] = a3; + context->context = AUDIT_CTX_PIDFD_SPAWN; + context->current_state = state; + ktime_get_coarse_real_ts64(&context->stamp.ctime); +} + +/** + * __audit_pidfd_spawn_exit - finish a pidfd spawn child audit context + * @success: whether child-side setup succeeded + * @return_code: child-side setup result + */ +void __audit_pidfd_spawn_exit(int success, long return_code) +{ + struct audit_context *context = audit_context(); + + if (!context || context->dummy || + context->context != AUDIT_CTX_PIDFD_SPAWN) + goto out; + + if (!list_empty(&context->killed_trees)) + audit_kill_trees(context); + + audit_return_fixup(context, success, return_code); + audit_filter_syscall(current, context); + audit_filter_inodes(current, context); + if (context->current_state == AUDIT_STATE_RECORD) + audit_log_exit(); + +out: + audit_reset_context(context); +} + /** * __audit_syscall_entry - fill in an audit record at syscall entry * @major: major syscall type (function) -- 2.52.0