home intel cve-2025-58411-imagination-gpu-driver-uaf-rce
CVE Analysis 2026-01-13 · 9 min read

CVE-2025-58411: Imagination GPU Driver Use-After-Free via Refcount Mismanagement

A reference counting flaw in Imagination Technologies' GPU kernel driver allows an unprivileged user to trigger a write use-after-free via malformed GPU syscalls, enabling potential RCE at kernel privilege.

#gpu-memory-safety#use-after-free#reference-counting#privilege-escalation#resource-management
Technical mode — for security professionals
▶ Attack flow — CVE-2025-58411 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2025-58411Cross-platform · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2025-58411 is a CVSS 8.8 HIGH use-after-free vulnerability in the Imagination Technologies PowerVR/IMG GPU kernel driver. An unprivileged local process — the class of attacker that owns a normal Android or embedded Linux application sandbox — can issue a crafted sequence of GPU system calls that causes the driver to decrement a reference count below zero on an internal GPU resource object. The object is subsequently freed while a live pointer to it still resides in a per-context table. A follow-on write through that dangling pointer produces a kernel write primitive. No physical access, no special permissions, and no additional memory-unsafety primitives are required to reach the vulnerable path.

Root cause: The GPU driver's internal resource management layer decrements the reference count of a firmware-mapped object before removing all references to it from the per-context handle table, allowing a second syscall to write through the freed object pointer.

Affected Component

The bug lives in the Imagination Technologies GPU driver stack, specifically inside the Services layer — the kernel-resident component that mediates between userspace GPU calls and the firmware running on the PowerVR hardware block. The Services layer exposes a device node (typically /dev/pvr0 or /dev/dri/renderD128) to unprivileged processes. Resource objects — render contexts, transfer queues, sync primitives, memory allocations — are tracked internally using a PVRSRV_DEVICE_NODE-scoped handle base and reference-counted via IMG_ATOMIC_T fields embedded in each object header.

Affected versions are listed in the NVD entry and the Imagination advisory at https://www.imaginationtech.com/gpu-driver-vulnerabilities/. The driver ships inside Android BSPs for devices carrying PowerVR Series6, Series8XE, Series9XE, and AXM-class GPU IP.

Root Cause Analysis

Each GPU resource that crosses the user-kernel boundary is wrapped in a PVRSRV_HANDLE entry. The object underneath carries an atomic reference count. The canonical teardown sequence is: (1) remove from handle table, (2) drop reference, (3) free if refcount hits zero. The driver incorrectly inverts steps 1 and 2 for a specific resource class under a particular destruction path — the reference is dropped first, potentially reaching zero and triggering OSFreeMem(), while the handle table entry still holds a raw pointer to the now-freed object.

/*
 * Simplified pseudocode of the vulnerable resource destruction path.
 * Real function: PVRSRVRGXDestroyTransferContextKM()
 * Located in: services/server/devices/rgx/rgxtransfer.c
 */
PVRSRV_ERROR PVRSRVRGXDestroyTransferContextKM(
        RGX_SERVER_TQ_CONTEXT *psTransferContext)
{
    PVRSRV_ERROR eError;

    /* Step 1 — issue firmware command to quiesce the context */
    eError = _TQCCBPoll(psTransferContext);
    if (eError != PVRSRV_OK)
        return eError;

    /* Step 2 — BUG: drops refcount; if this was the last reference,
     *           OSFreeMem() is called HERE, freeing psTransferContext.
     *           The handle table entry in psDeviceNode->hHandleBase
     *           still contains a raw pointer to the freed object.     */
    PVRSRVServerSyncPrimSetKernelCallbackCtx(
            psTransferContext->psSync, NULL);          // drops internal ref
    SyncServerFreeSync(psTransferContext->psSync);     // BUG: may free object
                                                       //      backing storage

    /* Step 3 — handle removal happens AFTER the potential free */
    eError = PVRSRVHandleUnref(                        // BUG: too late; object
            psDeviceNode->hHandleBase,                 //      may already be
            psTransferContext->hPrivData,              //      freed above
            PVRSRV_HANDLE_TYPE_RGX_SERVER_TQ_CONTEXT,
            PVRSRV_HANDLE_BASE_INTERNALTYPE);
    if (eError != PVRSRV_OK)
        return eError;

    /* Any subsequent lookup via the still-valid handle returns
     * a dangling psTransferContext pointer.                      */
    OSFreeMem(psTransferContext);                      // double-free risk here
    return PVRSRV_OK;
}

