WARNING in do_epoll_wait (2)

Status: moderation: reported on 2026/11/12 03:28
Subsystems: fs
Labels: prio:low
[Documentation on labels]
Reported-by: syzbot+1f7e1fcbe90a12307de9@syzkaller.appspotmail.com
First crash: 1h34m, last: 1h34m
✨ AI-generated root cause summary:

A lockdep state desynchronization occurs during syscall entry in do_syscall_64() where hardware interrupts are enabled (EFLAGS.IF == 1), but lockdep's per-CPU hardirqs_enabled variable remains 0. When a lockless syscall such as epoll_wait() immediately executes without acquiring locks that would restore irq flags, it reaches lockdep_assert_irqs_enabled() in ep_poll(), triggering a warning because lockdep believes interrupts are disabled. This stems from an interruption or asynchronous transition occurring during or immediately following local_irq_enable() in the entry path (syscall_enter_from_user_mode_randomize_stack) where the exit path leaves hardirqs_enabled cleared.

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

Technical Root Cause Analysis

1. Bug Manifestation & Call Context

The bug report shows a lockdep warning firing at fs/eventpoll.c:2272:

__lockdep_enabled && !this_cpu_read(hardirqs_enabled)
WARNING: fs/eventpoll.c:2272 at ep_poll fs/eventpoll.c:2272 [inline], CPU#1: udevd/2859
WARNING: fs/eventpoll.c:2272 at do_epoll_wait+0x746/0x1790 fs/eventpoll.c:2837, CPU#1: udevd/2859

At the crash site:

  • The CPU registers show EFLAGS: 00010293, where bit 9 (0x200, the Interrupt Flag IF) is set (1), indicating hardware interrupts are actively enabled on CPU#1.
  • Register RBX holds 0, reading this_cpu_read(hardirqs_enabled).
  • The system is Not tainted and __lockdep_enabled (debug_locks && !this_cpu_read(lockdep_recursion)) is true, confirming lockdep is active, debugging locks are enabled, and no prior assertions or warnings have triggered since boot.
  • The task udevd invoked epoll_wait(2), traversing: entry_SYSCALL_64_after_hwframe → do_syscall_64() → __x64_sys_epoll_wait() → do_epoll_wait() → ep_poll().
  • At the top of ep_poll(), lockdep_assert_irqs_enabled() checks WARN_ON_ONCE(__lockdep_enabled && !this_cpu_read(hardirqs_enabled)) and splats because lockdep's software tracking state disagrees with the hardware CPU flag state.

2. Root Cause Mechanism

  1. Syscall Entry State Tracking: In arch/x86/entry/syscall_64.c, do_syscall_64() invokes syscall_enter_from_user_mode_randomize_stack() (include/linux/entry-common.h:199):

    #define syscall_enter_from_user_mode_randomize_stack(regs, syscall)	\
    ({									\
        enter_from_user_mode_randomize_stack(regs);			\
        instrumentation_begin();					\
        local_irq_enable();						\
        long _ret = syscall_enter_from_user_mode_work(regs, syscall);	\
        instrumentation_end();						\
        _ret;								\
    })
    

    First, enter_from_user_mode_randomize_stack() → enter_from_user_mode() (include/linux/irq-entry-common.h:98) calls lockdep_hardirqs_off(CALLER_ADDR0), setting hardirqs_enabled = 0 while hardware interrupts are disabled upon entering kernel space from usermode.

  2. Interrupt Re-enabling and Lockless Execution in epoll_wait: At line 204 of include/linux/entry-common.h, local_irq_enable() runs:

    trace_hardirqs_on();
    raw_local_irq_enable();
    

    Under normal circumstances, trace_hardirqs_on() calls lockdep_hardirqs_on(), which writes __this_cpu_write(hardirqs_enabled, 1). Following this, do_syscall_64() executes the system call. Unlike many syscalls that acquire spinlocks or mutexes (which repeatedly execute local_irq_save() / local_irq_restore() or lockdep tracking hooks that resynchronize hardirqs_enabled), epoll_wait with valid arguments runs completely locklessly until ep_poll():

    • CLASS(fd, f)(epfd) acquires an RCU-based or reference-counted file pointer without IRQ-disabling spinlocks.
    • ep_check_params() performs validation checks.
    • ep_poll() immediately runs lockdep_assert_irqs_enabled().
  3. Desynchronization Mechanism: Because lockdep_hardirqs_off() in kernel/locking/lockdep.c:4546 asserts DEBUG_LOCKS_WARN_ON(!irqs_disabled()), hardirqs_enabled can only be set to 0 while hardware interrupts are off (IF == 0). Therefore, either:

    • An interrupt or exception occurred immediately around or after local_irq_enable() in the entry path (include/linux/entry-common.h:204-205), entering via irqentry_enter_from_kernel_mode() (which called lockdep_hardirqs_off()), but returned to the kernel without properly invoking trace_hardirqs_on() / lockdep_hardirqs_on() on the exit path (irqentry_exit_to_kernel_mode_after_preempt()); OR
    • A race condition exists in the non-atomic sequence in local_irq_enable() where trace_hardirqs_on() marks hardirqs_enabled = 1 before raw_local_irq_enable() sets EFLAGS.IF = 1, and an exception/trap firing in that window hits an entry/exit path that inspects regs_irqs_disabled(regs) (finding IF == 0) and skips enabling tracing on exit, returning to raw_local_irq_enable() which sets IF = 1 while leaving hardirqs_enabled = 0.

