
Backports de tres correcciones publicadas de f_hid (incl. CVE-2026-31721, CVE-2026-31606) a un kernel de proveedor Android Linux 4.14.190 EOL, con registros de verificación en el dispositivo.
Tres correcciones a drivers/usb/gadget/function/f_hid.c, trasladadas a mano a un kernel de proveedor Android 4.14.190 y verificadas en el dispositivo donde se ejecutan.
4.14 está al final de su vida útil. No existe ningún backport estable para ninguna de estas correcciones, y no habrá ninguno. Esa es la razón de ser de este repositorio: el dispositivo es mío, y uno de estos defectos lo estaba reiniciando.
Para ser preciso sobre quién mantiene qué: el árbol de NetHunter sobre el que se construye esto (AzeAstro/samsung_kernel_sm7125, rama OneUI-6) sí se mantiene activamente, y ese trabajo no está en cuestión aquí. Pero es habilitación de dispositivo y de NetHunter — la base permanece fijada en el vendor drop 4.14.190 de Samsung. Nadie está trasladando las correcciones de seguridad estables de 4.14.y hacia abajo, y Samsung tampoco las está distribuyendo — 4.14.y terminó en 4.14.336 el 10 de enero de 2024, y la serie ya no aparece entre las versiones soportadas de kernel.org. Ese vacío es donde se sitúan estos tres parches.
No descubrí estas vulnerabilidades. Las tres están publicadas, con parches upstream escritos por otras personas, acreditadas más abajo. Lo que hice:
El punto 3 es la parte que pediría al lector que sopesara. Dos de estas están confirmadas mediante medición contra una línea base. La tercera no lo está, y no puede estarlo, en este dispositivo. Esa distinción se mantiene a lo largo de todo el documento en lugar de suavizarse.
| # | Parche | Autor upstream | Estado en hardware |
|---|---|---|---|
| 1 | Tiempo de vida de f_hidg frente a cdev (use-after-free) | John Keeping, 2022-11-22 | ✅ eficacia medida |
| 2 | CVE-2026-31721 — estado del tiempo de vida del objeto re-inicializado en hidg_bind | Michael Zimmermann, 2026-03-31 | ✅ eficacia medida |
| 3 | CVE-2026-31606 — cdev_init sobre un cdev en uso | Michael Zimmermann (81ebd43cc0d6d), 2026-03-27; seguimiento de -ENOMEM por Ethan Tidmore, 2026-04-02 | ⛔ no observable en este dispositivo |
f_hidg frente a cdevf_hidg_open almacenaba el f_hidg * y no tomaba ninguna referencia. f_hidg_poll registraba esperadores de poll en colas de espera embebidas en el f_hidg asignado con kzalloc. hidg_unbind llamaba a cdev_del(), que no invalida los fds abiertos, y hidg_free luego hacía kfree del objeto. Nada lo contabilizaba por referencias frente a los archivos abiertos — un grep de todo el archivo buscando refcount|kref|atomic_|open_count solo encontró opts->lock.
Así que un bucle select()/poll() sobre /dev/hidg0 que sobreviviera a que Android recompusiera el gadget llamaría a remove_wait_queue() sobre slab liberado, tomaría spin_lock_irqsave sobre una palabra de ticket basura, y giraría con las IRQ enmascaradas para siempre. El temporizador local se detiene, el hilo que alimenta al watchdog nunca se despierta, y el watchdog no seguro ladra ~11 s después. En la práctica: el terminal se reinicia por completo. El UsbDeviceManager de Android recompone por su cuenta — medido dos veces en ~550 ms — sin acción del usuario, así que esto no necesitaba ningún disparador exótico.
Corrección upstream: "usb: gadget: f_hid: fix f_hidg lifetime vs cdev", John Keeping, 2022-11-22, Fixes: 71adf1189469. El backport es limpio — toda primitiva existe en 4.14.190 — excepto el kfree(hidg->set_report_buf) del parche, un campo que esta versión no tiene, que se descarta.
CVSS 3.1 5.5, vector AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H. hidg_bind re-inicializaba estado perteneciente al tiempo de vida del objeto, no al del bind: dos spinlocks, dos cabezas de cola de espera y una cabeza de lista, ninguna de las cuales aparece en hidg_alloc. init_waitqueue_head restablece la cabeza de lista para que apunte a sí misma, así que un esperador de poll/select registrado antes de un rebind queda huérfano silenciosamente, y su poll_freewait → remove_wait_queue encuentra entonces prev->next incorrecto.
Upstream mueve nueve inicializaciones fuera de hidg_bind; solo cinco de ellas existen en 4.14.190.
⚠ Varios avisos de proveedores titulan esto como "escalada de privilegios". El vector es C:N/I:N/A:H —
solo disponibilidad, es decir, una denegación de servicio local. Lo señalo porque la lectura más alarmante habría
justificado una prioridad que el vector no respalda, y nadie le pide a una página de proveedor que se demuestre a sí misma.
Este parche también va un paso más allá que upstream. Mover las inicializaciones fuera de hidg_bind deja dos
almacenamientos sin bloqueo sobre campos protegidos por bloqueo, enmascarados hoy solo porque el bloqueo se estaba
re-inicializando justo encima de ellos. Una vez que el bloqueo es persistente, la carrera es real: un escritor bloqueado
en el bucle de espera de f_hidg_write, sobre un fd que sobrevivió al unbind, puede tomar el bloqueo, pasar la comprobación
y leer hidg->req mientras bind almacena NULL fuera de él — la re-comprobación llega después de la desreferencia.
Así que el parche toma write_spinlock alrededor de ambos almacenamientos.
La forma general merece enunciarse: mover una inicialización fuera de bind puede crear una carrera que la re-inicialización estaba ocultando. Pregúntate qué estaba protegiendo accidentalmente la init eliminada.
CVSS 5.5, mismo vector. struct cdev cdev está embebido en struct f_hidg, y hidg_bind llamaba
a cdev_init() sobre él — que es memset(cdev, 0, sizeof *cdev) más kobject_init — inmediatamente
antes de cdev_device_add. Un fd abierto mantiene una referencia sobre ese mismo kobject vía chrdev_open →
cdev_get → kobject_get_unless_zero. En unbind→rebind con un fd abierto, bind borra el estado de referencia vivo
sobre un objeto referenciado.
La corrección es un cambio de struct en lugar de una guarda: el struct cdev embebido pasa a ser un struct cdev *
con un cdev_alloc() por bind. cdev_init entonces aparece cero veces en el archivo — no queda ningún objeto en uso
que re-inicializar.
⛔ Este dispositivo no puede detectar este defecto, ni antes ni después de la corrección. Véase
verification/03-cve-2026-31606.md. Es una afirmación positiva sobre la instrumentación del dispositivo, no
una excusa.
Método y registros por defecto en verification/. La versión corta: las correcciones 1 y 2
se cerraron mediante regresión de variable única contra la compilación inmediatamente anterior, con
controles positivos, y la corrección 2 se confirmó una segunda vez desde una fuente de registro diferente. La corrección 3 se evidencia solo
mediante lectura del código fuente.
Los parches son salida de git format-patch contra un árbol de proveedor Samsung 4.14.190, en orden:
git am patches/0001-*.patch patches/0002-*.patch patches/0003-*.patch
Son acumulativos y se espera que se apliquen en secuencia — el 3 asume la estructura tal como la dejó el 1.
GPL-2.0, como obras derivadas del kernel de Linux. Véase LICENSE. La autoría original de las correcciones originales se acredita arriba; el backporting, el bloqueo añadido en el parche 2 y el trabajo de verificación son míos.