WARNING in ring_buffer_nr_dirty_pages (2)

Status: moderation: reported on 2026/11/07 06:27
Subsystems: trace
Labels: prio:low
[Documentation on labels]
Reported-by: syzbot+5d4a961c994f780e369d@syzkaller.appspotmail.com
First crash: 1h28m, last: 1h28m
✨ AI-generated root cause summary:

ring_buffer_subbuf_order_set() updates the sub-buffer order by allocating new pages, freeing all old pages, and pointing head_page, tail_page, commit_page, and reader_page to the newly allocated pages. However, it fails to reset the per-CPU buffer accounting counters (pages_read, pages_touched, pages_lost, etc.) or invoke rb_reset_cpu(). When pages had previously been read from the buffer, pages_read remains positive; subsequent event reads on the newly allocated buffer further increment pages_read while pages_touched remains 0 (since writes have not yet filled and advanced the tail page). This violates the ring buffer invariant that pages_read cannot exceed (pages_touched - pages_lost) + 1, triggering the WARN_ON_ONCE(read > cnt + 1) warning in ring_buffer_nr_dirty_pages().

▶ ▼ ✨ Full AI root cause analysis
Based on kernel commit d48723711bae:

Root Cause Analysis

Involved Functions, Structs, and Files

  • File: kernel/trace/ring_buffer.c
  • Functions: ring_buffer_subbuf_order_set(), ring_buffer_nr_dirty_pages(), __rb_get_reader_page()
  • Structs & Fields: struct ring_buffer_per_cpu (pages_read, pages_touched, pages_lost, head_page, tail_page, commit_page, reader_page)

Mechanism of the Bug

  1. Ring Buffer Page Counter Invariants: In kernel/trace/ring_buffer.c, struct ring_buffer_per_cpu maintains several counters:

    • pages_touched: Incremented in rb_tail_page_update() whenever a writer completely fills a sub-buffer and moves tail_page forward to next_page.
    • pages_lost: Incremented when events or sub-buffers are discarded (e.g., when overwriting old pages in rb_handle_head_page()).
    • pages_read: Incremented in __rb_get_reader_page() whenever a reader swaps cpu_buffer->reader_page with head_page.

    When a freshly initialized ring buffer receives events, the writer writes to the initial page without advancing tail_page, leaving pages_touched == 0. When a reader then reads that sub-buffer, __rb_get_reader_page() swaps the page into reader_page and increments pages_read to 1. In ring_buffer_nr_dirty_pages(), cnt = pages_touched - pages_lost is 0, and read = pages_read is 1. Because read <= cnt + 1 holds, the check:

    /* The reader can read an empty page, but not more than that */
    if (cnt < read) {
        WARN_ON_ONCE(read > cnt + 1);
        return 0;
    }
    

    does not trigger a warning. Under normal lifecycle conditions, a reader cannot swap another page until the writer writes enough events to advance tail_page away from reader_page, which increments pages_touched, preserving the invariant pages_read <= (pages_touched - pages_lost) + 1.

  2. Incomplete Reinitialization in ring_buffer_subbuf_order_set(): When userspace reconfigures the sub-buffer size via buffer_subbuf_size_kb (calling ring_buffer_subbuf_order_set()), all existing sub-buffers and data are discarded because their sub-buffer size/order is incompatible with the new configuration.

    In ring_buffer_subbuf_order_set():

    list_add(&old_pages, cpu_buffer->pages);
    list_add(&cpu_buffer->reader_page->list, &old_pages);
    
    cpu_buffer->reader_page = list_entry(cpu_buffer->new_pages.next,
                                         struct buffer_page, list);
    list_del_init(&cpu_buffer->reader_page->list);
    
    cpu_buffer->pages = cpu_buffer->new_pages.next;
    list_del_init(&cpu_buffer->new_pages);
    cpu_buffer->cnt++;
    
    cpu_buffer->head_page
        = list_entry(cpu_buffer->pages, struct buffer_page, list);
    cpu_buffer->tail_page = cpu_buffer->commit_page = cpu_buffer->head_page;
    ...
    /* Free old sub buffers */
    list_for_each_entry_safe(bpage, tmp, &old_pages, list) {
        list_del_init(&bpage->list);
        free_buffer_page(bpage);
    }
    

    All old pages are freed, and head_page, tail_page, commit_page, and reader_page are re-pointed to freshly allocated, empty pages. However, ring_buffer_subbuf_order_set() never resets cpu_buffer->pages_read, pages_touched, pages_lost, entries, or read, nor does it invoke rb_reset_cpu().

  3. Triggering the Warning:

    • Prior to resizing, if the reader had swapped a page, pages_read was 1 (or higher) and pages_touched was 0.
    • After ring_buffer_subbuf_order_set(), pages_read retains its stale value (1).
    • A writer writes an event into the newly allocated ring buffer (fitting within the first sub-buffer, so tail_page is not advanced and pages_touched remains 0).
    • A reader reads the new event via rb_get_reader_page() -> __rb_get_reader_page(). Because reader_page != commit_page, __rb_get_reader_page() swaps the new head_page and increments pages_read again, making pages_read == 2.
    • When a polling operation (such as epoll, poll, or io_uring poll via ring_buffer_poll_wait()) checks watermarks via rb_watermark_hit() -> full_hit() -> ring_buffer_nr_dirty_pages(), it observes read == 2, lost == 0, and cnt == 0.
    • The check read > cnt + 1 evaluates to 2 > 1 (true), tripping the WARN_ON_ONCE(read > cnt + 1) warning at kernel/trace/ring_buffer.c:866.
