Thursday, November 10, 2011

Filters And IRP_MJ_QUERY_INFORMATION


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.