Click here to Skip to main content
Click here to Skip to main content

File System Filter Driver Tutorial

, , 20 Jan 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
This tutorial will show you how to develop a simple file system filter driver.

Contents

  1. Introduction
  2. Creating a simple File System Filter Driver
  3. How to install a driver
  4. Running a sample
  5. Improvements
  6. Conclusion
  7. Useful references

Introduction

This tutorial will show you how to develop a simple file system filter driver. The demo driver will print the names of opening files to the debug output.

The article requires basic Windows driver development and C/C++ knowledge. However, it may also be interesting to people without Windows driver development experience.

What is a file system filter driver?

A file system filter driver is called on every file system I/O operation (create, read, write, rename, and etc.), and thus it can modify the file system behavior. File system filter drivers are almost similar to legacy drivers, but they require some special steps to do. Such drivers are used by anti-viruses, security, backup, and snapshot software.

Creating a simple File System Filter Driver

Before starting

To build a driver, you need WDK or the IFS Kit. You can get them from Microsoft’s website. Also, you have to set an environment variable %WINDDK% to the path where you have installed the WDK/IFS Kit.

Be careful: Even a small error in the driver may cause a BSOD or system instability.

Main.c

Driver entry

This is the entry point of any driver. The first thing that we do is to store DriverObject to a global variable (we will need it later).

////////////////////////////////////////////////////////
// Global data

PDRIVER_OBJECT   g_fsFilterDriverObject = NULL;

////////////////////////////////////////////////////////
// DriverEntry - Entry point of the driver

NTSTATUS DriverEntry(
    __inout PDRIVER_OBJECT  DriverObject,
    __in    PUNICODE_STRING RegistryPath
    )
{    
    NTSTATUS status = STATUS_SUCCESS;
    ULONG    i      = 0;

    //ASSERT(FALSE); // This will break to debugger

    //
    // Store our driver object.
    //

    g_fsFilterDriverObject = DriverObject;
    ...
}

Set IRP dispatch table

The next step is to populate the IRP dispatch table with function pointers to IRP handlers. In our filter driver, there is a generic pass-through IRP handler (which sends the request further). And, we will need a handler for IRP_MJ_CREATE to retrieve the names of the opening files. The implementation of the IRP handlers will be described later.

//////////////////////////////////////////////////////////////////////////
// DriverEntry - Entry point of the driver

NTSTATUS DriverEntry(
    __inout PDRIVER_OBJECT  DriverObject,
    __in    PUNICODE_STRING RegistryPath
    )
{    
    ...
    //
    //  Initialize the driver object dispatch table.
    //

    for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; ++i) 
    {
        DriverObject->MajorFunction[i] = FsFilterDispatchPassThrough;
    }

    DriverObject->MajorFunction[IRP_MJ_CREATE] = FsFilterDispatchCreate;
    ...
}

Set Fast-IO dispatch table

A file system filter driver must have the fast-IO dispatch table. If you’ve forgot to set up the fast-IO dispatch table, it will lead to system crash. Fast-IO is an alternative way to initiate I/O operations (and it’s faster than IRP). Fast-IO operations are always synchronous. If the Fast-IO handler returns FALSE, it means the Fast-IO way is impossible and an IRP will be created.

//////////////////////////////////////////////////////////////////////////
// Global data

FAST_IO_DISPATCH g_fastIoDispatch =
{
    sizeof(FAST_IO_DISPATCH),
    FsFilterFastIoCheckIfPossible,
    ...
};
//////////////////////////////////////////////////////////////////////////
// DriverEntry - Entry point of the driver

NTSTATUS DriverEntry(
    __inout PDRIVER_OBJECT  DriverObject,
    __in    PUNICODE_STRING RegistryPath
    )
{    
    ...
    //
    // Set fast-io dispatch table.
    //

    DriverObject->FastIoDispatch = &g_fastIoDispatch;
    ...
}

Register a notification for file system changes

We should track the file system being activated/deactivated to perform attaching/detaching of our file system filter driver. How to start tracking file system changes is shown below.

//////////////////////////////////////////////////////////////////////////
// DriverEntry - Entry point of the driver

