CVE-2026-23342
Severity CVSS v4.0:
Pending analysis
Type:
CWE-362
Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
Publication date:
25/03/2026
Last modified:
23/04/2026
Description
In the Linux kernel, the following vulnerability has been resolved:<br />
<br />
bpf: Fix race in cpumap on PREEMPT_RT<br />
<br />
On PREEMPT_RT kernels, the per-CPU xdp_bulk_queue (bq) can be accessed<br />
concurrently by multiple preemptible tasks on the same CPU.<br />
<br />
The original code assumes bq_enqueue() and __cpu_map_flush() run<br />
atomically with respect to each other on the same CPU, relying on<br />
local_bh_disable() to prevent preemption. However, on PREEMPT_RT,<br />
local_bh_disable() only calls migrate_disable() (when<br />
PREEMPT_RT_NEEDS_BH_LOCK is not set) and does not disable<br />
preemption, which allows CFS scheduling to preempt a task during<br />
bq_flush_to_queue(), enabling another task on the same CPU to enter<br />
bq_enqueue() and operate on the same per-CPU bq concurrently.<br />
<br />
This leads to several races:<br />
<br />
1. Double __list_del_clearprev(): after bq->count is reset in<br />
bq_flush_to_queue(), a preempting task can call bq_enqueue() -><br />
bq_flush_to_queue() on the same bq when bq->count reaches<br />
CPU_MAP_BULK_SIZE. Both tasks then call __list_del_clearprev()<br />
on the same bq->flush_node, the second call dereferences the<br />
prev pointer that was already set to NULL by the first.<br />
<br />
2. bq->count and bq->q[] races: concurrent bq_enqueue() can corrupt<br />
the packet queue while bq_flush_to_queue() is processing it.<br />
<br />
The race between task A (__cpu_map_flush -> bq_flush_to_queue) and<br />
task B (bq_enqueue -> bq_flush_to_queue) on the same CPU:<br />
<br />
Task A (xdp_do_flush) Task B (cpu_map_enqueue)<br />
---------------------- ------------------------<br />
bq_flush_to_queue(bq)<br />
spin_lock(&q->producer_lock)<br />
/* flush bq->q[] to ptr_ring */<br />
bq->count = 0<br />
spin_unlock(&q->producer_lock)<br />
bq_enqueue(rcpu, xdpf)<br />
bq->q[bq->count++] = xdpf<br />
/* ... more enqueues until full ... */<br />
bq_flush_to_queue(bq)<br />
spin_lock(&q->producer_lock)<br />
/* flush to ptr_ring */<br />
spin_unlock(&q->producer_lock)<br />
__list_del_clearprev(flush_node)<br />
/* sets flush_node.prev = NULL */<br />
<br />
__list_del_clearprev(flush_node)<br />
flush_node.prev->next = ...<br />
/* prev is NULL -> kernel oops */<br />
<br />
Fix this by adding a local_lock_t to xdp_bulk_queue and acquiring it<br />
in bq_enqueue() and __cpu_map_flush(). These paths already run under<br />
local_bh_disable(), so use local_lock_nested_bh() which on non-RT is<br />
a pure annotation with no overhead, and on PREEMPT_RT provides a<br />
per-CPU sleeping lock that serializes access to the bq.<br />
<br />
To reproduce, insert an mdelay(100) between bq->count = 0 and<br />
__list_del_clearprev() in bq_flush_to_queue(), then run reproducer<br />
provided by syzkaller.
Impact
Base Score 3.x
4.70
Severity 3.x
MEDIUM
Vulnerable products and versions
| CPE | From | Up to |
|---|---|---|
| cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* | 6.18.1 (including) | 6.18.17 (excluding) |
| cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:* | 6.19 (including) | 6.19.7 (excluding) |
| cpe:2.3:o:linux:linux_kernel:6.18:-:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:7.0:rc1:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:7.0:rc2:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:7.0:rc3:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:7.0:rc4:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:7.0:rc5:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:7.0:rc6:*:*:*:*:*:* | ||
| cpe:2.3:o:linux:linux_kernel:7.0:rc7:*:*:*:*:*:* |
To consult the complete list of CPE names with products and versions, see this page



