Vulnerabilidad en Linux (CVE-2026-23294)
Fecha de publicación:
25/03/2026
En el kernel de Linux, la siguiente vulnerabilidad ha sido resuelta:<br />
<br />
bpf: Corrige condición de carrera en devmap en PREEMPT_RT<br />
<br />
En kernels PREEMPT_RT, la xdp_dev_bulk_queue (bq) por CPU puede ser accedida concurrentemente por múltiples tareas preemptivas en la misma CPU.<br />
<br />
El código original asume que bq_enqueue() y __dev_flush() se ejecutan atómicamente con respecto la una a la otra en la misma CPU, confiando en local_bh_disable() para prevenir la expropiación. Sin embargo, en PREEMPT_RT, local_bh_disable() solo llama a migrate_disable() (cuando PREEMPT_RT_NEEDS_BH_LOCK no está configurado) y no deshabilita la expropiación, lo que permite que la planificación CFS expropie una tarea durante bq_xmit_all(), permitiendo que otra tarea en la misma CPU entre en bq_enqueue() y opere en la misma bq por CPU concurrentemente.<br />
<br />
Esto lleva a varias condiciones de carrera:<br />
<br />
1. Doble liberación / uso después de liberación en bq-&gt;q[]: bq_xmit_all() toma una instantánea de cnt = bq-&gt;count, luego itera bq-&gt;q[0..cnt-1] para transmitir tramas. Si es expropiada después de la instantánea, una segunda tarea puede llamar a bq_enqueue() -&gt; bq_xmit_all() en la misma bq, transmitiendo (y liberando) las mismas tramas. Cuando la primera tarea se reanuda, opera con punteros obsoletos en bq-&gt;q[], causando uso después de liberación.<br />
<br />
2. Corrupción de bq-&gt;count y bq-&gt;q[]: bq_enqueue() concurrente modificando bq-&gt;count y bq-&gt;q[] mientras bq_xmit_all() los está leyendo.<br />
<br />
3. Condición de carrera de desmontaje de dev_rx/xdp_prog: __dev_flush() borra bq-&gt;dev_rx y bq-&gt;xdp_prog después de bq_xmit_all(). Si es expropiada entre el retorno de bq_xmit_all() y bq-&gt;dev_rx = NULL, una bq_enqueue() expropiadora ve dev_rx aún configurado (no-NULL), omite añadir bq a la flush_list, y encola una trama. Cuando __dev_flush() se reanuda, borra dev_rx y elimina bq de la flush_list, dejando huérfana la trama recién encolada.<br />
<br />
4. __list_del_clearprev() en flush_node: similar a la condición de carrera de cpumap, ambas tareas pueden llamar a __list_del_clearprev() en el mismo flush_node, la segunda desreferencia el puntero prev ya establecido en NULL.<br />
<br />
La condición de carrera entre la tarea A (__dev_flush -&gt; bq_xmit_all) y la tarea B (bq_enqueue -&gt; bq_xmit_all) en la misma CPU:<br />
<br />
Tarea A (xdp_do_flush) Tarea B (redirección ndo_xdp_xmit)<br />
---------------------- --------------------------------<br />
__dev_flush(flush_list)<br />
bq_xmit_all(bq)<br />
cnt = bq-&gt;count /* ej. 16 */<br />
/* comienza a iterar bq-&gt;q[] */<br />
&lt;-- CFS expropia la Tarea A --&gt;<br />
bq_enqueue(dev, xdpf)<br />
bq-&gt;count == DEV_MAP_BULK_SIZE<br />
bq_xmit_all(bq, 0)<br />
cnt = bq-&gt;count /* ¡los mismos 16! */<br />
ndo_xdp_xmit(bq-&gt;q[])<br />
/* tramas liberadas por el controlador */<br />
bq-&gt;count = 0<br />
&lt;-- La Tarea A se reanuda --&gt;<br />
ndo_xdp_xmit(bq-&gt;q[])<br />
/* uso después de liberación: ¡tramas ya liberadas! */<br />
<br />
Solucione esto añadiendo un local_lock_t a xdp_dev_bulk_queue y adquiriéndolo en bq_enqueue() y __dev_flush(). Estas rutas ya se ejecutan bajo local_bh_disable(), así que use local_lock_nested_bh() que en no-RT es una anotación pura sin sobrecarga, y en PREEMPT_RT proporciona un bloqueo de suspensión por CPU que serializa el acceso a la bq.
Gravedad CVSS v3.1: ALTA
Última modificación:
02/04/2026