BUG: unable to handle kernel paging request in percpu_counter_add_batch (3)

Status: moderation: reported on 2026/11/10 23:21
Subsystems: net
Labels: prio:normal
[Documentation on labels]
Reported-by: syzbot+84a1bc1e9e0b2a41993e@syzkaller.appspotmail.com
First crash: 4d01h, last: 5h36m
✨ AI-generated root cause summary:

During network namespace destruction, IPVS flushes its virtual services and destinations in __ip_vs_cleanup_batch() via ip_vs_flush() -> __ip_vs_unlink_dest() -> __ip_vs_dst_cache_reset(). When resetting dest->dest_dst, __ip_vs_dst_cache_reset() defers releasing the cached dst_entry via call_rcu(&old->rcu_head, ip_vs_dest_dst_rcu_free). Because IPVS does not invoke rcu_barrier() during netns teardown, earlier-registered pernet subsystem exit handlers (such as ip6_route_net_exit) proceed to tear down the namespace's dst_ops via dst_entries_destroy(), which destroys the percpu counter and sets pcpuc_entries.counters to NULL. When the deferred ip_vs_dest_dst_rcu_free() callback eventually runs in softirq context, dst_release() invokes dst_entries_add() -> percpu_counter_add_batch(), which dereferences the NULL fbc->counters pointer via this_cpu_read() (%gs:0), causing a fatal kernel page fault at the GS segment base address.

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

Technical Root Cause Analysis

1. Involved Files, Functions, and Data Structures


2. Triggering Flow and Lifecycle Invariant Violation

In IPVS, destinations (struct ip_vs_dest) can cache output route entries (struct dst_entry *) inside an RCU-protected wrapper struct struct ip_vs_dest_dst. When a destination route cache is invalidated or the destination is unlinked (such as during service teardown or device down events), __ip_vs_dst_cache_reset() in net/netfilter/ipvs/ip_vs_ctl.c unlinks the cache entry and defers releasing it:

static void __ip_vs_dst_cache_reset(struct ip_vs_dest *dest)
{
	struct ip_vs_dest_dst *old;

	old = rcu_dereference_protected(dest->dest_dst, 1);
	if (old) {
		RCU_INIT_POINTER(dest->dest_dst, NULL);
		call_rcu(&old->rcu_head, ip_vs_dest_dst_rcu_free);
	}
}

The RCU callback ip_vs_dest_dst_rcu_free() releases the held destination entry reference via dst_release(dest_dst->dst_cache):

void ip_vs_dest_dst_rcu_free(struct rcu_head *head)
{
	struct ip_vs_dest_dst *dest_dst = container_of(head,
						       struct ip_vs_dest_dst,
						       rcu_head);

	dst_release(dest_dst->dst_cache);
	kfree(dest_dst);
}

During network namespace destruction (cleanup_net() in net/core/net_namespace.c), ops_undo_list() iterates through pernet_list in reverse order of subsystem registration, executing each subsystem's .exit or .exit_batch method:

  1. IPVS Netns Cleanup: IPVS is registered as a pernet_subsys (ipvs_core_ops) after the core routing protocols. Its exit handler __ip_vs_cleanup_batch() executes first, calling ip_vs_service_nets_cleanup() -> ip_vs_flush() -> __ip_vs_del_service() -> __ip_vs_unlink_dest() -> __ip_vs_dst_cache_reset(). This enqueues an RCU callback to ip_vs_dest_dst_rcu_free(). However, __ip_vs_cleanup_batch() does not call rcu_barrier() (unlike full module unload in ip_vs_cleanup(), which relies on rcu_barrier()).

  2. Routing / DST Ops Teardown: ops_undo_list() continues to later exit callbacks in the reverse list, eventually calling ip6_route_net_exit() (and/or xfrm*_net_exit()). In ip6_route_net_exit():

    dst_entries_destroy(&net->ipv6.ip6_dst_ops);
    

    dst_entries_destroy() invokes percpu_counter_destroy() -> percpu_counter_destroy_many(), which frees the per-CPU counter memory and explicitly zeroes the pointer:

    free_percpu(fbc[0].counters);
    for (i = 0; i < nr_counters; i++)
        fbc[i].counters = NULL;
    
  3. Deferred Callback Execution: Subsequently, after an RCU grace period elapses, softirq context executes rcu_do_batch() and runs ip_vs_dest_dst_rcu_free(). ip_vs_dest_dst_rcu_free() calls dst_release(dest_dst->dst_cache). In net/core/dst.c:

    void dst_release(struct dst_entry *dst)
    {
        if (dst && rcuref_put(&dst->__rcuref)) {
            ...
            dst_count_dec(dst);
            call_rcu_hurry(&dst->rcu_head, dst_destroy_rcu);
        }
    }
    

    dst_count_dec() calls dst_entries_add(dst->ops, -1). In include/net/dst_ops.h:

    static inline void dst_entries_add(struct dst_ops *dst, int val)
    {
        percpu_counter_add_batch(&dst->pcpuc_entries, val,
                                 DST_PERCPU_COUNTER_BATCH);
    }
    

