All of lore.kernel.org
 help / color / mirror / Atom feed
* [BUG] WARNING in io_ring_exit_work (io_uring.c:2187) via IORING_REGISTER_BPF_FILTER — confirmed on 7.0-rc5 and rc6
@ 2026-03-31 13:32 antonius
  2026-03-31 13:39 ` Jens Axboe
  0 siblings, 1 reply; 3+ messages in thread
From: antonius @ 2026-03-31 13:32 UTC (permalink / raw)
  To: io-uring; +Cc: axboe, asml.silence, linux-kernel, syzkaller-bugs


[-- Attachment #1.1: Type: text/plain, Size: 5846 bytes --]

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

[-- Attachment #1.2: Type: text/html, Size: 6580 bytes --]

[-- Attachment #2: repro_io_ring_exit_work_loop.c --]
[-- Type: text/x-csrc, Size: 3393 bytes --]

/*
 * 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@bluedragonsec.com>
 * 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;
}

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [BUG] WARNING in io_ring_exit_work (io_uring.c:2187) via IORING_REGISTER_BPF_FILTER — confirmed on 7.0-rc5 and rc6
  2026-03-31 13:32 [BUG] WARNING in io_ring_exit_work (io_uring.c:2187) via IORING_REGISTER_BPF_FILTER — confirmed on 7.0-rc5 and rc6 antonius
@ 2026-03-31 13:39 ` Jens Axboe
  2026-03-31 14:21   ` Jens Axboe
  0 siblings, 1 reply; 3+ messages in thread
From: Jens Axboe @ 2026-03-31 13:39 UTC (permalink / raw)
  To: antonius, io-uring; +Cc: asml.silence, linux-kernel, syzkaller-bugs

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


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [BUG] WARNING in io_ring_exit_work (io_uring.c:2187) via IORING_REGISTER_BPF_FILTER — confirmed on 7.0-rc5 and rc6
  2026-03-31 13:39 ` Jens Axboe
@ 2026-03-31 14:21   ` Jens Axboe
  0 siblings, 0 replies; 3+ messages in thread
From: Jens Axboe @ 2026-03-31 14:21 UTC (permalink / raw)
  To: antonius, io-uring; +Cc: asml.silence, linux-kernel, syzkaller-bugs

On 3/31/26 7:39 AM, Jens Axboe wrote:
> 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()...

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

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-03-31 14:21 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-31 13:32 [BUG] WARNING in io_ring_exit_work (io_uring.c:2187) via IORING_REGISTER_BPF_FILTER — confirmed on 7.0-rc5 and rc6 antonius
2026-03-31 13:39 ` Jens Axboe
2026-03-31 14:21   ` Jens Axboe

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.