(CVE-2024-27828) Apple IOSurfaceRoot Reference Count Leak Leading to Kernel Panic and Code Execution

CVE: CVE-2024-27828

Affected Versions: iOS and iPadOS before 17.5; tvOS before 17.5; watchOS before 10.5; visionOS before 1.2

CVSS3.1: 7.8 (High) — CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Summary

Product Apple IOSurface (IOSurfaceRoot)
Vendor Apple
Severity High — an app may be able to execute arbitrary code with kernel privileges
Affected Versions iOS/iPadOS < 17.5; tvOS < 17.5; watchOS < 10.5; visionOS < 1.2
Tested Versions iOS/macOS 14.1 beta (23B5056e)
CVE Identifier CVE-2024-27828
CVE Description A memory handling issue may allow an app to execute arbitrary code with kernel privileges; addressed via improved memory handling
CWE Classification(s) CWE-786: Access of Memory Location Before Start of Buffer; CWE-788: Access of Memory Location After End of Buffer

CVSS3.1 Scoring System

Base Score: 7.8 (High) Vector String: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Metric Value
Attack Vector (AV) Local
Attack Complexity (AC) Low
Privileges Required (PR) None
User Interaction (UI) Required
Scope (S) Unchanged
Confidentiality (C) High
Integrity (I) High
Availability (A) High

Product Background

IOSurface.kext is a kernel extension for managing pixel buffers, controlled by the userland IOSurface framework. It provides a framebuffer object suitable for sharing across process boundaries, commonly used to offload image decompression and drawing logic into a separate process. Beyond the framework, the low-level IOKit API can be used to communicate directly with the IOSurfaceRoot IOService instance.

Technical Details

Calling IOServiceOpen(IOSurfaceRoot) creates an IOSurfaceRootUserClient connection. The vulnerability exists in IOSurfaceRootUserClient::s_create_shared_event, which delegates to create_shared_event to create an IOSurfaceSharedEvent object identified by a user-supplied string name:

__int64 __fastcall IOSurfaceRootUserClient::s_create_shared_event(
        IOSurfaceRootUserClient *this,
        IOSurfaceRootUserClient *a2,
        IOExternalMethodArguments *a3,
        IOExternalMethodArguments *a4)
{
    __int64 result;
    uint64_t *scalarOutput;
    __int64 v7 = 0LL;
    unsigned int v8 = 0;

    result = IOSurfaceRootUserClient::create_shared_event(
                 this, *a3->scalarInput != 0, &v8, (unsigned __int64 *)&v7);
    scalarOutput = a3->scalarOutput;
    *scalarOutput = v8;
    scalarOutput[1] = v7;
    return result;
}

By manipulating the user-supplied input and calling s_create_shared_event repeatedly, a reference count leak on the IOSurfaceRoot object can be triggered. Each call leaks a retain without a corresponding release. Eventually, OSObject::taggedRelease detects that the object’s retain count has dropped below the number of collections it belongs to and triggers a kernel panic:

// OSObject.cpp
//
// This panic means that we have just attempted to release an object
// whose retain count has gone to less than the number of collections
// it is a member of.
//
if ((UInt16) actualCount < (actualCount >> 16)) {
    panic("A kext releasing a(n) %s has corrupted the registry.",
          getClassName(this));
}

The reference count corruption is exploitable for arbitrary code execution with kernel privileges. Running the PoC as a normal user is sufficient — no elevated privileges are required. Multiple runs may be needed to trigger the panic depending on timing:

cc poc.c -o poc -framework IOKit && ./poc

Credit

Pan Zhenpeng of STAR Labs SG Pte. Ltd.

Timeline

  • 2024-05-13 — Patch released: iOS/iPadOS 17.5, tvOS 17.5, watchOS 10.5, visionOS 1.2
  • 2024-05-13 — CVE-2024-27828 published by Apple