The internal resource header that carries the reference count looks like this:

/* PVRSRV_REFCOUNT_OBJECT — embedded at the head of every server-side
 * resource object.  Hex offsets reflect 64-bit LP64 ABI.           */
struct _PVRSRV_REFCOUNT_OBJECT_ {
    /* +0x00 */ IMG_ATOMIC_T        iRefCount;     // atomic int32; free at 0
    /* +0x04 */ IMG_UINT32          ui32Padding;   // alignment
    /* +0x08 */ PFN_FREE_CALLBACK   pfnFreeCallback; // called when iRefCount==0
    /* +0x10 */ void               *pvData;        // points to owning object
    /* +0x18 */ struct list_head    sNode;         // membership in per-dev list
};

/* RGX_SERVER_TQ_CONTEXT — the full transfer queue context object.
 * psSync is a PVRSRV_SERVER_SYNC_PRIM that embeds the above header. */
struct _RGX_SERVER_TQ_CONTEXT_ {
    /* +0x00 */ PVRSRV_REFCOUNT_OBJECT sRefCount;  // must be first member
    /* +0x28 */ PVRSRV_DEVICE_NODE    *psDeviceNode;
    /* +0x30 */ IMG_HANDLE             hPrivData;  // handle table key
    /* +0x38 */ PVRSRV_SERVER_SYNC_PRIM *psSync;   // sync backing the context
    /* +0x40 */ RGX_CCB_CMD_HELPER_DATA asCmdHelperData[2];
    /* ...   */ /* firmware-mapped command ring state follows */
};

Memory Layout

KERNEL HEAP STATE — before malformed destroy syscall:

  [RGX_SERVER_TQ_CONTEXT @ 0xffffff8012c40000]
    +0x00  iRefCount  = 0x00000001   (live)
    +0x08  pfnFree    = 0xffffff800981a340
    +0x10  pvData     = 0xffffff8012c40000
    +0x30  hPrivData  = 0x0000004b   (handle table slot 0x4b)
    +0x38  psSync     = 0xffffff8013008800

  [HANDLE TABLE SLOT 0x4b @ psDeviceNode->hHandleBase]
    pvData   = 0xffffff8012c40000  <-- raw pointer to TQ context
    eType    = PVRSRV_HANDLE_TYPE_RGX_SERVER_TQ_CONTEXT
    iRefCount= 0x00000001

--- SyncServerFreeSync() called first (inverted order) ---

KERNEL HEAP STATE — after premature refcount drop:

  [0xffffff8012c40000]  <-- FREED, returned to kmalloc-512 slab
    contents now: SLAB FREELIST METADATA (overwritten)
    next_free  = 0xffffff8012c40200  (slab internal pointer)

  [HANDLE TABLE SLOT 0x4b]  <-- STILL VALID, dangling pointer
    pvData   = 0xffffff8012c40000  <-- UAF: points to freed slab chunk
    eType    = PVRSRV_HANDLE_TYPE_RGX_SERVER_TQ_CONTEXT

--- Attacker reallocates freed chunk with controlled data ---

  [0xffffff8012c40000]  <-- reallocated (e.g. via sendmsg spray)
    +0x00  attacker data starts here
    +0x38  fake psSync ptr = 0xffffff8041414141  (attacker-controlled)

--- Second GPU syscall resolves handle 0x4b, writes through fake psSync ---
    PVRSRVServerSyncPrimSetKernelCallbackCtx(fake_psSync, cb_ptr)
    --> kernel write to 0xffffff8041414141 + offset