NTSTATUS DriverEntry(
    __inout PDRIVER_OBJECT  DriverObject,
    __in    PUNICODE_STRING RegistryPath
    )
{    
    ...
    //
    //  Registered callback routine for file system changes.
    //

    status = IoRegisterFsRegistrationChange(DriverObject, 
                       FsFilterNotificationCallback); 
    if (!NT_SUCCESS(status)) 
    {
        return status;
    }
    ...
}

Set driver unload routine

The last part of the driver initialization sets an unload routine. Setting the driver unload routine makes the driver unloadable, and you can load/unload it multiple times without system restart. However, this driver is made unloadable only for debugging purpose, because file system filters can’t be unloaded safely. Never do this in production code.

//////////////////////////////////////////////////////////////////////////
// DriverEntry - Entry point of the driver

NTSTATUS DriverEntry(
    __inout PDRIVER_OBJECT  DriverObject,
    __in    PUNICODE_STRING RegistryPath
    )
{    
    ...
    //
    // Set driver unload routine (debug purpose only).
    //

    DriverObject->DriverUnload = FsFilterUnload;

    return STATUS_SUCCESS;
}

Driver unload implementation

Driver unload routine is responsible for cleaning up and deallocation of resources. First of all, unregister the notification for file system changes.

//////////////////////////////////////////////////////////////////////////
// Unload routine

VOID FsFilterUnload(
    __in PDRIVER_OBJECT DriverObject
    )
{
    ...

    //
    //  Unregistered callback routine for file system changes.
    //

    IoUnregisterFsRegistrationChange(DriverObject, FsFilterNotificationCallback);

    ...
}

Then loop through the devices we created, detach and delete them. Wait for 5 seconds to let all outstanding IRPs to be completed. As it was mentioned before, this is a debug only solution. It works in the majority of cases, but there is no guarantee for all.

//////////////////////////////////////////////////////////////////////////
// Unload routine

VOID FsFilterUnload(
    __in PDRIVER_OBJECT DriverObject
    )
{
    ...

    for (;;)
    {
        IoEnumerateDeviceObjectList(
            DriverObject,
            devList,
            sizeof(devList),
            &numDevices);

        if (0 == numDevices)
        {
            break;
        }

        numDevices = min(numDevices, RTL_NUMBER_OF(devList));

        for (i = 0; i < numDevices; ++i) 
        {
            FsFilterDetachFromDevice(devList[i]);
            ObDereferenceObject(devList[i]);
        }
        
        KeDelayExecutionThread(KernelMode, FALSE, &interval);
    }
}

IrpDispatch.c

Dispatch pass-through

This IRP handler does nothing except passing requests further to the next driver. We have the next driver object stored in our device extension.

/////////////////////////////////////////////////////////////////
// PassThrough IRP Handler

