Understanding File System Minifilter and Legacy Filter Load Order

Hello, my name is Fred Jeng from the Global Escalation Services team. For today’s post, I want to go over how Windows 7 and Windows Server 2008 R2 load file system mini-filters in a mixed environment when legacy filters are also present. I recently came across an issue where the filters were being loaded out of order based on their altitudes. This can cause all sorts of problems with a filter driver’s functionality if they are incorrectly positioned on the stack.  Take for example the following filter stack, obtained using the fltmc command from the cmd prompt:

 

C:\Windows\system32>fltmc

 

Filter Name                     Num Instances    Altitude    Frame

------------------------------  -------------  ------------ -----

AVLegacy                                        389998.99   <Legacy>

EncryptionLegacy                                149998.99   <Legacy>

AVMiniFilter                           3        328000         0

luafv                                  1        135000         0

FileInfo                               13       45000          0

 

At first glance it looks like there is a problem causing the legacy encryption filter to be loaded above the antivirus minifilter, which has a higher altitude. This may cause issues with AVMiniFilter as the IOs that it receives are still encrypted. Due to limitations in how the filter drivers attach to the driver stack, this is actually the intended behavior. However, there is a solution to manipulate the load order to load the legacy filters correctly based on their altitude.

 

First some background information regarding legacy filters and minifilters.

In the old days before minifilters, legacy drivers can only attach at the top of the driver stack so the load order also controlled the attachment order. The earlier a legacy driver loads, the lower it can attach on the file system stack. Minifilters on the other hand can load at any time, but their positions relative to other minifilters are controlled by their altitude. When a minifilter loads, it needs to register with an appropriate frame created by fltmgr. Each frame is a fltmgr device object and represents a range of altitudes. There can be more than one frame on the file system stack but the range of altitudes that each frame represents cannot overlap with the altitude range of another frame. For interoperability with legacy drivers, minifilters must still maintain a load order group. The frames are created and managed by fltmgr, which itself is a legacy driver. The ramification of this is that fltmgr must follow the old legacy filter driver rules and attach only at the top of the stack.

 

From the above example, let’s walk through how the legacy and minifilters above are loaded to get us into the state such that the altitude appears to be out of order.

First, here are the details for the 5 drivers.

 

Driver Name

Type

Load Order

Start Type

Altitude

AVLegacy

Legacy

FsFilter Anti-Virus

SERVICE_BOOT_START

329998.99

AVMiniFilter

Minifilter

FsFilter Anti-Virus

SERVICE_BOOT_START

328000

EncryptionLegacy

Legacy

FsFilter Encryption

SERVICE_BOOT_START

149998.99

Luafv

Minifilter

FsFilter Virtualization

SERVICE_AUTO_START

135000

FileInfo

Minifilter

FSFilter Bottom

SERVICE_BOOT_START

45000

 

 

MSDN has an article that describes load order groups and altitudes for minifilters: https://msdn.microsoft.com/en-us/library/windows/hardware/ff549689%28v=vs.85%29.aspx.

Referencing this article regarding load order groups and altitudes for minifilter drivers, we can determine that our filters will load in the following order.

FileInfo

EncryptionLegacy

AVLegacy

AVMiniFilter

luafv

 

On system bootup, when fltmgr.sys loads it will create Frame 0 with a default altitude range of 0 to 49999. When FileInfo loads with an altitude of 45000, it will fit into the default Frame 0. Next to load is EncryptionLegacy. Since this is a legacy driver, it will attach on top of the legacy driver fltmgr.sys. So this is how our file system stack looks right now.

 

image001

 

Next up is the AVLegacy driver.  This is a legacy driver, so it has to attach above EncryptionLegacy.

 

image002

 

Now the AVMinifilter will load with an altitude of 328000. The OS will check if it will fit in the Frame 0 Fltmgr, but this frame only supports an altitude of 0-45000. Before deciding to create a new fltmgr Frame instance, it will check if there are any legacy filters attached above Frame 0 and adjust Frame 0’s altitude if there are. So in our case, we do have legacy filters on the file system stack at this point and so we go up the list of legacy drivers. First we see EncryptionLegacy with an altitude of 149998.99 so we adjust Frame 0 to cover from 0 to 149998.99. We continue up the list and see AVLegacy with an altitude of 329998.99 so we again adjust the altitude of Frame 0 to now cover 0-329998.99. The reason we do this is because Frame 0 now must handle all minifilters below 329998.99. Since we can only attach legacy filters to the top of the stack, if we add an additional FltMgr frame instance, it has to sit above AVLegacy and can only support minifilters with altitude of 329998.99 or higher. Now that Frame 0 supports 0-329998.99, we can register AVMinifilter with Frame 0.

 

 image003

 

