[BUG] WARNING in io_ring_exit_work (io_uring.c:2187) via IORING_REGISTER_BPF_FILTER — confirmed on 7.0-rc5 and rc6

0 views
Skip to first unread message

antonius

unread,
Mar 31, 2026, 9:32:53 AM (yesterday) Mar 31
to io-u...@vger.kernel.org, ax...@kernel.dk, asml.s...@gmail.com, linux-...@vger.kernel.org, syzkall...@googlegroups.com
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
anto...@bluedragonsec.com

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 <anto...@bluedragonsec.com>
Blue Dragon Security — https://bluedragonsec.com

repro_io_ring_exit_work_loop.c

Jens Axboe

unread,
Mar 31, 2026, 9:39:04 AM (yesterday) Mar 31
to antonius, io-u...@vger.kernel.org, asml.s...@gmail.com, linux-...@vger.kernel.org, syzkall...@googlegroups.com
On 3/31/26 7:32 AM, antonius wrote:
> 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.

Interesting, that's why I added those WARN_ON's. I'll take a look
at this.

And yes, they would only fire once, because are WARN_ON_ONCE()...


--
Jens Axboe

Jens Axboe

unread,
Mar 31, 2026, 10:21:22 AM (yesterday) Mar 31
to antonius, io-u...@vger.kernel.org, asml.s...@gmail.com, linux-...@vger.kernel.org, syzkall...@googlegroups.com
diff --git a/io_uring/register.c b/io_uring/register.c
index 5f3eb018fb32..837324bf0223 100644
--- a/io_uring/register.c
+++ b/io_uring/register.c
@@ -178,9 +178,17 @@ static __cold int io_register_restrictions(struct io_ring_ctx *ctx,
return -EBUSY;

ret = io_parse_restrictions(arg, nr_args, &ctx->restrictions);
- /* Reset all restrictions if an error happened */
+ /*
+ * Reset all restrictions if an error happened, but retain any COW'ed
+ * settings.
+ */
if (ret < 0) {
+ struct io_bpf_filters *bpf = ctx->restrictions.bpf_filters;
+ bool cowed = ctx->restrictions.bpf_filters_cow;
+
memset(&ctx->restrictions, 0, sizeof(ctx->restrictions));
+ ctx->restrictions.bpf_filters = bpf;
+ ctx->restrictions.bpf_filters_cow = cowed;
return ret;
}
if (ctx->restrictions.op_registered)

--
Jens Axboe
Reply all
Reply to author
Forward
0 new messages