Implementation notes / C and the Windows kernel

A Windows file-system
minifilter,
explained.

Follow a file request beneath the application. A small C driver uses Filter Manager callbacks to observe file activity, inspect write buffers, and demonstrate opt-in blocking.

Educational kernel driver. Monitor-only by default.
Source-based walkthroughs—not live kernel traces.

01 Native C / WDK 02 Pre + post callbacks 03 Bounded inspection 04 VM-only testing

01 / The problem

See the requests an application leaves behind.

Saving a document looks like one action in an application. Underneath, it can involve opening a file, writing bytes, renaming a temporary file, and closing handles. A file-system minifilter is a kernel component that can receive callbacks around selected operations as they pass through the Windows file-system stack.

This project makes that mechanism visible. It reports activity for paths containing \FSDRIVERTEST and demonstrates how a callback can allow a request or complete it with an error. The goal is to understand kernel contracts and ownership—not to claim a production antivirus, audit trail, or access-control system.

  1. 01RequestAn application opens, reads, writes, or changes a file.
  2. 02DispatchFilter Manager invokes registered callbacks.
  3. 03DecisionFSDriver checks the name and optional debugger flags.
  4. 04ContinuationAllow the request, or complete it with access denied.

The browser only explains this flow. It cannot load the driver, read your files, or change any kernel flags.

02 / Architecture

One driver. Three explicit responsibilities.

PLATFORM / FLTMGR.SYS

Windows handles the stack.

Filter Manager manages volume attachment, filter ordering, and callback dispatch. FSDriver does not build a legacy attachment chain, create its own device, or expose an IRP dispatch table to a client.

IMPLEMENTATION / FSDRIVER.C

The driver supplies policy.

A registration table selects operations. Helpers obtain and release normalized file names, match the watched substring, print diagnostic messages, and optionally inspect or reject selected requests.

PACKAGING / FSDRIVER.INF

The INF makes it loadable.

The package declares a demand-start file-system service, its dependency on FltMgr, and an instance altitude. The source, stamped INF, and signed catalog have distinct jobs.

Not a directory watcher

This is not a user-mode FileSystemWatcher. Its callbacks run inside the kernel request path. A mistake can affect the whole OS, which is why installation and experiments belong in a disposable VM.

No companion client required

Ordinary file operations drive the callbacks. Messages use DbgPrintEx; WinDbg can change three private-symbol flags. There is no service UI, communication port, or browser-to-driver connection.

Read the implementation ↗ · Inspect the installation manifest ↗

03 / Driver lifecycle

Register first. Unregister before leaving.

  1. Enter. DriverEntry reports the watched substring and default monitor-only state.
  2. Register. FltRegisterFilter receives FilterRegistration, which references the operation table and unload callback.
  3. Start. FltStartFiltering begins filtering. If startup fails after registration, the driver unregisters before returning the failure.
  4. Unload. FsDriverUnload calls FltUnregisterFilter and clears the handle. Unregistration waits for callbacks and instance teardown.

Lifecycle sketch / simplified from source

DriverEntry
  ├─ FltRegisterFilter
  │    └─ failure → return status
  └─ FltStartFiltering
       ├─ failure → unregister → return
       └─ success → callbacks can run

FsDriverUnload
  └─ FltUnregisterFilter
       └─ g_FilterHandle = NULL

The callback wiring is data, not a hidden framework convention: Callbacks[]FilterRegistrationFltRegisterFilter. An operation not registered in the table does not reach this driver.

04 / Callback contracts

A request is not the same as a result.

Most messages are emitted before the file system finishes. Create/open is different: its post-operation callback can distinguish a successful new creation from opening or overwriting an existing file.

Operations registered by this implementation
OperationWhere it is handledWhat it tells you
IRP_MJ_CREATEPre + post-createRequests a post callback; reports successful create, open, overwrite, or supersede outcomes for watched names.
IRP_MJ_READPre-operationReports a read request. It does not report bytes actually read.
IRP_MJ_WRITEPre-operationReports a write request; can inspect a bounded prefix or reject the request when its flag is enabled.
IRP_MJ_SET_INFORMATIONPre-operationDistinguishes rename, delete-disposition, and end-of-file size requests. Only delete-disposition requests have an optional block here.
IRP_MJ_CLEANUPPre-operationReports last-handle cleanup for a file object when name lookup succeeds.
IRP_MJ_CLOSERegistered, reporting skippedThe reporting helper avoids querying the name during file-object teardown.
ALLOW / NO POST CALLBACK

