CVE-2026-23294

Source
https://cve.org/CVERecord?id=CVE-2026-23294
Import Source
https://storage.googleapis.com/cve-osv-conversion/osv-output/CVE-2026-23294.json
JSON Data
https://api.osv.dev/v1/vulns/CVE-2026-23294
Downstream
Published
2026-03-25T10:26:51.946Z
Modified
2026-04-02T13:12:20.679069Z
Summary
bpf: Fix race in devmap on PREEMPT_RT
Details

In the Linux kernel, the following vulnerability has been resolved:

bpf: Fix race in devmap on PREEMPT_RT

On PREEMPTRT kernels, the per-CPU xdpdevbulkqueue (bq) can be accessed concurrently by multiple preemptible tasks on the same CPU.

The original code assumes bq_enqueue() and __devflush() run atomically with respect to each other on the same CPU, relying on localbhdisable() to prevent preemption. However, on PREEMPTRT, localbhdisable() only calls migratedisable() (when PREEMPTRTNEEDSBHLOCK is not set) and does not disable preemption, which allows CFS scheduling to preempt a task during bqxmitall(), enabling another task on the same CPU to enter bqenqueue() and operate on the same per-CPU bq concurrently.

This leads to several races:

  1. Double-free / use-after-free on bq->q[]: bqxmitall() snapshots cnt = bq->count, then iterates bq->q[0..cnt-1] to transmit frames. If preempted after the snapshot, a second task can call bqenqueue() -> bqxmit_all() on the same bq, transmitting (and freeing) the same frames. When the first task resumes, it operates on stale pointers in bq->q[], causing use-after-free.

  2. bq->count and bq->q[] corruption: concurrent bqenqueue() modifying bq->count and bq->q[] while bqxmit_all() is reading them.

  3. devrx/xdpprog teardown race: __devflush() clears bq->devrx and bq->xdp_prog after bqxmitall(). If preempted between bqxmitall() return and bq->devrx = NULL, a preempting bqenqueue() sees devrx still set (non-NULL), skips adding bq to the flushlist, and enqueues a frame. When _devflush() resumes, it clears devrx and removes bq from the flushlist, orphaning the newly enqueued frame.

  4. __listdelclearprev() on flush_node: similar to the cpumap race, both tasks can call _listdelclearprev() on the same flushnode, the second dereferences the prev pointer already set to NULL.

The race between task A (__devflush -> bqxmitall) and task B (bqenqueue -> bqxmitall) on the same CPU:

Task A (xdpdoflush) Task B (ndoxdpxmit redirect) ---------------------- -------------------------------- _devflush(flushlist) bqxmitall(bq) cnt = bq->count /* e.g. 16 / / start iterating bq->q[] */ <-- CFS preempts Task A --> bqenqueue(dev, xdpf) bq->count == DEVMAPBULKSIZE bqxmitall(bq, 0) cnt = bq->count /* same 16! */ ndoxdpxmit(bq->q[]) /* frames freed by driver */ bq->count = 0 <-- Task A resumes --> ndoxdp_xmit(bq->q[]) /* use-after-free: frames already freed! */

Fix this by adding a locallockt to xdpdevbulkqueue and acquiring it in bqenqueue() and _devflush(). These paths already run under localbhdisable(), so use locallocknestedbh() which on non-RT is a pure annotation with no overhead, and on PREEMPTRT provides a per-CPU sleeping lock that serializes access to the bq.

Database specific
{
    "cna_assigner": "Linux",
    "osv_generated_from": "https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/23xxx/CVE-2026-23294.json"
}
References

Affected packages

Git / git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

Affected ranges

Type
GIT
Repo
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
Events
Introduced
3253cb49cbad4772389d6ef55be75db1f97da910
Fixed
6c10b019785dc282c5f45d21e4a3f468b8fd6476
Fixed
ab1a56c9d99189aa5c6e03940d06e40ba6a28240
Fixed
1872e75375c40add4a35990de3be77b5741c252c

Database specific

source
"https://storage.googleapis.com/cve-osv-conversion/osv-output/CVE-2026-23294.json"

Linux / Kernel

Package

Name
Kernel

Affected ranges

Type
ECOSYSTEM
Events
Introduced
6.18.0
Fixed
6.18.17
Type
ECOSYSTEM
Events
Introduced
6.19.0
Fixed
6.19.7

Database specific

source
"https://storage.googleapis.com/cve-osv-conversion/osv-output/CVE-2026-23294.json"