Exploiting an OOB read/Type Confusion in iOS <= 14.7

We discussed this vulnerability as part of our weekly podcast on 01 December 2021

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