Another week, another way to locally crash the Windows kernel with an unhandled exception in ring-0 code (if you haven’t yet, see last week’s DoS in win32k!NtUserThunkedMenuItemInfo). Today, the bug is in the win32k!NtDCompositionBeginFrame system call handler, whose beginning can be translated into the following C-like pseudo-code:
NTSTATUS STDCALL NtDCompositionBeginFrame(HANDLE hDirComp, PINPUT_STRUCTURE lpInput, POUTPUT_STRUCTURE lpOutput) {
NTSTATUS st;
INPUT_STRUCTURE Input;
DirectComposition::CConnection *Connection;
if (lpInput != NULL) {
try {
ProbeForRead(lpInput, sizeof(INPUT_STRUCTURE), 1);
RtlCopyMemory(&Input, lpInput, sizeof(INPUT_STRUCTURE));
st = STATUS_SUCCCESS;
} __except(EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode();
}
} else {
st = STATUS_INVALID_PARAMETER;
}
KeEnterCriticalRegion();
if (NT_SUCCESS(st)) {
st = DirectComposition::CConnection::ReferenceHandle(hDirComp, &Connection);
if (NT_SUCCESS(st)) {
if (Microsoft_Windows_Win32kEnableBits & 1) {
Template_xq(&DCompBeginFrameEvent, hDirComp, lpInput->SomeField);
}
[...]
}
}
[...]
}
Since the i/o structure names and definitions are not known to me, I just generically called them INPUT_STRUCTURE and OUTPUT_STRUCTURE; their details are non-essential to understand the bug. Here, we can see that the 2nd argument (lpInput) is accessed twice: once in line 9, with a proper sanitization with an inlined ProbeForRead call and a try/except block, but then also in line 23, where a field at offset 0x10 (SomeField in the above listing) is read from the user pointer while exception handling is disabled. The Template_xq function is just a thin wrapper around EtwWrite, which is used for logging kernel-mode events. This is the bug we want to exploit.