3. Mechanics of the Page Fault Crash

In lib/percpu_counter.c:

void percpu_counter_add_batch(struct percpu_counter *fbc, s64 amount, s32 batch)
{
    s64 count;
    unsigned long flags;

    count = this_cpu_read(*fbc->counters);
  • fbc is &dst->ops->pcpuc_entries.
  • fbc->counters was previously set to NULL by dst_entries_destroy().
  • On x86_64, this_cpu_read(*fbc->counters) evaluates to:
    mov 0x58(%r13), %r12        ; %r12 = fbc->counters (NULL)
    movslq %gs:(%r12), %rbp     ; reads %gs:(0x0)
    

Because %r12 is 0x0, the instruction attempts to read offset 0x0 relative to %gs. Since the kernel per-CPU GS segment base (ffff8880d4775000) has offset 0 unmapped to catch NULL per-CPU dereferences, this supervisor read immediately causes a page fault:

BUG: unable to handle page fault for address: ffff8880d4775000
#PF: supervisor read access in kernel mode
RIP: 0010:percpu_counter_add_batch+0x4b/0x230 lib/percpu_counter.c:98

Because the fault occurs in softirq / IRQ context during RCU callback processing, it results in an unhandled kernel Oops.

▶ ▼ ✨ AI Jobs (3)
ID Workflow Result Correct Bug Created Started Finished Revision
80b11ea2 repro-c 💥 BUG: unable to handle kernel paging request in percpu_counter_add_batch (3) 2026/11/10 06:12 2026/11/10 06:12 2026/11/10 12:20 01625235
9026594f repro-c 💥 BUG: unable to handle kernel paging request in percpu_counter_add_batch (3) 2026/11/09 00:00 2026/11/09 00:00 2026/11/09 05:37 5cecaf1f
4e92ff5d assessment-security DenialOfService: ✅ Exploitable: ❌ FilesystemTrigger: ❌ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ✅ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ BUG: unable to handle kernel paging request in percpu_counter_add_batch (3) 2026/11/06 22:33 2026/11/06 22:33 2026/11/06 23:11 5cecaf1f
▶ ▼ Similar bugs (3)
Kernel Title Rank 🛈 Repro Cause bisect Fix bisect Count Last Reported Patched Status
upstream BUG: unable to handle kernel paging request in percpu_counter_add_batch mm 8 140 1379d 1481d 22/29 fixed on 2023/02/24 13:50
upstream BUG: unable to handle kernel paging request in percpu_counter_add_batch (2) prio:normal nfs net 8 1 95d 90d 0/29 auto-obsoleted due to no activity on 2026/11/05 20:31
linux-4.19 general protection fault in percpu_counter_add_batch 2 syz, C 38 1347d 1487d 0/1 upstream: reported C repro on 2022/10/15 02:19

Sample crash report:
BUG: unable to handle page fault for address: ffff8880d4571000
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page
PGD 1d12e067 P4D 1d12e067 PUD 0 
Oops: Oops: 0000 [#1] SMP KASAN NOPTI
CPU: 1 UID: 0 PID: 0 Comm: swapper/1 Tainted: G             L      syzkaller #0 PREEMPT(full) 
Tainted: [L]=SOFTLOCKUP
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:percpu_counter_add_batch+0x4b/0x230 lib/percpu_counter.c:98
Code: 8d 45 58 48 89 c2 48 89 44 24 08 48 b8 00 00 00 00 00 fc ff df 48 c1 ea 03 80 3c 02 00 0f 85 9f 01 00 00 4d 8b 65 58 4c 89 ff <65> 49 63 2c 24 4a 8d 54 35 00 48 89 6c 24 10 48 89 eb 48 89 d0 48
RSP: 0018:ffffc900006b0d68 EFLAGS: 00010246
RAX: dffffc0000000000 RBX: ffff88802492cc80 RCX: 0000000000000101
RDX: 1ffff11006f23bab RSI: ffffffff856fce00 RDI: 0000000000000020
RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000003 R11: 0000000000000001 R12: 0000000000000000
R13: ffff88803791dd00 R14: ffffffffffffffff R15: 0000000000000020
FS:  0000000000000000(0000) GS:ffff8880d4571000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffff8880d4571000 CR3: 000000002435a000 CR4: 0000000000352ef0
Call Trace:
 <IRQ>
 dst_entries_add include/net/dst_ops.h:59 [inline]
 dst_count_dec net/core/dst.c:163 [inline]
 dst_count_dec net/core/dst.c:160 [inline]
 dst_release net/core/dst.c:177 [inline]
 dst_release+0x2aa/0x330 net/core/dst.c:166
 ip_vs_dest_dst_rcu_free+0x32/0x50 net/netfilter/ipvs/ip_vs_ctl.c:1081
 rcu_do_batch kernel/rcu/tree.c:2650 [inline]
 rcu_core+0x5f0/0x1220 kernel/rcu/tree.c:2919
 handle_softirqs+0x216/0xa20 kernel/softirq.c:669
 __do_softirq kernel/softirq.c:703 [inline]
 invoke_softirq kernel/softirq.c:542 [inline]
 __irq_exit_rcu+0x178/0x220 kernel/softirq.c:786
 irq_exit_rcu+0x9/0x30 kernel/softirq.c:809
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
 sysvec_apic_timer_interrupt+0xa3/0xc0 arch/x86/kernel/apic/apic.c:1062
 </IRQ>
 <TASK>
 asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:pv_native_safe_halt+0xf/0x20 arch/x86/kernel/paravirt.c:64
Code: 46 a0 02 c3 cc cc cc cc 0f 1f 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 f3 0f 1e fa eb 07 0f 00 2d 33 4e 1c 00 fb f4 <e9> 4c 62 03 00 66 2e 0f 1f 84 00 00 00 00 00 66 90 90 90 90 90 90
RSP: 0018:ffffc90000187e00 EFLAGS: 00000206
RAX: 00000000007e7f11 RBX: ffff888020ab4b00 RCX: ffffffff8bef7375
RDX: 0000000000000001 RSI: ffffffff8e629af0 RDI: ffffffff8c835a80
RBP: ffffed1004156960 R08: 0000000000000001 R09: ffffed100d426825
R10: ffff88806a13412b R11: 0000000000000001 R12: 0000000000000000
R13: 0000000000000000 R14: 1ffff92000030fc4 R15: dffffc0000000000
 arch_safe_halt arch/x86/include/asm/paravirt.h:62 [inline]
 default_idle+0x9/0x10 arch/x86/kernel/process.c:768
 default_idle_call+0x6c/0xb0 kernel/sched/idle.c:122
 cpuidle_idle_call kernel/sched/idle.c:199 [inline]
 do_idle+0x3ae/0x5b0 kernel/sched/idle.c:355
 cpu_startup_entry+0x4f/0x60 kernel/sched/idle.c:458
 start_secondary+0x21d/0x2d0 arch/x86/kernel/smpboot.c:312
 common_startup_64+0x13e/0x158
 </TASK>
Modules linked in:
CR2: ffff8880d4571000
---[ end trace 0000000000000000 ]---
RIP: 0010:percpu_counter_add_batch+0x4b/0x230 lib/percpu_counter.c:98
Code: 8d 45 58 48 89 c2 48 89 44 24 08 48 b8 00 00 00 00 00 fc ff df 48 c1 ea 03 80 3c 02 00 0f 85 9f 01 00 00 4d 8b 65 58 4c 89 ff <65> 49 63 2c 24 4a 8d 54 35 00 48 89 6c 24 10 48 89 eb 48 89 d0 48
RSP: 0018:ffffc900006b0d68 EFLAGS: 00010246
RAX: dffffc0000000000 RBX: ffff88802492cc80 RCX: 0000000000000101
RDX: 1ffff11006f23bab RSI: ffffffff856fce00 RDI: 0000000000000020
RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000003 R11: 0000000000000001 R12: 0000000000000000
R13: ffff88803791dd00 R14: ffffffffffffffff R15: 0000000000000020
FS:  0000000000000000(0000) GS:ffff8880d4571000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffff8880d4571000 CR3: 000000002435a000 CR4: 0000000000352ef0
----------------
Code disassembly (best guess):
   0:	8d 45 58             	lea    0x58(%rbp),%eax
   3:	48 89 c2             	mov    %rax,%rdx
   6:	48 89 44 24 08       	mov    %rax,0x8(%rsp)
   b:	48 b8 00 00 00 00 00 	movabs $0xdffffc0000000000,%rax
  12:	fc ff df
  15:	48 c1 ea 03          	shr    $0x3,%rdx
  19:	80 3c 02 00          	cmpb   $0x0,(%rdx,%rax,1)
  1d:	0f 85 9f 01 00 00    	jne    0x1c2
  23:	4d 8b 65 58          	mov    0x58(%r13),%r12
  27:	4c 89 ff             	mov    %r15,%rdi
* 2a:	65 49 63 2c 24       	movslq %gs:(%r12),%rbp <-- trapping instruction
  2f:	4a 8d 54 35 00       	lea    0x0(%rbp,%r14,1),%rdx
  34:	48 89 6c 24 10       	mov    %rbp,0x10(%rsp)
  39:	48 89 eb             	mov    %rbp,%rbx
  3c:	48 89 d0             	mov    %rdx,%rax
  3f:	48                   	rex.W

Crashes (2):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/10 18:12 upstream b3dfbde62cf1 d3056657 .config console log report info [disk image (non-bootable)] [vmlinux] [kernel image] ci-qemu-upstream BUG: unable to handle kernel paging request in percpu_counter_add_batch
2026/11/06 22:33 upstream f66469d9905b 5cecaf1f .config console log report info [disk image (non-bootable)] [vmlinux] [kernel image] ci-qemu-upstream BUG: unable to handle kernel paging request in percpu_counter_add_batch
* Struck through repros no longer work on HEAD.