# CVE-2026-65398: IOMobileFrameBuffer | David Strnadel

Canonical source: https://davidstrnadel.com/research/cve-2026-65398/
Author: David Strnadel
Source content last updated: 2026-10-30T12:42:20+00:00

> Text edition of the public page. Sources, shared attribution and evidence limits are retained.

CVE-2026-65398 Technical writeup

## One missing check. A kernel boundary crossed.

How a sibling comparison led from an unchecked array index to a repeatable, crash-based kernel-read witness.

[David Strnadel](https://davidstrnadel.com/) / Security & AI Research 30 October 2026 · ~6 min

- **Tested build:** iOS 27 beta 4 · 24A5390f
- **Entry point:** IOKit · selector 95
- **Evidence:** Physical-device archive
- **Attribution:** Shared Apple credit

The short version 60 SEC

## An array index became a pointer.

Selector 95 omitted a bound enforced by its sibling. An out-of-range slot could reinterpret adjacent provider state as an object pointer. The investigation progressed from register control to a kernel-load witness, with each claim tied to a separate experiment.

IOMobileFrameBuffer [Go to the mechanism ↓](https://davidstrnadel.com/research/cve-2026-65398/#mechanism)

01

## Context & attack surface

During the iOS beta cycle, I compared new kernel functionality with neighbouring operations. Selector 95 in `IOMobileFramebufferUserClient` used the same provider array as selector 93, but the two paths disagreed about index validation. [\[E1\]](https://davidstrnadel.com/research/cve-2026-65398/#e1)

The tested entry point was reachable from an ordinary development-signed app through connection type `0`. Its dispatch contract accepted one scalar and a 20-byte structure, with no outputs and no private entitlement requirement. The experiment was performed on a physical `iPhone14,4 / D16AP`, running iOS 27 beta 4, build `24A5390f`.

**Scope of this article**

The addresses and offsets below belong to the recorded beta-4 build. Apple’s public advisory covers the CVE; it is not a claim that these exact layouts apply to every affected platform.

02

## The missing invariant

The intended array has 189 entries. A valid index must satisfy `(uint32_t)index < 0xbd`. The adjacent selector 93 enforced that condition before computing a slot; selector 95 did not. [\[E1\]](https://davidstrnadel.com/research/cve-2026-65398/#e1)

**Listing 01** The sibling’s guard ARM64 · unslid addresses

```text
0xfffffff009a868d8  cmp  w2, #0xbc
0xfffffff009a868dc  b.hi 0xfffffff009a8699c
```

The unsigned comparison rejects indexes above 0xbc. This excerpt is from selector 93, not a proposed patch for selector 95.

The general slot computation also includes signed-low-32-bit and pointer-poisoning behaviour. For the small index used in the read demonstration, the relevant arithmetic simplifies to:

**Listing 02** Why index 0xd4 reaches the carrier Derived arithmetic

```text
array_base  = provider + 0x1740
slot        = array_base + 0xd4 * 0x38
            = provider + 0x45a0
slot + 0x18 = provider + 0x45b8
```

This equation is specific to index 0xd4. It is not a complete reconstruction of the handler’s overflow behaviour.

The out-of-range slot overlaps provider state populated by a legitimate transaction. The bug is the unchecked reinterpretation of that state as an array element. `SwapDebugInfo` is a value carrier here, not a second vulnerability. [\[E2\]](https://davidstrnadel.com/research/cve-2026-65398/#e2)

Figure 01 / Value flow 24A5390f · explanatory diagram

APPLICATION **Current-boot target**`transaction + 0x48`

PROVIDER STATE **Unchecked index 0xd4**`slot + 0x18`

KERNEL **Pointer interpretation**`ldr x16, [x0]`

The app reads the current-boot Target Address dynamically. A legitimate transaction carries that qword into provider state.

**Read the complete value flow**

- The app captures the current-boot Target Address with three equal, nonzero reads.

- SwapDebugInfo places it at transaction +0x48; selector 5 submits the transaction.

- The DCP path copies transaction +0x40..+0x4f into swap-node +0xc40..+0xc4f.

- Layer-2 completion copies node +0xc48 into provider +0x45b8.

- Index 0xd4 selects provider +0x45a0. The stored qword is now interpreted as slot +0x18.

- The helper loads that qword into x0 and executes ldr x16, [x0].

03

## From hypothesis to witness

The original report demonstrated full 64-bit register control. The helper loaded the app’s qword into `x0`, then faulted on an out-of-bounds access without overwriting it. That established a register primitive; it did not yet establish a selected-address kernel read. [\[E1\]](https://davidstrnadel.com/research/cve-2026-65398/#e1)

**Listing 03** The original register-control witness ARM64 · annotated excerpt

```text
0xfffffff009a866a4  ldur x0, [x3, #0xc]
0xfffffff009a866a8  cbz  x0, 0xfffffff009a867f0
0xfffffff009a866ac  mov  w21, #0
0xfffffff009a866b0  ldr  w9, [x19, #0x28]
```

Line 1 loads the full application qword. Line 4 is the first invalid operation in this witness; x0 remains unchanged between them.

The follow-up used the same missing check to reach a stronger, distinct witness: a kernel load from the current-boot Apple Target Address. It used `_COMM_PAGE_ASB_TARGET_KERN_ADDRESS`, rather than the Target Value used for register control. [\[E2\]](https://davidstrnadel.com/research/cve-2026-65398/#e2)

**Listing 04** The read witness ARM64 · relevant operations

```text
0xfffffff009a867f4  x0 ← [slot + 0x18]
                  …
0xfffffff009a867fc  ldr x16, [x0]
```

Line 1 is a semantic annotation of the load recorded in the technical note, not a verbatim disassembly line. Line 3 is the faulting native instruction.

| Check | Run 1 | Run 2 |
| --- | --- | --- |
| Runtime target capture | Three matching reads | Three matching reads |
| Panic x0 and FAR | Match same-boot target | Match same-boot target |
| Index in x22 | 0xd4 | 0xd4 |
| Unslid PC | 0xfffffff009a867fc | 0xfffffff009a867fc |
| Recorded pair verification | 27 / 27 | 27 / 27 |

The retained supplement records 8/8 cross-run checks. Both runs used the same installed binary and different boot sessions. These are archived results, not fresh device runs performed for this article. Boot identifiers and raw panic files are not reproduced here. [\[E2\]](https://davidstrnadel.com/research/cve-2026-65398/#e2)

04

## Impact & limits

### What the evidence supports

- Ordinary-app reachability on the tested build.

- A missing index check and a native out-of-bounds operation.

- Full-width kernel register control in the original witness.

- A crash-based kernel-load demonstration satisfying the recorded Target-Flag read contract.

### What it does not establish

- Kernel data returned to the application as a general readback oracle.

- An arbitrary-write primitive.

- Program-counter control or kernel code execution.

- The exact beta-4 mechanism on every affected Apple platform.

**A useful distinction**

A panic can prove which address a kernel instruction attempted to load. It does not, by itself, prove that the app received the contents of mapped kernel memory. The writeup keeps those two claims separate.

Apple’s public advisory describes an out-of-bounds access issue with potential system termination or kernel-memory corruption, addressed through improved bounds checking. Credit is shared with other researchers. [\[E3\]](https://davidstrnadel.com/research/cve-2026-65398/#e3)

05

## Remediation & research history

**Listing 05** Restore the array invariant Proposed remediation · pseudocode

```text
if ((uint32_t)index >= 0xbd)
    return kIOReturnBadArgument;

// Only then calculate and use the slot.
```

This is the remediation proposed in the original report. It is not presented as Apple’s source code or a verified patch diff.

- DISCOVERY **A sibling comparison** Binary diffing narrowed the search; the inconsistent guard supplied the hypothesis.

- VALIDATION **Register control, then a kernel-read witness** The second demonstration reused the original root cause and established a different primitive.

- DISCLOSURE **Reported to Apple** Technical reports and reproducible evidence were supplied through Apple Security.

- PUBLIC RECORD **CVE-2026-65398** Apple lists shared credit and a bounds-checking remediation.

I directed target selection, hypothesis formation and validation. AI accelerated reverse analysis, PoC development and reporting. The conclusions came from the evidence. This chronology does not assert that Apple’s patch was created in response to my report.

06

## Evidence & references

The article separates retained research records from the vendor’s public acknowledgement. The downloadable excerpt contains the selected technical passages used here, not the complete original submission or a runnable exploit.

E1

### Original register-control report

Author’s retained APPLE_REPORT.md: dispatch contract, sibling check, register witness and proposed remediation.

E2

### Kernel-read supplement

Author’s retained TECHNICAL_NOTE.md: same-root-cause value flow, exact executed binary and cross-boot results.

E3

### Apple security advisory

Public CVE attribution and vendor impact description. The linked advisory is for macOS Golden Gate 27; the experiment in this article was on iOS beta 4.

[Open source ↗](https://support.apple.com/en-asia/149035)

[Download selected evidence · TXT](https://davidstrnadel.com/research/evidence/iomfb-selected-evidence.txt)

**Provenance**

Kernel-read witness executable SHA-256: `3c702ee05c472e0725bc5c4f170cc6f0848731a6de28b6213cd7ed82139dc906`Recorded component UUID: `F96D5B5D-7440-3947-B481-F5AC2E379FF7`. This identifies the archived build; it is not a hash of the downloadable editorial excerpt.

Continue exploring

## A different path through the same boundary.

The AuthKit branch of a preference-access investigation.

[Read the related investigation](https://davidstrnadel.com/research/cve-2026-43730/)
