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.
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.
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.
TcpIoControlEndpoint arbitrary read/write
0x18 would invoke
TcpSetSecurityEndpoint, allowing you to get full control of the pointer for the security descriptor.
UdpSetSockOptEndpoint arbitrary increment
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.
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.
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.
DOMWindow::open method receives a frame name of
_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