
Android-Kernel-LPE-Forschung für CVE-2026-98260 (posix-cpu-timers UAF): Trigger-PoC, Fake-Cred- und Real-Cred-uid-0-Shell-Ketten sowie eine Shizuku/KernelSU-Bootstrap-App.
English | 简体中文
Android-Forschung zur lokalen Rechteausweitung für einen Use-after-Free im posix-cpu-timers-Exit-Pfad. Das Repository enthält einen Trigger-only-PoC, einen Fake-Cred-Verifikations-Exploit, eine Real-Cred-uid-0-Shell-Stage, eine Shizuku-basierte KernelSU-App und eine Vorabprüfung auf die Schwachstelle.
Branch
magicos-11.0.0.109-6.12.58: Dieser Branch ist dual-profile. Die APK und die nativen Binaries wählen zur Laufzeit anhand des exaktenuname -rein Profil aus: MagicOS 10.0.0 / 6.12.38 ist das verifizierte Profil, während MagicOS 11.0.0.109 / 6.12.58 statisch angepasst ist und seine geräteseitige Laufzeitvalidierung noch aussteht. Die APK bündelt beide 226-Symbol-ko-syms- Tabellen und wählt die passende aus.
| Element | Wert |
|---|---|
| CVE | CVE-2026-98260 |
| Klasse | LPE (Use-after-Free, Löschen im Timerqueue-rb-tree) |
| Ziel | Honor MagicPad3 Pro 13.3 YLP-W00, MagicOS 10.0.0 / 11.0.0.109 (Android 16), Branch magicos-11.0.0.109-6.12.58 |
| Kernel | 6.12.38-android16-5-gfde7767f6ef6-abogki481467632-4k (verifiziert)6.12.58-android16-6-g3a7ac1674240-abogki541502914-4k (angepasst) |
| Profilstatus | dual-profile; 6.12.38 verifiziert, 6.12.58 Laufzeitvalidierung ausstehend |
| SELinux | Enforcing |
| Patches | auf dem getesteten Build nicht vorhanden |
| Mitigations | KASLR an, SMEP/SMAP/PAN an, KASAN kompiliert, aber deaktiviert |
| Bootloader | gesperrt und nicht entsperrbar; keine Partition-Schreibzugriffe |
| Komponente | Status | Ergebnis |
|---|---|---|
src/poc.c | verifiziert auf 6.12.38 | destruktiver Trigger; reap löst Panic in cleanup_timerqueue aus |
src/exp.c | verifiziert auf 6.12.38 | Fake-Cred; getuid()==0 && geteuid()==0; kann nicht exec ausführen |
src/uid0.c | verifiziert auf 6.12.38 | echte Credentials; uid-0-Shell in u:r:shell:s0 |
app/ | verifiziert auf 6.12.38 | Shizuku + KernelSU-Bootstrap; uid 0 in u:r:ksu:s0 |
src/vuln_precheck.h | hinzugefügt | Profil-/Umgebungs-Gate; --check / --force |
| 6.12.58-Profil | angepasst | Sites, selinux_state und die zugehörige 226-Symbol-ko-syms-Tabelle neu abgeleitet; Gerätelauf ausstehend |
| Dual-Profile-APK | getestet auf 6.12.38 | Profil- und ko-syms-Auswahl per uname -r; 6.12.38 Live-KSU-Pfad bestanden |
Aktuelles Release: v1.4.3-app.
Das Pre-Release v1.4.2-app wurde am 2026-10-30 zurückgezogen; sein Tag bleibt
nur aus historischen Gründen erhalten. Siehe docs/release-notes.md
für den Release-Datensatz und bekannte Lücken.
src/poc.c trigger only; the reap panics on the tested kernel
src/exp.c fake-cred chain; verifies uid 0 via getuid/geteuid
src/uid0.c real-cred chain; zeroes cred->uid/gid and execs sh
src/kernel_profile.h per-release call sites, symbols and structure offsets
src/trace_ids.h runtime kmem trace event IDs; unprivileged probe
src/vuln_precheck.h shared profile/environment gate
app/ Android root app (Shizuku + KernelSU bootstrap)
docs/analysis.md root cause, exploit chain and device notes
scripts/dump_device.sh root device dump: all .ko/modinfo, kernel/config/policy, perf/tracefs, vendor mount, harness, active boot.img
Makefile single build entry (NDK cross-compile, app APK)
README.zh-CN.md Chinese version of this document
Ein Nicht-Leader-Thread, der einen CLOCK_THREAD_CPUTIME_ID-Posix-Timer erstellt hat und dann nach de_thread() bei execve() fehlschlägt, behält den Timer-Knoten in der Warteschlange. Das SIGKILL, das den Exec unterbricht, lässt den Thread über exit_itimers() beenden, was einen Timer nicht deaktivieren kann, dessen Task bereits beendet wird. Der k_itimer wird freigegeben, während sein timerqueue_node noch in der posix_cputimers-Warteschlange des Tasks verlinkt ist. Wenn der Parent den Zombie erntet, ruft release_task() -> posix_cpu_timers_exit() -> cleanup_timerqueue() timerqueue_del()/rb_erase() auf dem freigegebenen Knoten auf.
Das Löschen ist die Schreibprimitive: rb_erase() schreibt einen kontrollierten __rb_parent_color-Wert in ein kontrolliertes rb_left-Ziel. Der Exploit choreografiert Slab-/Page-Reclaim so, dass der freigegebene Knoten in einer Seite liegt, die mit der Payload des Angreifers gefüllt ist, und wählt dann Ziel und Wert.
Siehe docs/analysis.md für den beobachteten Panic-Trace, die YLP-W00-Offsets, die tracefs-basierten Leaks, die SLUB/RCU-Choreografie, die zweistufige Fake-Cred-Kette und die Real-Cred-Kette mit einem einzigen Schreibvorgang.
Voraussetzungen: Android NDK r29 unter /opt/android-ndk-r29 (überschreibbar mit NDK=). Ziele sind aarch64, standardmäßig API 24. Die App benötigt zusätzlich das Android SDK (build-tools 37.0.0, platform 37), ein JDK und die Shizuku-Jars unter ~/.local/share/android-cve-env/cve98260-app (überschreibbar mit APP_DEPS=). Build-Artefakte bleiben in build/ und werden von Git ignoriert. Die Shizuku-Jars werden vom Benutzer bereitgestellt; scripts/fetch-deps.sh verifiziert sie gegen deps.lock (SHA-256) und lädt niemals etwas herunter.
| Ziel | Ausgabe / Aktion |
|---|---|
make / make poc | build/poc |
make exp | build/exp |
make uid0 | build/uid0 (statisch; die Shell-Stage re-exect sich selbst) |
make build-all | baut poc + exp + uid0 + apk |
make host | build/poc.static für hostseitige qemu-Validierung |
make emu | führt build/poc.static unter qemu aus (mit --force) |
make apk | app/CVE98260Root.apk |
make clean | entfernt build/ |
make help | listet Ziele auf |
poc, exp und uid0 rufen ein Profil-Gate auf, bevor sie den Kernel berühren. Es akzeptiert entweder ein exaktes Profil-Release (6.12.38 verifiziert, 6.12.58 angepasst), erfordert arm64 und 4 KiB-Seiten und prüft für exp/uid0 zusätzlich die Verfügbarkeit von tracefs/kmem sowie (für die Fake-Cred-Kette) panic_on_warn=0. Jedes andere Release ist unbestätigt und das Programm beendet sich mit 2:
build/uid0 --check # print the verdict, do not trigger
build/uid0 --check --force # same, overriding an unconfirmed environment
Verwende --force oder CVE98260_FORCE=1 erst, nachdem der Kernel
unabhängig verifiziert wurde. --force lockert nur die Umgebungsprüfungen; exp, uid0
und die App erfordern weiterhin ein exaktes Profil aus kernel_profile.h. Die Sperre
ist bewusst keine Laufzeitprüfung: Eine Prüfung, die den Fehler erreicht, würde
selbst einen veralteten timerqueue-Knoten hinterlassen.
Auf YLP-W00 kann eine unprivilegierte Shell /proc/sys/kernel/panic_on_warn
oder /proc/cmdline nicht lesen; für ein bekanntes Profil nimmt die Sperre den für 6.12.38 validierten
Wert 0 an. Dieselbe Annahme wird für das angepasste 6.12.58-Profil verwendet, bis
seine Laufzeitvalidierung abgeschlossen ist. Unbekannte Releases bleiben blockiert.
Der Exploit startet als gewöhnlicher Shell-Prozess (uid 2000, SELinux
u:r:shell:s0) und verwendet kein su. Starte ihn nicht über su: Die
getestete Bedingung ist ein unprivilegierter Start.
make uid0
adb push build/uid0 /data/local/tmp/uid0
adb shell chmod 755 /data/local/tmp/uid0
adb shell /data/local/tmp/uid0 # runs id and prints the context
adb shell /data/local/tmp/uid0 shell # interactive sh -i
Vor der vollständigen Kette die nicht-destruktiven Modi verwenden: