[BUG] WARNING in io_ring_exit_work (io_uring.c:2187) via IORING_REGISTER_BPF_FILTER — confirmed on 7.0-rc5 and rc6
From: antonius
Date: Tue Mar 31 2026 - 09:33:19 EST
Hello,
I am reporting a kernel WARNING discovered via Syzkaller fuzzing of Linux
7.0-rc5, targeting the new IORING_REGISTER_BPF_FILTER subsystem (new in 7.0).
The bug is confirmed on both 7.0-rc5 and 7.0-rc6. It is NOT fixed in rc6.
In rc6, the WARNING appears to have changed from WARN_ON to WARN_ON_ONCE
(fires only once per boot), which may explain why it was initially missed.
REPORTER
--------
Antonius / Blue Dragon Security
https://bluedragonsec.com
antonius@xxxxxxxxxxxxxxxxx
AFFECTED VERSIONS
-----------------
Confirmed: Linux 7.0.0-rc5 (QEMU, KASAN+KFENCE build, Syzkaller)
Confirmed: Linux 7.0.0-rc6 (QEMU, PREEMPT(lazy), PROVE_LOCKING build)
Not affected: kernels prior to 7.0 (IORING_REGISTER_BPF_FILTER is new in 7.0)
Status: NOT fixed in rc6
NOTE ON rc6 BEHAVIOR: The WARNING fires only once per boot in rc6
(WARN_ON_ONCE semantics), confirmed by:
- trace hash "0000000000000000" in the dump
- Silent on subsequent runs within same boot session
- Fires again after reboot
Reset via: echo 0 > /sys/kernel/debug/clear_warn_once (then retest)
CRASH OUTPUT — rc6 (7.0.0-rc6, PREEMPT(lazy))
----------------------------------------------
[ 1021.589216] ------------[ cut here ]------------
[ 1021.589240] WARNING: io_uring/io_uring.c:2187
at io_ring_exit_work+0xbea/0xd4b, CPU#0: kworker/u4:1/14
[ 1021.589298] CPU: 0 UID: 0 PID: 14 Comm: kworker/u4:1
Not tainted 7.0.0-rc6 #1 PREEMPT(lazy)
[ 1021.589326] Workqueue: iou_exit io_ring_exit_work
[ 1021.589346] RIP: 0010:io_ring_exit_work+0xbea/0xd4b
[ 1021.589393] RAX: 0000000000000000 RBX: ffff88810e659778
[ 1021.589432] R13: 0000000000000000 R14: ffff888115c64000
R15: dffffc0000000000
[ 1021.589474] Call Trace:
[ 1021.589487] ? check_prev_add+0x333/0xd30 ← lockdep active
[ 1021.589533] ? __pfx_io_tctx_exit_cb+0x10/0x10
[ 1021.589577] process_one_work+0xa16/0x1900
[ 1021.590019] worker_thread+0x5eb/0xe50
[ 1021.590084] kthread+0x366/0x450
[ 1021.590122] ret_from_fork+0x660/0xa80
[ 1021.590200] </TASK>
[ 1021.590280] ---[ end trace 0000000000000000 ]---
CRASH OUTPUT — rc5 (7.0.0-rc5, PREEMPT(lazy), KASAN)
-----------------------------------------------------
WARNING: io_uring/io_uring.c:2187
at io_ring_exit_work+0xf84/0x1290
Workqueue: iou_exit io_ring_exit_work
R14: ffff88800c2e7000
COMPARISON rc5 vs rc6:
- Bug location: IDENTICAL (io_uring.c:2187, same workqueue)
- WARN type: rc5=WARN_ON (fires every time), rc6=WARN_ON_ONCE (once/boot)
- Function size: rc5=0x1290, rc6=0xd4b (refactoring occurred)
- R14 non-null in both (ctx->bpf_filters still set during teardown)
REPRODUCER (3 syscalls, minimized by Syzkaller)
-----------------------------------------------
Requires: root / CAP_SYS_ADMIN
# Step 1: Register BPF filter at task level (fd=-1)
io_uring_register(-1, IORING_REGISTER_BPF_FILTER=0x25, &filter, 1)
# Step 2: Create ring with DEFER_TASKRUN + R_DISABLED
r0 = io_uring_setup(0x1bcf, {flags=IORING_SETUP_R_DISABLED|
IORING_SETUP_SUBMIT_ALL|IORING_SETUP_SINGLE_ISSUER|
IORING_SETUP_DEFER_TASKRUN, ...})
# Step 3: Register restrictions (NULL arg)
io_uring_register(r0, IORING_REGISTER_RESTRICTIONS=0xb, NULL, 2)
# closing r0 triggers io_ring_exit_work → WARNING at line 2187
BPF filter used: cmd_type=1, opcode=0x3d (BPF_JMP|BPF_JSET), flags=3,
2 instructions: [{code=0x02,jt=0x26,jf=0x02,k=0}, {code=0x06,jt=0x5c,jf=0x06,k=5}]
Syzlang repro:
io_uring_register$IORING_REGISTER_BPF_FILTER(0xffffffffffffffff, 0x25,
&(0x7f0000002280)={0x1, 0x0, 0x0, {0x3d, 0x3, 0x2, 0x0, '\x00',
&(0x7f0000002300)=[{0x2, 0x26, 0x2}, {0x6, 0x5c, 0x6, 0x5}]}}, 0x1)
r0 = io_uring_setup(0x1bcf, &(0x7f0000000000)={0x8, 0x1, 0x30c0, 0x0,
0x8000, 0x7, 0xffffffffffffffff, '\x00', ...})
io_uring_register(r0, 0xb, 0x0, 0x2)
The WARNING at io_uring.c:2187 fires inside io_ring_ctx_free() during ring
teardown via the iou_exit workqueue. Register R14 is non-null (pointing to
a live kernel object) at the WARN site in both rc5 and rc6, indicating that
ctx->bpf_filters is unexpectedly non-NULL when io_ring_ctx_free() asserts
it should be NULL.
Root cause hypothesis: When IORING_REGISTER_BPF_FILTER is called with
fd=-1 (task-level filter registration, new in Linux 7.0), and a ring is
subsequently created with IORING_SETUP_DEFER_TASKRUN, the ring inherits
the task-level BPF filter via io_ctx_restriction_clone()/io_bpf_filter_clone().
During ring teardown in io_ring_ctx_free(), ctx->bpf_filters is not
properly nulled/freed, triggering the assertion.
The exact WARN_ON is in io_ring_ctx_free() at line 2187 — likely
WARN_ON(ctx->bpf_filters) or similar check on the bpf_filters pointer.
The specific cleanup path in io_bpf_filter_clone() / io_bpf_filters_free()
interaction needs review.
CLASSIFICATION
--------------
CWE: CWE-459 (Incomplete Cleanup) — ctx->bpf_filters not cleaned up
properly on ring teardown when filter was inherited from task context
Impact: Memory leak (bpf_filters object leaked per ring close),
assertion violation in io_ring_ctx_free()
Requires: root/CAP_SYS_ADMIN
No memory corruption (KASAN clean, no double-free detected)
DISCOVERY
---------
Found via Syzkaller fuzzing campaign targeting Linux 7.0-rc5 io_uring
BPF filter subsystem (Blue Dragon Security, March 2026).
No matching syzbot entry found for this specific call path
(IORING_REGISTER_BPF_FILTER with fd=-1 + DEFER_TASKRUN).
Reported-by: Antonius <antonius@xxxxxxxxxxxxxxxxx>
Blue Dragon Security — https://bluedragonsec.com
/*I am reporting a kernel WARNING discovered via Syzkaller fuzzing of Linux
7.0-rc5, targeting the new IORING_REGISTER_BPF_FILTER subsystem (new in 7.0).
The bug is confirmed on both 7.0-rc5 and 7.0-rc6. It is NOT fixed in rc6.
In rc6, the WARNING appears to have changed from WARN_ON to WARN_ON_ONCE
(fires only once per boot), which may explain why it was initially missed.
REPORTER
--------
Antonius / Blue Dragon Security
https://bluedragonsec.com
antonius@xxxxxxxxxxxxxxxxx
AFFECTED VERSIONS
-----------------
Confirmed: Linux 7.0.0-rc5 (QEMU, KASAN+KFENCE build, Syzkaller)
Confirmed: Linux 7.0.0-rc6 (QEMU, PREEMPT(lazy), PROVE_LOCKING build)
Not affected: kernels prior to 7.0 (IORING_REGISTER_BPF_FILTER is new in 7.0)
Status: NOT fixed in rc6
NOTE ON rc6 BEHAVIOR: The WARNING fires only once per boot in rc6
(WARN_ON_ONCE semantics), confirmed by:
- trace hash "0000000000000000" in the dump
- Silent on subsequent runs within same boot session
- Fires again after reboot
Reset via: echo 0 > /sys/kernel/debug/clear_warn_once (then retest)
CRASH OUTPUT — rc6 (7.0.0-rc6, PREEMPT(lazy))
----------------------------------------------
[ 1021.589216] ------------[ cut here ]------------
[ 1021.589240] WARNING: io_uring/io_uring.c:2187
at io_ring_exit_work+0xbea/0xd4b, CPU#0: kworker/u4:1/14
[ 1021.589298] CPU: 0 UID: 0 PID: 14 Comm: kworker/u4:1
Not tainted 7.0.0-rc6 #1 PREEMPT(lazy)
[ 1021.589326] Workqueue: iou_exit io_ring_exit_work
[ 1021.589346] RIP: 0010:io_ring_exit_work+0xbea/0xd4b
[ 1021.589393] RAX: 0000000000000000 RBX: ffff88810e659778
[ 1021.589432] R13: 0000000000000000 R14: ffff888115c64000
R15: dffffc0000000000
[ 1021.589474] Call Trace:
[ 1021.589487] ? check_prev_add+0x333/0xd30 ← lockdep active
[ 1021.589533] ? __pfx_io_tctx_exit_cb+0x10/0x10
[ 1021.589577] process_one_work+0xa16/0x1900
[ 1021.590019] worker_thread+0x5eb/0xe50
[ 1021.590084] kthread+0x366/0x450
[ 1021.590122] ret_from_fork+0x660/0xa80
[ 1021.590200] </TASK>
[ 1021.590280] ---[ end trace 0000000000000000 ]---
CRASH OUTPUT — rc5 (7.0.0-rc5, PREEMPT(lazy), KASAN)
-----------------------------------------------------
WARNING: io_uring/io_uring.c:2187
at io_ring_exit_work+0xf84/0x1290
Workqueue: iou_exit io_ring_exit_work
R14: ffff88800c2e7000
COMPARISON rc5 vs rc6:
- Bug location: IDENTICAL (io_uring.c:2187, same workqueue)
- WARN type: rc5=WARN_ON (fires every time), rc6=WARN_ON_ONCE (once/boot)
- Function size: rc5=0x1290, rc6=0xd4b (refactoring occurred)
- R14 non-null in both (ctx->bpf_filters still set during teardown)
REPRODUCER (3 syscalls, minimized by Syzkaller)
-----------------------------------------------
Requires: root / CAP_SYS_ADMIN
# Step 1: Register BPF filter at task level (fd=-1)
io_uring_register(-1, IORING_REGISTER_BPF_FILTER=0x25, &filter, 1)
# Step 2: Create ring with DEFER_TASKRUN + R_DISABLED
r0 = io_uring_setup(0x1bcf, {flags=IORING_SETUP_R_DISABLED|
IORING_SETUP_SUBMIT_ALL|IORING_SETUP_SINGLE_ISSUER|
IORING_SETUP_DEFER_TASKRUN, ...})
# Step 3: Register restrictions (NULL arg)
io_uring_register(r0, IORING_REGISTER_RESTRICTIONS=0xb, NULL, 2)
# closing r0 triggers io_ring_exit_work → WARNING at line 2187
BPF filter used: cmd_type=1, opcode=0x3d (BPF_JMP|BPF_JSET), flags=3,
2 instructions: [{code=0x02,jt=0x26,jf=0x02,k=0}, {code=0x06,jt=0x5c,jf=0x06,k=5}]
Syzlang repro:
io_uring_register$IORING_REGISTER_BPF_FILTER(0xffffffffffffffff, 0x25,
&(0x7f0000002280)={0x1, 0x0, 0x0, {0x3d, 0x3, 0x2, 0x0, '\x00',
&(0x7f0000002300)=[{0x2, 0x26, 0x2}, {0x6, 0x5c, 0x6, 0x5}]}}, 0x1)
r0 = io_uring_setup(0x1bcf, &(0x7f0000000000)={0x8, 0x1, 0x30c0, 0x0,
0x8000, 0x7, 0xffffffffffffffff, '\x00', ...})
io_uring_register(r0, 0xb, 0x0, 0x2)
C reproducer attached.
REPRODUCE
sudo ./repro_io_ring_exit_work_loop
ANALYSIS
--------The WARNING at io_uring.c:2187 fires inside io_ring_ctx_free() during ring
teardown via the iou_exit workqueue. Register R14 is non-null (pointing to
a live kernel object) at the WARN site in both rc5 and rc6, indicating that
ctx->bpf_filters is unexpectedly non-NULL when io_ring_ctx_free() asserts
it should be NULL.
Root cause hypothesis: When IORING_REGISTER_BPF_FILTER is called with
fd=-1 (task-level filter registration, new in Linux 7.0), and a ring is
subsequently created with IORING_SETUP_DEFER_TASKRUN, the ring inherits
the task-level BPF filter via io_ctx_restriction_clone()/io_bpf_filter_clone().
During ring teardown in io_ring_ctx_free(), ctx->bpf_filters is not
properly nulled/freed, triggering the assertion.
The exact WARN_ON is in io_ring_ctx_free() at line 2187 — likely
WARN_ON(ctx->bpf_filters) or similar check on the bpf_filters pointer.
The specific cleanup path in io_bpf_filter_clone() / io_bpf_filters_free()
interaction needs review.
CLASSIFICATION
--------------
CWE: CWE-459 (Incomplete Cleanup) — ctx->bpf_filters not cleaned up
properly on ring teardown when filter was inherited from task context
Impact: Memory leak (bpf_filters object leaked per ring close),
assertion violation in io_ring_ctx_free()
Requires: root/CAP_SYS_ADMIN
No memory corruption (KASAN clean, no double-free detected)
DISCOVERY
---------
Found via Syzkaller fuzzing campaign targeting Linux 7.0-rc5 io_uring
BPF filter subsystem (Blue Dragon Security, March 2026).
No matching syzbot entry found for this specific call path
(IORING_REGISTER_BPF_FILTER with fd=-1 + DEFER_TASKRUN).
Reported-by: Antonius <antonius@xxxxxxxxxxxxxxxxx>
Blue Dragon Security — https://bluedragonsec.com
* Reproducer v2 (loop mode): WARNING in io_ring_exit_work
*
* Untuk memudahkan triggering bug di lab:
* Jalankan loop untuk meningkatkan probabilitas trigger.
*
* Discovered by: Antonius <antonius@xxxxxxxxxxxxxxxxx>
* Blue Dragon Security - bluedragonsec.com
*
* Compile: gcc -o repro2 repro_io_ring_exit_work_loop.c
* Run: ./repro2
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <errno.h>
#ifndef __NR_io_uring_setup
#define __NR_io_uring_setup 425
#endif
#ifndef __NR_io_uring_register
#define __NR_io_uring_register 427
#endif
#define IORING_SETUP_R_DISABLED (1U << 6)
#define IORING_SETUP_SUBMIT_ALL (1U << 7)
#define IORING_SETUP_SINGLE_ISSUER (1U << 12)
#define IORING_SETUP_DEFER_TASKRUN (1U << 13)
#define IORING_REGISTER_RESTRICTIONS 11
#define IORING_REGISTER_BPF_FILTER 37
struct io_uring_params {
uint32_t sq_entries;
uint32_t cq_entries;
uint32_t flags;
uint32_t sq_thread_cpu;
uint32_t sq_thread_idle;
uint32_t features;
uint32_t wq_fd;
uint8_t pad[84];
};
struct sock_filter_insn { uint16_t code; uint8_t jt, jf; uint32_t k; };
struct io_uring_bpf_reg {
uint16_t cmd_type;
uint16_t cmd_flags;
uint32_t resv;
uint32_t opcode;
uint32_t flags;
uint32_t filter_len;
uint8_t pdu_size;
uint8_t resv2[3];
uint64_t filter_ptr;
uint8_t resv3[40];
};
int main(void)
{
struct sock_filter_insn insns[2] = {
{0x02, 0x26, 0x02, 0x00000000},
{0x06, 0x5c, 0x06, 0x00000005},
};
struct io_uring_bpf_reg bpf_reg = {
.cmd_type = 1,
.cmd_flags = 0,
.resv = 0,
.opcode = 0x3d,
.flags = 0x3,
.filter_len = 2,
.pdu_size = 0,
.filter_ptr = (uint64_t)(uintptr_t)insns,
};
memset(bpf_reg.resv3, 0, sizeof(bpf_reg.resv3));
printf("[*] io_ring_exit_work reproducer (loop mode)\n");
printf("[*] Iterating to trigger async WARNING in workqueue...\n\n");
for (int i = 0; i < 10; i++) {
/* Step 1: BPF_FILTER with fd=-1 */
syscall(__NR_io_uring_register, (long)-1, IORING_REGISTER_BPF_FILTER,
&bpf_reg, 1);
/* Step 2: io_uring_setup with DEFER_TASKRUN | R_DISABLED */
struct io_uring_params p = {
.sq_entries = 8,
.cq_entries = 1,
.flags = IORING_SETUP_R_DISABLED | IORING_SETUP_SUBMIT_ALL |
IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN,
.sq_thread_idle = 0x8000,
.features = 7,
.wq_fd = (uint32_t)-1,
};
int fd = (int)syscall(__NR_io_uring_setup, 0x1bcf, &p);
printf("[iter %2d] io_uring_setup fd=%d\n", i, fd);
if (fd >= 0) {
/* Step 3: RESTRICTIONS with NULL - triggers inconsistency */
syscall(__NR_io_uring_register, fd, IORING_REGISTER_RESTRICTIONS, 0, 2);
/* Close triggers io_ring_exit_work workqueue */
close(fd);
}
usleep(100000); /* 100ms - let workqueue process */
}
printf("\n[*] Check dmesg for: WARNING at io_uring/io_uring.c:2187\n");
printf("[*] Workqueue: iou_exit io_ring_exit_work\n");
return 0;
}