At this point, you can already see that AVMinifilter which has a higher altitude then EncryptionLegacy will be loaded below EncryptionLegacy. The last driver to load is the luafv minifilter, and it will fit into Frame 0.

 

image004

 

A couple of things to point out.

Why can’t we insert a Frame between AVLegacy and EncryptionLegacy when ACMinifilter loads?

This is due to how the file system stack is constructed with legacy drivers only being able to attach to the top of the stack. Since FltMgr is a legacy driver, it has to conform to these rules.

 

Why do we adjust the altitude in Frame 0 to cover 0-329998.99? Why not stop at 149998.99?

If Frame 0 only adjusts its altitude to the legacy filter directly attached above it and not all the way to the highest attached legacy filter, we won’t be able to handle some range of mini-filters. For example, assume we only adjust Frame 0 to cover 0-149998.99, then when the AVMinifilter with an altitude of 328000 comes along, it won’t fit in Frame 0, and we’re unable to insert a Frame between AVLegacy (329998.99) and EncryptionLegacy(149998.99) so we would either be unable to load AVMinifilter, or we would have to create Frame 1 above AVLegacy and load AVMinifilter there. In which case we would again be faced with the altitude disordering issue.

 

If this is the expected behavior, how do we resolve the problem of EncryptionLegacy being loaded above AVMinifilter? The solution is to inject a dummy minifilter that loads at the appropriate time to force fltmgr to create a Frame between the legacy filters. For our case above, I used the DDK to create the NullFilter minifilter driver and changed the load order to FSFilter Compression and gave it an altitude of 160030 which is within the assigned altitude for FSFilterCompression and set the start type to SERVICE_BOOT_START.  Please note that I only used this driver in a test environment, production minfilter drivers must use an altitude assigned by Microsoft.

 

For information on minifilter load order groups and altitude, reference https://msdn.microsoft.com/en-us/windows/hardware/gg462963.aspx.

 

Our list of filters are as follows:

 

Driver Name

Type

Load Order

Start Type

Altitude

AVLegacy

Legacy

FsFilter Anti-Virus

SERVICE_BOOT_START

329998.99

AVMiniFilter

Minifilter

FsFilter Anti-Virus

SERVICE_BOOT_START

328000

EncryptionLegacy

Legacy

FsFilter Encryption

SERVICE_BOOT_START

149998.99

Luafv

Minifilter

FsFilter Virtualization

SERVICE_AUTO_START

135000

FileInfo

Minifilter

FSFilter Bottom

SERVICE_BOOT_START

45000

NullFilter

Minifilter

FSFilter Compression

SERVICE_BOOT_START

160030

 

So with the new NullFilter dummy driver, our filter load order should be as follows:

FileInfo

EncryptionLegacy

NullFilter

AVLegacy

AVMiniFilter

luafv

 

After FileInfo and EncryptionLegacy loads, the stack is the same as what we had earlier.

 

 

image001

 

Now when the NullFilter minifilter loads with an altitude of 160030, we see that it doesn’t fit in Frame 0. As before, we check for any attached legacy filter drivers and see EncryptionLegacy so we adjust Frame 0 to cover 0-149998.99. Since NullFilter still does not fit in Frame 0, we will create a new Frame and attach it above the EncryptionLegacy driver.

 

 

image005

 

 

       
     

The AVLegacy driver will load next, and since it is a legacy driver, it will attach above the Frame 1 instance of FltMgr.

 

image006 

 

The last two minifilters to load are AVMiniFilter and luafv. When AVMinifilter loads into Frame 1 with an altitude of 328000, it will see that Frame 1 at the time only supports 149998.99-160030. It follows the same algorithm to check if there are any legacy filters attached above the frame. In this case, we have AVLegacy attached above Frame 1 so we adjust Frame 1 to cover 149998.99-329998.99 before inserting AVMinifilter into Frame 1.

 

image007

 

By strategically injecting a dummy minifilter driver, we can get the legacy and minifilter drivers to all load at the correct altitude.