IRP_MJ_QUERY_INFORMATION is a request that file system filters must interact with quite frequently,
either to process it or to issue a query to get some information from the
underlying file system. The semantics are fairly simple and fairly well
documented but still there are some implementation details that might make things
interesting for a filter.
Looking at
the documentation for
IRP_MJ_QUERY_INFORMATION the following phrase stands out:
Although the
FileAccessInformation, FileAlignmentInformation, and FileModeInformation
information types can also be passed as a parameter to ZwQueryInformationFile,
this information is file-system-independent. Thus ZwQueryInformationFile
supplies this information directly, without sending an IRP_MJ_QUERY_INFORMATION
request to the file system.
What this
means is that the IO manager can extract the information from some other place,
and considering this information can be requested on a per-handle basis, it's
pretty clear that the information must come from the FILE_OBJECT or the handle
information that the IO manager keeps in its handle tables. And indeed, if we
look at the information classes that are singled out (FileAccessInformation,
FileAlignmentInformation, and FileModeInformation) we can see where the
information might come from in each case:
- FileAccessInformation
- the access rights for each handle are managed by the IO manager internally
and they are not visible on the FILE_OBJECT or even stored in the file system.
So even if the IO manager were to send an IRP, the file system itself wouldn't
be able to answer the request because it just doesn't have that information. I
should mentioned that this isn't necessarily true for remote file systems since
the remote file system must perform access checks for the requests it receives
over the wire anyway.
- FileAlignmentInformation
- this alignment is not something required by the file system anyway. This is related to the storage
device on top of which the file system is mounted and so the IO manager could
get it by querying the storage device. However, that's not really necessary
since each DEVICE_OBJECT has an AlignmentRequirement member (again, this might
not be true for remote file systems).
- FileModeInformation
- this information comes from the FILE_OBJECT and it's pretty transparent how it maps
to the various FILE_OBJECT flags.
Frankly I
expected to see another information class on the list, the
FilePositionInformation. I thought the current pointer is maintained in the
FILE_OBJECT->CurrentByteOffset and so the IO manager could just get it from
there and not bother to send a request into the file system (after all the
information must be stored on a per-FILE_OBJECT basis and so it can't be in the
SCB or anything like that).
Anyway,
it's interesting to see what the FastFat file system does for these requests.
So looking at \src\filesys\fastfat\Win7\fileinfo.c we can see that for the three
information classes mentioned in the documentation the IRP would be failed with
STATUS_INVALID_PARAMETER. I was also curious to see what FastFat does for
FilePositionInformation and while FastFat doesn't fail the request, it does
what I thought it would do, which is to return the
FILE_OBJECT->CurrentByteOffset value.
So far
it's all pretty clear and it's not really problematic for filters since in most
cases they don't really care about these information classes anyway and there
is no chance to get an IRP_MJ_QUERY_INFORMATION request for any of them from the
IO manager. However, there is a twist here. The FileAllInformation class
includes all the four information classes mentioned above
(FileAccessInformation, FileAlignmentInformation, FileModeInformation and
FilePositionInformation) and is actually sent in the form of an IRP. So how
does the file system get that information ?
Looking
again at the FastFat implementation we can see the code fragment that is used
to implement the FileAllInformation call:
case FileAllInformation:
//
// For the all information class we'll typecast a local
// pointer to the output buffer and then call the
// individual routines to fill in the buffer.
//
AllInfo = Buffer;
Length -= (sizeof(FILE_ACCESS_INFORMATION)
+ sizeof(FILE_MODE_INFORMATION)
+ sizeof(FILE_ALIGNMENT_INFORMATION));
FatQueryBasicInfo( IrpContext, Fcb, FileObject, &AllInfo->BasicInformation, &Length );
FatQueryStandardInfo( IrpContext, Fcb, &AllInfo->StandardInformation, &Length );
FatQueryInternalInfo( IrpContext, Fcb, &AllInfo->InternalInformation, &Length );
FatQueryEaInfo( IrpContext, Fcb, &AllInfo->EaInformation, &Length );
FatQueryPositionInfo( IrpContext, FileObject, &AllInfo->PositionInformation, &Length );
FatQueryNameInfo( IrpContext, Fcb, Ccb, &AllInfo->NameInformation, &Length );
break;
So as we
can see FastFat doesn't even attempt to return the data for those information
classes mentioned in the documentation. So how are they populated ? My first
guess was that the IO manager populates them after the request completes and
before returning the buffer to the caller. But when I tried to validate my
assumption by setting a write breakpoint on the location in the buffer where
the FILE_ACCESS_INFORMATION structure is the breakpoint never got hit in the
path I expected it to.. After some more investigation I realized that by the
time my filter got the request, the FILE_ACCESS_INFORMATION was already
populated:
1: kd> kn
# ChildEBP RetAddr
00 a625eb6c 96016aeb myfilter!PreQueryInformation+0x29c
01 a625ebd8 960199f0 fltmgr!FltpPerformPreCallbacks+0x34d
02 a625ebf0 96019f01 fltmgr!FltpPassThroughInternal+0x40
03 a625ec14 9601a3ba fltmgr!FltpPassThrough+0x203
04 a625ec44 828884bc fltmgr!FltpDispatch+0xb4
05 a625ec5c 82aa8f24 nt!IofCallDriver+0x63
06 a625ed18 8288f44a nt!NtQueryInformationFile+0x779
1: kd> ?? ((PFILE_ALL_INFORMATION)Data->Iopb->Parameters.QueryFileInformation.InfoBuffer)->AccessInformation
struct _FILE_ACCESS_INFORMATION
+0x000 AccessFlags : 0x120089
So the way
NtQueryInformationFile works for FileAllInformation is by populating the buffer with the information it has access to before sending it to the file system and
then the file system fills in the rest. With this in mind there are a couple of
things that filters must be careful about:
- When
processing a FileAllInformation request the filter must be careful not to
overwrite the information that was already written by the IO manager. So don't
call RtlZeroMemory() for that buffer or reuse it for some other purpose. Also,
if completing an FileAllInformation query from assembling bits from some other
sources (other queries into an underlying file system or some such) the filter
must be careful about how it copies the data into the user's buffer. I've see
cases where in response to a FileAllInformation request the filter allocated
its own buffer, sent its request using FltQueryInformationFile() and then
copied the resulting buffer over the user's buffer and that is broken. This is
because:
- FltQueryInformationFile()
is not meant to be identical to ZwQueryInformationFile(). It is simply a
wrapper over allocating an IRP and sending the request to the file system, so
some (all ?) of the requests that would be completed by the IO manager without
sending an IRP will just fail for FltQueryInformationFile().
- Filters
that implement more of the file system functionality need to behave more like a
file system so for example a filter that owns its own FILE_OBJECTs must make
sure to keep the CurrentByteOffset updated since the FilePositionInformation request
might be completed above them by the IO manager or some other filter that will
simply look in the FILE_OBJECT.
Finally,
I wanted to mention one particular documentation page on MSDN that I find very
useful when dealing with Information classes, the page for
FileInformation Classes. I have a hard time remembering
which ones are only for set and which ones are query-only and which are both and
how they are handled and this page helps a lot. Please note however that this page is written with remote file systems in
mind and so some of the information isn't exactly the same for local file
systems. Still I find it quite useful whenever I have to deal with this
topic.
No comments:
Post a Comment