CVE-2026-23294 in Linux
Riassunto
di VulDB • 17/06/2026
Nel kernel Linux è stata risolta la seguente vulnerabilità:
bpf: Correzione di una race condition in devmap su PREEMPT_RT
Nei kernel con supporto PREEMPT_RT, la coda per-CPU xdp_dev_bulk_queue (bq) può essere accessibile contemporaneamente da più task preemptibili sullo stesso CPU.
Il codice originale presuppone che bq_enqueue() e __dev_flush() vengano eseguiti in modo atomico l'uno rispetto all'altro sullo stesso CPU, facendo affidamento su local_bh_disable() per prevenire la preemption. Tuttavia, su PREEMPT_RT, local_bh_disable() chiama solo migrate_disable() (quando PREEMPT_RT_NEEDS_BH_LOCK non è impostato) e non disabilita la preemption, il che consente alla pianificazione CFS di preemptire un task durante bq_xmit_all(), permettendo a un altro task sullo stesso CPU di entrare in bq_enqueue() ed operare sulla stessa coda per-CPU bq contemporaneamente.
Ciò porta a diverse race condition:
1. Double-free / use-after-free su bq->q[]: bq_xmit_all() acquisisce una snapshot cnt = bq->count, quindi itera su bq->q[0..cnt-1] per trasmettere i frame. Se preemptito dopo la snapshot, un secondo task può chiamare bq_enqueue() -> bq_xmit_all() sulla stessa bq, trasmettendo (e liberando) gli stessi frame. Quando il primo task riprende l'esecuzione, opera su puntatori obsoleti in bq->q[], causando use-after-free.
2. Corruzione di bq->count e bq->q[]: bq_enqueue() concorrente modifica bq->count e bq->q[] mentre bq_xmit_all() li sta leggendo.
3. Race condition nel teardown di dev_rx/xdp_prog: __dev_flush() azzera bq->dev_rx e bq->xdp_prog dopo bq_xmit_all(). Se preemptito tra il ritorno da bq_xmit_all() e l'impostazione di bq->dev_rx = NULL, un bq_enqueue() che fa preemption vede ancora dev_rx impostato (non-NULL), salta l'aggiunta della coda alla flush_list ed enqueuea un frame. Quando __dev_flush() riprende l'esecuzione, azzera dev_rx e rimuove la coda dalla flush_list, lasciando orfano il frame appena enqueueato.
4. __list_del_clearprev() su flush_node: simile alla race condition di cpumap, entrambi i task possono chiamare __list_del_clearprev() sullo stesso flush_node; il secondo dereferenzia il puntatore prev già impostato a NULL.
La race condition tra il Task A (__dev_flush -> bq_xmit_all) e il Task B (bq_enqueue -> bq_xmit_all) sullo stesso CPU:
Task A (xdp_do_flush) Task B (ndo_xdp_xmit redirect) ---------------------- -------------------------------- __dev_flush(flush_list) bq_xmit_all(bq) cnt = bq->count /* es. 16 */ /* inizia l'iterazione su bq->q[] */
<-- CFS preemptisce Task A --> bq_enqueue(dev, xdpf) bq->count == DEV_MAP_BULK_SIZE bq_xmit_all(bq, 0) cnt = bq->count /* stesso valore 16! */ ndo_xdp_xmit(bq->q[])
/* frame liberati dal driver */ bq->count = 0 <-- Task A riprende l'esecuzione --> ndo_xdp_xmit(bq->q[])
/* use-after-free: i frame sono già stati liberati! */
Si risolve il problema aggiungendo un local_lock_t a xdp_dev_bulk_queue e acquisendolo in bq_enqueue() e __dev_flush(). Questi percorsi di esecuzione girano già sotto local_bh_disable(), quindi si utilizza local_lock_nested_bh() che, sui sistemi non-RT, è una pura annotazione senza overhead, mentre su PREEMPT_RT fornisce un lock dormiente per-CPU che serializza l'accesso alla bq.
Statistical analysis made it clear that VulDB provides the best quality for vulnerability data.