▶ ▼ ✨ AI Jobs (1)
ID Workflow Result Correct Bug Created Started Finished Revision
17235463 assessment-security DenialOfService: ❌ Exploitable: ❌ FilesystemTrigger: ❌ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ❌ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ WARNING in ring_buffer_nr_dirty_pages (2) 2026/11/07 05:33 2026/11/07 05:34 2026/11/07 06:19 5cecaf1f
▶ ▼ Similar bugs (1)
Kernel Title Rank 🛈 Repro Cause bisect Fix bisect Count Last Reported Patched Status
upstream WARNING in ring_buffer_nr_dirty_pages prio:low trace -1 5 148d 243d 0/29 auto-obsoleted due to no activity on 2026/09/19 08:54

Sample crash report:
------------[ cut here ]------------
read > cnt + 1
WARNING: kernel/trace/ring_buffer.c:866 at ring_buffer_nr_dirty_pages+0x1c6/0x1d0 kernel/trace/ring_buffer.c:866, CPU#1: syz.9.1552/10668
Modules linked in:
CPU: 1 UID: 0 PID: 10668 Comm: syz.9.1552 Tainted: G             L      syzkaller #0 PREEMPT_{RT,(full)} 
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/15/2026
RIP: 0010:ring_buffer_nr_dirty_pages+0x1c6/0x1d0 kernel/trace/ring_buffer.c:866
Code: 02 fe ff 4c 89 f8 5b 41 5c 41 5d 41 5e 41 5f 5d c3 cc cc cc cc cc e8 19 02 fe ff 90 0f 0b 90 45 31 ff eb df e8 0b 02 fe ff 90 <0f> 0b 90 45 31 ff eb d1 66 90 90 90 90 90 90 90 90 90 90 90 90 90
RSP: 0018:ffffc90005caf880 EFLAGS: 00010087
RAX: ffffffff81c9d795 RBX: 0000000000000002 RCX: 0000000000080000
RDX: ffffc9001e6fa000 RSI: 00000000000000ef RDI: 00000000000000f0
RBP: 1ffff11027fc7a14 R08: 0000000000000000 R09: 0000000000000000
R10: dffffc0000000000 R11: ffffed1027fc8423 R12: 0000000000000000
R13: dffffc0000000000 R14: 0000000000000001 R15: fffffffffffffffe
FS:  00007fbb96ae66c0(0000) GS:ffff888124811000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f5b66f85ff8 CR3: 0000000036bc0000 CR4: 00000000003526f0
Call Trace:
 <TASK>
 full_hit kernel/trace/ring_buffer.c:888 [inline]
 rb_watermark_hit+0x1e3/0x2d0 kernel/trace/ring_buffer.c:984
 ring_buffer_poll_wait+0x254/0x2f0 kernel/trace/ring_buffer.c:1141
 vfs_poll include/linux/poll.h:82 [inline]
 __io_arm_poll_handler+0x395/0xbb0 io_uring/poll.c:612
 io_poll_add+0x1a0/0x2c0 io_uring/poll.c:987
 __io_issue_sqe+0x180/0x450 io_uring/io_uring.c:1514
 io_issue_sqe+0x177/0x12a0 io_uring/io_uring.c:1537
 io_queue_sqe io_uring/io_uring.c:1794 [inline]
 io_submit_sqe io_uring/io_uring.c:2089 [inline]
 io_submit_sqes+0xea7/0x2500 io_uring/io_uring.c:2212
 __do_sys_io_uring_enter io_uring/io_uring.c:2903 [inline]
 __se_sys_io_uring_enter+0x364/0x1bb0 io_uring/io_uring.c:2852
 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
 do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fbb9888fb19
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fbb96ae6028 EFLAGS: 00000246 ORIG_RAX: 00000000000001aa
RAX: ffffffffffffffda RBX: 00007fbb98b15fa0 RCX: 00007fbb9888fb19
RDX: 00000000fffffffd RSI: 0000000000000b38 RDI: 0000000000000004
RBP: 00007fbb98927b70 R08: 0000000000000000 R09: 0000000000000000
R10: 000000000000004b R11: 0000000000000246 R12: 0000000000000000
R13: 00007fbb98b16038 R14: 00007fbb98b15fa0 R15: 00007ffd91292ae8
 </TASK>

Crashes (1):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/07 05:33 upstream d48723711bae 5cecaf1f .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-kasan-gce-smack-root WARNING in ring_buffer_nr_dirty_pages
* Struck through repros no longer work on HEAD.