Ned Williamson

The Basics

Disclosure or Patch Date: 2022 September 12

Product: Apple iOS, macOS

Advisory:

iOS: https://support.apple.com/en-us/HT213445 macOS: https://support.apple.com/en-us/HT213443

Affected Versions: iOS 15.7, macOS 11.7 and earlier

First Patched Version: iOS 15.7, macOS 11.7

Issue/Bug Report: N/A

Patch CL: N/A

Bug-Introducing CL: N/A

Reporter(s): Anonymous

The Code

Proof-of-concept: This can be triggered by creating an IOUserClient to send messages to the _asyncMessage object. This can be done by its exposed RTBuddy interface.

Exploit sample: N/A

Did you have access to the exploit sample when doing the analysis? No

The Vulnerability

Bug class: Buffer overflow

Vulnerability details:

AppleSPU.kext is a kernel extension that manages various drivers on macOS and iOS. It registers a message handler AppleSPU::asyncMessage with an RTBuddy endpoint. These endpoints can be accessed by instances of IOUserClient. One such user-exposed message with opcode 0x89 (called ALLOCATE_BUFFER in the pseudocode below) for the AppleSPU endpoint allocates a new buffer in the AppleSPU object. Active buffers are tracked with an array of buffer entries that has a static size of 16.

There is no bounds check on the number of allocated buffers, so if more than 16 are allocated, an out of bounds write occurs within the AppleSPU object. The pseudocode can be found below:

struct BufferEntry {
    _WORD word1;
    _QWORD *buffer;
    _QWORD unk2;
    _QWORD unk3;
    _QWORD size;
}

struct AppleSPU {
  // ...
  BufferEntry buffer_entries[16];
  unsigned int num_entries;
  // ...
}

void __fastcall AppleSPU::_asyncMessage(AppleSPU *this, void *a2, MessageDataType *message_data)
{
  while ( 1 )
  {
    // ...
    case ALLOCATE_BUFFER:
      size = this->AppleSPU.processor_memory_alignment * message_data->num_elements;
      buffer = ((__int64 (*)(void))this->AppleSPU.processor->vtable->procesor.allocate_buffer)(size);
      if ( !buffer )
        panic();
      v33 = (*(__int64 (**)(void))(*(_QWORD *)buffer + 120LL))();
      v34 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)buffer + 128LL))(v32);
      entry = this->AppleSPU.buffer_entries[this->AppleSPU.num_entries];
      entry->word1 = message_data->word1;
      entry->buffer = buffer;
      entry->unk2 = v33;
      entry->unk3 = v34;
      entry->size = size;
      this->AppleSPU.num_entries++;

Patch analysis: A bounds check was added on AppleSPU::num_entries.

    case ALLOCATE_BUFFER:
+      if (this->AppleSPU.num_entries >= 16u)
+        panic("out of buffer entries");
      size = this->AppleSPU.processor_memory_alignment * message_data->num_elements;
      buffer = ((__int64 (*)(void))this->AppleSPU.processor->vtable->procesor.allocate_buffer)(size);
      if ( !buffer )
        panic();

Thoughts on how this vuln might have been found (fuzzing, code auditing, variant analysis, etc.): This bug is fairly simple, so it could have been found with either fuzzing or code auditing. For fuzzing, some knowledge of the structure of message types would be helpful to find the ALLOCATE_BUFFER message type, either by using a seed corpus of valid AppleSPU messages, coverage feedback, or using a grammar derived from manual analysis.

(Historical/present/future) context of bug: N/A

The Exploit

(The terms exploit primitive, exploit strategy, exploit technique, and exploit flow are defined here.)

Exploit strategy (or strategies): N/A, exploit not available

Exploit flow:

Known cases of the same exploit flow:

Part of an exploit chain?

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why): Audit for usage of statically-sized arrays and ensure bounds checks are used. Fuzz all endpoints exposed to IOUserClient, including drivers.

Found variants: N/A

Structural improvements

What are structural improvements such as ways to kill the bug class, prevent the introduction of this vulnerability, mitigate the exploit flow, make this type of vulnerability harder to exploit, etc.?

Ideas to kill the bug class: Use containers with checked accesses or prove that accesses are safe (e.g. with a language extension, compiler support, etc.).

Ideas to mitigate the exploit flow: This is an overflow within a struct, so memory tagging that uses per-allocation granularity is insufficient to detect this issue unless fine-grained tags for each type are used. This is a challenge to accomplish while respecting the C structure layout specification. Therefore broader solutions such as banning C-style arrays within structures or inserting bounds checks with the compiler using the static size is a good approach here.

Other potential improvements:

0-day detection methods

What are potential detection methods for similar 0-days? Meaning are there any ideas of how this exploit or similar exploits could be detected as a 0-day?

Other References