
Analyse, POC und arm64-Exploit-Kette für einen Linux cifs.ko DACL Heap-Overflow (CVE-2026-97555), der auf dem Referenz-Build zu einer Überschreibung der uid-0-Anmeldeinformationen eskaliert.
Deutsch | 简体中文
Kernel-Heap-Overflow beim Umschreiben der DACL im SMB-Client. id_mode_to_cifs_acl()
dimensioniert den Ausgabe-Sicherheitsdeskriptor anhand der vom Server zurückgegebenen DACL,
aber replace_sids_and_copy_aces() schreibt jede ACE mit der neuen
Owner/Group-SID (bis zu 68 Bytes) neu. Ein Server, der eine DACL mit kurzen SIDs
zurückgibt, lässt das Umschreiben daher die Allokation überwachsen und über das
kmalloc-Objekt hinausschreiben.
Dieses Repository enthält die Analyse, einen POC, der den Cross-Object- Write reproduziert, und einen EXP, der den Overflow in eine kontrollierte End-to-End-Kette (Heap-Leak, Page-Table-Reclaim, beliebiger PTE-Write, Self-Cred-Overwrite auf uid 0) auf dem Referenz-Build verwandelt.
| Element | Wert |
|---|---|
| CVE | CVE-2026-97555 |
| Klasse | Heap-Buffer-Overflow (Write, CWE-122) in id_mode_to_cifs_acl() |
| Komponente | cifs.ko (SMB-Client) |
| Betroffen | Linux cifs.ko ohne den Fix (validiert gegen einen 6.12-GKI-basierten Android-Kernel) |
| Patch | Upstream 0ee150794c75 (Stable fb736ab4fb8c) |
| Referenz-Build | Android 16 GKI-basierter 6.12-Kernel, arm64, 4 KiB Pages, 64-Byte struct page |
| POC | Cross-Object-Write reproduziert (SET_INFO-Sicherheitsdeskriptor von 696 Bytes) |
| EXP | Vollständige Kette bis zu einer uid-0-Shell auf dem Referenz-Build |
/proc/kpageflags als root. Das Erlangen der Mount-
Capability ist ein Test-Fixture, nicht Teil des Bugs, und liegt außerhalb des Umfangs
dieses Repositories.uid/suid/euid/fsuid ihrer eigenen Credential neu schreibt;
kein Capability- oder Securebits-Feld wird berührt. Die Bereinigung ist ein Reboot.src/exp.c (Symboladressen, vmemmap-Geometrie, BTF-
Offsets) gehören zum Referenz-Build. Leite sie für jeden anderen
Build neu ab, bevor du ihn ausführst; siehe scripts/derive_offsets.sh.id_mode_to_cifs_acl() berechnet die Allokation aus dacl_ptr->size oder
num_aces * sizeof(struct smb_ace), dann baut replace_sids_and_copy_aces()
jede ACE mit der neuen Owner/Group-SID neu auf. Wenn die SIDs des Servers
kurz sind (0-Subauth) und die neue SID 68 Bytes groß ist, ist die umgeschriebene DACL länger
als die Allokation und build_sec_desc() schreibt über das Slab-Objekt hinaus.
Die dynamische FORTIFY-Instrumentierung des Kernels prüft die inlined ACE-Header-
Stores, aber die SID-Kopien laufen über das Out-of-Line-cifs_copy_sid() und
sind nicht instrumentiert. Eine DACL, die so ausgerichtet ist, dass jeder ACE-Header innerhalb von
nsecdesclen - 28 bleibt, erreicht die verwundbare Kopie.
Das cross7-Server-Layout (7 ACEs, Quellgrößen 76/76/76/44/44/16/16) macht
die Allokation exakt 512 Bytes groß; die 68-Byte-SID-Kopie der 7. ACE beginnt bei
Offset 492 und überschreitet die Objektgrenze, und der umgeschriebene Deskriptor
wächst auf 696 Bytes — 184 Bytes über das Objekt hinaus. Der erweiterte Deskriptor wird
weiterhin in SET_INFO ausgeliefert, sodass der Write von der Server-
Seite aus beobachtbar ist.
fb736ab4fb8c gebaut wurde.kpageflags, Mount,
chown) und ein SELinux-Permissive-Fenster (oder eine gleichwertige Policy-
Erlaubnis) für den cifs-idmap-Upcall.src/poc_smb2_acl.py bereitgestellte Freigabe, gemountet mit
cifsacl/modefromsid über den Standard-Kernel-Client
(scripts/mount_cifs.sh). Der Mount selbst ist ein privilegiertes Fixture; wie
der Tester dieses Privileg erlangt, liegt außerhalb dieses Repositories.cifs.idmap-Keys, vorab geladen für die Ziel-IDs: Die Referenzumgebung
hat keinen /sbin/cifs.idmap-Upcall-Helper (src/poc.c und src/exp.c
laden sie vor; src/acl_key_preload.c erledigt das eigenständig).# host
python3 src/poc_smb2_acl.py # ACL_LAYOUT=cross7 for the overflow
adb reverse tcp:1468 tcp:1468
# device (root)
sh /data/local/tmp/e2e/mount_cifs.sh 127.0.0.1 share /mnt/cifs
make && make push
/data/local/tmp/poc /mnt/cifs
Erwarteter Nachweis: Das Server-Log gibt
SET_INFO security: len=696 ... aus (cross7), d. h. einen Deskriptor, der länger als
die 512-Byte-Zuweisung ist, und scripts/trace_acl02.sh zeigt den
id_mode_to_cifs_acl-Eintritt/-Rückkehr um das chown herum.
Der POC löst nur den verwundbaren Pfad aus; er führt kein Grooming, kein Leaken und keine Eskalation durch. Der Trigger führt einen Out-of-Bounds-Kernel-Schreibvorgang aus: Builds, deren Layout die dynamischen FORTIFY-Prüfungen auslöst, können panicen, daher sollte er nur auf einem Test- Gerät ausgeführt werden, das neu gestartet werden kann.
Der EXP ist die vollständige Kette, Ende-zu-Ende auf dem Referenz-Build:
cross7-Overflow überschreibt das datalen einer benachbarten Key-Payload;
keyctl_read gibt 64 KiB aus, einschließlich eines Live-pipe_buffer-
Arrays. Der geleakte anon_pipe_buf_ops-Zeiger liefert den KASLR-Slide und
die Buffer-Seite P liefert das reale struct page-Ziel.buffer[0] einer Hit-Pipe wird
mit page=P, len=0x1234, ops=default_pipe_buf_ops überschrieben.P frei; ein Fault bei
base beansprucht sie als Page-Table-Seite zurück, abgesichert durch eine kretprobe, die beweist,
dass __alloc_pages_noprof P von __pte_alloc zurückgegeben hat.P+16 (PTE[2]) beginnt; seine ersten acht Bytes sind ein
Self-Map-Deskriptor für P selbst. Der Userland-Code liest und schreibt dann die
Page-Table-Seite über base+8 KiB.KPF_SLAB-Seiten werden über
frische PTEs gemappt und nach dem comm dieser Task durchsucht; pid/tgid, das Linear-Map-
Fenster und cred->uid/euid gleichen den Treffer ab, und frische, nie verwendete
VAs verifizieren die physische Seite erneut.setresuid(2000) aus (der
Kernel weist ein frisches cred zu), liest task->cred über die Self-Map,
nullt nur uid/suid/euid/fsuid, bestätigt getuid()==0, deaktiviert die
Self-Map und führt execve auf /system/bin/sh aus. Die Shell läuft als uid 0.make exp
make push
# on the device, as root:
sh /data/local/tmp/e2e/launch_exp.sh /mnt/cifs 10 1 r1 4000 400 2400 7 0 0 cred
# then read /data/local/tmp/e2e/e2e_status.txt and
# /data/local/tmp/e2e/root_shell.txt
Die cred-Stufe ist optional über das letzte Argument (cred) aktivierbar; scout stoppt
nach der schreibgeschützten Validierung. Der EXP behält seine PTEs innerhalb des Prozesses
und die Bereinigung erfolgt durch einen Neustart.
Voraussetzungen: Android NDK r29 (überschreibbar mit NDK=), GNU make, adb.