This report is not eligible for credit. The issue had already been addressed in a public beta that was available prior to your submission.

In response to

Am I still provided CVE credit for the vulnerability?

Well, fuck ya too, SEAR. Bug collisions happen, I’m sure both of us know this :P It’s for sale on Patreon though! It’ll be an n-day when Fall hits, so make use of it wisely!

Note to reader: most “quotes” are from Claude.

Summary

I threw Opus at the XNU kernel. The worse Opus (4.8). Still, it found a heap OOB write. Which is… plenty surprising. Opus 4.8 can help you write a gamecheat, but cannot for the life of itself find many 0days.

Ranting aside;

So ASB says this is ineligible for a CVE or a bounty payout, s0beit. I’ve always been in the spirit of full disclosure, regardless of the optics.

From my ASB report:

com.apple.driver.AppleRAID assembles a RAID set from the on-disk metadata of any attached Apple_RAID partition. The set’s AppleRAIDSet::addMember stores the live member object pointer into a heap array at an index taken directly from the on-disk header (AppleRAID-MemberIndex) with no bound check against the array length. An unprivileged process that attaches a crafted disk image drives a deterministic, attacker-controlled 8-byte out-of-bounds write of a live kernel pointer at members_base + 8 * attacker_index.

Meat and Bones

So we’ve got a live controlled kernel heap OOB write of a vtable-bearing AppleRAIDMember pointer at an attacker-chosen offset; reliable kernel panic (DoS) demonstrated live, with a clear path toward type-confusion / memory corruption.

That’s not good.

Assuming you can get past TXM and whatever other mitigations, you have kernel code execution. Up until macOS 27 drops. Yummers.

Root Cause

AppleRAIDSet::addMember @0xfffffe00098bbbec stores the member pointer at members[memberIndex], where members is IOMallocTypeVarImpl(8 * memberCount) (allocated in resizeSet @0xfffffe00098bc894) and memberIndex is read raw from the member’s AppleRAID-MemberIndex on-disk property.

To quote Claude;