Exploitation Mechanics

EXPLOIT CHAIN — CVE-2025-58411 unprivileged → kernel write UAF:

1. Open /dev/pvr0 as an unprivileged process (DAC allows world-read/write
   on affected Android BSPs; SELinux context: untrusted_app → gpu_device).

2. Allocate a Transfer Queue context via PVRSRV_BRIDGE_RGXTQ_RGXCREATETRANSFERCONTEXT
   ioctl. Kernel returns handle H (e.g. 0x4b). Object lands at addr A in
   kmalloc-512 (size 0x1c0 bytes on arm64).

3. Issue PVRSRV_BRIDGE_RGXTQ_RGXDESTROYTRANSFERCONTEXT with handle H.
   Driver calls PVRSRVRGXDestroyTransferContextKM():
     a. SyncServerFreeSync() drops iRefCount to 0 → OSFreeMem(A) fires.
     b. Object at A is freed to kmalloc-512 slab freelist.
     c. Handle slot 0x4b still holds raw pointer A (not yet unregistered).

4. Race the slab: spray ~128 sendmsg() ancillary data buffers of size 0x1c0
   each carrying 0x1c0 bytes of attacker-controlled payload. One allocation
   reclaims slab chunk at A. Payload places a fake psSync pointer at
   offset +0x38 (0xffffff8041414141 or a kernel object under attacker
   influence, e.g. a pipe_buffer whose content is attacker-controlled).

