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.