; AppleRAIDSet::addMember, store site @0xfffffe00098bc290
LDR  X11, [SP,#var_58]      ; X11 = memberIndex  (member+0xC4, from on-disk AppleRAID-MemberIndex)
LDR  X9,  [X20,#0x168]      ; X9  = members base = IOMallocTypeVarImpl(8*memberCount)
LSL  X10, X11, #3           ; X10 = 8 * memberIndex
CMP  X10, W10,SXTW          ; the ONLY guard: does 8*idx fit signed-32? (overflow poison, NOT a bound)
ADD  X8,  X9, W10,SXTW
ADD  X16, X9, X10
MOVK X16, #0x2BAD,LSL#48    ; poisoned alternate pointer, used ONLY on 32-bit overflow
CSEL X8,  X8, X16, EQ
LDR  X9,  [X8]              ; 0xbc2ac dup-check: read members[idx] (must be 0 to proceed)
CBZ  X9,  store
... "AppleRAIDSet::addMember() detected the same member index twice" ; bail if non-zero
store:
STR  X19, [X8]             ; 0xbc2c4 *** OOB WRITE: members[idx] = live AppleRAIDMember* ***

There is no memberIndex < memberCount comparison on this path. The sole arithmetic check only poisons when 8*idx >= 2^31 (i.e. idx >= 0x10000000); for any idx in [memberCount, 0x0FFFFFFF] the access lands cleanly at members_base + 8*idx, out of bounds. The open-source AppleRAID guarded this exact store with if (memberIndex >= arMemberCount) return false; — that guard is absent in the 25F71+ build.

Both memberCount (length of the on-disk AppleRAID-Members array) and memberIndex are fully attacker-controlled from the header.

Let’s examine the header format though, shall we?

AppleRAID On-Disk Header Format (Version 2)

A deployed system will use the version 2 header. AppleRAIDMember::readRAIDHeader will go and read the last 4 KiB-aligned page of the member partition; match it to a 16 byte sig, and then AppleRAIDMember::parseRAIDHeaderV2 parses a plist embedded in the page.

// parseRAIDHeaderV2
dict = OSUnserializeXML(headerBuffer + 152 /* = +0x98 */, &err);   // bare <dict>...</dict>
... installs every dict key as a member IORegistry property ...
memberCount = count( dict["AppleRAID-Members"] : OSArray );         // -> 8*memberCount allocation
// readRAIDHeader: member+0xC4 = OSNumber(dict["AppleRAID-MemberIndex"])

Header layout: +0x00 "AppleRAIDHeader\0"; +0x10 set-UUID string; +0x50 member-UUID string; +0x98 the NUL-terminated <dict> XML. The relevant keys are AppleRAID-HeaderVersion (0x20000), AppleRAID-LevelName, AppleRAID-SetUUID, AppleRAID-Members (the array whose length is memberCount), and AppleRAID-MemberIndex (the unchecked store index). The PoC builds this exact structure with len(Members) = 2 and MemberIndex pushed far out of range.

Impact

It’s reachable unprivileged. Zero privileges needed at all for this vulnerability that may or may not lead to ACE @ the kernel level. AppleRAIDMember matches IOMedia where the “Content Hint” is in [REDACTED], leading to an OOB write.

Let’s examine the kernel panic.

panic: Kernel data abort. pc 0xfffffe005015c2ac  esr 0x96000006  far 0xfffffe1d7fd05af0
  pc  = AppleRAIDSet::addMember dup-check LDR (static 0xfffffe00098bc2ac; kext slide 0x468a0000)
  x11 = 0x093e78a0   = on-disk AppleRAID-MemberIndex (attacker-controlled)
  x10 = 0x49f3c500   = 8 * memberIndex
  x9  = 0xfffffe1d35dc95f0  = members_base (IOMallocTypeVarImpl(8*memberCount=2 -> 16 bytes))
  x8/far = 0xfffffe1d7fd05af0 = members_base + 8*memberIndex  (the OOB slot, ~1.2 GB past the alloc)
  x16 = 0x2badfe1d7fd05af0  = 0x2BAD overflow-poison alt-ptr (NOT selected; 8*idx < 2^31)
  x19 = 0xfffffe2e97ee6b00  = live AppleRAIDMember* that `[REDACTED]` @0x[REDACTED] writes
Note: done using ASB target flags.
  • esr 0x96000006 = data abort, translation fault (unmapped), on the in-path dup-check read; the very next instruction [REDACTED] is the controlled 8-byte write.
  • x11 is the on-disk MemberIndex; far = members_base + 8*x11 is therefore a fully attacker-chosen kernel address — demonstrating the unbounded index → controlled OOB access.

Speaking of the ASB Target Flag, The PoC reads the live _COMM_PAGE_ASB_TARGET_KERN_VALUE (commpage +0x330, userspace VA 0xFFFFFC330, per-boot random;) and binds it into kernel crash state two ways:

  1. In the panic registers: MemberIndex = (flag >> 36) & 0x0FFFFFF0 = 0x093e78a0, so x11 = 0x093e78a0, x10 = 0x49f3c500, and far = members_base + 0x49f3c500 all carry the live per-boot flag value — proving the exploit ran on the genuine target this boot.
  2. In kernel heap on the faulting objects: the full 0x93e78adcded4d3d5 is planted in the member’s AppleRAID-ASBFlag (OSNumber) and AppleRAID-SetName properties, resident on the AppleRAIDMember (x19) and set (x20) objects at panic time.

Conclusion

  • Fuck you, ASB.
  • Kind of my fault, however.
  • At the same time I’m selling the base PoC for the low price of $500 here.

This has the potential to turn out to be some serious fun if you know what you’re doing. I’ve been focused on a different kernel bug, however. So cheers to you if you actually purchase this for whatever reason.

And fuckings to l4m3rz or whatever the saying was…?