Windows File System Filter Driver Development Guide
Table Of Contents
6. Instance
Notification Support
6.2. Controlling Instance Teardown
6.3. Synchronizing Instance Detach
8. Manipulating
Callback Data Parameters
10.1. Filter
Communication Port Object
10.2. Connecting
to Communication Port from User Mode
10.3. Disconnecting
from the Communication Port
11.1. Retrieving
a File Name during an Operation
11.2. Additional
Support for File Names
11.3. Interfaces
for Name Providing Filters
13. Rules for Unload/Unregister/Detach
1.
Overview
This document pertains to filter drivers between
the I/O manager and the base filesystem, which can be
local or remote. It does not pertain to filter drivers that sit
between the filesystem and the storage driver(s),
such as FtDisk or DMIO.
We will refer to a file system filter driver
written using the new model as a mini file system filter driver/minifilter.
The existing file system filters based on the sfilter sample – using IRP and device-object based
filtering will be referred to as 'legacy filters'.
One of the key components of the new
architecture is a legacy file system filter which is called 'Filter
Manager'. In future Windows
operating system releases, this driver will be installed by default on the
system. This driver manages the minifilters by
providing export libraries to which the minifilters link. The necessary header files,
libraries and binaries are available in the Minifilter IFSKit.
Why write a mini file system filter driver?
·
Simpler, get a more reliable
filter driver with less development effort.
·
Dynamic load and unload, attach
and detach.
·
Attach at a well-defined
location in the filter stack.
·
Context management: fast,
clean, reliable contexts for file objects, streams, files, instances, and
volumes
·
A host of utility routines
including support for looking up names and caching them for efficient access,
communication between minifilters and user mode services,
and IO queuing.
·
Support non-recursive I/O so minifilter generated I/O can easily be seen only by lower minifilters and the file system.
·
Filter only operations of
interest to minifilter – unlike the legacy model
where a filter has to hook every entry point to pass through operation.
2.
Terms
In the Filter Manager architecture, some new
objects are defined. For clarity
these objects will be defined up front.
Filter: A driver that performs some type of filtering operation on the file
system.
Volume: For local file systems,
the object that represents logical volume the file system manages. For network redirectors, this represents
the object to which all network requests are directed. The volume maps directly to the file
system (local or remote) DEVICE_OBJECT in the legacy filter model.
Instance: An instantiation of a
filter on a volume at a unique altitude. The Filter Manager manages presenting IOs to each instance in the stack
of instances attached to a volume. There may be more than one instance of a given minifilter for a given volume. The canonical
example is a filter like FileSpy. It would be helpful to attach FileSpy both above and below another filter on the same
volume with each instance maintaining a private context. This context could contain the log of
IOs seen by the instance for later comparison of the IO as seen above and below
the filter.
File: A named object of data
that could be composed of multiple streams that is stored on a disk by the file
system.
Stream: Represents a physical
stream of data in a file.
FileObject: Represents a user’s open of a physical stream of data in a file.
CallbackData: The Filter Manager structure that encapsulates all the information about
an operation. This is equivalent to
an IRP in the legacy filter model.
3.Minifilter Installation
Minifilters will be installed via an INF file. The INF file indicates what instances
the minifilter supports. Instances are defined in
Section 5. Each instance is also
accompanied by an altitude value which indicates its absolute position in a minifilter stack and a set of flags.
The list of instances with their altitudes in
the INF is the preferred means for file system filter vendors to ship their minifilters. The flags indicate if the minifilter needs
'automatic attachment'. If this is the case, for every volume in the system
(and for new ones as they arrive), the minifilter gets a notification that it can respond to and attach. The altitude in the INF
file determines the altitude to which the minifilter attaches if it chooses to do so.
A file system filter vendor may also create an
instance dynamically at a specified altitude at any time when the minifilter is running via the FilterAttachAtAltitude() function. This is provided as an override and a potential debugging
and testing aid.
4.Minifilter Registration
Mini file system filter drivers are kernel-mode
drivers. As such, they must export
a function called DriverEntry(), which will be the first function invoked when the driver is
loaded. Most minifilters will call FltRegisterFilter() in their DriverEntry().
FltRegisterFilter() takes as a parameter a FLT_REGISTRATION structure, which
contains an unload routine, instance notification
callbacks, a list of context callback function pointers and a list of file system
operation callback pointers. In the
most common case, where the minifilter hooks just a
few operations, this list can be very short.
The minifilter also
can specify additional flags for each operation that controls whether it
receives the callbacks in all cases. For instance, if FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO is supplied, the minifilter will not
receive any paging I/O operations for that particular IRP major code.
Similarly, if FLTFL_OPERATION_REGISTRATION_SKIP_CACHED_IO is specified, only the non-cached I/O path will be seen for that
operation (i.e., if it is supplied for IRP_MJ_READ, all cached read
paths will be skipped by that minifilter).
5.
Initiating filtering
Once a minifilter registers itself, it should initiate filtering at some point by calling the
function FltStartFiltering(). It is not necessary to call it from DriverEntry(), though most filters will probably prefer to.
This function will kick-off the necessary
notifications that will result in the minifilter attaching to the appropriate volumes and start filtering I/O.
For this the Filter Manager walks through the
list of instances that are registered by the minifilter via
its INF.
Each instance specifies an "altitude".
An altitude is an infinite precision string (e.g., "100.123456") that
defines where it will be attached in a stack of minifilter instances. Altitudes for
commercially released minifilter drivers will be
assigned by Microsoft.
The larger the altitude in numeric value, the
higher the instance is attached in the minifilter stack for a volume. Several sample
altitudes are provided for developers to use while implementing minifilters and these are the only altitudes an unsigned
driver may use. There are two
purposes for altitudes. The first
is to enforce relative minifilter ordering when it is
necessary for proper functioning regardless of when the individual drivers
load. For instance, an encryption
filter and an antivirus filter must attach with the antivirus filter on top,
otherwise the antivirus filter could not scan the clear text for viruses. The second purpose is to provide a
smaller test matrix for minifilter interoperability
testing. Each minifilter instance will have a well-specified altitude, therefore, when testing the interoperability
between one or more minifilters, there is exactly one
well-defined configuration to test.
An INF instance specification also has
associated flags. If bit 1 is set, the minifilter will not be automatically notified when volumes appear in the system so that it
can attach its instance. An
instance with this flag set in its specification would be manually attached via
a Filter Manager API. If bit 2 is
set, the minifilter will not be asked to attach this
instance to a volume when a manual attachment request is made.
6.
Instance Notification Support
A set of callbacks are provided to notify a minifilter when an instance is being created or torn
down. Through these callbacks, the minifilter can control when instances are attached and
detached from volumes.
6.1.
Setting up an Instance
The InstanceSetupCallback() routine
gets called for the following reasons:
·
When a minifilter is loaded it is called for each volume that currently exists in the system.
·
When a new volume is mounted.
·
When FltAttachVolume() is called (kernel mode).
·
When FltAttachVolumeAtAltitude() is called (kernel mode).
·
When FilterAttach() is called (user mode).
·
When FilterAttachAtAltitude() is called (user mode).
It is during the processing of the InstanceSetupCallback() that the minifilter decides
whether or not the instance should be created on the volume specified. This callback has the following
signature:
typedef
NTSTATUS
(*PFLT_INSTANCE_SETUP_CALLBACK)
(
IN PCFLT_RELATED_OBJECTS
FltObjects,
IN FLT_INSTANCE_SETUP_FLAGS Flags,
IN DEVICE_TYPE VolumeDeviceType,
IN FLT_FILESYSTEM_TYPE
VolumeFilesystemType
);
The FltObjects structure contains
pointers to the minifilter, volume and instance that
define the instance being created by this InstanceSetupCallback(). The Flags explain what operation
triggered this InstanceSetupCallback():
·
FLTFL_INSTANCE_SETUP_AUTOMATIC_ATTACHMENT: This is an automatic attachment that is
occurring because the minifilter has just registered;
therefore the Filter Manager is enumerating all existing volumes in the systems
for the newly loaded minifilter. This flag is not set when a user
explicitly request an instance to attach to a volume.
·
FLTFL_INSTANCE_SETUP_MANUAL_ATTACHMENT: A manual attachment
request has been issued via the FilterAttach() (user-mode), FilterAttachVolumeAtAltitude() (user-mode), FltAttachVolume() (kernel-mode), or FltAttachVolumeAtAltitude() (kernel-mode) call.
·
FLTFL_INSTANCE_SETUP_NEWLY_MOUNTED_VOLUME: The file system has
just mounted this volume, therefore it is calling the InstanceSetupCallback() for the minifilter to create an instance
on the volume if desired.
During the InstanceSetupCallback(), the minifilter also receives
the VolumeDeviceType and the VolumeFilesystemType to help the minifilter decide whether or not it is
interested in attaching to this volume. From the InstanceSetupCallback(), a minifilter can query the
volume properties by calling FltGetVolumeProperties() and set a context using FltSetInstanceContext() on this
instance if it plans to attach this instance to the volume. It can also open and close files on the
volume.
If the minifilter returns
a success code from the instance setup callback, the instance will be attached
to the given volume. If the minifilter returns a
warning or an error code, the instance will not be attached to the
volume.
If a minifilter does
not register an InstanceSetup() routine, the
system will treat this as if the user returned STATUS_SUCCESS and allow the instance to be created on
the volume.
6.2.
Controlling Instance Teardown
The InstanceQueryTeardown() callback
is called only when a manual detach request is made. The following operations cause manual detach requests:
·
FltDetachVolume() (kernel-mode)
·
FilterDetach() (user-model)
If a minifilter does
not provide this callback than manual detach is not supported. However, volume dismounts and minifilter unloads will still be allowed.
If this callback is defined and a success code
is returned, Filter Manager will start tearing down the given instance.
Eventually the instance's InstanceTeardownStart() and InstanceTeardownComplete() routines will be called. If the minifilter returns a warning/error status, the manual detach will fail. The recommended error code is STATUS_FLT_DO_NOT_DETACH but any failure
status may be returned.
The signature for the InstanceQueryTeardown() callback is as follows:
typedef
NTSTATUS
(*PFLT_INSTANCE_QUERY_TEARDOWN_CALLBACK)
(
IN PCFLT_RELATED_OBJECTS
FltObjects,
IN
FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
);
As in the InstanceSetupCallback(), the FltObjects specify the minifilter, volume and
instance to which this InstanceQueryTeardown() operation is related.
6.3.
Synchronizing Instance Detach
Once
the decision to detach has been made the InstanceTeardownStart() callback
is called. This routine must do the
following things:
·
Restart any pended I/O
operations (both pre & post operations)
·
Guarantee no new I/O operations
will be pended
·
Start doing minimal work on new
operations as they arrive.
This
routine may also do the following work:
·
Close opened files
·
Cancel filter initiated I/O’s
·
Stop queuing new work items
The minifilter then returns control back to the Filter
Manager to continue its teardown processing.
After
all operations in which this instance participated have drained or completed,
the InstanceTeardownComplete() callback is called to allow the minifilter to do the final cleanup for the teardown of this instance. When InstanceTeardownComplete() is
called, the Filter Manager guarantees that all existing operation callbacks
have been completed for this instance. At this time, the minifilter must close any
files it still has open that are associated with this
instance.
The InstanceTeardownStart() and InstanceTeardownComplete() callbacks have the same signature:
typedef VOID
(*PFLT_INSTANCE_TEARDOWN_CALLBACK) (
IN
PCFLT_RELATED_OBJECTS FltObjects,
IN
FLT_INSTANCE_TEARDOWN_FLAGS Reason
);
The FltObjects parameter describes
the minifilter, volume and instance of interest for
the callback. The Reason parameter
describes the reason for this teardown callback and may be a combination of the
following flag values:
·
FLTFL_INSTANCE_TEARDOWN_MANUAL: This teardown
operation is the result of a manual request to detach this instance (FilterDetach() or FltDetachVolume()).
·
FLTFL_INSTANCE_TEARDOWN_FILTER_UNLOAD: This teardown
operation is the result of the minifilter being
unloaded and the minifilter could have chosen to fail
the unload request.
·
FLTFL_INSTANCE_TEARDOWN_MANDATORY_FILTER_UNLOAD: This teardown operation is the result of
the minifilter being unloaded and the minifilter could not have chosen to fail the unload request.
·
FLTFL_INSTANCE_TEARDOWN_VOLUME_DISMOUNT: This teardown request
is the result of the volume being dismounted.
·
FLTFL_INSTANCE_TEARDOWN_INTERNAL_ERROR: This teardown request
is the result of some error that occurred while trying to setup the instance,
like insufficient memory.
Notice that there is no return value — the InstanceTeardownStart() and InstanceTeardownComplete() cannot be failed. The Filter
Manager guarantees that these callbacks will be called at passive IRQL.
7.
Callback Support
7.1.
Callback data
The callback data structure is the new I/O
packet descriptor for the Filter Manager, and it is analogous to the IRP in the
legacy model. Minifilters talk to Filter Manager and
each other via this structure. Unlike an IRP however, minifilters do not manage stack locations but instead indicate how the callback data should
be managed via well-defined Filter Manager interfaces and return status values
to the Filter Manager.
The FLT_CALLBACK_DATA type describes all the information provided to a minifilter to describe an I/O. The following description goes through
each field in this structure to describe the information it contains.
Flags: Provides information
about this operation. One or more
of the following flags could be set in a callback data structure:
·
FLTFL_CALLBACK_DATA_IRP_OPERATION: This callback data
structure represents an IRP operation.
·
FLTFL_CALLBACK_DATA_FAST_IO_OPERATION: This callback data
structure represents a FAST IO operation.
·
FLTFL_CALLBACK_DATA_FS_FILTER_OPERATION: This callback data
structure represents an FsFilter operation.
·
FLTFL_CALLBACK_DATA_SYSTEM_BUFFER: The buffer for the
operation described by this callback data is a system allocated buffer.
·
FLTFL_CALLBACK_DATA_GENERATED_IO: This callback data represents an
operation that was generated by another minifilter.
·
FLTFL_CALLBACK_DATA_REISSUED_IO: This callback data
represents an operation that has been sent back down to the file system by a minifilter instance above the current instance in the
stack.
·
FLTFL_CALLBACK_DATA_DRAINING_IO: Only set for a
post-operation callback, this callback data represents an IO operation that is
being drained so that the minifilter can unload.
·
FLTFL_CALLBACK_DATA_POST_OPERATION: Only set for a
post-operation callback, this callback data is currently in the post-operation
processing for this I/O.
·
FLTFL_CALLABACK_DATA_DIRTY: This is set after a minifilter has changed one or more of the changeable parameters for this operation. This flag is only set during
pre-operation processing. Minifilters should use FLT_SET_CALLBACK_DATA_DIRTY() and FLT_CLEAR_CALLBACK_DATA_DIRTY() to manipulate
this flag.
Thread: The address of the thread which initiated this operation.
Iopb: Pointer to the changeable parameters for
this operation. This structure is
described in more detail later in this document.
IoStatus: The IO_STATUS_BLOCK which will receive the final
status for this operation. If a minifilter wants to complete an operation, the status
should be set in this field before completing the operation. For operations that are passed through
to the file system, this field contains the final status of the operation
during post-operation processing.
TagData: This field is only valid in
post-callback processing for CREATE operations when the target file of the
operation has a reparse point set on it.
QueueLinks: The list entry structure used to add
this callback data structure to a work queue if needed.
QueueContext[2]: An array of PVOID structures to allow additional
context to be passed to the work queue processing routine.
FilterContext[4]: An array of PVOID structures that can be
used as desired by a minifilter if this callback data
has been queued in a manner that does not rely on the queuing infrastructure
provided by the Filter Manager.
RequestorMode: The requestor mode of the caller who
initiated this IO operation.
The FLT_IO_PARAMETER_BLOCK is the
structure pointed to by the Iopb field of the callback data structure and contains the
changeable portion of the callback data structure. To extend the IRP analogy, this is like
the “current stack location” of the IRP. Minifilters should access this structure to
retrieve I/O parameters in both pre- and post-callbacks. The following is a more detailed
description of the fields this structure contains.
IrpFlags: The flags from the IRP which describe
this operation. These flags begin
with the IRP_ prefix.
MajorFunction: The IRP_MJ function which describes this
operation.
MinorFunction: The IRP_MN
function which describes this operation.
OperationFlags: The flag values in the IO_STACK_LOCATION.Flags field in the legacy filter
model. These flags being with the
SL_ prefix.
TargetFileObject: The file object which represents the
file stream which this operation affects.
TargetInstance: The instance to which this IO is
directed.
Parameters: The FLT_PARAMETERS
union describes parameters specific to the operation specified in the MajorFunction and MinorFunction fields.
Minifilters may change any of the parameters in the FLT_IO_PARAMETER_BLOCK except the MajorFunction in the pre-callbacks. If
parameters have been changed, the minifilter should
call FLT_SET_CALLBACK_DIRTY() to indicate this change. More information regarding changing the parameters of an operation are in Section 8.
Minifilters will see the same parameters in both their pre- and post-callback
routines for the same I/O — even though they or other minifilters below may have changed the parameters. This is guaranteed by the Filter
Manager. Although the contents of
the FLT_IO_PARAMETER_BLOCK will be the same in the pre- and post-callback routines for the
same I/O, the address to the FLT_IO_PARAMETER_BLOCK may not be the same, therefore minifilters should not rely on this.
The callback data structure also contains the
IO_STATUS_BLOCK that describes the status of the operation. A minifilter can change this value and it will be honored by the Filter Manager without
marking the callback data dirty. The minifilter should set the status for the
operation if it is completing the operation in its pre-operation callback or
undoing the operation in the post-operation callback.
7.2.
Pre-Operation Callbacks
All pre-operation
callbacks have the same signature:
typedef FLT_PREOP_CALLBACK_STATUS
(*PFLT_PRE_OPERATION_CALLBACK) (
IN OUT PFLT_CALLBACK_DATA
Data,
IN PCFLT_RELATED_OBJECTS
FltObjects,
OUT PVOID *CompletionContext
);
All pre-operation callbacks return an FLT_PRE_OPERATION_CALLBACK_STATUS. The values are defined
as follows:
·
FLT_PREOP_SUCCESS_WITH_CALLBACK: The operation succeeded and the minifilter wants to have its post-operation callback called.
·
FLT_PREOP_SUCCESS_NO_CALLBACK: The operation succeeded, but the minifilter does not want to have its post-operation callback called.
·
FLT_PREOP_PENDING: The minifilter driver will complete the
operation (by calling FltCompletePendedPreOperation()) sometime in the future. There is no need for the minifilter to call
any special preparation routine before returning this status, like IoMarkIrpPending() in the legacy filter model. If this status is returned, all further processing on the I/O is
suspended by the Filter Manager (i.e. no pre-callbacks of lower drivers are
called etc.) until FltCompletePendedPreOperation() is called.
·
FLT_PREOP_COMPLETE: The minifilter completed the
operation. The minifilter sets the I/O status of the operation in Data->IoStatus.Status. Minifilters attached below this minifilter, legacy filters, and
the base file system will not see this I/O. Minifilters attached above this minifilter will see the operation
complete with the appropriate status. For CLEANUP and CLOSE operations, it is incorrect for a minifilter to complete these operations with a failure
status since these operations cannot fail.
·
FLT_PREOP_SYNCHRONIZE: This status is valid only for non-create operations (creates are
automatically synchronized). A minifilter MUST have a post-callback for the operation when
this status is returned. This indicates that the minifilter wants this I/O to be completed in the same thread context as the pre-operation
process. The post-callback will be
called after the I/O is completed in this thread context. Filter Manager guarantees this —
regardless of whether a lower filter/file system may pend the I/O or abort completion processing etc. This status should be used with care
because it requires Filter Manager tosynchronizes the
entire I/O and this has negative performance impacts on the systems overall
throughput.
·
FLT_PREOP_DISALLOW_FAST_IO: This status is
valid only for fast I/O operations that return BOOLEAN in the legacy model.
This status indicates to I/O to retry the operation using the IRP path instead.
Filters may change the parameters for the I/O
before returning from the pre-operation callback. However the only parameters
that may be changed are in the Data->Iopb structure (FLT_IO_PARAMETER_BLOCK). When a minifilter changes any of the
parameters in this structure, it needs to issue FLT_SET_CALLBACK_DATA_DIRTY() on the passed in CallbackData, or the changes will not be honored, and unpredictable failures may
occur.
There are a couple exceptions to this. If a minifilter is completing the operation or changing the operations status, it may set the IoStatus appropriately in the callback data structure and it is not
necessary to mark the callback data dirty for this change to be honored by the
Filter Manager.
Another exception is during the post-operation
processing of IRP_MJ_CREATE operations. When a reparse point is encountered,
the Data->TagData will point to the reparse data buffer. If a minifilter wishes to change this buffer in any way, it can free the existing buffer and
replace Data->TagData with a new buffer (or NULL) without calling FLT_SET_CALLBACK_DATA_DIRTY().
7.3.
Post-Operation Callbacks
All post-operation callbacks have the same
signature:
typedef
FLT_POSTOP_CALLBACK_STATUS
(*PFLT_POST_OPERATION_CALLBACK)
(
IN OUT PFLT_CALLBACK_DATA
Data,
IN PCFLT_RELATED_OBJECTS
FltObjects,
IN PVOID CompletionContext,
IN FLT_POST_OPERATION_FLAGS
Flags
);
The flag FLTFL_CALLBACK_DATA_POST_OPERATION is set in the Data->Flags field for post-operation callbacks.
If a minifilter returns FLT_PREOP_SUCCESS_WITH_CALLBACK from its pre-operation callback, it is guaranteed to receive
exactly one completion callback per pre-operation callback. Post operations
can return a status of type FLT_POSTOP_CALLBACK_STATUS. The values are:
·
FLT_POSTOP_FINISHED_PROCESSING —The minifilter has completed its processing of the request and control should be returned to
whoever initiated the I/O.
·
FLT_POSTOP_STATUS_MORE_PROCESSING_REQUIRED — The minifilter has not completed its processing of the request, and will later complete
the request using FltCompletePendedPostOperation().
Like completion routines in the legacy filter
model, post-operation callback routines can be called at DPC. If a minifilter needs to do work once the operation is completed but this work cannot be
performed at DPC, the minifilter can call FltDoCompletionProcessingWhenSafe(). This routine will
queue this work to a worker thread only if it is necessary (i.e., we are at
DPC_LEVEL) and it is safe to do so. This call can fail if the completion processing must be queued to a
worker thread, but this is an I/O that cannot be queued (e.g., paging I/Os).
For filters who want to cancel an opened file in
the post-operation callback, FltCancelFileOpen() is provided which does a cleanup and close on the specified FileObject on behalf of the minifilter.
The minifilter should fill in an appropriate error
status code and return FLT_POSTOP_FINISHED_PROCESSING from its post-operation callback.
When an instance is being torn down, the Filter
Manager may call the post-operation callback before it is actually completed by
the lower filters or file system. In this case, the flag FLTFL_POST_OPERATION_DRAINING will be set when the post-operation is called. Minimal information about the operation
will be provided, so at this time, the minifilter should cleanup any operation context passed in from its pre-operation and
return FLT_POSTOP_FINISHED_PROCESSING.
The operations defined are based on the IRP_MJ
codes with the addition of some new codes to represent Fast I/O operations that
have no IRP equivalents. The goal
is to present IRP-based and Fast I/O operations in a consistent manner to the minifilter. There will be a flag in the callback data so that it can determine the
difference between an Irp-based, FsFilter based, or Fast I/O based operation, but it will not have to register separate
callbacks for operations that are similar (i.e., Reads, Writes, and Locking
operations).
The operations for which pre- and post-operation
callbacks can be provided are:
·
All the existing IRP_MJ codes
from IRP_MJ_CREATE to IRP_MJ_PNP
·
IRP_MJ_FAST_IO_CHECK_IF_POSSIBLE
·
IRP_MJ_NETWORK_QUERY_OPEN
·
IRP_MJ_MDL_READ
·
IRP_MJ_MDL_READ_COMPLETE
·
IRP_MJ_PREPARE_MDL_WRITE
·
IRP_MJ_MDL_WRITE_COMPLETE
·
IRP_MJ_VOLUME_MOUNT
·
IRP_MJ_VOLUME_DISMOUNT
·
IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION
·
IRP_MJ_RELEASE_FOR_SECTION_SYNCHRONIZATION
·
IRP_MJ_ACQUIRE_FOR_MOD_WRITE
·
IRP_MJ_RELEASE_FOR_MOD_WRITE
·
IRP_MJ_ACQUIRE_FOR_CC_FLUSH
·
IRP_MJ_RELEASE_FOR_CC_FLUSH
A few special notes for some of the IRP_MJ
codes:
·
The pre-operation callback for IRP_MJ_CREATE can not set or get file, stream, or streamHandle contexts as it is not yet determined at
pre-create time what file or stream (if any) is going to be created.
·
The post-operation callback for IRP_MJ_CLOSE will not be able to set or get contexts for file, stream, or stream
handle, as the system-internal structures with which they are associated are
freed before the post-close routine is called.
·
IRP_MJ_CLEANUP and IRP_MJ_CLOSE must never be failed by the minifilter.
They can be pended, passed on, or completed with success. It is illegal to
return FLT_PREOP_COMPLETE and fill in a failure status in the IoStatus block.
·
Post callbacks cannot fail, as
the operation has already taken place. If the minifilter wishes to fail an operation
from the post-operation callback, it must take the necessary action to undo the
operation that has succeeded. For
failing IRP_MJ_CREATE operations, the Filter Manager provides some help with
the FltCancelFileOpen() routine to teardown the opened
file object, but the filter is still responsible to restore any file contents
that were lost by CREATE operation which overwrote the original file.
Any changes made to the operation parameters in
the post-operation callbacks will not be honored by the Filter Manager.
8.
Manipulating Callback Data
Parameters
8.1.
I/O Parameter Block
As mentioned earlier, the callback data
encapsulates two important data structures for the I/O.
1.)
The I/O Status Block: Data->IoStatus is used to indicate the
status of the operation when the minifilter is
completing the I/O by itself (or contains the status it can read in its
post-callback if a filter/filesystem below completed
the I/O)
2.)
The I/O Parameter Block: Data->Iopb points to the parameters that are specific to that minifilter for the I/O.
This section will discuss accessing/changing the
parameters in more detail.
The MajorFunction and MinorFunction in the Iopb indicate the IRP/FastIo/FsFilter major/minor function for the operation. The MajorFunction may not be changed by the filters – Filter Manager does not support
it.
The TargetFileObject indicates the
stream’s file object that the I/O is targeted to. This can be changed by the minifilter (the minifilter must
call FLT_SET_CALLBACK_DATA_DIRTY()) and will be
honored.
The TargetInstance parameter is
different from instance to instance as the I/O is passed down, and indicates
the current instance. However,
filters can change this to point to another instance that they own, at the same
altitude on another volume. Again, FLT_SET_CALLBACK_DATA_DIRTY() must be called to have this change honored.
Minifilters are NOT allowed to change the TargetInstance to an instance on
the same volume. For example consider a minifilter that has 2 instances on volume C:, one at altitude 200
called Sample_Instance_C_200, and another at altitude 100, called
Sample_Instance_C_100
Assume it also has another instance, called
Sample_Instance_D_200 at altitude 200 on volume D:
Now suppose the IRP_MJ_READ pre-callback for the
instance at altitude 200 on C: is called (i.e., TargetInstance points to Sample_Instance_C_200.) The minifilter can change the TargetInstance parameter to point to
Sample_Instance_D_200, in which case it is re-directing I/O to the D: volume.
However it should not change it to Sample_Instance_C_100.
This is to prevent filters from illegally
bypassing other filters sitting below them on the same volume.
The parameters structure in the I/O parameter
block contains operation-specific parameters. Minifilters must use the operation-appropriate union to access them. For IOCTLs and FSCTLs,
there are method-specific unions based on the method of the control code. Minifilters should test for the method explicitly and use the appropriate union.
8.2.
Accessing buffer/MDL
In the IRP world, buffers are passed to the
drivers through a number of mechanisms. For device objects which supported
‘neither’ I/O – which is the most common for file systems – the buffer was
available via Irp->UserBuffer. For device objects which only supported buffered I/O, the buffer
was buffered by the IO Manager and supplied via Irp->AssociatedIrp.SystemBuffer. For device objects that supported direct I/O, the buffer was
supplied via Irp->MdlAddress, a locked down MDL describing the buffer.
However, there are exceptions and certain IRPs
passed the buffer directly through one of the stack location parameters. In that case it could come from either
kernel memory or raw user memory, independent of the device object’s I/O
requirements. These buffers are not
passed to the hardware; hence, they ignored the device object’s I/O support.
There are also certain IRPs which are always
buffered – such as IRP_MJ_QUERY/SET_INFORMATION.
For minifilters, the
buffers are always passed through the appropriate operation-specific union.
There is no ‘common’ place where they can grab the buffer/MDL address from.
This was designed to eliminate the confusion with the location of the buffers.
The flag FLTFL_CALLBACK_DATA_SYSTEM_BUFFER is set in the callback data’s flag field if the operation is
buffered. If this flag is set, it
indicates that the buffer is from non-paged pool.
If the flag is not set, it means the IO Manager
is not buffering the operation. However it is possible that a minifilter switched the buffers (see next section), so the
buffer could still be from one of the system pools. However if the flag is not
set, filters should always assume that the buffer is a raw user-buffer. We will
discuss a rule in the next section that requires that a MDL describing the
locked down pages will always be present if the buffer is not a user-buffer in
such a case, and that should be used by the caller to obtain a system address
to the buffer.
Finally, for those filters which wish to locate
the buffer/length/MDL for the most common operations, FltDecodeParameters() is provided which does a fast lookup and returns the pointer to the
buffer /MDL/length fields in the Parameter structures. For operations for which
buffers are not applicable, this routine returns STATUS_INVALID_PARAMETER.
NTSTATUS
FLTAPI
FltDecodeParameters(
IN PFLT_CALLBACK_DATA
CallbackData,
OUT PMDL **MdlAddressPointer
OPTIONAL,
OUT PVOID **Buffer OPTIONAL,
OUT PULONG *Length OPTIONAL,
OUT LOCK_OPERATION *DesiredAcces OPTIONAL
);
The DesiredAccess field indicates the
kind of access to the buffer the minifilter can
assume. For instance, for IRP_MJ_READ, the DesiredAccess can indicate IoWriteAccess, implying that the buffer can be written to.
For IRP_MJ_WRITE, it’s usually IoReadAccess indicating the minifilter can potentially only read
the contents of the buffer but not modify it, since it is legal for an
application to issue a write with a buffer that has read/only access page-level
protection.
Filters that wish to ensure the buffer is locked
down, should follow these guidelines: if the FLTFL_CALLBACK_DATA_SYSTEM_BUFFER flag is set, they may assume that the buffer is already locked down and access
it safely.
If it is not set they should call FltLockUserBuffer() to lock the pages down. This API will ensure that the pages are
locked down with the right access based on the operation, and if successful, it
will set the MdlAddress field in the
operation-specific parameter portion, to the MDL describing the pages.
8.3.
Swapping buffers
Certain minifilters need to swap the supplied buffer for certain operations. Consider a minifilter that implements custom encryption. On a
non-cached IRP_MJ_READ, it normally wishes to decrypt the contents of the
buffer that was read from the filesystem. Similarly
on a write, it wishes to encrypt the contents. Take the latter case: the
contents cannot be encrypted in place, because for IRP_MJ_WRITE, the maximal
access to the buffer that the minifilter can assume
is IoReadAccess.
Hence the minifilter needs to supplant its own buffer which has read/write access, encrypt the
contents of the original buffer into the new one, and send the I/O down.
For this scenario, the Filter Manager supports
buffer-switching. However there are a few rules to which the minifilter must adhere:
1.
Minifilters that
switch a buffer must supply a post-callback. This is so that the buffer can be
switched back by Filter Manager automatically.
2.
If the minifilter is switching the buffer
for an operation for which FLTFL_CALLBACK_DATA_SYSTEM_BUFFER flag was set, it MUST ensure that the new buffer is from non-paged
memory (i.e. either from non-paged pool or from locked down memory).
3.
If the above flag was not set, minifilter must adhere to the requirements of the device (by looking at the DeviceObjectFlags etc. in the volume properties) when
supplanting the buffer, based on the operation. For instance if it supports
direct I/O, it must supply a MDL etc.
4.
If the minifilter supplants a non-paged
pool buffer for an operation for which the FLTFL_CALLBACK_DATA_SYSTEM_BUFFER flag is not set, it must also build a MDL via MmBuildMdlForNonPagedPool() and supply it in the MdlAddress field.
This is so that a filter/filesystem below will not
attempt to lock non-paged pool (which can assert on checked Windows builds, and
is also not good from a performance perspective). If a MDL is supplied, filters/file
systems will always access the buffer through the MDL (by obtaining a system
address for it).
5.
When switching a buffer, the minifilter should also switch the MDL (i.e. the buffer and the MDL must be in sync). It is
fine to leave the MDL NULL subject to the usual direct I/O exceptions.
6.
Minifilter should
NOT free the old buffer/old MDL.
7.
Minifilter should
NOT attempt to switch back the old buffer /MDL in its post-callback. Filter
Manager does this automatically. In fact the buffer/MDL the minifilter sees in the Iopb in its post-callback are the
original ones. Minifilters can remember the switched
buffer by passing it in via their completion context.
8.
Minifilters are
expected to free the buffer they allocated (and supplanted) in the
post-callback. Filter Manager however, will automatically free the MDL for the
swapped buffer if any.
9.
Minifilters that
do not want Filter Manager to automatically free the MDL for the swapped buffer
can call FltRetainSwappedBufferMdl().
10.
Minifilters that
wish to access the swapped buffer’s MDL can use FltGetSwappedBufferMdl() in the post-callback. Since a filter/filesystem below the minifilter that swapped the new buffer in, may potentially
create a MDL for it, Filter Manager saves any such MDL for the swapped buffer
before calling the post-callback for the minifilter that swapped the buffers. This API can be used to access the MDL in that case.
9.
Context Support
All filters need to track their own state as
different objects in the system are being operated on. One of the features of the Filter
Manager is the ability for it to track these contexts on behalf of a minifilter.
A context is a piece of dynamically allocated
memory that is created by calling FltAllocateContext(). The caller specifies the amount of
memory to allocate and is returned a pointer to this memory. The system attaches an internal header
used for tracking the context in front of the returned pointer.
Contexts can be created for the following types
of objects:
·
Volume — This represents a mounted device in the
system.
·
Instance — This is a minifilter's attachment to a given volume. A minifilter may attach more then once to a given volume. If a minifilter supports only once instance per volume, it is
recommended that instance contexts be used instead of volume contexts because
they are more efficient.
·
File — This represents all opens across all data streams of
a file. Currently these contexts are not supported.
·
Stream — This represents all opens across a
single data stream of a file.
·
StreamHandle — This represents a single open of a file,
i.e., a file object.
9.1.
Context Registration
At
registration time, a minifilter defines the types of
contexts it will use, the size of the context and the routine used to cleanup
that context. The minifilter uses an array of FLT_CONTEXT_REGISTRATION structures to define these parameters.
The FLT_CONTEXT_REGISTRATION structure is explained in detail below:
ContextType: The type of context
this context registration describes.
Flags: The flags to denote special handling for
this context. The flags currently
defined are:
·
FLTFL_CONTEXT_REGISTRATION_NO_EXACT_SIZE_MATCH: By default, the Filter
Manager matches exactly a given context allocation request with a size
specified at context registration time. If this flag is specified, Filter Manager will use the allocation
routine specified if the requested allocation size is less than or equal to the
size specified in the registration entry. This flags is ignored for entries where the size is specified as FLT_VARIABLE_SIZED_CONTEXTS or allocation and free routines are specified.
CleanupContext: The routine to be
called when the Filter Manager determines that it is time to cleanup this
context. This may be NULL if no
cleanup is required before this context is freed.
Size: The size in bytes for this context. This is used to allow the Filter Manager
to use pooling techniques (such as look aside lists) to make the allocation and
freeing of these structures more efficient. This field is ignored if Allocate and
Free routines are specified.
PoolTag: The pool tag the minifilter would like to associate with context allocations
of this type. This field is ignored
if Allocate and Free routines are specified.
Allocate: This should be set to a non-NULL value
if the minifilter would like to specify its own
allocation routine. If the minifilter would like to rely on the Filter Manager’s
pooling techniques for allocation, this should be set to NULL.
Free: This should be set to a non-NULL value
if the minifilter would like to specify its own free
routine.
If a minifilter has contexts of the same type that may be
variable sizes, it can register up to 3 different FLT_CONTEXT_REGISTRATION structures for the same context type to make use of the Filter
Manager supported memory pooling support.
9.2.
Context Creation APIs
Following is the prototype definition for FltAllocateContext():
NTSTATUS
FLTAPI
FltAllocateContext
(
IN PFLT_FILTER Filter,
IN FLT_CONTEXT_TYPE
ContextType,
IN SIZE_T ContextSize,
IN POOL_TYPE PoolType,
OUT PFLT_CONTEXT
*ReturnedContext
);
The ContextType can be
one of the following for a context on the associated object:
FLT_VOLUME_CONTEXT, FLT_INSTANCE_CONTEXT, FLT_FILE_CONTEXT, FLT_STREAM_CONTEXT,
or FLT_STREAMHANDLE_CONTEXT.
The following set context routines are used to
attach a context to an object. The
context being attached must match the type of the object.
NTSTATUS
FLTAPI
FltSetVolumeContext
(
IN PFLT_VOLUME Volume,
IN FLT_SET_CONTEXT_OPERATION
Operation,
IN PFLT_CONTEXT NewContext,
OUT PFLT_CONTEXT *OldContext
OPTIONAL
);
NTSTATUS
FLTAPI
FltSetInstanceContext
(
IN PFLT_INSTANCE Instance,
IN FLT_SET_CONTEXT_OPERATION
Operation,
IN PFLT_CONTEXT NewContext,
OUT PFLT_CONTEXT *OldContext
OPTIONAL
);
NTSTATUS
FLTAPI
FltSetFileContext
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
IN FLT_SET_CONTEXT_OPERATION
Operation,
IN PFLT_CONTEXT NewContext,
OUT PFLT_CONTEXT *OldContext
OPTIONAL
);
NTSTATUS
FLTAPI
FltSetStreamContext
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
IN FLT_SET_CONTEXT_OPERATION
Operation,
IN PFLT_CONTEXT NewContext,
OUT PFLT_CONTEXT *OldContext
OPTIONAL
);
NTSTATUS
FLTAPI
FltSetStreamHandleContext
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
IN FLT_SET_CONTEXT_OPERATION
Operation,
IN PFLT_CONTEXT NewContext,
OUT PFLT_CONTEXT *OldContext
OPTIONAL
);
When a context is being set there are 2 types of
operations that can occur. They
are:
·
FLT_SET_CONTEXT_KEEP_IF_EXISTS: If there is no
existing context, the new context will be set. If there is an existing context the new
context will not be set and an error
will be returned. If the OldContext parameter is defined the existing context will
be returned. The caller must
release the returned context if any. If the new context was not set the caller must also release that
context.
·
FLT_SET_CONTEXT_REPLACE_IF_EXISTS: This will set a new
context even if a context already exists. If the OldContext parameter is defined the
context being replaced will be returned. The caller must release the returned context. If the OldContext parameter is not defined then the old context will be released by the filter
manager.
While
the Filter Manager will call the minifilter at the
appropriate time to cleanup a context because the object to which it is
associated has been freed by the operating system, the minifilter may want to delete a context on an object at some other time. To do this, the minifilter should call one of the following routines to delete the appropriate context if
they already have the pointer to the context:
VOID
FLTAPI
FltDeleteContext
(
IN PFLT_CONTEXT Context
);
If the minifilter does
not have the context, it can also delete the context by specifying the object
to which the context is attached using the appropriate API below:
NTSTATUS
FLTAPI
FltDeleteVolumeContext
(
IN PFLT_FILTER Filter,
IN PFLT_VOLUME Volume,
OUT PFLT_CONTEXT *OldContext
OPTIONAL
);
NTSTATUS
FLTAPI
FltDeleteInstanceContext
(
IN PFLT_INSTANCE Instance,
OUT PFLT_CONTEXT *OldContext
OPTIONAL
);
NTSTATUS
FLTAPI
FltDeleteFileContext
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
OUT PFLT_CONTEXT *OldContext
OPTIONAL
);
NTSTATUS
FLTAPI
FltDeleteStreamContext
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
OUT PFLT_CONTEXT *OldContext
OPTIONAL
);
NTSTATUS
FLTAPI
FltDeleteStreamHandleContext
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
OUT PFLT_CONTEXT *OldContext
OPTIONAL
);
If the option OldContext parameter is NULL, the
Filter Manager will release the residual reference on the context. Otherwise, the context will be returned
via the OldContext parameter and the minifilter is
responsible for calling FltReleaseContext() on this context when it has finished using it.
9.3.
Context Retrieval APIs
The following routines can be used to retrieve a
context for an individual object. The caller must release the returned context by calling FltReleaseContext() when it is done using it. Contexts can not be retrieved at DPC level so
if a context is needed in a post-operation callback it may need to be passed
from the pre-operation callback.
NTSTATUS
FLTAPI
FltGetVolumeContext (
IN PFLT_FILTER Filter,
IN PFLT_VOLUME Volume,
OUT PFLT_CONTEXT *Context
);
NTSTATUS
FLTAPI
FltGetInstanceContext
(
IN PFLT_INSTANCE Instance,
OUT PFLT_CONTEXT *Context
);
NTSTATUS
FLTAPI
FltGetFileContext
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
OUT PFLT_CONTEXT *Context
);
NTSTATUS
FLTAPI
FltGetStreamContext
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
OUT PFLT_CONTEXT *Context
);
NTSTATUS
FLTAPI
FltGetStreamHandleContext
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
OUT PFLT_CONTEXT *Context
);
The following routine is used to release a
context when a minifilter is done using it. It is advised that a minifilter not hold on to contexts across operations. Context tracking is very efficient and it is intended that a minifilter query for desired contexts on each operation.
VOID
FLTAPI
FltReleaseContext
(
IN PFLT_CONTEXT Context
);
As with the Instance notification routines, each
operation callback routine receives a FLT_RELATED_OBJECTS structure. This structure contains
all known objects for the given operation. To simplify context retrieval there is an equivalent FLT_RELATED_CONTEXTS which can be used to retrieve more then one context at a time. This
structure has the following definition:
typedef
struct _FLT_RELATED_CONTEXTS {
PFLT_CONTEXT VolumeContext;
PFLT_CONTEXT
InstanceContext;
PFLT_CONTEXT FileContext;
PFLT_CONTEXT StreamContext;
PFLT_CONTEXT
StreamHandleContext;
} FLT_RELATED_CONTEXTS, *PFLT_RELATED_CONTEXTS;
The following two routine are used to get
multiple contexts at once as well as release multiple contexts at once. For FltGetContexts() the caller
specifies (with the DesiredContexts parameter) which contexts are wanted. Internally, there are performance
advantages to getting multiple contexts at once versus getting each context
individually. Of course, if the minifilter does not need a context it is best not to
request it. The FLT_ALL_CONTEXTS definition can be used to return all available contexts.
VOID
FltGetContexts (
IN
PFLT_RELATED_OBJECTS FltObjects,
IN FLT_CONTEXT_TYPE
DesiredContexts,
OUT
PFLT_RELATED_CONTEXTS Contexts
);
VOID
FltReleaseContexts (
IN OUT
PFLT_RELATED_CONTEXTS Contexts
);
9.4.
Context Freeing APIs
When the Filter Manager determines it is time
for a context to be freed a minifilter supplied
callback routine is called. This
routine should do any necessary cleanup on the contents of the given context
structure (cleanup other allocated memory, free resource, etc.). Upon returning from this routine the
Filter Manager will free the passed in context structure.
A separate cleanup routine is defined for each
type of context. These routines have the following definition:
typedef VOID
(*PFLT_CONTEXT_CLEANUP_CALLBACK) (
IN PFLT_CONTEXT Context,
IN FLT_CONTEXT_TYPE
ContextType
);
The same context free routine may be registered
for multiple types of contexts.
10.
User Mode Communication
10.1.
Filter Communication Port
Object
To
implement security and enable multiple communication channels, a new object has
been introduced called a minifilter communication
port. It is intended to be used for
kernel-user communication and vice versa. Kernel-kernel communication is not presently supported. A port is a named NT object, with a
security descriptor.
Filter
Manager creates a new object type, FilterConnectionPort,
to facilitate this. Filter Manager
creates this new object type during its driver entry initialization before any minifilters are loaded.
Only
kernel-mode drivers can create a minifilter communication port, with the following API:
NTSTATUS
FltCreateCommunicationPort(
IN PFLT_FILTER
Filter,
OUT PHANDLE
PortHandle,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PVOID
ServerPortCookie OPTIONAL,
IN
PFLT_CONNECT_NOTIFY ConnectNotifyCallback,
IN
PFLT_DISCONNECT_NOTIFY DisconnectNotifyCallback,
IN
PFLT_MESSAGE_NOTIFY MessageNotifyCallback,
IN ULONG
MaxConnections
);
The Filter is the handle to the filter object of the minifilter that is creating this communication port object. The handle created by a successful call
to FltCreateCommunicationPort() is returned in the PortHandle parameter.
As
with other NT objects, the ObjectAttributes parameter defines the OBJECT_ATTRIBUTES structure to initialize the name, object attribute flags and
security descriptor for the new communication port object being created. Note that for the communication port
objects, the object attribute OBJ_KERNEL_HANDLE must be specified since this object can only be created as kernel
objects.
The ServerPortCookie is a context that minifilters can
associate with the port and this context is opaque to the Filter Manager. All
connect/disconnect notifications to the minifilter for this port will be passed this cookie. This is useful for minifilters which create an
array of ports since the cookie can be used to identify which port in the array
is being accessed without defining unique port notification routines for each
port in the array.
The
caller also registers three callbacks when a communication port is created:
·
ConnectNotifyCallback(): This will be called
whenever a user mode process tries to open the port. The minifilter will have the option to fail the connection request. The notify routine will receive a handle
that the minifilter must use for communicating on
this connection. Each connection
has a unique handle. The ServerPortCookie associated with the server port will also be passed in. The minifilter may fill in the ConnectionCookie argument with a context that will be passed in to all further
user-kernel messages and the disconnect routine for this connection.
·
DisconnectNotifyCallback(): This will be called
when the port is closed by user mode (i.e. the handle count goes to zero).
·
MessageNotifyCallback(): This is called
whenever a message is received.
The MaxConnections specifies the maximum number of outstanding connections that will
be allowed on this communication port. There is no default value, but this value must be greater than 0.
There
is no guarantee that the object name will be created in the root of the name
space – it’s possible that the Filter Manager may map it under the \FileSystem\Filters object directory internally. If such a mapping occurs, it will be transparent to the minifilter and the user-mode application. When referencing the communication port
by name, both components should use the same name. The Scanner minifilter example show how this is done.
In
association with the new object type, new access types will be introduced for
objects of this type. The minimal new access types are:
·
FLT_PORT_CONNECT
·
FLT_PORT_ALL_ACCESS
These
are the access types that callers of this API can grant to users when
constructing the security descriptor for the object via InitializeObjectAttributes().
At the
moment FLT_PORT_CONNECT will be sufficient to let user mode connect to the port and both
send and receive messages.
When a minifilter creates the port, it implicitly starts
listening on the port for incoming connections. It continues to listen until it
closes the handle to the port by calling ZwClose().
10.2.
Connecting to Communication Port
from User Mode
The minifilter communication port model – like the legacy model
– is asymmetric. The kernel-mode
end creates the communication port. The user-mode end connects to it. There is an API to open the port for the
user-mode application. When a
connection is established, the ConnectNotify() routine is called notifying the minifilter of the connection creation request as well the minifilter end of the port handle that should be used to send messages across this
connection.
The
prototype of the user-mode API to connect to the port is:
HRESULT
FilterConnectCommunicationPort(
IN LPWSTR
lpPortName,
IN DWORD
dwOptions,
IN LPVOID
lpContext,
IN WORD
wSizeOfContext,
IN LPSECURITY_ATTRIBUTES lpSecurityAttributes,
OUT HANDLE
*hPort
);
The lpPortName is a pointer to a wide-character string that specifies the name of
the port to which to connect. This
name should be the same name used by the minifilter which created the port. The port
name may start with a ‘\’ – indicating it is in the root directory. Filter Manager may however open it
relatively in the actual appropriate directory that hosts minifilter communication ports.
The dwOptions parameter is currently unused.
The lpContext is a pointer to an
opaque parameter that will be passed to the minifilter’sConnectNotify() routine. This can be
used to authenticate that the application requesting to create the
communication channel is the expected application of the appropriate
version. The wSizeOfContext specifies the size of lpContext in bytes.
The lpSecurityAttributes specifies the security attributes for the user end of the
connection, and if the handle will be inherited.
If the
call is not successful, the appropriate HRESULT will be returned.
The
returned handle may be closed/duplicated etc via the
usual APIs. It can also be associated with an I/O completion port.
When
this API is called, a new kernel-mode only unnamed port object is created (of
type FilterCommunicationPort) that is used to
represent the connection. The minifilter will be
notified by calling the ConnectNotify() routine it supplied in the create operation for the server port, and
given the handle to the connection port which should be used for sending messages
from the kernel-side.
Of
course if the caller does not have sufficient access to the server port, or the
maximum number of connections are exceeded this will fail appropriately.
10.3.
Disconnecting from the Communication
Port
When
the user-mode calls CloseHandle() on the handle obtained for the connection or the kernel-mode side
calls ZwClose() on the connection port handle, the connection is broken.
The minifilter’sDisconnectNotify() routine will be called only when the user mode closes its handle.
Ideally
the minifilter should always close its end of the
connection in the DisconnectNotify() routine. If a minifilter closes the handle at other times, it should make
sure the handle will not be double-closed in its DisconnectNotify() by using some sort of synchronization.
When a
communication port is disconnected on either the kernel or user-mode end:
1.
Any user-mode waiters (via FilterGetMessage()) are flushed out and completed with STATUS_FLT_PORT_DISCONNECTED (which translates to win32 error ERROR_DISCONNECTED )
2.
Any kernel mode senders that are blocked on waiters are woken up and
completed with STATUS_FLT_PORT_DISCONNECTED
3.
The port is ‘invalidated’ so no more futures waiters/senders will be
allowed.
The minifilter can always close the server port by calling ZwClose() on the server port handle. This does not force existing connections to break, but simply stops the minifilter from accepting any more connections.
10.4.
Unload issues
Minifilters must always close the
server side port handle in their FilterUnload() routines (or earlier) before they call FltUnregisterFilter() to avoid system hangs during the unload process.
Minifilters will be permitted to
unload even though there are connections open (i.e. the user-mode side has
handles open to the connections). If this is the case, the connections will be
attempted to be forcibly terminated by Filter Manager. The Filter Manager will call the DisconnectNotify() routine. The minifilter is expected to close the kernel side handle to the connection in the DisconnectNotify() thereby preventing a handle leak when the minifilter unloads.
11.
File Name Support
The Filter Manager provides library routines
that retrieve the name of the object in the current operation through looking
at the operation parameters or querying the file system. For improved efficiency, the Filter
Manager also caches the names as they are retrieved so that the cost of
generating a file name can be amortized among all minifilters interested in that name.
The Filter Manager name APIs return names in FLT_FILE_NAME_INFORMATION structures so as to avoid data copies when a minifilter requests a name. These structures
are reference counted and possibly shared by multiple minifilters requesting names. Only the Filter
Manager APIs should ever change the data in these structures. There is more information about these
structures in the following sections.
11.1.
Retrieving a File Name during
an Operation
To retrieve a file name for the CallbackData->Iopb->TargetFileObject for the current operation, the minifilter should call the following routine:
NTSTATUS
FLTAPI
FltGetFileNameInformation
(
IN PFLT_CALLBACK_DATA
CallbackData,
IN FLT_FILE_NAME_FORMAT
NameFormat,
IN
FLT_FILE_NAME_QUERY_METHOD QueryMethod,
OUT
PFLT_FILE_NAME_INFORMATION *FileNameInformation
);
The CallbackData is the FLT_CALLBACK_DATA structure for the operation on the FileObject for which the minifilter wants to query the name.
The NameFormat is one of the following three formats:
·
FLT_FILE_NAME_NORMALIZED_FORMAT: A name requested in
this format contains the full path for the name, including the volume
name. All short names in the path
have been expanded to their long name. Any stream name component will have any trailing “:$DATA” removed. If this is the name for a directory
other than the root directory, the final ‘\’ in the path will be removed.
·
FLT_FILE_NAME_OPENED_FORMAT: A name requested in
this format contains a full path for the name, including the volume name, but
the name is in the same format that the caller used to open this object. Therefore, it could include short names
for any of the components in the path.
·
FLT_FILE_NAME_SHORT_FORMAT: A name requested in
this format contains the short name (DOS name) for only the final component of
the name. The full path for this
object is not returned.
The QueryMethod is one of the following:
·
FLT_FILE_NAME_QUERY_DEFAULT: When looking for a
name, the Filter Manager will look in the cache first to find the name, then,
if possible, query the file system to retrieve the name requested.
·
FLT_FILE_NAME_QUERY_CACHE_ONLY: When looking to
fulfill a name request, the Filter Manager will only look in the name cache to
find the name. If the name is not
found, STATUS_FLT_NAME_CACHE_MISS will be returned.
·
FLT_FILE_NAME_QUERY_FILE_SYSTEM_ONLY: When looking to
fulfill a name request, the Filter Manager will not look in the name cache and
query the file system for the name if possible.
The name is returned in the final parameter, FileNameInformation. This structure is a
set of Unicode strings that share the same buffer. The various Unicode strings denote
varying sections of the name.
typedef struct _FLT_FILE_NAME_INFORMATION {
USHORT
Size;
FLT_FILE_NAME_FORMAT Format;
FLT_FILE_NAME_PARSED_FLAGS NamesParsed;
UNICODE_STRING Name;
UNICODE_STRING Volume;
UNICODE_STRING Share;
UNICODE_STRING Extension;
UNICODE_STRING Stream;
UNICODE_STRING FinalComponent;
UNICODE_STRING ParentDir;
} FLT_FILE_NAME_INFORMATION,
*PFLT_FILE_NAME_INFORMATION;
When a file name information structure is
returned from FltGetFileNameInformation(), the Name, Volume, and Share (for remote file names) will be
parsed. If a minifilter needs the other names parsed, it should call FltParseFileNameInformation().
A minifilter can call FltGetFileNameInformation() at any point during its IO processing when it is executing an IRQL
less than DPC. If the minifilter is requesting to query the name at a time when
it is possible for the name query to cause the system to deadlock (e.g., while
processing paging IO), the call will fail if the name is not found in the cache
or the caller requested to only query the file system.
A minifilter can use FltGetFileNameInformationUnsafe() to query a name for a file object if it does not have a callback
data to describe the current operation targeting this file object and the
filter knows that this is a safe time to potentially query the file system to
get a name. This routine cannot
detect that a file system query could potentially deadlock the system and
return a failure status as FltGetFileNameInformation() can.
When a minifilter is
finished using the name, it should be released by calling:
FLTAPI
FltReleaseFileNameInformation
(
IN
PFLT_FILE_NAME_INFORMATION FileNameInformation
);
Filter Manager’s name cache is
designed to be efficient enough for minifilters to query the name during the
operations it needs to process. The
name cache manages the invalidation of names due to file or directory
renames. Minifilters are isolated from the complex logic necessary to maintain a name cache, but
minifilters still need to be aware that names can be invalidated by renames in
the system. When a rename occurs,
the Filter Manager purges any affected cached entries, but minifilters may have outstanding references to the now stale file name information
structure. When all the references
are released the stale file name information structure will be freed. If a minifilter queries a name on an
object that has been renamed, the new name will be
returned if possible.
11.2.
Additional Support for File
Names
The Filter Manager also provides an API to help minifilters retrieve the destination name for rename and hardlink creation operations:
NTSTATUS
FltGetDestinationFileNameInformation
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
IN HANDLE RootDirectory
OPTIONAL,
IN PWSTR FileName,
IN ULONG FileNameLength,
IN FLT_FILE_NAME_FORMAT
NameFormat,
IN FLT_FILE_NAME_QUERY_METHOD QueryMethod,
OUT
PFLT_FILE_NAME_INFORMATION *RetFileNameInformation
);
This API should be used during a
minifilters pre-operation callback for IRP_MJ_SET_INFORMATION,
FileSetNameInformation or FileSetLinkInformation. The caller specifies
the parameters from the operation, and the Filter Manager will return a
FLT_FILE_NAME_INFORMATION structure that contains the destination name for the
operation in the format requested. Just as with FltGetFileNameInformation(), the minifilter must call FltReleaseFileNameInformation() when it is finished using the name
returned.
Name tunneling is another aspect of
file names where filter commonly make mistakes. The Filter Manager provides the
following API to detect and retrieve a new name when needed due to name tunneling:
NTSTATUS
FltGetTunneledName (
IN PFLT_CALLBACK_DATA
CallbackData,
IN
PFLT_FILE_NAME_INFORMATION FileNameInformation,
OUT
PFLT_FILE_NAME_INFORMATION *RetTunneledFileNameInformation
);
Name tunneling will only affect a
minifilter that is working with names in normalized format. If a minifilter needs a normalized name
in its pre-operation callback for CREATE, rename or hardlink creation operations,
it should call FltGetTunneledName() in its post-operation callback to
validate that the name provided in the pre-operation callback was not affected
by name tunneling in the file system. If name tunneling did occur, a new file name information structure is
returned in RetTunneledFileNameInformation. The minifilter should use this name for
its name processing and call FltReleaseFileName() on this structure when
complete.
11.3.
Interfaces for Name Providing
Filters
If a filter is interested in providing a way to
change the name space, it will need to register three name additional callbacks
with the Filter Manager. These
callbacks allow the name providing filter to be responsible for providing the
name content for the FLT_FILE_NAME_INFORMATION structure that is returned when a higher filter calls FltGetFileNameInformation or FltGetDestinationFileName. The name providing
filter will also be able to tell the Filter Manager whether or not the name
being returned should be cached.
As a name provider, the filter needs to be able
to return a name for a given file object in the opened name format. The filter manager will do the work of
iterating through the components in the name and then call the name provider
back to expand components as necessary to generate a normalized name if that
format was requested. In the
process of expanding all the components for a given path, the filter’s name
component normalization routine may be called more than once. The filter is allowed to provide a
context that will be passed to future calls of the name component normalization
routine while normalizing the current file name path. At the end of all the processing, the
filter will be asked to cleanup this context if it was returned.
When the name providing filter must provide a
name, its PFLT_GENERATE_FILE_NAME routine will be called. The filter is given the parameters for
this name query, such as the file object for this query, the filter’s instance
which is receiving this query, the callback data describing the operation if
one is present, and the name query options which the caller requested be
used. The filter then must generate
the name
typedef NTSTATUS
(*PFLT_GENERATE_FILE_NAME) (
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject,
IN PFLT_CALLBACK_DATA
CallbackData OPTIONAL,
IN ULONG NameOptions,
OUT PBOOLEAN
CacheFileNameInformation,
OUT PFLT_NAME_CONTROL
FileName
);
typedef NTSTATUS
(*PFLT_NORMALIZE_NAME_COMPONENT) (
IN PFLT_INSTANCE Instance,
IN
CONST PUNICODE_STRING ParentDirectory,
IN USHORT VolumeNameLength,
IN CONST PUNICODE_STRING
Component,
IN OUT
PFILE_NAMES_INFORMATION ExpandComponentName,
IN ULONG
ExpandComponentNameLength,
IN OUT PVOID
*NormalizationContext
);
typedef VOID
(*PFLT_NORMALIZE_CONTEXT_CLEANUP) (
IN PVOID
*NormalizationContext
);
If a filter which provides names needs to
invalidate all cached entries that it provided, it can do so through the
following API. If other filters are
still using a FLT_FILE_NAME_INFORMATION structure provided by the name providing filter after the name
provider has requested for the name to be invalidated, the memory will be freed
once the FLT_FILE_NAME_INFORMATION structure’s reference count goes to 0.,
NTSTATUS
FltPurgeFileNameInformationCache
(
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject
OPTIONAL
);
To ensure that a name providing filter is able
to unload, the Filter Manager will be responsible for managing the caching and
freeing of names that are generated by name providing filters.
The Filter Manager will do the work of
initializing the FLT_FILE_NAME_INFORMATION structure returned to the caller of FltGetFileNameInformation or FltGetDestinationFileName based on the name returned by the name providing filter.
For efficiency reasons, the Filter Manager
passes in a buffer in which the name provider is to put the name when its PFLT_GENERATE_FILE_NAME callback is called. This buffer is a UNICODE_STRING wrapped in a FLT_NAME_CONTROL structure which contains both public and private information. Before a filter tries to fill this
buffer with data, it must check to see if the buffer is large enough for the
data by calling:
NTSTATUS
FltCheckAndGrowNameControl (
IN OUT PFLT_NAME_CONTROL NameCtrl,
IN USHORT NewSize
);
If this routine returns STATUS_SUCCESS, the buffer in the FLT_NAME_CONTROL structure is large enough to hold the name from the name provider.
12.
Filter Initiated I/O
Certain minifilters need to perform I/O of their own. This I/O is only seen by minifilters below the
current minifilter in the minifilter stack of the Volume. For instance,
an anti-virus minifilter may wish to read a file before
an open has completed. In the new minifilter model, a minifilter will be able to generate I/O in one of two ways: using general routines similar
to today's routines, and using simplified routines that provide an interface
similar to the ZwXxx routines.
The general routines that the system provides to minifilters for I/O generation are:
NTSTATUS
FLTAPI
FltAllocateCallbackData (
IN PFLT_INSTANCE Instance,
IN PFILE_OBJECT FileObject OPTIONAL,
OUT PFLT_CALLBACK_DATA *RetNewCallbackData
);
VOID
FLTAPI
FltPerformSynchronousIo
(
IN PFLT_CALLBACK_DATA
CallbackData
);
NTSTATUS
FLTAPI
FltPerformAsynchronousIo
(
IN PFLT_CALLBACK_DATA
CallbackData,
IN
PFLT_COMPLETED_ASYNC_IO_CALLBACK CallbackRoutine,
IN PVOID CallbackContext
);
Using the general routines, a minifilter can call FltAllocateCallbackData() to
allocate a CallbackData for the operation, and then fill in the appropriate parameters in
the CallbackData for the desired operation. The minifilter then calls FltPerformSynchronousIo() or FltPerformAsynchronousIo() to actually initiate the I/O. The instance parameter should always be for the current instance doing
the I/O operation.
In addition Filter Manager exports some common
utility routines. Examples:
NTSTATUS
FLTAPI
FltCreateFile
(
IN PFLT_FILTER Filter,
IN PFLT_INSTANCE Instance
OPTIONAL,
OUT PHANDLE FileHandle,
IN ACCESS_MASK
DesiredAccess,
IN POBJECT_ATTRIBUTES
ObjectAttributes,
OUT PIO_STATUS_BLOCK
IoStatusBlock,
IN PLARGE_INTEGER
AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG CreateDisposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength,
IN ULONG Flags
);
If InstanceHandle is omitted, the CREATE will be sent to the top of the stack (so the minifilter initiating the operation will itself see
the I/O recursively.) This is discouraged unless absolutely necessary as it can
cause deadlocks and stack overflows if minifilters misuse
it.
If InstanceHandle is provided (this
should always be your own instance), then the create is initiated with the minifilter just below the
caller of this API, by passing all legacy minifilters above the Filter Manager and minifilters above the
caller.
The FileHandle returned by this API can be used in all Zw* calls
that accept a file handle as a parameter. If the Instance parameter is non-NULL
in a call to FltCreateFile(), it is guaranteed that all future I/O (via the Zw APIs, FltClose() etc.), on this handle will only be seen by the instances below the InitiatingInstance.
FltReadFile() and FltWriteFile() are support routines to allow minifilters to generate IOs that are only seen by instances below them when they have only
the FileObject to represent the file. These routines are analogous to “rolling
an IRP” in the legacy filter model.
IMPORTANT
NOTE: Filters need NOT use FltReadFile()/FltWriteFile() to initiate I/O on the handle
returned by FltCreateFile(). For handles created
via FltCreateFile(), the normal Zw*() APIs will be targeted to the correct instance
relative to the InitiatingInstance specified.
NTSTATUS
FLTAPI
FltReadFile (
IN PFLT_INSTANCE InitiatingInstance,
IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN ULONG Length,
OUT PVOID Buffer,
IN
FLT_IO_OPERATION_FLAGS Flags,
OUT
PULONG BytesRead OPTIONAL,
IN PFLT_COMPLETED_ASYNC_IO_CALLBACK CallbackRoutine OPTIONAL,
IN PVOID CallbackContext OPTIONAL
);
NTSTATUS
FLTAPI
FltWriteFile
(
IN PFLT_INSTANCE
InitiatingInstance,
IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER ByteOffset
OPTIONAL,
IN ULONG Length,
IN PVOID Buffer,
IN FLT_IO_OPERATION_FLAGS Flags,
OUT PULONG BytesWritten OPTIONAL,
IN PFLT_COMPLETED_ASYNC_IO_CALLBACK
CallbackRoutine OPTIONAL,
IN PVOID CallbackContext
OPTIONAL
);
By default, all minifilter-initiated
I/O is sent to the next attached instance for the given volume, bypassing any
instances attached above the minifilter initiating
the I/O.
Minifilter initiated I/O can be synchronous or asynchronous. When the I/O is asynchronous, the minifilter provides a callback routine that the system will
call when the I/O is completed.
13.
Rules for
Unload/Unregister/Detach
Detaching means a minifilter instance is going to be destroyed. That minifilter will no longer be called for
any operations on that volume (unless, of course, there's still another
instance of that minifilter attached to that volume).
Unloading a minifilter means its code is no longer in memory. This will most often be done at system shutdown time and when a new
version of a minifilter is being installed without
shutting the system down.
A minifilter instance
can be detached from a volume from within the minifilter (by calling FltDetachVolume()) but the more common method will be via the UI. A minifilter instance can be detached even when
there is outstanding I/O. In that
case, the minifilter's completion routine(s) will be
called for any outstanding I/O operations with the flag FLTFL_POST_OPERATION_DRAINING set. The minifilter will not receive completion callbacks
when those I/O operations actually complete.
When a minifilter instance is detached, the system will call the minifilter's context free routines for all outstanding contexts for files, streams, and
stream file objects associated with that instance.
14.
Support Routines
In addition to the interfaces already
discussed, the Filter Manager provides a number of support routines to help minifilters perform their required tasks. Here’s a list of some of the additional
routines. Please look to the Filter
Manager IFS Kit documentation for more detailed information on these routines:
Routines
for name / object translation:
·
FltGetFilterFromName()
·
FltGetVolumeFromName()
·
FltGetVolumeInstanceFromName()
Routines
for volume, instance, device object translation:
·
FltGetVolumeFromInstance(), FltGetFilterFromInstance()
·
FltGetVolumeFromDeviceObject()
·
FltGetDeviceObject()
·
FltGetDiskDeviceObject()
Routines
for accessing information on objects:
·
FltGetVolumeProperties()
·
FltIsVolumeWriteable
·
FltQueryVolumeInformation(), FltSetVolumeInformation()
·
FltGetInstanceInformation()
·
FltGetFilterInformation()
Enumeration
routines:
·
FltEnumerateFilters()
·
FltEnumerateVolumes()
·
FltEnumerateInstances()
·
FltEnumerateFilterInformation()
·
FltEnumerateInstanceInformationByFilter()
·
FltEnumerateInstanceInformationByVolume()
·
FltEnumerateVolumeInformation()
Oplock routines:
·
FltInitializeOplock()
·
FltUninitializeOplock()
·
FltOplockFsctrl()
·
FltCheckOplock()
·
FltOplockIsFastIoPossible()
·
FltCurrentBatchOplock()
Directory
Change Notification routines:
·
FltNotifyFilterChangeDirectory()
Other:
·
FltGetRequestorProcess(), FltGetRequestorProcessId()
15.
Building a Minifilter
Everything needed to build an application that
uses the minifilter architecture can be found in the
Filter Manager IFS Kit. This
includes:
·
The full build environment
needed to build a minifilter and a user-mode
application that uses the user-mode Filter Manager interfaces
·
An install package to install
the Filter Manager components on a machine for development until the version of
the OS which contains the Filter Manager is released by Microsoft
·
Minifilter source code samples
All minifilters include the header FltKernel.h and link with FltMgr.lib. User mode filter components will include the header FltUser.h and link with FltLib.lib.