
Local root exploit for CVE-2025-21479 (Adreno KGSL) on iQOO Neo8 (SM8475) - physical memory r/w, disables SELinux, spawns root shell
Local privilege escalation proof-of-concept code targeting CVE-2025-21479, aimed at the Qualcomm Adreno GPU (KGSL) on the vivo V2301A (iQOO Neo8 / PD2301, Snapdragon 8+ Gen 1 / SM8475, Adreno A730, Android 14, GKI kernel 5.10).
In principle, the exploit drives the GPU's SMMU through KGSL's CP_SMMU_TABLE_UPDATE command,
gaining arbitrary physical memory read/write capability, and then disables SELinux, locates kernel symbols, and obtains a root
shell—all without flashing the device or relying on an unlocked bootloader.
For security research only on devices you own or are authorized to test.
and $12, 0x3) is flawed and misclassifies Self-Draw-State (SDS) packets as
RingBuffer (RB). Therefore, by submitting a forged CP_SMMU_TABLE_UPDATE inside an SDS
block from user space, the GPU's TTBR0 can be overwritten, making the GPU page table point to an arbitrary physical page—from then on the GPU
can read and write arbitrary physical memory.stabilize_rw, the mapping is hardened into a 2 MB self-mapping region,
supporting high-volume fast reads (fast_pread_bytes, etc.).selinux_state.enforcing field in physical memory.
Then dump the kernel image from the fixed physical base address 0xA8000000 (default limit 43 MB)—
SM8475 has no physical KASLR—and parse the kallsyms structures directly from the raw dump
(automatic endianness detection), so virtual KASLR does not matter either.prepare_kernel_cred(0) →
commit_creds(), and disable seccomp) to patch __do_sys_capset. The forked child process
calls capset() → becomes uid 0 → restores the overwritten kernel code → the child process re-executes your specified command in the
u:r:shell:s0 domain (default sh). The kernel code ultimately
matches byte-for-byte what it was before running.| Model | vivo V2301A (iQOO Neo8, codename PD2301, board taro) |
| SoC | SM8475 (Snapdragon 8+ Gen 1) + Adreno 730 |
| Android | 14 (UP1A.231005.007, SDK 34, qssi-user) |
| Kernel | 5.10.198-gki-gfb2f5774872c |
| SELinux | Enforcing (disabled at runtime by this tool) |
See device_info.txt for full device information.
├── source/
│ ├── cheese.c # 漏洞利用主程序(include 了下面两个文件)
│ ├── kallsyms_lookup.c # 针对原始内核映像的 kallsyms 解析器
│ └── adrenaline.h # KGSL ioctl / 命令定义(作者 hawkes)
├── exploit # 预编译 arm64 二进制(动态链接,minSdk 21,
│ # NDK r30-beta2)
├── device_info.txt # 用于调参的目标设备信息
└── README.md
注意:cheese.c 会 #include kallsyms_lookup.c 和 adrenaline.h,
编译时三个文件需要放在同一目录。
任意较新的 Android NDK(r25+)均可,示例:
# 静态链接(自包含、任意 API 等级可运行;体积较大)
aarch64-linux-android34-clang -o cheese source/cheese.c -static
# 动态链接(体积小,依赖设备自带的 libc;API 等级需匹配设备)
aarch64-linux-android34-clang -o exploit_dyn source/cheese.c
# 若要兼容 API 21+ 设备,请改用 aarch64-linux-android21-clang
#推送exploit
adb push exploit /data/local/tmp/
#运行exploit
adb shell
chmod +x /data/local/tmp/exploit
/data/local/tmp/exploit
Click here for the detailed tutorial (it includes a one-click tool)
On success, you get a uid=0 shell, and SELinux is disabled for this session. After completing
privilege escalation, the process re-executes argv[1..] (defaults to sh). Each run has a countdown
("pte wait 3 second") and a 43 MB kernel read progress ("Read X / 43 MB").

| Variable | Purpose |
|---|---|
CHEESE_PHYADDR=<phys> | Force a specific kernel physical base address (skips the built-in gPhyAddrs probe table). |
CHEESE_ATTEMPT=<n> | Use gPhyAddrs[n] instead of gPhyAddrs[0]; after a GPU initialization failure, maybe_retry automatically increments and retries. |
CHEESE_SWAPPER_PG_DIR_OFF=<off> | Skip the swapper_pg_dir scan and use this absolute offset directly. |
CHEESE_READ_FULL_KERNEL=1 | Read the full kernel image (the default read limit is DEFAULT_KERNEL_SIZE_MB = 43 MB). |
CHEESE_KALLSYMS_MAX_MB=<MB> | Override the kallsyms read limit. |
CHEESE_FORCE_MANUAL_PATCHFINDER=1 | Force the manual patchfinder fallback (init_cred, prepare_kernel_cred). |
CHEESE_SKIP_GPU=1 | Skip the GPU read/write stage (for debugging only). |
CHEESE_NO_RETRY=1 | Exit immediately on failure without retrying with a different attempt number. |
Due to the special mechanism of vivo devices, on the iQOO Neo8 it must be disabled to keep root privileges stable (the specific principle is not explained here. Other vivo models and kernel versions may not be compatible; you need to extract vr.ko yourself and reverse engineer it). Before performing the following operations, make sure the current shell session has obtained root privileges.
#备份原始apanic
dd if="$(readlink -f /dev/block/by-name/apanic)" of=/sdcard/apanic_full.bin
#adb shell内执行
adb pull /sdcard/apanic_full.bin ./
#主机端执行
#禁用vr.ko(风险自行评估)
printf '\370\037\000\000\000\000\000\000\000\000\000\000' | dd of=$(readlink -f /dev/block/by-name/apanic) bs=1 seek=$(( $(blockdev --getsize64 /dev/block/by-name/apanic) - 65472 )) conv=notrunc && sync
If a cold reboot of the phone later causes root privileges to be lost, simply re-run the exploit and then perform the jailbreak
$(find /data/app -type f -name "libksud.so" | grep resukisu) late-load --kmi android12-5.10
Device-related constants are located at the top of source/cheese.c:
KERNEL_PHYS_BASE — kernel physical base address (default 0xA8000000, SM8475)IDMAP_PG_DIR_OFFSET — idmap_pg_dir offset (default 0x245a000)selinux_state_offset — offset used to locate selinux_state (default
0x729030, actual physical address = idmap_pg_dir physical address + this offset)gPhyAddrs[] — candidate physical base addresses to try in sequenceDEFAULT_KERNEL_SIZE_MB — fallback value / upper limit for kernel readsPrerequisites: Adreno A730/A7xx series GPU (affected firmware), and the device system has not installed the security patch released by Qualcomm in June 2025, a kernel whose kallsyms can be parsed from the physical dump, and no physical KASLR.
can't get GPU r/w — GPU initialization failed; wait first, and if it still fails, try CHEESE_ATTEMPT=1..n or manually
specify CHEESE_PHYADDR; add CHEESE_NO_RETRY=1 when debugging a single attempt.CHEESE_READ_FULL_KERNEL=1 to read the full image (together with
CHEESE_KALLSYMS_MAX_MB to increase the limit), then consider CHEESE_FORCE_MANUAL_PATCHFINDER=1.