Show Notes

104 - KVM Bugs and an iOS IOMFB Kernel Exploit

Out-of-bounds (OOB) access in the VMGExit handler, which is triggered for string I/O instructions. The sev_es_string_io() function is responsible for doing the string copy between the unencrypted guest memory regions and the virtualized target. When doing so, it’ll setup a scratch buffer at svm->ghcb_sa (GHCB is short for Guest-Hypervisor-Communication-Block), and it’ll set it up using guest-controlled values for the length, I/O access size, and contents for the scratch buffer. When the control flow reaches emulator_pio_out() for outgoing I/O, it’ll copy the contents of the scratch buffer to the virtual CPU pio data using the I/O access size multiplied against the provided length. The problem is, pio_data is only one page size in length. If you provide a count or size that makes the memcpy() go beyond one virtual page, there’s an out of bounds access on adjacent pages.

Since this bug is in both the input and output paths, it provides an OOB read and an OOB write. The patch here was to ensure you couldn’t provide a size that would exceed the page size of the system.

In the recv_server-device_response_msg_process() handler, a nums field gets pulled out of the packet’s JSON payload, and is used to represent the total number of UDP server domains. The application then iterates based on this field, looking for its respective domain%d key in the JSON. If the key is found, the value (maximum of 0x80 bytes) is copied out to an offset calculated based on the current iteration count.

As there is no bounds checking, and the destination is a fixed size region of memory, by proving a high nums value attacker controlled data can be written well beyond the expected memory region. Additionally as the loop will not write any data if the domain%d an exploit could avoid smashing too much data on the way ot a desired target by providing an high nums value and only one domain%d value.

This same bug also exists in the read_udp_push_config_file() path.

Uninitialized use found in Apple’s ColorSync via fuzzing. When parsing an image, the library will calculate the start address for reading from a Color Lookup Table (CLUT) data point array for pixel data. It will do this by indexing into it using 2 * x * y. But if the in_channels field in the header is set to 0, the y coordinate never gets initialized, and is left as whatever was left on the stack in that location. As such, if that stale value is really large or negative, the array access goes out of bounds and triggers the crash.

Focuses on exploiting an Out-of-Bounds (OOB) read in the IOSurface subsystem. The vulnerability was an unchecked scalar0 index into the scalar input array in IOMobileFramebufferUserClient::get_displayed_surface() called by IOMobileFramebuffers::s_displayed_fb_service(). This gives you the ability to get an arbitrary pointer interpreted and used as an IOSurface object.

Exploiting this bug turned out to be very tricky though due to iOS 14’s introduction of kheaps, a form of kernel heap isolation. It isolates data_buffer (user-data objects), default heap, kernel extension heap, and temp heap into their own zones. Furthermore, kheap provides guarantees that pages will not be reclaimed by other zones via sequestering, making spraying difficult. Fortunately for the researcher though, large objects (>37,668 bytes) weren’t affected by kheap isolation and sequestering. By spraying with large OSData buffers, and abusing various external methods with the type confusion, it’s possible to derive arbitrary read/write as well as an infoleak.

The primary gadget these primitives rely on is a pointer at 0xC0 inside of the IOSurface object which can be faked via the type confusion.

Arbitrary Read It turns out there’s an external method called IOSurfaceRootUserClient::get_surface_use_count(), which will dereference the pointer at offset 0xC0 in the object you can spray into. This gives an easy arbitrary read primitive.

Arbitrary Write Another external method, IOSurfaceRootUserClient::set_compressed_tile_data_region_memory_used_of_plane(), will similarly perform a write using the pointer at 0xC0 using contents you control, giving arbitrary write.

Infoleak It was possible to derive an infoleak by abusing the internal references that IOSurfaceClient would keep to it’s child IOSurface object and it’s parent IOSurfaceRootUserClient object. Most functions would get the client array from the IOSurfaceRootUserClient, index into that array using the IOSurface ID, and pass the IOSurface pointer from the IOSurfaceClient into the function for whatever work that needs to be done. The researcher pondered what would happen if you caused another type confusion and passed a pointer to an IOSurfaceRootUserClient instead. By doing some careful heap shaping and abusing a 32-bit increment on the 0xC0 pointer to shift the IOSurface reference to point to IOSurfaceRootUserClient, a bunch of kernel pointers are leaked, including;

  • The IOSurfaceRoot object
  • The parent IOSurfaceRootUserClient
  • The IOSurfaceClient array