3. Scope and Incomplete/Unverified Aspects

  • Verified:
    • Hardware interrupts are definitely enabled (EFLAGS.IF == 1) at the point of failure.
    • No lockdep assertion fired prior to ep_poll(), and debug_locks was non-zero.
    • The failure only manifests visibly on syscalls like epoll_wait() that do not take spinlocks or change interrupt states before checking lockdep_assert_irqs_enabled().
  • Inconclusive / Unverified:
    • The exact asynchronous event (e.g. reschedule IPI, timer tick, preemption check via irqentry_exit_cond_resched, or an in-kernel exception/probe) that executed between local_irq_enable() and ep_poll() leaving hardirqs_enabled cleared could not be definitively pinpointed due to disabled tools, but the bug definitely resides in the entry/irqentry common framework (include/linux/entry-common.h, include/linux/irq-entry-common.h, and kernel/entry/common.c).
▶ ▼ ✨ AI Jobs (1)
ID Workflow Result Correct Bug Created Started Finished Revision
2796bac9 assessment-security DenialOfService: ❌ Exploitable: ❌ FilesystemTrigger: ❌ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ❌ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ WARNING in do_epoll_wait (2) 2026/11/12 02:26 2026/11/12 02:26 2026/11/12 03:19 8d68b303
▶ ▼ Similar bugs (1)
Kernel Title Rank 🛈 Repro Cause bisect Fix bisect Count Last Reported Patched Status
upstream WARNING in do_epoll_wait fs -1 1 670d 666d 0/29 auto-obsoleted due to no activity on 2025/04/10 18:43

Sample crash report:
------------[ cut here ]------------
__lockdep_enabled && !this_cpu_read(hardirqs_enabled)
WARNING: fs/eventpoll.c:2272 at ep_poll fs/eventpoll.c:2272 [inline], CPU#1: udevd/2859
WARNING: fs/eventpoll.c:2272 at do_epoll_wait+0x746/0x1790 fs/eventpoll.c:2837, CPU#1: udevd/2859
Modules linked in:
CPU: 1 UID: 0 PID: 2859 Comm: udevd Not tainted syzkaller #0 PREEMPT(lazy) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 11/06/2026
RIP: 0010:ep_poll fs/eventpoll.c:2272 [inline]
RIP: 0010:do_epoll_wait+0x746/0x1790 fs/eventpoll.c:2837
Code: db 0f 85 17 fb ff ff e8 b8 72 6e ff 65 8b 1d 05 bf e3 0a 31 ff 89 de e8 18 6d 6e ff 85 db 0f 85 fa fa ff ff e8 9b 72 6e ff 90 <0f> 0b 90 e8 92 72 6e ff 48 8b 5c 24 58 48 85 db 0f 85 f1 fa ff ff
RSP: 0018:ffffc90001c6fcc0 EFLAGS: 00010293
RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000000
RDX: ffff8881170f9e40 RSI: ffffffff824883a5 RDI: 0000000000000005
RBP: dffffc0000000000 R08: 0000000000000000 R09: 0000000000000000
R10: ffff888116d24003 R11: 0000000000000001 R12: 00007ffffffff000
R13: ffff888117372700 R14: 00007ffd4b58c760 R15: ffff88811739d800
FS:  00007f756c3d3880(0000) GS:ffff888268460000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055bfede5cec0 CR3: 000000011723a000 CR4: 00000000003506f0
Call Trace:
 <TASK>
 __do_sys_epoll_wait fs/eventpoll.c:2845 [inline]
 __se_sys_epoll_wait fs/eventpoll.c:2840 [inline]
 __x64_sys_epoll_wait+0x194/0x290 fs/eventpoll.c:2840
 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
 do_syscall_64+0x119/0x710 arch/x86/entry/syscall_64.c:84
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f756c4c1407
Code: 48 89 fa 4c 89 df e8 38 aa 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff
RSP: 002b:00007ffd4b58c600 EFLAGS: 00000202 ORIG_RAX: 00000000000000e8
RAX: ffffffffffffffda RBX: 00007f756c3d3880 RCX: 00007f756c4c1407
RDX: 0000000000000008 RSI: 00007ffd4b58c760 RDI: 000000000000000b
RBP: 00000000000000b9 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000bb8 R11: 0000000000000202 R12: 0000000000000000
R13: 000055bfd6a73100 R14: 000055bfed97d140 R15: 0000000000000000
 </TASK>

Crashes (1):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/12 02:22 https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing 6628e7d4c6b2 8d68b303 .config console log report info [disk image] [vmlinux] [kernel image] ci2-upstream-usb WARNING in do_epoll_wait
* Struck through repros no longer work on HEAD.