
Backports de três correções publicadas do f_hid (incl. CVE-2026-31721, CVE-2026-31606) para um kernel de fornecedor Android Linux 4.14.190 em fim de vida (EOL), com registros de verificação no dispositivo.
Três correções em drivers/usb/gadget/function/f_hid.c, transportadas manualmente para um kernel vendor Android 4.14.190 e verificadas no dispositivo em que são executadas.
4.14 está em fim de vida. Não existe backport estável para nenhuma delas, e nenhum está por vir. Essa é a razão de ser deste repositório: o dispositivo é meu, e um desses defeitos o estava reiniciando.
Para ser preciso sobre quem mantém o quê: a árvore NetHunter a partir da qual isto é construído (AzeAstro/samsung_kernel_sm7125, branch OneUI-6) é ativamente mantida, e esse trabalho não está em questão aqui. Mas trata-se de habilitação de dispositivo e NetHunter — a base permanece fixada no vendor drop 4.14.190 da Samsung. Ninguém está transportando correções de segurança estáveis do 4.14.y para dentro dela, e a Samsung também não as está distribuindo — o próprio 4.14.y terminou em 4.14.336 em 10 de janeiro de 2024, e a série não está mais listada entre as versões suportadas do kernel.org. É nessa lacuna que estes três patches se inserem.
Eu não descobri estas vulnerabilidades. Todas as três são publicadas, com patches upstream escritos por outras pessoas, creditadas abaixo. O que fiz:
O ponto 3 é a parte que eu pediria ao leitor para ponderar. Duas destas são confirmadas por medição contra uma linha de base. A terceira não é, e não pode ser, neste dispositivo. Essa distinção é mantida ao longo de todo o texto em vez de suavizada.
| # | Patch | Autor upstream | Status no hardware |
|---|---|---|---|
| 1 | Tempo de vida de f_hidg vs cdev (use-after-free) | John Keeping, 2022-11-22 | ✅ eficácia medida |
| 2 | CVE-2026-31721 — estado de tempo de vida do objeto reinicializado em hidg_bind | Michael Zimmermann, 2026-03-31 | ✅ eficácia medida |
| 3 | CVE-2026-31606 — cdev_init em um cdev em uso | Michael Zimmermann (81ebd43cc0d6d), 2026-03-27; follow-up de -ENOMEM por Ethan Tidmore, 2026-04-02 | ⛔ não observável neste dispositivo |
f_hidg vs cdevf_hidg_open armazenava o f_hidg * e não tomava nenhuma referência. f_hidg_poll registrava waiters de poll em wait queues embutidas no f_hidg alocado por kzalloc. hidg_unbind chamava cdev_del(), que não invalida fds abertos, e hidg_free então fazia kfree do objeto. Nada o contava por referência contra arquivos abertos — um grep de arquivo inteiro por refcount|kref|atomic_|open_count encontrou apenas opts->lock.
Assim, um loop de select()/poll() em /dev/hidg0 que sobrevivesse à recomposição do gadget pelo Android chamaria remove_wait_queue() em slab liberado, tomaria spin_lock_irqsave em uma palavra de ticket inválida, e giraria com IRQs mascaradas para sempre. O timer local para, a thread que alimenta o watchdog nunca é acordada, e o watchdog não seguro dispara ~11 s depois. Na prática: o aparelho reinicia forçadamente. O UsbDeviceManager do Android recompõe por conta própria — medido duas vezes em ~550 ms — sem ação do usuário, então isto não precisava de nenhum gatilho exótico.
Correção upstream: "usb: gadget: f_hid: fix f_hidg lifetime vs cdev", John Keeping, 2022-11-22, Fixes: 71adf1189469. O backport é limpo — toda primitiva existe no 4.14.190 — exceto o kfree(hidg->set_report_buf) do patch, um campo que esta versão não possui, o qual é descartado.
CVSS 3.1 5.5, vetor AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H. hidg_bind reinicializava estado pertencente ao tempo de vida do objeto, não ao do bind: dois spinlocks, duas cabeças de wait queue e uma cabeça de lista, nenhum dos quais aparece em hidg_alloc. init_waitqueue_head redefine a cabeça da lista para apontar para si mesma, então um waiter de poll/select registrado antes de um rebind fica silenciosamente órfão, e seu poll_freewait → remove_wait_queue então encontra prev->next errado.
Upstream move nove inicializações para fora de hidg_bind; apenas cinco delas existem em 4.14.190.
⚠ Vários avisos de fornecedores intitulam isto de "escalada de privilégios". O vetor é C:N/I:N/A:H —
apenas disponibilidade, ou seja, uma negação de serviço local. Sinalizo isto porque a leitura mais assustadora teria
justificado uma prioridade que o vetor não sustenta, e ninguém pede a uma página de fornecedor que se prove.
Este patch também vai um passo além do upstream. Mover as inicializações para fora de hidg_bind deixa duas
escritas sem bloqueio em campos protegidos por lock, mascaradas hoje apenas porque o lock estava a ser
re-inicializado imediatamente acima delas. Uma vez que o lock é persistente, a corrida é real: um escritor bloqueado
no ciclo de espera de f_hidg_write, num fd que sobreviveu ao unbind, pode adquirir o lock, passar a verificação
e ler hidg->req enquanto o bind armazena NULL fora dele — a re-verificação vem depois da desreferenciação.
Assim, o patch aplica write_spinlock em torno de ambas as escritas.
A forma geral merece ser enunciada: mover uma inicialização para fora do bind pode criar uma corrida que a re-inicialização estava a esconder. Pergunte o que a inicialização removida estava acidentalmente a proteger.
CVSS 5.5, mesmo vetor. struct cdev cdev está embutido em struct f_hidg, e hidg_bind chamava
cdev_init() sobre ele — que é memset(cdev, 0, sizeof *cdev) mais kobject_init — imediatamente
antes de cdev_device_add. Um fd aberto mantém uma referência sobre esse mesmo kobject via chrdev_open →
cdev_get → kobject_get_unless_zero. No unbind→rebind com um fd aberto, o bind apaga estado de referência
vivo num objeto referenciado.
A correção é uma alteração de struct em vez de uma guarda: o struct cdev embutido passa a ser um struct cdev *
com um cdev_alloc() por bind. cdev_init passa então a aparecer zero vezes no ficheiro — não resta nenhum
objeto em uso para re-inicializar.
⛔ Este dispositivo não consegue detetar este defeito, antes ou depois da correção. Ver
verification/03-cve-2026-31606.md. Isso é uma afirmação positiva sobre a instrumentação do dispositivo, não
uma ressalva.
Método e registos por defeito em verification/. A versão curta: as correções 1 e 2
foram fechadas por regressão de variável única contra a build imediatamente anterior, com controlos
positivos, e a correção 2 foi confirmada uma segunda vez a partir de uma fonte de log diferente. A correção 3 é evidenciada apenas
pela leitura do código-fonte.
Os patches são saída de git format-patch contra uma árvore de fornecedor Samsung 4.14.190, por ordem:
git am patches/0001-*.patch patches/0002-*.patch patches/0003-*.patch
São cumulativos e devem ser aplicados em sequência — o 3 pressupõe a struct como o 1 a deixou.
GPL-2.0, como obras derivadas do kernel Linux. Consulte LICENSE. A autoria original das correções originais está creditada acima; o backporting, o locking adicionado no patch 2 e o trabalho de verificação são meus.
Nenhum artefato compilado é distribuído aqui — nenhuma imagem de kernel, nenhum módulo, nenhum pacote flashável. Este repositório contém apenas código-fonte e evidências.