Implementing the Win32 Non-BMN Filter in Your Windows DriverNote: “Win32 Non‑BMN Filter” is a relatively niche/obscure term; if you mean a specific Microsoft-supplied filter driver, a third‑party library, or a particular filter type (e.g., network, file system, audio), tell me and I’ll tailor the article. Below I assume you mean a custom filter component in the Win32/Windows driver model that is not part of BMN (an unspecified acronym) and focus on creating, integrating, and testing a generic kernel-mode filter driver that follows Windows best practices.
Overview
A filter driver is a kernel-mode component that attaches to an existing driver stack to observe, modify, or extend device I/O behavior. Implementing a Win32 “Non‑BMN” filter means building a filter that operates within Windows Driver Model (WDM) or Windows Driver Frameworks (KMDF/UMDF) but is not tied to BMN-specific conventions. This article walks through design considerations, development steps, sample architecture, key APIs, error handling, signing and deployment, and testing strategies.
When to use a filter driver
- To monitor or log I/O requests (IRPs) transparently.
- To modify requests or responses (e.g., sanitizing data, altering parameters).
- To enforce policies (security, throttling, filtering).
- To add functionality without rewriting the lower-level driver.
If your goal can be met in user mode, prefer that—user-mode components are safer and easier to develop. Kernel-mode filters are for scenarios where performance or low-level access is essential.
Architecture and types
Filter drivers can be classified by:
- Stack position: upper filter (above the function driver) or lower filter (below it).
- Target: device filter (per-device) or global filter (system-wide, e.g., file system filter).
- Framework: WDM (classic IRP-based), KMDF (event-driven), UMDF (user-mode).
Common patterns:
- Pass-through filter: forwards IRPs with minimal change.
- Transforming filter: inspects and modifies data.
- Policy filter: blocks or redirects I/O based on rules.
Development options: WDM vs KMDF vs UMDF
- KMDF (Kernel‑Mode Driver Framework) simplifies driver structure, memory management, and synchronization. Use KMDF for most new kernel-mode filters.
- WDM offers full control but higher complexity—use only if you need functionality not supported by KMDF.
- UMDF runs in user space (safer); choose UMDF if you can avoid kernel-mode requirements.
This article focuses on KMDF for kernel-mode filtering and briefly covers WDF specifics where relevant.
Prerequisites and tools
- Windows Driver Kit (WDK) and Visual Studio.
- Test machine or virtual machine with driver test signing enabled.
- Knowledge of C/C++ and kernel concepts.
- Debugging tools: WinDbg, KD, tracing (ETW).
- Driver signing certificate for production.
Project structure and templates
Use Visual Studio + WDK templates:
- Create a KMDF Driver (Kernel Mode) project.
- Choose a template close to a filter driver (some templates exist for USB, storage, etc.).
- Organize code into: driver entry/unload, device add/remove, I/O dispatch callbacks, and helper modules (logging, policy engine).
Example file layout:
- driver.c / driver.cpp — DriverEntry, DriverUnload
- device.c — EvtDeviceAdd, device context, power handling
- filter_io.c — I/O intercept callbacks, forwarding logic
- utils.c — logging, configuration parsing
- INF file — installation instructions
Key KMDF concepts and APIs
- DriverEntry: perform framework initialization via WdfDriverCreate.
- EvtDriverDeviceAdd: create device objects with WdfDeviceCreate and attach as filter.
- Device context: store per-device state using WDF_DECLARE_CONTEXT_TYPE_WITH_NAME.
- I/O queue and callbacks: create WDFQUEUE with WdfIoQueueCreate and handlers like EvtIoRead, EvtIoWrite, EvtIoDeviceControl.
- Request forwarding: use WdfRequestForwardToIoQueue / WdfRequestSend, or build and send an IRP with IoCallDriver for lower-level control.
- Completion callbacks: EvtRequestCompletionRoutine to inspect results.
- Synchronization: WDF spin locks, mutexes, and execution levels.
- Power and PnP: EvtDevicePrepareHardware, EvtDeviceReleaseHardware, EvtDeviceSelfManagedIoInit, and PnP callbacks if needed.
Attaching your filter
- For function device objects (FDO) and filter device objects (FDO vs filter DO): create the filter device as an upper filter in EvtDriverDeviceAdd.
- Use IoAttachDeviceToDeviceStack or in KMDF let the framework create the device and attach automatically by setting appropriate device characteristics.
- Ensure proper handling of device removal and stop sequences; detach cleanly during PnP removal.
Intercepting and forwarding I/O
Typical flow for an IRP-based filter:
- Receive request in EvtIoXxx or dispatch routine.
- Inspect or modify parameters (buffer, IOCTL codes, lengths).
- If processing locally, complete the request; otherwise, forward to lower driver.
- If forwarding asynchronously, set a completion routine to handle returned status and possibly modify data before completing the original request.
KMDF example pattern (pseudocode):
EvtIoRead(Request) { if (shouldHandleInFilter) { // process and complete WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, bytesRead); return; } // forward WdfRequestFormatRequestUsingCurrentType(Request); WdfRequestSend(Request, WdfDeviceGetIoTarget(Device), &options); }
For IRP-level control in WDM:
- Use IoCopyCurrentIrpStackLocationToNext, IoSetCompletionRoutine, and IoCallDriver.
Buffering models
Be explicit about buffer models:
- Direct I/O (MDLs) — use when transferring large data; map system buffer with MmGetSystemAddressForMdlSafe.
- Buffered I/O — driver uses Irp->AssociatedIrp.SystemBuffer.
- Neither/MethodNeither — careful with user-mode pointers; probe and lock pages.
KMDF abstracts many details via WdfRequestRetrieveOutputBuffer, WdfRequestRetrieveInputBuffer, and WdfRequestRetrieveInputWdmMdl.
IOCTL handling
- Validate IOCTL codes and input/output buffer sizes strictly.
- Use access checks (MethodBuffered, METHOD_IN_DIRECT, etc.) and enforce user-mode pointer probing if using METHOD_NEITHER.
- For device control forwarding, copy or modify IOCTL parameters as needed before forwarding.
Error handling and robustness
- Fail fast on invalid parameters; return appropriate NTSTATUS codes.
- Protect against reentrancy and race conditions using WDF synchronization.
- Always complete requests—never leak WDFREQUEST handles.
- Handle timeouts and canceled requests via EvtRequestCancel.
Logging and diagnostics
- Use WPP tracing for kernel logging; avoid heavy logging in performance-sensitive paths.
- Expose diagnostics via ETW events or a configuration interface (e.g., registry keys).
- Provide verbose logging under a debug flag only.
Signing, testing, and deployment
- Enable test signing on developer machines via bcdedit /set testsigning on.
- Use HLK (Hardware Lab Kit) / Driver Verifier for stress and conformance tests.
- For distribution, sign with an EV code signing certificate and follow Microsoft’s driver submission requirements.
Example: simple KMDF pass-through filter (conceptual)
High-level behavior:
- Attach as upper filter.
- For read/write requests, log sizes and forward to lower driver.
- For specific IOCTL, block or alter parameters.
Pseudocode flow:
DriverEntry -> WdfDriverCreate EvtDriverDeviceAdd -> WdfDeviceCreate + create default queue with EvtIoRead/EvtIoWrite/EvtIoDeviceControl EvtIoRead -> log length; WdfRequestSend to lower IO target EvtRequestCompletion -> log status; WdfRequestCompleteWithInformation
Security considerations
- Validate all inputs and never trust user-mode pointers.
- Minimize privileged operations; prefer least privilege.
- Consider impacts on system stability — a buggy kernel filter can crash the system.
Performance tips
- Avoid heavy processing in I/O paths; offload to worker threads if necessary.
- Use direct I/O/MDLs for large transfers.
- Batch operations where possible and minimize context switches.
- Reduce locking granularity; use per-device contexts.
Testing checklist
- Functional: Basic I/O, IOCTLs, removal, insertion, power transitions.
- Stress: high throughput, random cancellations, concurrent requests.
- Compatibility: test with various stack partners (different lower drivers).
- Security: fuzz IOCTLs and buffer sizes.
- Stability: run Driver Verifier and long-running stress tests.
Troubleshooting common issues
- Leaked requests: ensure every request path ends in a complete.
- Deadlocks: use lock ordering rules and avoid blocking at high IRQL.
- Data corruption: verify buffer lengths and mapping.
- Unexpected detach: ensure proper PnP callbacks to handle surprise removal.
Final notes
If you want, I can:
- Provide a concrete KMDF C code sample for a pass-through filter (complete DriverEntry, EvtDriverDeviceAdd, EvtIoRead/EvtIoWrite/EvtIoDeviceControl, and INF).
- Tailor the article to a specific filter target (file system, network, storage, USB, audio).
- Convert examples to WDM or UMDF.
Which of those would you prefer?