
Ricerca LPE sul kernel Android per CVE-2026-98260 (UAF in posix-cpu-timers): PoC di trigger, catene shell uid-0 con credenziali false e reali, più un'app di bootstrap Shizuku/KernelSU.
Italiano | 简体中文
Ricerca di privilege escalation locale su Android per un use-after-free nel percorso di uscita di posix-cpu-timers. Il repository include un PoC di solo trigger, un exploit di verifica con credenziali false, uno stage shell uid-0 con credenziali reali, un'app KernelSU basata su Shizuku e un pre-check della vulnerabilità.
Branch
magicos-11.0.0.109-6.12.58: questo branch è dual-profile. L'APK e i binari nativi selezionano un profilo dal valore esatto diuname -ra runtime: MagicOS 10.0.0 / 6.12.38 è il profilo verificato, mentre MagicOS 11.0.0.109 / 6.12.58 è adattato staticamente e la sua validazione runtime lato dispositivo è ancora in sospeso. L'APK include entrambe le tabelleko-symsda 226 simboli e seleziona quella corrispondente.
| Voce | Valore |
|---|---|
| CVE | CVE-2026-98260 |
| Classe | LPE (use-after-free, cancellazione dall'albero rb di timerqueue) |
| Target | 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 (verificato)6.12.58-android16-6-g3a7ac1674240-abogki541502914-4k (adattato) |
| Stato del profilo | dual-profile; 6.12.38 verificato, validazione runtime di 6.12.58 in sospeso |
| SELinux | Enforcing |
| Patch | assenti sulla build testata |
| Mitigazioni | KASLR attivo, SMEP/SMAP/PAN attivi, KASAN compilato ma disabilitato |
| Bootloader | bloccato e non sbloccabile; nessuna scrittura su partizione |
| Componente | Stato | Risultato |
|---|---|---|
src/poc.c | verificato su 6.12.38 | trigger distruttivo; il reap va in panic in cleanup_timerqueue |
src/exp.c | verificato su 6.12.38 | credenziali false; getuid()==0 && geteuid()==0; non può eseguire exec |
src/uid0.c | verificato su 6.12.38 | credenziali reali; shell uid-0 in u:r:shell:s0 |
app/ | verificato su 6.12.38 | bootstrap Shizuku + KernelSU; uid 0 in u:r:ksu:s0 |
src/vuln_precheck.h | aggiunto | gate profilo/ambiente; --check / --force |
| Profilo 6.12.58 | adattato | siti, selinux_state e la sua tabella ko-syms da 226 simboli ri-derivati; esecuzione su dispositivo in sospeso |
| APK dual-profile | testato su 6.12.38 | selezione profilo e ko-syms tramite uname -r; percorso live-KSU su 6.12.38 superato |
Release corrente: v1.4.3-app.
La pre-release v1.4.2-app è stata ritirata il 2026-10-30; il suo tag è mantenuto
solo per motivi storici. Vedi docs/release-notes.md per il
registro delle release e le lacune note.
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
Un thread non leader che ha creato un timer posix CLOCK_THREAD_CPUTIME_ID e
che poi fallisce execve() dopo de_thread() mantiene il nodo del timer in coda. Il
SIGKILL che interrompe l'exec fa uscire il thread tramite
exit_itimers(), che non può disattivare un timer il cui task è già in uscita.
Il k_itimer viene liberato mentre il suo timerqueue_node è ancora collegato nella
coda posix_cputimers del task. Quando il processo padre raccoglie lo zombie,
release_task() -> posix_cpu_timers_exit() -> cleanup_timerqueue() chiama
timerqueue_del()/rb_erase() sul nodo liberato.
L'erase è la primitiva di scrittura: rb_erase() memorizza un valore
__rb_parent_color controllato in un target rb_left controllato. L'exploit
coreografa il reclaim di slab/page in modo che il nodo liberato si trovi in una pagina riempita con
il payload dell'attaccante, poi sceglie il target e il valore.
Vedi docs/analysis.md per il trace del panic osservato, gli
offset YLP-W00, i leak basati su tracefs, la coreografia SLUB/RCU, la
catena fake-cred a due stadi e la catena real-cred a scrittura singola.
Requisiti: Android NDK r29 in /opt/android-ndk-r29 (sovrascrivibile con
NDK=). I target sono aarch64, API 24 per impostazione predefinita. L'app necessita inoltre
dell'Android SDK (build-tools 37.0.0, platform 37), di un JDK e dei jar Shizuku
in ~/.local/share/android-cve-env/cve98260-app (sovrascrivibile con
APP_DEPS=). Gli artefatti di build restano in build/ e sono ignorati da git. I
jar Shizuku sono forniti dall'utente; scripts/fetch-deps.sh li verifica rispetto a
deps.lock (SHA-256) e non scarica mai nulla.
| Target | Output / azione |
|---|---|
make / make poc | build/poc |
make exp | build/exp |
make uid0 | build/uid0 (statico; lo stadio shell riesegue se stesso) |
make build-all | build di poc + exp + uid0 + apk |
make host | build/poc.static per la validazione qemu lato host |
make emu | esegue build/poc.static sotto qemu (con --force) |
make apk | app/CVE98260Root.apk |
make clean | rimuove build/ |
make help | elenca i target |
poc, exp e uid0 chiamano un controllo del profilo prima di toccare il kernel. Accetta
o la release esatta del profilo (6.12.38 verificata, 6.12.58 adattata),
richiede arm64 e pagine da 4 KiB, e per exp/uid0 controlla anche la
disponibilità di tracefs/kmem e (per la catena fake-cred) panic_on_warn=0. Qualsiasi altra
release non è confermata e il programma esce con codice 2:
build/uid0 --check # print the verdict, do not trigger
build/uid0 --check --force # same, overriding an unconfirmed environment
Usa --force o CVE98260_FORCE=1 solo dopo aver verificato il kernel in modo indipendente. --force allenta solo i controlli sull'ambiente; exp, uid0 e l'app richiedono comunque un profilo esatto da kernel_profile.h. Il gate non è deliberatamente una sonda a runtime: una sonda che raggiungesse il bug lascerebbe essa stessa un nodo timerqueue obsoleto.
Su YLP-W00 una shell non privilegiata non può leggere /proc/sys/kernel/panic_on_warn né /proc/cmdline; per un profilo noto il gate assume il valore 0 validato per la 6.12.38. La stessa assunzione viene usata per il profilo adattato 6.12.58 finché la sua validazione a runtime non sarà completata. Le release sconosciute restano bloccate.
L'exploit parte come un normale processo shell (uid 2000, SELinux u:r:shell:s0) e non usa su. Non avviarlo tramite su: la condizione testata è un avvio non privilegiato.
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
Prima della catena completa, utilizzare le modalità non distruttive:
adb shell /data/local/tmp/uid0 --probe-ids # discover kmem trace event IDs
adb shell /data/local/tmp/uid0 leak # timer/cred leak only; no UAF/reap