
Backports de trois correctifs f_hid publiés (dont CVE-2026-31721, CVE-2026-31606) vers un noyau Android vendor Linux 4.14.190 en fin de vie, avec enregistrements de vérification sur l'appareil.
Trois correctifs pour drivers/usb/gadget/function/f_hid.c, portés à la main vers un noyau vendeur Android 4.14.190 et vérifiés sur l'appareil sur lequel ils s'exécutent.
La 4.14 est en fin de vie. Aucun rétroportage stable n'existe pour aucun de ces correctifs, et aucun n'est à venir. C'est précisément la raison d'être de ce dépôt : l'appareil est le mien, et l'un de ces défauts le réinitialisait.
Pour être précis sur qui maintient quoi : l'arborescence NetHunter à partir de laquelle ceci est construit (AzeAstro/samsung_kernel_sm7125, branche OneUI-6) est activement maintenue, et ce travail n'est pas remis en question ici. Mais il s'agit de l'activation de l'appareil et de NetHunter — la base reste figée sur la livraison vendeur 4.14.190 de Samsung. Personne ne rétroporte les correctifs de sécurité stables 4.14.y dans cette base, et Samsung ne les fournit pas non plus — la branche 4.14.y elle-même s'est arrêtée à 4.14.336 le 10 janvier 2024, et la série n'est plus listée parmi les versions supportées de kernel.org. C'est dans cet écart que s'inscrivent ces trois correctifs.
Je n'ai pas découvert ces vulnérabilités. Toutes les trois sont publiées, avec des correctifs amont écrits par d'autres personnes, créditées ci-dessous. Ce que j'ai fait :
Le point 3 est celui que je demanderais au lecteur de peser. Deux d'entre eux sont confirmés par mesure par rapport à une référence. Le troisième ne l'est pas, et ne peut pas l'être, sur cet appareil. Cette distinction est maintenue tout au long plutôt que lissée.
| # | Correctif | Auteur amont | État sur le matériel |
|---|---|---|---|
| 1 | Durée de vie de f_hidg vs cdev (use-after-free) | John Keeping, 2022-11-22 | ✅ efficacité mesurée |
| 2 | CVE-2026-31721 — état de durée de vie d'objet réinitialisé dans hidg_bind | Michael Zimmermann, 2026-03-31 | ✅ efficacité mesurée |
| 3 | CVE-2026-31606 — cdev_init sur un cdev en cours d'utilisation | Michael Zimmermann (81ebd43cc0d6d), 2026-03-27 ; suivi -ENOMEM par Ethan Tidmore, 2026-04-02 | ⛔ non observable sur cet appareil |
f_hidg vs cdevf_hidg_open stockait le f_hidg * et ne prenait aucune référence. f_hidg_poll enregistrait des attentes de poll sur des files d'attente intégrées au f_hidg alloué par kzalloc. hidg_unbind appelait cdev_del(), qui n'invalide pas les descripteurs de fichiers ouverts, puis hidg_free libérait l'objet avec kfree. Rien ne le comptait par rapport aux fichiers ouverts — un grep sur tout le fichier pour refcount|kref|atomic_|open_count ne trouvait que opts->lock.
Ainsi, une boucle select()/poll() sur /dev/hidg0 qui survivait à une recomposition du gadget par Android appelait remove_wait_queue() sur de la mémoire slab libérée, prenait spin_lock_irqsave sur un mot de ticket corrompu, et tournait avec les IRQ masquées indéfiniment. Le minuteur local s'arrête, le thread de surveillance du watchdog n'est jamais réveillé, et le watchdog non sécurisé aboie environ 11 s plus tard. En pratique : le combiné se réinitialise brutalement. Le UsbDeviceManager d'Android recompose de lui-même — mesuré deux fois à environ 550 ms — sans action de l'utilisateur, donc cela ne nécessitait aucun déclencheur exotique.
Correctif amont : « usb: gadget: f_hid: fix f_hidg lifetime vs cdev », John Keeping, 2022-11-22, Fixes: 71adf1189469. Le rétroportage est propre — chaque primitive existe en 4.14.190 — sauf le kfree(hidg->set_report_buf) du correctif, un champ absent de cette version, qui est abandonné.
CVSS 3.1 5.5, vecteur AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H. hidg_bind réinitialisait un état appartenant à la durée de vie de l'objet, et non à celle du bind : deux spinlocks, deux têtes de file d'attente, et une tête de liste, dont aucun n'apparaît dans hidg_alloc. init_waitqueue_head réinitialise la tête de liste pour qu'elle pointe vers elle-même, si bien qu'une attente poll/select enregistrée avant un rebind est silencieusement orpheline, et son poll_freewait → remove_wait_queue trouve alors prev->next incorrect.
Upstream déplace neuf initialisations hors de hidg_bind ; seules cinq d'entre elles existent en 4.14.190.
⚠ Plusieurs avis de fournisseurs intitulent ceci « élévation de privilèges ». Le vecteur est C:N/I:N/A:H —
disponibilité uniquement, c'est-à-dire un déni de service local. Je le signale parce que la lecture la plus alarmante aurait
justifié une priorité que le vecteur ne soutient pas, et personne ne demande à une page de fournisseur de faire ses preuves.
Ce correctif va aussi un cran plus loin qu'upstream. Déplacer les initialisations hors de hidg_bind laisse deux
écritures non verrouillées vers des champs protégés par verrou, masquées aujourd'hui uniquement parce que le verrou était
ré-initialisé immédiatement au-dessus d'elles. Une fois le verrou persistant, la course est réelle : un écrivain bloqué
dans la boucle d'attente de f_hidg_write, sur un fd qui a survécu au unbind, peut prendre le verrou, passer la vérification
et lire hidg->req tandis que bind y stocke NULL en dehors — la re-vérification intervient après le déréférencement.
Le correctif prend donc write_spinlock autour des deux écritures.
La forme générale mérite d'être énoncée : déplacer une initialisation hors de bind peut créer une course que la ré-initialisation masquait. Demandez-vous ce que l'init supprimée protégeait accidentellement.
CVSS 5.5, même vecteur. struct cdev cdev est intégré dans struct f_hidg, et hidg_bind appelait
cdev_init() dessus — ce qui correspond à memset(cdev, 0, sizeof *cdev) plus kobject_init — immédiatement
avant cdev_device_add. Un fd ouvert détient une référence sur ce même kobject via chrdev_open →
cdev_get → kobject_get_unless_zero. Lors d'un unbind→rebind avec un fd ouvert, bind efface un état de référence
vivant sur un objet référencé.
Le correctif est un changement de structure plutôt qu'une garde : le struct cdev intégré devient un struct cdev *
avec un cdev_alloc() par bind. cdev_init n'apparaît alors plus aucune fois dans le fichier — il ne reste aucun
objet en cours d'utilisation à ré-initialiser.
⛔ Ce dispositif ne peut pas détecter ce défaut, avant ni après le correctif. Voir
verification/03-cve-2026-31606.md. Il s'agit d'une affirmation positive sur l'instrumentation du dispositif, non
d'une précaution oratoire.
Méthode et enregistrements par défaut dans verification/. En bref : les correctifs 1 et 2
ont été validés par régression à variable unique contre la compilation immédiatement précédente, avec des
contrôles positifs, et le correctif 2 a été confirmé une seconde fois à partir d'une source de journal différente. Le correctif 3 n'est étayé que par
la lecture du code source.
Les correctifs sont la sortie de git format-patch contre une arborescence fournisseur Samsung 4.14.190, dans l'ordre :
git am patches/0001-*.patch patches/0002-*.patch patches/0003-*.patch
Elles sont cumulatives et doivent être appliquées dans l'ordre — la 3 suppose la struct telle que la 1 l'a laissée.