WDF / KMDF
The Windows Driver Framework provides a modern driver development model that manages object lifetimes and IRP routing, but misuse of WDF APIs still introduces vulnerabilities -- particularly around request buffer validation, object lifecycle, and callback safety.
Attack Surface Overview
- Entry points: WDF I/O queue callbacks including
EvtIoDeviceControl,EvtIoRead,EvtIoWrite,EvtIoInternalDeviceControl, andEvtIoDefaultregistered viaWdfIoQueueCreate - Buffer retrieval:
WdfRequestRetrieveInputBufferandWdfRequestRetrieveOutputBufferwith aMinimumRequiredLengthparameter that the framework enforces -- but only if the driver specifies a non-zero value - Object model: WDFOBJECT hierarchy with parent-child relationships governing automatic cleanup;
WdfObjectDelete, reference counting viaWdfObjectReference/WdfObjectDereference, and typed context areas - File object context:
WDFFILEOBJECTcallbacks (EvtDeviceFileCreate,EvtFileCleanup,EvtFileClose) with per-file context that must be synchronized across concurrent access - Request forwarding:
WdfRequestSendandWdfRequestFormatRequestUsingCurrentTypefor forwarding requests to lower drivers, with completion routine registration viaWdfRequestSetCompletionRoutine - UMDF consideration: User-Mode Driver Framework runs drivers in a host process with restricted kernel access, significantly reducing kernel attack surface but still relevant for driver-specific logic bugs and information disclosure
- Key risk: The framework handles IRP routing and basic buffering but does NOT validate the semantic content of IOCTL buffers -- drivers must still validate all input data within their callbacks
Mechanism Deep-Dive
KMDF abstracts the WDM IRP model behind a framework that manages many common driver operations automatically. When a driver calls WdfIoQueueCreate with the appropriate dispatch type (sequential, parallel, or manual), the framework intercepts incoming IRPs, performs buffering (for buffered I/O), and dispatches them to the driver's registered EvtIo* callbacks. The driver retrieves input and output buffers using WdfRequestRetrieveInputBuffer(request, MinimumRequiredLength, &buffer, &length). If MinimumRequiredLength is non-zero, the framework returns STATUS_BUFFER_TOO_SMALL when the actual buffer is smaller. However, many drivers pass 0 for this parameter and then cast the buffer to a structure pointer without any size check, effectively bypassing the framework's built-in protection.
The WDF object model uses a parent-child hierarchy for automatic lifetime management. When a parent object is deleted, all child objects are deleted automatically. This simplifies resource management but introduces new failure modes. If a driver creates an object with the wrong parent, the object may be freed too early (parent deleted before the object is done being used) or too late (memory leak). For example, a WDFMEMORY object created as a child of WDFDEVICE persists for the device's entire lifetime, but if it was intended to track per-request data, it should have been created as a child of WDFREQUEST so it is automatically freed when the request completes. Additionally, WdfObjectReference and WdfObjectDereference provide manual reference counting that must be balanced correctly -- an extra dereference causes use-after-free, a missing dereference causes a leak.
Request completion is another critical area. Each WDFREQUEST must be completed exactly once via WdfRequestComplete, WdfRequestCompleteWithInformation, or WdfRequestCompleteWithPriorityBoost. Completing a request twice causes a double-free of the underlying IRP, which can corrupt pool metadata and lead to code execution. Failing to complete a request causes the calling process to hang indefinitely and leaks kernel resources. The framework provides WdfRequestIsCanceled and cancellation callbacks (EvtRequestCancel), but race conditions between normal completion and cancellation can still cause double-completion if the driver does not use proper synchronization, such as an interlocked flag or WdfRequestUnmarkCancelable before completing.
The queue dispatch type has significant implications for thread safety. A sequential queue delivers one request at a time, providing implicit serialization. A parallel queue delivers requests concurrently, and the driver must protect shared state with its own synchronization. Choosing the wrong dispatch type -- or correctly using parallel dispatch but forgetting to protect shared state -- introduces race conditions that can be triggered by sending multiple IOCTLs simultaneously from separate threads.
Common Vulnerability Patterns
MinimumRequiredLengthset to 0: Driver callsWdfRequestRetrieveInputBuffer(request, 0, &buf, &len), then castsbufto a structure pointer without checkinglen >= sizeof(STRUCT), allowing out-of-bounds access from a short buffer- Double completion: Both a normal completion path and a cancellation callback complete the same request, causing a double-free of the underlying IRP and pool corruption
- Missing output buffer size validation: Driver calls
WdfRequestRetrieveOutputBufferwith a minimal length check but writes a larger structure, overflowing into adjacent pool memory - Object parent misconfiguration: A
WDFMEMORYorWDFTIMERcreated as a child ofWDFDEVICEwhen it should be a child ofWDFREQUEST, causing the memory or timer to outlive the request context it references - File object context race: Multiple threads access per-file context (
WdfObjectGetTypedContextonWDFFILEOBJECT) without synchronization between create, I/O, and cleanup callbacks when using parallel dispatch queues - Request forwarding without completion routine: Driver forwards a request to a lower driver with
WdfRequestSendbut does not set a completion routine, losing the ability to clean up driver-specific state when the lower driver completes the request - Sequential queue starvation: Using a sequential I/O queue with long-running or blocking operations blocks all subsequent requests, creating denial-of-service conditions
- Framework version mismatch: Driver compiled against one KMDF version but loaded with a different framework DLL version, causing structure layout mismatches in context areas
Driver Examples
Most modern Windows drivers use KMDF, including HID drivers (HID miniport drivers via hidclass.sys), sensor drivers, USB function drivers, battery drivers (cmbatt.sys), simple PCI device drivers, and serial port drivers. UMDF is used for printers, point-of-sale devices, portable device protocol (MTP) drivers, and sensors. Notable KMDF-based system drivers include SerCx2.sys (serial framework), SpbCx.sys (simple peripheral bus), UCX01000.sys (USB host controller extension), and GPIO client drivers. Third-party KMDF drivers for custom hardware devices -- particularly industrial control, scientific instruments, and IoT peripherals -- are common sources of buffer validation vulnerabilities because their developers may be less familiar with the nuances of the MinimumRequiredLength parameter and WDF object lifetime model.
Detection Approach
- Callback tracing: Identify
WdfIoQueueCreatecalls to find all registeredEvtIo*callbacks. For each callback, traceWdfRequestRetrieveInputBufferandWdfRequestRetrieveOutputBuffercalls to verify thatMinimumRequiredLengthis set to at leastsizeof(expected_struct)or that the returned length is explicitly checked before casting. - Completion analysis: Track all paths that call
WdfRequestCompleteor related functions. Verify that each request is completed exactly once on every code path, including error paths and cancellation callbacks. Check for races between cancellation routines and normal completion by looking forWdfRequestUnmarkCancelableusage. - Object lifetime auditing: Map parent-child relationships for all WDF objects created by the driver. Verify that child object lifetimes do not exceed the validity of referenced data, and that per-request allocations use the request as parent.
- WDF Driver Verifier: Enable KMDF Verifier via the registry (
HKLM\System\CurrentControlSet\Services\{driver}\Parameters\Wdf\VerifierOn) to catch double completions, unreturned requests, incorrect object deletion, and other framework contract violations at runtime. - Patch diffing: KMDF patches frequently change
MinimumRequiredLengthfrom 0 tosizeof(STRUCT), add explicit length checks after buffer retrieval, or add cancellation synchronization around request completion.
Related CVEs
| CVE | Driver | Description |
|---|---|---|
| CVE-2024-35250 | ks.sys |
Untrusted pointer dereference through kernel streaming framework dispatch |
| CVE-2024-26229 | csc.sys |
Driver missing access check on privileged IOCTL |
| CVE-2024-38054 | ksthunk.sys |
Integer overflow in streaming header thunking across framework boundary |
| CVE-2024-38238 | ksthunk.sys |
Unsafe MDL mapping in WDF-managed thunking path |
AutoPiff Detection
wdf_request_buffer_size_check_added--MinimumRequiredLengthchanged from 0 tosizeof(struct)inWdfRequestRetrieveInputBufferorWdfRequestRetrieveOutputBufferwdf_request_completion_guard_added-- Double completion guard, interlocked flag, or cancellation synchronization added to request handlingwdf_object_parent_fixed-- WDF object parent-child relationship corrected to match intended lifetime semanticswdf_queue_dispatch_type_changed-- I/O queue dispatch type changed (e.g., sequential to parallel or vice versa) to address synchronization requirementswdf_output_buffer_validation_added-- Output buffer size check added before writing response data to prevent pool overflow