
PoC et chaîne d'exploitation pour CVE-2026-89633, une écriture hors limites dans le tas dans le chemin de coalescence SMB1 TRANS2 de cifs.ko sous Linux, permettant une élévation à l'uid 0 via l'écrasement des cred et l'aliasing des PTE.
coalesce_t2 de cifs SMB1English | 简体中文
PoC/EXP de recherche pour une écriture hors limites dans le tas dans le client SMB1 cifs.ko de Linux. Un serveur SMB1 malveillant contrôle DataOffset dans une réponse secondaire TRANS2, et coalesce_t2() copie la charge utile secondaire de 8 octets vers &primary->Protocol + DataOffset + accumulated sans aucune vérification des limites de la cible. La destination de la copie se trouve là où le serveur la pointe, relativement au tampon de réponse (server->bigbuf).
Ce dépôt contient deux démonstrations aux rôles distincts ; elles ne sont pas interchangeables :
DataOffset à l'exécution.uid/gid/suid/sgid/euid/egid/fsuid/fsgid. C'est la preuve la plus directe que l'écriture hors limites est contrôlable et peut modifier un cred, avec le moins de dépendances.selinux_state, et le clone devient uid 0 avec toutes les capacités sans observation côté root. C'est une extension de l'EXP 1, pas un remplacement.Le chargement de modules noyau est délibérément hors du périmètre de ce dépôt : la chaîne s'arrête à uid 0 (et éventuellement selinux_state en permissif) et n'installe pas de module. Même sans module chargé, uid 0 + permissif constitue la démonstration complète de LPE revendiquée par ce dépôt.
Les coûts de l'EXP 2 restent explicites : une loterie par fenêtre (~25–33 %, ~97–99 % sur 12 tentatives), une PTE nue qui reste active jusqu'au redémarrage, une dépendance à un tracefs inscriptible par le shell, et des hypothèses de direct-map/site d'appel/SEL_PFN propres à la build. L'EXP 1 reste la preuve de primitive la plus simple et la solution de repli lorsque le tracefs inscriptible par le shell n'est pas disponible.
| Élément | Valeur |
|---|---|
| CVE | CVE-2026-89633 |
| Classe | Écriture hors limites dans le tas (CWE-787) |
| Composant | cifs.ko, coalescence secondaire TRANS2 SMB1 |
| Correctif amont | 36d01415d881 ("smb: client: fix ... coalesce_t2", 6.12.113) |
| POC | Déclenchement et écriture hors limites vérifiés (après montage et pendant le montage) |
| EXP 1 (primitive minimale) | Écriture de cred hors limites à 4 trames sous observation côté root ; uid 0 vérifié (après montage et réponse retenue au moment du montage) |
| EXP 2 (primitive avancée) | chaîne PTE shell-only ; uid 2000 → uid 0 + toutes les capacités + Permissive vérifié (SEL_PFN/SEL_OFF) ; s'arrête à une PTE nue, aucun chargement de module |
| Prérequis | un montage SMB1 vers un serveur contrôlé par l'attaquant (barrière privilégiée) ; la barrière de référence utilise su, et le placement shell-only nécessite un tracefs inscriptible par le shell |
Environnement testé : une build vendor arm64 Android 16, noyau 6.12.38 avec cifs.ko compilé à partir de la base KMI 6.12.23, SELinux en mode enforcing.
statfs, sans accès au point de montage), le placement shell-only de l'EXP 2 (tracefs uniquement, sans observation côté root), l'acceptation complète de l'EXP 2 (uid 0 + tous les bits de capacité + Permissive avec SEL_PFN/SEL_OFF), et la barrière de montage pilotée par su (seul le montage s'exécute en root ; le placement s'exécute en uid 2000) ont été testés là-bas sous SELinux en mode enforcing.Pour une réponse SMB1 TRANS2 supérieure à MAX_CIFS_SMALL_BUFFER_SIZE - 4
(444 octets), le client conserve la réponse primaire dans server->bigbuf et la stocke dans
mid->resp_buf. Les réponses SMB_COM_TRANSACTION2_SECONDARY suivantes passent
par cifs_check_trans2() -> check2ndT2() -> coalesce_t2() :
data_area_of_tgt = &primary->Protocol + primary->DataOffset + total_in_tgt;
memcpy(data_area_of_tgt, data_area_of_src, secondary->DataCount);
DataOffset est repris tel quel de la réponse du serveur. Les vérifications de compteur
(débordement de total_in_tgt/byte_count, limites du tampon source) sont présentes, mais
il n'y a aucune vérification que data_area_of_tgt reste à l'intérieur du tampon de réponse.
Chaque trame secondaire se place 8 octets plus loin (le DataCount accumulé), donc une
réponse multi-trames produit une écriture de 8*N octets à un décalage choisi par l'attaquant
(16 bits) depuis la base du tampon.
Conditions de déclenchement :
vers=1.0) depuis un serveur contrôlé par l'attaquant ;statfs) ;TotalDataCount > DataCount et
suffisamment grande pour le chemin bigbuf, puis une ou plusieurs trames secondaires avec
TotalDataCount > DataCount.Makefile
src/poc/poc_smb1_trans2.py malicious SMB1 server (QFS hook, runtime ctl)
src/poc/smb1_proxy.py TCP proxy: patches DataOffset, repeats secondaries
src/exp/pipesearch.c pipe placement/write driver (modes 3, 8, 9)
src/exp/shcred.c shell-side drain + clone helper (uid 2000)
src/exp/shp4a2.c trace-based drainer/observer/clone (modes 8/9)
src/exp/pte_fault.c 2 MiB fault + PTE alias writes
src/exp/bscan.c bigbuf discovery by call site (no kprobes)
src/exp/kslide.c KASLR slide from a raw call site
src/exp/smbd.c device-side SMB1 server (held TRANS2)
src/exp/smbd_templates.h SMB1 response templates for smbd
src/exp/pfn.c linear-map VA to pfn
src/exp/kflags.c /proc/kpageflags reader
scripts/setup_device.sh trace instance + kprobes (root on device)
scripts/start_servers.sh host: SMB1 server + proxy + mount helper
scripts/probe_round.sh one probe round: bigbuf base + free target page
scripts/place.sh placement: pipesearch mode 3 + shcred
scripts/write_cred.sh 4-frame coalesce write + /proc verification
scripts/mount_su.sh generic su mount gate (target/decoy/abort/unmount)
scripts/chain_shell.sh shell-only placement for an already-held TRANS2
scripts/gen_smb_templates.py regenerates smbd_templates.h from a capture
docs/analysis.md vulnerability and chain analysis
docs/exp-protocol.md shared-page and smbd ctl protocols
make deps # checks impacket
make helpers # NDK cross-build of all C helpers
make push # adb push to /data/local/tmp
NDK, API, ARCH, ADB, DEVICE_DIR sont surchargeables.