5. Reopen /dev/pvr0 in a second thread (handle base is per-fd, but the
   freed object's hPrivData slot 0x4b was not purged). Issue a second
   PVRSRV_BRIDGE_RGXTQ_RGXSUBMITTRANSFER2 ioctl referencing H.
   Driver dereferences handle 0x4b → gets A (freed, now attacker heap).
   Reads fake psSync from A+0x38.

6. Driver calls PVRSRVServerSyncPrimSetKernelCallbackCtx(fake_psSync, ptr):
     → kernel write of a callback function pointer to fake_psSync + 0x20.
   With fake_psSync pointing at a known kernel object (e.g. a pipe_inode
   inode's i_fop field), this overwrites a function pointer.

7. Trigger the overwritten function pointer (e.g. read() on the pipe fd)
   → control flow hijack → ROP chain → commit_creds(prepare_kernel_cred(0))
   → privilege escalation to UID 0 / full kernel code execution.

Patch Analysis

The fix reorders the teardown sequence so the handle table entry is invalidated — and all live references through it are gone — before the reference count is decremented. Additionally, the patch adds an explicit reference count guard that asserts the object is still live before any field access.

/* BEFORE (vulnerable) — PVRSRVRGXDestroyTransferContextKM():
 * refcount drop precedes handle table removal.                 */
PVRSRVServerSyncPrimSetKernelCallbackCtx(
        psTransferContext->psSync, NULL);   // drops ref → possible free HERE
SyncServerFreeSync(psTransferContext->psSync);

eError = PVRSRVHandleUnref(                // handle still live during free
        psDeviceNode->hHandleBase,
        psTransferContext->hPrivData,
        PVRSRV_HANDLE_TYPE_RGX_SERVER_TQ_CONTEXT,
        PVRSRV_HANDLE_BASE_INTERNALTYPE);

OSFreeMem(psTransferContext);

/* AFTER (patched):
 * 1. Unregister from handle table first — no new lookups possible.
 * 2. Only then release the sync reference.
 * 3. Explicit liveness assertion guards field access.           */

/* Step 1: invalidate handle table entry before any refcount drop */
eError = PVRSRVHandleUnref(
        psDeviceNode->hHandleBase,
        psTransferContext->hPrivData,
        PVRSRV_HANDLE_TYPE_RGX_SERVER_TQ_CONTEXT,
        PVRSRV_HANDLE_BASE_INTERNALTYPE);
if (eError != PVRSRV_OK) {
    PVR_DPF((PVR_DBG_ERROR, "%s: Failed to unref handle (%s)",
             __func__, PVRSRVGetErrorString(eError)));
    return eError;
}
psTransferContext->hPrivData = IMG_NULL;   // poison the stale field

/* Step 2: assert liveness, then drop sync reference safely */
PVR_ASSERT(OSAtomicRead(&psTransferContext->sRefCount.iRefCount) >= 1);

PVRSRVServerSyncPrimSetKernelCallbackCtx(
        psTransferContext->psSync, NULL);
SyncServerFreeSync(psTransferContext->psSync);
psTransferContext->psSync = IMG_NULL;      // poison

/* Step 3: unconditional free — only reached if above succeeded */
OSFreeMem(psTransferContext);
return PVRSRV_OK;

Detection and Indicators

At runtime, exploitation of this path is detectable through several kernel-observable signals:

  • KASAN report: BUG: KASAN: use-after-free in PVRSRVServerSyncPrimSetKernelCallbackCtx+0x?/0x? with an allocation/free traceback pointing into PVRSRVRGXDestroyTransferContextKM.
  • Slab integrity checker: SLUB: Poison overwritten @ 0x... Object @ 0x... size=0x1c0 if SLUB_DEBUG is enabled on the device.
  • Unusual ioctl patterns: rapid repeated RGXCREATETRANSFERCONTEXT / RGXDESTROYTRANSFERCONTEXT pairs from a single PID, interleaved with high-volume sendmsg() calls on unconnected sockets — the slab spray signature.
  • Audit log: avc: denied { execmem } or avc: denied { sys_admin } from untrusted_app immediately following GPU ioctl bursts.
KASAN SPLAT (representative, arm64):
==================================================================
BUG: KASAN: use-after-free in
     PVRSRVServerSyncPrimSetKernelCallbackCtx+0x6c/0x98 [pvrsrvkm]
Write of size 8 at addr ffffff8012c40038 by task poc/4271

CPU: 3 PID: 4271 Comm: poc Not tainted 5.15.78-android13 #1
Call trace:
 kasan_report+0xd4/0x108
 __asan_store8+0x58/0x90
 PVRSRVServerSyncPrimSetKernelCallbackCtx+0x6c/0x98 [pvrsrvkm]
 PVRSRVRGXDestroyTransferContextKM+0x1b4/0x2a0 [pvrsrvkm]
 PVRSRVBridgeRGXDestroyTransferContext+0x88/0xc0 [pvrsrvkm]
 _IOC+0x1f8/0x350
 ...

Allocated by task 4271:
 kmalloc+0x... RGXCreateTransferContextKM+0x11c/0x340 [pvrsrvkm]

Freed by task 4271:
 kfree+0x... SyncServerFreeSync+0x74/0xa8 [pvrsrvkm]
 PVRSRVRGXDestroyTransferContextKM+0x194/0x2a0 [pvrsrvkm]
==================================================================

Remediation

Update to the patched driver version listed in the Imagination Technologies advisory at https://www.imaginationtech.com/gpu-driver-vulnerabilities/. For Android BSP integrators: the fix must be applied in the kernel driver tree (out-of-tree pvrsrvkm module or in-tree under drivers/gpu/drm/img/ depending on the SoC vendor integration), rebuilt, and re-signed before flashing. A userspace-only update does not mitigate this vulnerability.

Mitigating controls while patch deployment is pending:

  • Enable KASAN and SLUB_DEBUG on debug builds to detect exploitation attempts in QA/canary fleets.
  • Apply SELinux policy to restrict untrusted_app from opening /dev/pvr0 beyond the minimum required ioctl set — specifically deny PVRSRV_BRIDGE_RGXTQ_RGXCREATETRANSFERCONTEXT if Transfer Queue functionality is not required by the app.
  • Where the kernel supports it, enable init_on_free=1 — zero-on-free significantly raises the bar for controlled-data heap spray exploitation of this class of bug.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →