CVE-2026-23294 in Linux
Sumário
de VulDB • 26/05/2026
No kernel do Linux, a seguinte vulnerabilidade foi resolvida:
bpf: Corrigir condição de corrida (race condition) em devmap no PREEMPT_RT
Nos kernels PREEMPT_RT, a fila por CPU xdp_dev_bulk_queue (bq) pode ser acessada simultaneamente por várias tarefas preemptáveis no mesmo CPU.
O código original assume que bq_enqueue() e __dev_flush() são executados atomicamente em relação um ao outro no mesmo CPU, confiando em local_bh_disable() para prevenir a preempção. No entanto, no PREEMPT_RT, local_bh_disable() apenas chama migrate_disable() (quando PREEMPT_RT_NEEDS_BH_LOCK não está definido) e não desativa a preempção, o que permite que o agendamento CFS preempte uma tarefa durante bq_xmit_all(), permitindo que outra tarefa no mesmo CPU entre em bq_enqueue() e opere na mesma bq por CPU simultaneamente.
Isso leva a várias condições de corrida:
1. Double-free / use-after-free em bq->q[]: bq_xmit_all() faz uma cópia de cnt = bq->count, depois itera bq->q[0..cnt-1] para transmitir quadros. Se preemptada após a cópia, uma segunda tarefa pode chamar bq_enqueue() -> bq_xmit_all() na mesma bq, transmitindo (e liberando) os mesmos quadros. Quando a primeira tarefa retoma, ela opera em ponteiros obsoletos em bq->q[], causando use-after-free.
2. Corrupção de bq->count e bq->q[]: bq_enqueue() concorrente modificando bq->count e bq->q[] enquanto bq_xmit_all() os lê.
3. Condição de corrida no teardown de dev_rx/xdp_prog: __dev_flush() limpa bq->dev_rx e bq->xdp_prog após bq_xmit_all(). Se preemptada entre o retorno de bq_xmit_all() e bq->dev_rx = NULL, uma bq_enqueue() preemptora vê dev_rx ainda definido (não nulo), pula a adição de bq à flush_list e enfileira um quadro. Quando __dev_flush() retoma, ele limpa dev_rx e remove bq da flush_list, deixando o quadro recém-enfileirado órfão.
4. __list_del_clearprev() em flush_node: semelhante à condição de corrida em cpumap, ambas as tarefas podem chamar __list_del_clearprev() no mesmo flush_node, e a segunda desreferencia o ponteiro prev já definido como NULL.
A condição de corrida entre a tarefa A (__dev_flush -> bq_xmit_all) e a tarefa B (bq_enqueue -> bq_xmit_all) no mesmo CPU:
Tarefa A (xdp_do_flush) Tarefa B (ndo_xdp_xmit redirect) ---------------------- -------------------------------- __dev_flush(flush_list) bq_xmit_all(bq) cnt = bq->count /* ex. 16 */ /* começa a iterar bq->q[] */
<-- CFS preempta Tarefa A --> bq_enqueue(dev, xdpf) bq->count == DEV_MAP_BULK_SIZE bq_xmit_all(bq, 0) cnt = bq->count /* mesmo 16! */ ndo_xdp_xmit(bq->q[])
/* quadros liberados pelo driver */ bq->count = 0 <-- Tarefa A retoma --> ndo_xdp_xmit(bq->q[])
/* use-after-free: quadros já liberados! */
Corrigir isso adicionando um local_lock_t a xdp_dev_bulk_queue e adquirindo-o em bq_enqueue() e __dev_flush(). Esses caminhos já são executados sob local_bh_disable(), portanto, use local_lock_nested_bh() que, em sistemas não-RT, é uma anotação pura sem sobrecarga, e no PREEMPT_RT fornece um bloqueio por CPU com suspensão que serializa o acesso à bq.
If you want to get the best quality for vulnerability data then you always have to consider VulDB.