Observe and continue

FLT_PREOP_SUCCESS_NO_CALLBACK lets processing continue without requesting a post-operation callback from this filter.

ALLOW / REQUEST POST CALLBACK

Come back after create

FLT_PREOP_SUCCESS_WITH_CALLBACK asks Filter Manager to invoke this filter again after create processing.

COMPLETE / OPTIONAL BLOCK

Own the result

FLT_PREOP_COMPLETE stops the request here. The block path sets STATUS_ACCESS_DENIED and clears IoStatus.Information.

05 / Source-based walkthrough

Follow one decision at a time.

Illustrative / not captured

These curated scenarios explain branches in the C source. They are not a kernel simulator, a recorded test run, or proof of runtime behavior. Watched-file scenarios assume non-paging I/O at PASSIVE_LEVEL and successful name lookup unless stated otherwise.

Showing: Write / monitor. All flags off.

IRP_MJ_WRITEC:\FsDriverTest\notes.txt

A watched write, allowed to continue.

g_BlockWrites = 0 · g_BlockDeletes = 0 · g_InspectWrites = 0

  1. Filter Manager invokes FsDriverPreOperation for the write request.
  2. The reporting helper checks its context, obtains a normalized name, and matches the watched substring.
  3. The driver prints a Write request message and releases the name information.
  4. Inspection and blocking are off. The callback allows processing to continue without a post callback.

06 / Decisions and tradeoffs

The interesting part is the contract.

OWNERSHIP

Borrow the name. Release the name.

FltGetFileNameInformation returns a reference that must be released with FltReleaseFileNameInformation. The helper uses a length-bounded UNICODE_STRING comparison instead of treating a path as a null-terminated C string.

EXECUTION CONTEXT

Logging has IRQL constraints too.

The helper skips reporting above PASSIVE_LEVEL, for paging I/O, and at close. Unicode debug output requires that IRQL. Failed name queries also return without blocking: incomplete visibility is a deliberate limitation, not a retry loop.

BUFFER HANDLING

Inspect a prefix, not a whole payload.

The write-inspection helper uses an existing MDL or attempts FltLockUserBuffer, maps pages with MmGetSystemAddressForMdlSafe, and guards the read with structured exception handling. Text output is capped at 64 bytes and may stop at a null byte.

SCOPE

Off by default is not a security boundary.

Three volatile BOOLEAN flags default to false. Matching is a substring test across attached volumes: FsDriverTestOther can also match. It is not an exact C:\FsDriverTest allowlist.

Debugger detail that matters: use eb for the one-byte flags, not ed. A four-byte write can overwrite neighboring data. The historical design notes are preserved, but the standalone source and README correct those commands.

07 / Build and run it yourself

Build on the host. Experiment in a VM.

The supported package target is Windows 11 24H2 or newer. It uses Parameters\Instances registration and matching catalog targets. The INF section names do not enforce an OS minimum; check your test VM version before installing.

Kernel driver, real consequences. Use a disposable snapshot-backed VM with no valuable files. Keep a kernel debugger and recovery path available. Do not install this learning driver on the development host or a production machine.

01 / HOST

Build the package

Install Visual Studio with Desktop development with C++, a compatible Windows SDK and WDK, and WDK integration. Open FSDriver/FSDriver.slnx and choose Debug | x64, or run this from Developer PowerShell at the repository root:

msbuild .\FSDriver\FSDriver.vcxproj /m /p:Configuration=Debug /p:Platform=x64

Keep the generated package together

FSDriver.sys is the driver, the stamped FSDriver.inf installs it, and fsdriver.cat signs the package contents. Keep the matching FSDriver.pdb for debugging. Do not deploy the unstamped source INF.

02 / TEST VM

Prepare signing and debugging

Take a snapshot, configure kernel debugging and VM test-signing policy, and reboot as required. Secure Boot policy can restrict test signing; do not weaken the host to work around it.

Trust the build's public test certificate in the VM's Local Machine Trusted Root and Trusted Publishers stores as required by your signing setup. Never copy private signing keys. Transfer the complete generated package.

The repository does not provide a production signing certificate, an assigned production altitude, or an automatic driver installer.

