Performance Tips: Optimizing Systems Using the Win32 Non-BMN Filter

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:

  1. Receive request in EvtIoXxx or dispatch routine.
  2. Inspect or modify parameters (buffer, IOCTL codes, lengths).
  3. If processing locally, complete the request; otherwise, forward to lower driver.
  4. 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?

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *