The Filter Manager and Minifilter Drivers

Windows Internals
Time it takes to read this article 5 minutes.

If you have ever wondered how an antivirus scans a file the instant you open it, how ransomware protection blocks a rogue process from encrypting your documents, or how transparent encryption products work without changing your applications, the answer is almost always the same: a file system minifilter driver. Minifilters sit in the path of every file operation and get a chance to inspect, allow, block, or modify it. This article looks at the Filter Manager — the kernel framework that hosts them — and the model that makes file I/O interception both powerful and, when you understand it, predictable.

Legacy Filters vs Minifilters

Before the Filter Manager existed, a file system filter driver was a legacy filter: it created its own device object and attached it to the target file system’s device stack with IoAttachDeviceToDeviceStack, so that every IRP flowing to the file system passed through it first. This worked, but it was notoriously difficult. Ordering between multiple filters depended on load order and timing, attaching and detaching around mounts was fragile, and the driver had to correctly handle the full complexity of IRPs, fast I/O, and Filter Manager callbacks by hand. A single mistake produced blue screens or subtle data corruption.

The Filter Manager (fltmgr.sys) solves this. It is itself a legacy filter — a single, well-tested driver that attaches to file system stacks — but it exposes a clean model on top. Individual filtering components register with it as minifilters, and the Filter Manager takes care of attaching to volumes, routing operations, and ordering. A minifilter is dramatically simpler to write correctly because it never touches the device stack directly; it just registers the operations it cares about and lets fltmgr do the plumbing.

Altitudes

The old ordering problem is solved by a concept called the altitude. Every minifilter is assigned a unique numeric altitude by Microsoft, and the Filter Manager uses it to place the minifilter deterministically in the stack: a higher altitude sits closer to the user (it sees an operation earlier on the way down and later on the way back up), while a lower altitude sits closer to the file system. Because altitudes are centrally assigned and unique, two products can never disagree about who runs first — the order is the same on every machine.

Altitudes are grouped into ranges by the kind of work a filter does, so that, for example, an anti-virus scanner runs above an encryption filter (it needs to see plaintext). Common load-order groups include:

  • FSFilter Anti-Virus — altitudes around 320000-329999, where AV/EDR scanners live.
  • FSFilter Activity Monitor — around 360000-389999, for monitoring and auditing tools.
  • FSFilter Encryption — around 140000-149999, so encryption happens below the scanners.
  • FSFilter Replication, Quota, Compression, Virtualization — each with their own bands.

The deterministic ordering is not just tidiness — it is a correctness guarantee. A scanner that ran below an encryption filter would only ever see ciphertext and could scan nothing useful.

Operation Callbacks

A minifilter does its work through operation callbacks. When it registers, it supplies an array of callback entries, each naming an I/O operation (by its major function) and up to two routines: a pre-operation callback and a post-operation callback. The minifilter only lists the operations it cares about — a ransomware-protection filter might register only for IRP_MJ_CREATE, IRP_MJ_WRITE, and IRP_MJ_SET_INFORMATION (rename/delete), ignoring everything else.

The pre-operation callback runs as the operation travels down toward the file system. It can inspect the parameters and decide what happens next by its return value:

  • FLT_PREOP_SUCCESS_WITH_CALLBACK — let the operation continue, and call me again in the post phase.
  • FLT_PREOP_SUCCESS_NO_CALLBACK — let it continue, but I do not need the post callback.
  • FLT_PREOP_COMPLETEstop the operation here and return a status I supply (this is how a filter blocks access, e.g. returning STATUS_ACCESS_DENIED).
  • FLT_PREOP_PENDING — I have queued the operation and will complete it later.

The post-operation callback runs as the completed operation travels back up, so it can see the result — the data that was read, or the status the file system returned — and act on it (for example, scanning the buffer that a read just filled). Together, the pre/post pair lets a minifilter observe and control both sides of every operation it subscribes to.

Contexts and Communication

Filtering usually needs state — for instance, remembering a verdict about a file so you do not rescan it on every read. The Filter Manager lets a minifilter attach contexts to objects: volume contexts, instance contexts, file contexts, and stream contexts. The framework stores and retrieves them for you and cleans them up when the object goes away, which avoids a whole class of lifetime bugs.

Most security products are split between a kernel minifilter and a user-mode service (the service holds the scanning engine, signatures, and policy). They talk over a dedicated filter communication port. The kernel side calls FltCreateCommunicationPort to publish a named port; the user-mode service connects with FilterConnectCommunicationPort and then exchanges messages with FilterSendMessage/FilterGetMessage. This is how the kernel asks user mode “should I allow this file?” and receives a verdict.

Instances and Volumes

A running attachment of a minifilter to a particular volume is called an instance. When a new volume is mounted, the Filter Manager consults the minifilter’s registered altitude and its instance-setup callback to decide whether to attach, and creates an instance if so. There are matching callbacks for query-teardown and instance teardown, so a filter can cleanly detach from a volume — for example when it is being unloaded or the volume is dismounted. Because attachment is mediated entirely by fltmgr, the minifilter never has to reason about device-stack surgery.

Inspecting the Filter Stack

The built-in fltmc tool exposes the live minifilter configuration, and a kernel debugger goes deeper:

# List loaded minifilters, their altitude, and number of instances
fltmc filters

# List every instance (minifilter attached to a specific volume)
fltmc instances

# List the volumes Filter Manager knows about
fltmc volumes

# Unload a minifilter by name (requires privilege; some are unload-protected)
fltmc unload <filterName>
# In WinDbg (kernel mode), load the Filter Manager extension and enumerate
kd> !fltkd.filters      # all registered minifilters and their frames
kd> !fltkd.frame        # the Filter Manager frames and attached instances
kd> !fltkd.volumes      # volumes and their instance lists

fltmc filters is the fastest way to see, on a given host, exactly which security products (and which encryption or monitoring tools) are filtering the file system, and at what altitude they sit relative to one another. In the debugger, !fltkd lets you walk from the Filter Manager frames down to individual instances and their registered callbacks.

Security Relevance

Minifilters are one of the most important defensive technologies in Windows. They are the backbone of real-time anti-virus scanning (scan on IRP_MJ_CREATE/close), EDR file telemetry, ransomware protection (blocking mass rename/encrypt patterns), data loss prevention, and transparent encryption. Knowing the pre/post model and altitudes is what lets a defender reason precisely about where their sensor sits and what it can see.

The same knowledge matters from the adversary-awareness side, which is why it belongs in a defender’s toolkit. Attackers with sufficient privilege may attempt to unload or disable a security minifilter (fltmc unload or by tampering with its service), which is a high-value event that monitoring should treat as an alarm — a well-designed security filter marks itself unload-protected and its service as protected. A buggy third-party minifilter is also kernel attack surface, since its callbacks process attacker-influenced file operations. Finally, because ordering is altitude-based, defenders should understand the concept of altitude conflicts and ensure their sensor loads at the intended position. Everything here is for defensive understanding and authorized testing only.

Conclusion

The Filter Manager turned file system filtering from a dark art into a structured, altitude-ordered framework of pre- and post-operation callbacks, contexts, and communication ports. That structure is exactly why minifilters can safely carry so much of the Windows security stack. Once you can read fltmc filters and picture the stack it describes, the behavior of AV, EDR, and encryption products on a host stops being a black box. In the next article in the series we turn to another framework that both administrators and attackers lean on heavily: Windows Management Instrumentation (WMI) internals.

You Might Also Like

This article is part of the Windows Internals series. These related deep-dives cover adjacent parts of the system:

Comments

Copied title and URL