03 / ELEVATED VM TERMINAL

Install, load, and check attachment

pnputil /add-driver .\FSDriver.inf /install
sc.exe qc FSDriver
fltmc load FSDriver
fltmc filters
fltmc instances -f FSDriver

Non-PnP primitive driver package

Record the published oemNN.inf name. Verify service creation rather than relying on a device-match message. Altitude 370000 must not collide with another filter; it is for local testing only.

04 / DISPOSABLE TEST FILES

Generate a small workload

New-Item -ItemType Directory C:\FsDriverTest -Force
Set-Content C:\FsDriverTest\notes.txt 'hello filter'
Get-Content C:\FsDriverTest\notes.txt
Rename-Item C:\FsDriverTest\notes.txt renamed.txt
Remove-Item C:\FsDriverTest\renamed.txt

Observe in the kernel debugger

Look for FsDriver: messages. If needed, enable error-level output with ed nt!Kd_IHVDRIVER_Mask 1; this mask really is 32-bit. Callback messages do not map one-to-one to shell commands.

Optional WinDbg experiments / private symbols required / VM only
CommandDemonstratesRestore afterward
eb FSDriver!g_InspectWrites 1Bounded text inspection of eligible writes. Use only non-sensitive test content.eb FSDriver!g_InspectWrites 0
eb FSDriver!g_BlockWrites 1Access denied for an eligible write request to an existing watched file.eb FSDriver!g_BlockWrites 0
eb FSDriver!g_BlockDeletes 1Access denied for an eligible delete-disposition request.eb FSDriver!g_BlockDeletes 0

Finish the experiment: reset the flags, verify normal file operations, then run fltmc unload FSDriver. Remove the package with pnputil /delete-driver oemNN.inf /uninstall, using the exact published name recorded earlier. Reboot the VM if requested.

08 / Evidence and validation

A passing build is one checkpoint.

The standalone integration has a verified Debug x64 package build using Visual Studio 2026 and WDK 10.0.28000.0. That establishes build and packaging results—not installation, behavioral correctness, or kernel safety.

C compilation and Filter Manager linkageDEBUG x64 / PASSED
INF verification and catalog generationNO SIGNABILITY ERRORS OR WARNINGS
Driver binary and catalog test signingCOMPLETED
Release and ARM64 configurationsPRESENT / NOT YET VERIFIED
Installation, load/unload, and Driver VerifierTEST VM VALIDATION PENDING

What to check next

  1. Load, attach, and unload repeatedly with all flags off.
  2. Exercise watched files and a nonmatching path; compare expected messages.
  3. Test one optional flag at a time and confirm normal behavior after resetting it.
  4. Run Driver Verifier against FSDriver.sys only, with a debugger and recovery snapshot.

What is not being claimed

No automated kernel-runtime tests were found for this project. There is no bundled runtime capture, performance benchmark, or verified deployment in this write-up. The interactive walkthrough above is authored from source, not generated by the driver.

The GitHub Pages workflow publishes documentation only. It does not compile, sign, or test the kernel driver.

09 / Limits and lessons

Useful as a learning tool. Not a security boundary.

Visibility is intentionally incomplete.

  • Name-query failures are fail-open.
  • Paging I/O, raised-IRQL reporting, and close reporting are skipped.
  • Rename destinations are not separately inspected.
  • Read/write/delete messages describe requests, not confirmed outcomes.

Blocking is a demonstration.

  • Substring matching is not exact directory isolation.
  • Create-time truncation and delete-on-close are not comprehensively covered.
  • Memory-mapped changes are not comprehensively covered.
  • No persistent audit log, communication-port client, or production policy engine is included.

The transferable lesson is how to work inside a kernel callback contract: register only the operations you need, know which context you are in, pair every acquired reference with a release, initialize completion results when rejecting a request, and keep observed evidence separate from intended behavior.

10 / Continue in the repository

A short route through the source.

START HERE

Standalone README ↗

Build prerequisites, output locations, signing guidance, and the VM validation checklist.

FOLLOW THE CALLBACKS

FsDriver.c ↗

Start with DriverEntry and the registration tables, then read the pre-operation callback, post-create callback, and reporting helpers.

Extracted into a standalone project from the original internship work. The site shares its visual language with the Windows Heap Manager project.