NTSTATUS FsFilterDispatchPassThrough(
    __in PDEVICE_OBJECT DeviceObject, 
    __in PIRP           Irp
    )
{
    PFSFILTER_DEVICE_EXTENSION pDevExt = 
      (PFSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    IoSkipCurrentIrpStackLocation(Irp);
    return IoCallDriver(pDevExt->AttachedToDeviceObject, Irp);
}

Dispatch create

This IRP handler is invoked on every create file operation. We will grab a filename from PFILE_OBJECT and print it to the debug output. Then, we call the pass-through handler described above. Pay attention to the fact that a valid file name exists in PFILE_OBJECT only while the create file operation is being performed! Also there are relative opens, and opens by ID. Retrieving file names in those cases is beyond the scope of this article.

//////////////////////////////////////////////////////////////////////////////
// IRP_MJ_CREATE IRP Handler

NTSTATUS FsFilterDispatchCreate(
    __in PDEVICE_OBJECT DeviceObject,
    __in PIRP           Irp
    )
{
    PFILE_OBJECT pFileObject = IoGetCurrentIrpStackLocation(Irp)->FileObject;

    DbgPrint("%wZ\n", &pFileObject->FileName);

    return FsFilterDispatchPassThrough(DeviceObject, Irp);
}

FastIo.c

To test the Fast-IO dispatch table validity for the next driver, we will use the following helper macro (not all of the Fast-IO routines must be implemented by the underlying file system, so we have to be sure about that):

//  Macro to test if FAST_IO_DISPATCH handling routine is valid
#define VALID_FAST_IO_DISPATCH_HANDLER(_FastIoDispatchPtr, _FieldName) \
    (((_FastIoDispatchPtr) != NULL) && \
    (((_FastIoDispatchPtr)->SizeOfFastIoDispatch) >= \
    (FIELD_OFFSET(FAST_IO_DISPATCH, _FieldName) + sizeof(void *))) && \
    ((_FastIoDispatchPtr)->_FieldName != NULL))

Fast-IO pass-through

Passing through Fast-IO requests requires writing a lot of code (in contrast to passing through IRP requests) because each Fast-IO function has its own set of parameters. A typical pass-through function is shown below:

BOOLEAN FsFilterFastIoQueryBasicInfo(
    __in PFILE_OBJECT       FileObject,
    __in BOOLEAN            Wait,
    __out PFILE_BASIC_INFORMATION Buffer,
    __out PIO_STATUS_BLOCK  IoStatus,
    __in PDEVICE_OBJECT     DeviceObject
    )
{
    //
    //  Pass through logic for this type of Fast I/O
    //

    PDEVICE_OBJECT    nextDeviceObject = 
      ((PFSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->AttachedToDeviceObject;
    PFAST_IO_DISPATCH fastIoDispatch   = nextDeviceObject->DriverObject->FastIoDispatch;

    if (VALID_FAST_IO_DISPATCH_HANDLER(fastIoDispatch, FastIoQueryBasicInfo)) 
    {

        return (fastIoDispatch->FastIoQueryBasicInfo)(
            FileObject,
            Wait,
            Buffer,
            IoStatus,
            nextDeviceObject);
    }
    
    return FALSE;
}

Fast-IO detach device

This is a special Fast-IO request which we have to handle ourselves and not call the next driver. We have to detach our filter device from the file system device stack and delete our device. That can be done easily by the following code:

VOID FsFilterFastIoDetachDevice(
    __in PDEVICE_OBJECT     SourceDevice,
    __in PDEVICE_OBJECT     TargetDevice
    )
{
    //
    //  Detach from the file system's volume device object.
    //

    IoDetachDevice(TargetDevice);
    IoDeleteDevice(SourceDevice);
}

Notification.c

A typical file system consists of a control device and volume devices. A volume device is attached to the storage device stack. The control device is registered as a file system.

fs-filter-driver-tutorial/01-fsDevices.png

Figure 1 - Devices of the typical file system

We have a callback which is invoked for all active file systems and whenever a file system has either registered or unregistered itself as an active one. This is a good place to attach/detach our filter device. When a file system activates itself, we attach to its control device (only if we are not already attached), enumerate its volume devices, and attach to them too. On file system deactivation, we examine the file system control device stack, find our device, and detach it. Detaching from the file system volume devices is performed in the FsFilterFastIoDetachDevice routine described earlier.

////////////////////////////////////////////////////////////////////////////
// This routine is invoked whenever a file system has either registered or
// unregistered itself as an active file system.

VOID FsFilterNotificationCallback(
    __in PDEVICE_OBJECT DeviceObject,
    __in BOOLEAN        FsActive
    )
{
    //
    //  Handle attaching/detaching from the given file system.
    //

    if (FsActive)
    {
        FsFilterAttachToFileSystemDevice(DeviceObject);
    }
    else
    {
        FsFilterDetachFromFileSystemDevice(DeviceObject);
    }
}

AttachDetach.c

This file contains helper routines for attaching, detaching, and checking whether our filter is already attached.

Attaching

To perform attaching, we create a new device object with the device extension (call IoCreateDevice) and the propagate device object flags from the device object we are trying to attach to (DO_BUFFERED_IO, DO_DIRECT_IO, FILE_DEVICE_SECURE_OPEN). Then, we call IoAttachDeviceToDeviceStackSafe in a loop with a delay in the case of failure. It is possible for this attachment request to fail because the device object has not finished initialization. This situation can occur if we try to mount the filter that was loaded as the volume only. When attaching is finished, we save the “attached to” device object to the device extension and clear the DO_DEVICE_INITIALIZING flag. The device extension is shown below:

//////////////////////////////////////////////////////////////////////////
// Structures

typedef struct _FSFILTER_DEVICE_EXTENSION
{
    PDEVICE_OBJECT AttachedToDeviceObject;
} FSFILTER_DEVICE_EXTENSION, *PFSFILTER_DEVICE_EXTENSION;

Detaching

Detaching is quite simple. Get the “attached to” device object from the device extension and call IoDetachDevice and IoDeleteDevice.

void FsFilterDetachFromDevice(
    __in PDEVICE_OBJECT DeviceObject
    )
{    
    PFSFILTER_DEVICE_EXTENSION pDevExt = 
      (PFSFILTER_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    
    IoDetachDevice(pDevExt->AttachedToDeviceObject);
    IoDeleteDevice(DeviceObject);
}

Checking whether our device is attached

To check whether we are attached to a device, we have to iterate through the device stack (using IoGetAttachedDeviceReference and IoGetLowerDeviceObject) and search for our device there. We distinguish our devices by comparing the device driver object with our driver object (g_fsFilterDriverObject).

//////////////////////////////////////////////////////////////////////////
// Misc

BOOLEAN FsFilterIsMyDeviceObject(
    __in PDEVICE_OBJECT DeviceObject
    )
{
    return DeviceObject->DriverObject == g_fsFilterDriverObject;
}

Sources and makefile

Sources and makefile files are used by the build utility to build the driver. It contains project settings and source file names.

Sources file contents:

TARGETNAME  = FsFilter
TARGETPATH  = obj
TARGETTYPE  = DRIVER
DRIVERTYPE  = FS

SOURCES     = \
    Main.c \
    IrpDispatch.c \
    AttachDetach.c \
    Notification.c \
    FastIo.c

The makefile is standard:

!include $(NTMAKEENV)\makefile.def

The MSVC makefile project build command line is:

call $(WINDDK)\bin\setenv.bat $(WINDDK) chk wxp
cd /d $(ProjectDir)
build.exe –I

How to install a driver

SC.EXE overview

We will use sc.exe (sc – service control) to manage our driver. It is a command-line utility that can be used to query or modify the database of installed services. It is shipped with Windows XP and higher, or you can find it in the Windows SDK/DDK.

Install

To install the driver, call:

sc create FsFilter type= filesys binPath= c:\FSFilter.sys

A new service entry will be created with the name FsFilter; the service type will be file system, and the binary path, c:\FsFilter.sys.

Start

To start the driver, call:

sc start FsFilter

This starts a service named FsFilter.

Stop

To stop the driver, call:

sc stop FsFilter

This stop the service named FsFilter.

Uninstall

And to uninstall, call:

sc delete FsFilter

This instructs the service manager to delete the service entry with the name FsFilter.

Resulting script

All those commands are put into a single batch file to make driver testing easier. Here is the listing of the Install.cmd command file:

sc create FsFilter type= filesys binPath= c:\FsFilter.sys
sc start FsFilter
pause
sc stop FsFilter
sc delete FsFilter
pause

Running a sample

This is the most interesting part. To demonstrate the file system filter work, we will use Sysinternals DebugView for Windows to monitor the debug output, and OSR Device Tree to see the devices and drivers.

So, build the driver. Then, copy the build output file FsFilter.sys and the install the script Install.cmd to the root of the disk C.

fs-filter-driver-tutorial/02-diskC.PNG

Figure 2 - The driver and the install script on the disk.

Run Install.cmd. It will install and start the driver and then wait for user input.

fs-filter-driver-tutorial/03-install.PNG

Figure 3 - The driver is successfully installed and started.

Now start the DebugView utility.

fs-filter-driver-tutorial/04-debugView.PNG

Figure 4 - Monitoring the debug output.

We can see what files are being opened! Our filter works. Now, run the device tree utility and locate our driver there.

Figure 5 - Our filter driver in the device tree.

We can see numerous devices created by our driver. Now let’s open the NTFS driver and look at the device tree:

Figure 6 - Our filter is attached to NTFS.

We are attached. Let’s take a look at the other file systems.

Figure 7 - Our filter is attached to the other file systems too.

And finally, press any key to move our install script forward. It will stop and uninstall the driver.

fs-filter-driver-tutorial/08-uninstall.PNG

Figure 8 - The driver is stopped and uninstalled.

Refresh the device tree list by pressing F5:

Figure 9 - Our filter device isnot in the device tree.

Our filter is gone. The system is running as before.

Improvements

The sample driver lacks a commonly required functionality of attaching to the newly arrived volumes. It is done so to make the driver as easy to understand as possible. You can write a IRP_MJ_FILE_SYSTEM_CONTROL handler of your own to track the newly arrived volumes.

Conclusion

This tutorial showed how to create a simple file system filter driver, and how to install, start, stop, and uninstall it from a command line. Also, some file system filter driver aspects were discussed. We saw the file system device stack with the attached filters, and learned how to monitor the debug output from the driver. You may use the provided sources as a skeleton for your own file system filter driver or modify its behavior.

Useful references

  1. File System Filter Drivers
  2. Content for File System or File System Filter Developers
  3. Windows NT File System Internals (OSR Classic Reprints) (Paperback)
  4. sfilter DDK sample

Read more Driver Development tips at Apriorit Development Blog.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Authors

Sergey Podobry
Team Leader ApriorIT
Ukraine Ukraine
No Biography provided
Follow on   LinkedIn

Apriorit Inc
Apriorit Inc.
Ukraine Ukraine
ApriorIT is a Software Research and Development company that works in advanced knowledge-intensive scopes.
 
Company offers integrated research&development services for the software projects in such directions as Corporate Security, Remote Control, Mobile Development, Embedded Systems, Virtualization, Drivers and others.
 
Official site http://www.apriorit.com
Group type: Organisation

31 members

Follow on   LinkedIn

Comments and Discussions

 
Questionrename on file delete [modified] Pinmemberrehan11862-Jan-13 22:18 
AnswerRe: rename on file delete Pinmembercatface051112-Jan-13 4:43 
GeneralMy vote of 5 PinmemberArsalanMehmood29-Dec-12 8:14 
AnswerRe: My vote of 5 PinmemberSergey Podobry8-Jan-13 0:44 
QuestionError in release mode PinmemberArsalanMehmood29-Dec-12 7:14 
AnswerRe: Error in release mode PinmemberSergey Podobry8-Jan-13 0:47 
QuestionDriver blocked from loading [modified] PinmemberMember 879257724-Dec-12 5:18 
AnswerRe: Driver blocked from loading PinmemberSergey Podobry24-Dec-12 5:55 
Hi!
 
This question has been already discussed. Here is the link: Error while executing the driver.
 
Cheers!
QuestionCatch saved/new files Pinmembermarko_253-Dec-12 23:41 
AnswerRe: Catch saved/new files PinmemberSergey Podobry5-Dec-12 0:22 
QuestionQuestions for Fast IO... Pinmemberoutless19-Nov-12 18:59 
AnswerRe: Questions for Fast IO... PinmemberSergey Podobry20-Nov-12 1:03 
GeneralRe: Questions for Fast IO... Pinmemberoutless20-Nov-12 14:30 
AnswerRe: Questions for Fast IO... PinmemberSergey Podobry20-Nov-12 23:37 
AnswerRe: Questions for Fast IO... PinmemberSergey Podobry20-Nov-12 23:42 
GeneralRe: Questions for Fast IO... Pinmemberoutless21-Nov-12 13:37 
GeneralRe: Questions for Fast IO... PinmemberSergey Podobry22-Nov-12 1:51 
GeneralRe: Questions for Fast IO... Pinmemberoutless22-Nov-12 14:04 
AnswerRe: Questions for Fast IO... PinmemberSergey Podobry23-Nov-12 0:07 
QuestionInfo about code PinmemberMember 90968693-Nov-12 3:10 
AnswerRe: Info about code PinmemberSergey Podobry5-Nov-12 0:15 
QuestionNeed your Help! PinmemberSteve Ween2-Nov-12 13:13 
AnswerRe: Need your Help! PinmemberSergey Podobry5-Nov-12 22:41 
QuestionSend an event from usermode. PinmemberTheMrProgrammer29-Oct-12 10:53 
AnswerRe: Send an event from usermode. PinmemberSergey Podobry31-Oct-12 4:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.141015.1 | Last Updated 20 Jan 2014
Article Copyright 2009 by Sergey Podobry, Apriorit Inc
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid