Show Notes

220 - Windows Kernel Bugs, Safari Integer Underflow, and CONSTIFY

An object type confusion was discovered in the Microsoft Kernel Streaming Server (mskssrv.sys), which is used for virtualizing camera devices and allowing multiple applications to access camera streams. The driver uses two main object types for allowing userspace to acquire a context and send/receive data, a context and a stream. A context stores per-device file metadata that’s used for keeping track of memory allocations amongst other things, while stream objects are used for accessing camera stream data. The vulnerability here was that the FSRendezvousServer::PublishRx IOCTL would call FSRendezvousServer::FindObject() with a user-provided identifier to find the accompanying stream object, however FindObject() would search both the context list and the stream list, which made it possible for it to return a context in place of a stream.

Exploitation Due to context being a smaller object than stream, this vuln could be leveraged to get out-of-bounds write on an adjacent context. Particularly, PublishRx would call FsFrameMdl::UnmapPages, which would write the constant “2” to a field in the FrameMdl, the pointer for which came from the context they could corrupt, giving them a constant write-where primitive.

They used this in conjunction with a technique that abuses Windows’ I/O Ring functionality pioneered by Yarden Shafir. I/O Ring kept a pointer to RegBuffers (presumably “register buffers”) as well as a RegBuffersCount which consisted of an array of kernel pointers that would be used for reads/writes on the ioring. By setting RegBuffersCount to non-zero (0x2) and setting RegBuffers to a user-mappable address (such as 0x2000000) via exploiting the bug twice, they could establish an arbitrary read/write.

An integer underflow vuln in Safari/WebKit, which as is typical with JSC bugs, is rooted in the ability for callbacks to change the state of objects. The root of the bug is that you can cause a copy on a zero-sized array to a destination index of something like 0x20, and when the JS engine tries to clamp the copy, results in a copy size of 0 - 0x20 = 0xffffffffffffffe0.

Vulnerability Explanation It’s easier to understand the root bug after understanding what the Proof of Concept does. The PoC first creates an ArrayBuffer with a length of 0x1000 elements and sets a max byte length of 0x4000. It’ll then create a Uint8Array that’s instantiated from that ArrayBuffer, and defines a callback that’ll resize the ArrayBuffer to have a length of 0. Finally, it calls copyWithin() on the Uint8Array with specially crafted arguments. copyWithin is a JS function that does a shallow copy of elements in an array to another area inside that same array without the length changing. It takes a to index, from index, and an optional end of where the copy should stop.

The PoC invokes copyWithin() with a destination to value of 0x20, but the from value is set to {valueOf: callback}. Internally in the JS engine, copyWithin() will result in genericTypedArrayViewProtoFuncCopyWithin() being called, which calls argumentClampedIndexFromStartOrEnd() on each argument. When processing the from value, the callback is executed and changes the ArrayBuffer length to zero (and ultimately results in from being evaluated as zero). The function is now in a state where the ArrayBuffer is zero-sized but the to index is 0x20. The developers did try to account for a scenario where the ArrayBuffer length changes, and so they clamp the copy length to ArrayBuffer.length minus to or from (whichever is higher). The problem is that if the length is set to zero like it is here, this will underflow, resulting in a length of 0xffffffffffffffe0 being passed to memmove().

Exploitability The post doesn’t comment on exploitability, but this bug seems like it would be difficult to exploit. There’s no obvious way of interrupting the memmove(), and with such a large length you’re bound to hit unmapped memory or corrupt critical data that isn’t feasible to recover from. You also don’t have real parallelism to take advantage of in JavaScript, so trying to race the copy and exploit before you crash doesn’t seem viable either.