
Backport di tre correzioni f_hid pubblicate (incl. CVE-2026-31721, CVE-2026-31606) a un kernel vendor Android Linux 4.14.190 EOL, con registrazioni di verifica sul dispositivo.
Tre correzioni a drivers/usb/gadget/function/f_hid.c, trasportate a mano su un kernel vendor Android 4.14.190 e verificate sul dispositivo su cui vengono eseguite.
La 4.14 è a fine vita. Non esiste alcun backport stabile per nessuna di queste, e nessuno arriverà. È proprio questa la ragione per cui esiste questo repository: il dispositivo è mio, e uno di questi difetti lo stava resettando.
Per essere precisi su chi mantiene cosa: l'albero NetHunter da cui questo è costruito
(AzeAstro/samsung_kernel_sm7125, branch
OneUI-6) è attivamente mantenuto, e quel lavoro qui non è in discussione. Ma si tratta di abilitazione del dispositivo e di NetHunter — la base resta fissata al drop vendor 4.14.190 di Samsung. Nessuno vi trasporta le correzioni di sicurezza stabili della 4.14.y, e nemmeno Samsung le distribuisce — la 4.14.y stessa è terminata alla 4.14.336 il 10 gennaio 2024, e la serie non è più elencata tra le release supportate di kernel.org. È in questo vuoto che si collocano queste tre patch.
Non ho scoperto queste vulnerabilità. Tutte e tre sono pubblicate, con patch upstream scritte da altre persone, accreditate di seguito. Cosa ho fatto:
Il punto 3 è la parte che chiederei al lettore di soppesare. Due di queste sono confermate da misurazione rispetto a una baseline. La terza non lo è, e non può esserlo, su questo dispositivo. Tale distinzione è mantenuta in tutto il documento anziché essere appianata.
| # | Patch | Autore upstream | Stato sull'hardware |
|---|---|---|---|
| 1 | Durata di f_hidg vs cdev (use-after-free) | John Keeping, 2022-11-22 | ✅ efficacia misurata |
| 2 | CVE-2026-31721 — stato della durata dell'oggetto re-inizializzato in hidg_bind | Michael Zimmermann, 2026-03-31 | ✅ efficacia misurata |
| 3 | CVE-2026-31606 — cdev_init su un cdev in uso | Michael Zimmermann (81ebd43cc0d6d), 2026-03-27; follow-up -ENOMEM di Ethan Tidmore, 2026-04-02 | ⛔ non osservabile su questo dispositivo |
f_hidg vs cdevf_hidg_open memorizzava il f_hidg * e non prendeva alcun riferimento. f_hidg_poll registrava i waiter di poll sulle wait queue incorporate nel f_hidg allocato con kzalloc. hidg_unbind chiamava cdev_del(), che non invalida i fd aperti, e hidg_free eseguiva poi kfree sull'oggetto. Nulla lo contava tramite refcount rispetto ai file aperti — un grep sull'intero file per refcount|kref|atomic_|open_count trovava solo opts->lock.
Quindi un ciclo select()/poll() su /dev/hidg0 che sopravviveva alla ricomposizione del gadget da parte di Android avrebbe chiamato remove_wait_queue() su slab liberata, preso spin_lock_irqsave su una parola ticket spazzatura, e girato con gli IRQ mascherati per sempre. Il timer locale si ferma, il thread che tiene vivo il watchdog non viene mai svegliato, e il watchdog non-secure abbaia ~11 s dopo. In pratica: il telefono si resetta in modo brusco. UsbDeviceManager di Android ricompone da solo — misurato due volte a ~550 ms — senza alcuna azione dell'utente, quindi questo non richiedeva alcun trigger esotico.
Correzione upstream: "usb: gadget: f_hid: fix f_hidg lifetime vs cdev", John Keeping, 2022-11-22,
Fixes: 71adf1189469. Il backport è pulito — ogni primitiva esiste nella 4.14.190 — tranne il
kfree(hidg->set_report_buf) della patch, un campo che questa versione non ha, che viene omesso.
CVSS 3.1 5.5, vettore AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H. hidg_bind re-inizializzava stato
appartenente alla durata dell'oggetto, non a quella del bind: due spinlock, due teste di wait queue e una testa di lista, nessuna delle quali compare in hidg_alloc. init_waitqueue_head reimposta la testa di lista in modo che punti a se stessa, quindi un waiter di poll/select registrato prima di un rebind viene silenziosamente orfanato, e il suo poll_freewait → remove_wait_queue trova poi prev->next errato.
Upstream sposta nove inizializzazioni fuori da hidg_bind; solo cinque di esse esistono alla 4.14.190.
⚠ Diversi avvisi dei fornitori intitolano questo "privilege escalation". Il vettore è C:N/I:N/A:H —
solo disponibilità, cioè un denial of service locale. Lo segnalo perché la lettura più allarmante avrebbe
giustificato una priorità che il vettore non supporta, e nessuno chiede a una pagina di fornitore di dimostrare sé stessa.
Questa patch va anche un passo oltre upstream. Spostare le inizializzazioni fuori da hidg_bind lascia due
scritture non protette su campi protetti da lock, oggi mascherate solo perché il lock veniva
re-inizializzato immediatamente sopra di esse. Una volta che il lock è persistente la race è reale: uno scrittore bloccato
nel ciclo di attesa di f_hidg_write, su un fd sopravvissuto all'unbind, può acquisire il lock, superare il controllo
e leggere hidg->req mentre bind memorizza NULL al di fuori di esso — il ricontrollo avviene dopo il dereference.
Quindi la patch prende write_spinlock attorno a entrambe le scritture.
Vale la pena enunciare la forma generale: spostare un'inizializzazione fuori da bind può creare una race che la re-inizializzazione stava nascondendo. Chiediti cosa stava proteggendo accidentalmente l'init rimossa.
CVSS 5.5, stesso vettore. struct cdev cdev è incorporata in struct f_hidg, e hidg_bind chiamava
cdev_init() su di essa — che è memset(cdev, 0, sizeof *cdev) più kobject_init — immediatamente
prima di cdev_device_add. Un fd aperto mantiene un riferimento su quello stesso kobject tramite chrdev_open →
cdev_get → kobject_get_unless_zero. Su unbind→rebind con un fd aperto, bind cancella lo stato di riferimento vivo
su un oggetto referenziato.
La correzione è un cambio di struct anziché una guardia: la struct cdev incorporata diventa un struct cdev *
con un cdev_alloc() per ogni bind. cdev_init compare quindi zero volte nel file — non resta alcun
oggetto in uso da re-inizializzare.
⛔ Questo dispositivo non può rilevare questo difetto, né prima né dopo la correzione. Vedi
verification/03-cve-2026-31606.md. Questa è un'affermazione positiva sulla strumentazione del dispositivo, non
una cautela.
Metodo e registrazioni per singolo difetto in verification/. La versione breve: le correzioni 1 e 2
sono state chiuse tramite regressione a variabile singola rispetto alla build immediatamente precedente, con controlli
positivi, e la correzione 2 è stata confermata una seconda volta da una diversa fonte di log. La correzione 3 è dimostrata solo
dalla lettura del codice sorgente.
Le patch sono output di git format-patch rispetto a un albero vendor Samsung 4.14.190, in ordine:
git am patches/0001-*.patch patches/0002-*.patch patches/0003-*.patch
Sono cumulativi e vanno applicati in sequenza — il 3 presuppone la struct così come l'ha lasciata l'1.
GPL-2.0, in quanto opere derivate del kernel Linux. Vedi LICENSE. La paternità upstream delle correzioni originali è attribuita sopra; il backporting, il locking aggiunto nella patch 2 e il lavoro di verifica sono miei.