92 - WebKit Bugs, a Windows Race, and House of IO Improved
Seven vulnerabilities in Windows. Starts off with a lot of background information on Windows kernel I/O, how Time-of-Check Time-of-Use (TOCTOU) works, and an overview of Advanced Local Procedure Calling (ALPC), which is a set of high performance IPC syscalls. The important takeaways before reading into the vulnerabilities are:
- Windows ioctls can operate in three different modes:
- “Buffered”, where user data is copied into kernel controlled buffers.
- “Direct I/O”, where a usermode buffer is locked in memory and mapped to a kernel address.
- “Neither”, where the kernel will operate directly on a shared user mapping.
- Users of ALPC calls can optionally use I/O completion ports for processing asynchronous I/O requests.
Bug #1: AlpcpCompleteDispatchMessage
TOCTOU
This function copies a user-provided message header and body into a shared user memory region and operates directly on it. Because a user can write to it, there’s a TOCTOU where a user could overwrite a field such as the message length post-validation, allowing an out of bounds access.
Bug #2: Socket validation bypass
Normally, when sending socket I/O control requests, the request “codes” or commands are validated so users can’t call internal functions with dangerous values. However, if you create a socket with a transport name specified, you’ll get a special handle called a “TDX” (Transaction Driver) handle, which allows you to call TdxIssueIoControlRequest
function. This routine has no restrictions on user-provided codes, opening up a large attack surface for the next 4 bugs.
Bug #3: TcpIoControlEndpoint
infoleak
Code 0x20
would invoke TcpQuerySecurityEndpoint
, which would return a pointer to the security descriptor from the kernel PagedPool and write it directly to the output buffer in userland. This also works with UDP address endpoints.
Bug #4: TcpIoControlEndpoint
arbitrary read/write
Code 0x18
would invoke TcpSetSecurityEndpoint
, allowing you to get full control of the pointer for the security descriptor.
Bug #5: UdpSetSockOptEndpoint
arbitrary increment
The SIO_RESERVED_1
code on UDP address endpoints would pass requests to QimInspectAssociateQoS
for quality of service functionality. Normally messages going to this function are translated by the AFD.sys driver, but here the user is given direct access, and can pass an arbitrary pointer for the endpoint. This can be leveraged as an arbitrary increment via abusing a refcount increment.
Bug #6: UdpSetSockOptEndpoint
TOCTOU
SIO_SET_QOS
would call QimInspectSetQos
. In this case, the user-provided data is presumably validated, but it doesn’t properly lock the user buffer, so it’s vulnerable to a TOCTOU where the ProviderSpecific
buffer size can be increased post-allocation.
Bug #7: SIOCSMSFILTER
in multicast filtering (ipv4/ipv6)
Similar to bugs 1 and 6, user-data is mapped into user mode shared memory, and the gf_numsrc
field is vulnerable to a TOCTOU condition, allowing overflow in the kernel NonPagedPool.
This issue resides in the nt!ObpCreateSymbolicLinkName
syscall for creating symbolic links. One of the first things it does is creates a user handle for the symbolic link object. This handle can be used by other symbolic link related syscalls, like nt!ObpDeleteSymbolicLinkName
. Because of insufficient locking, as soon as the create syscall installs this handler, another thread can run and manipulate the symbolic link object before nt!ObpCreateSymbolicLinkName
is finished with it. This leads to an race where the delete syscall can swap in a 0 for the DosDeviceDriverIndex
field that’s used to access into an array later in the create syscall. When the create syscall decrements the index as part of it’s regular codepath, it accesses the drive table array out of bounds at -1.
Amazingly simple issue as far as browser bugs go. The removeFromFacesLookupTable
method in the CSSFontFaceSet
class failed to properly check if they reached the end of the table when looking up a font. This was because they checked the iterator against m_facesLookupTable.end()
in an assert instead of a proper check. In release builds, assert is a no-op. This lead to a situation where the method would try to remove a font that doesn’t exist (such as by adding an invalid font) from the table, accessing stale memory.
When the DOMWindow::open
method receives a frame name of _top
or _parent
, they’re treated as special cases which get an immediate scheduling for a location change. The scheduleLocationChange
function is usually invoked in a asynchronous manner if the URL is the same as the old one, but if the URL fragments differ, it’ll run synchronously and fire a popstate
event. An attacker can define an event handler that destroys the parent iframe to cause a UAF.
House of IO was a set of heap attack techniques detailed in 2020, which attacked the per-thread cache (tcache) to manipulate the allocator into providing arbitrary addresses for malloc, and thus arbitrary read/write primitives. One of the techniques (underflow) relied on the fact that while glibc does pointer masking on most entries in the freelist, the head of the freelist is not protected with safe linking. If you can corrupt the tcache_perthread_struct
object and smash the head of the tcache bin freelists, you can bypass safe linking and get arbitrary pointers. Due to the fact that the tcache_perthread_struct
object is allocated early in the heap, it’s difficult to corrupt, to corrupt it with an OOB write, it needed to be an underflow that allowed you to smash data at lower addresses.
Maxwell Dulin’s Improvement
The improvement here is the ability to use this technique in more situations than just underflows by using thread arena sharing to get a tcache_perthread_struct
object in the middle of a heap instead of the beginning. While new threads typically get their own heap arena, after apply pressure with creating a lot of threads, glibc falls back on sharing arenas between threads. This opens the potential for something like overflow-based corruption to be able to hit the tcache_perthread_struct
object.