Completing IRP_MJ_QUERY_INFORMATION requests isn't a terribly difficult task but I wanted to discuss some of the basics as well as share some code to show how a minifilter might go about doing this. I've already discussed some of the more advanced topics related to this in my previous posts on
Filters And IRP_MJ_QUERY_INFORMATION and on
Setting IoStatus.Information.
There are many information classes and many of them can be found on the documentation page for the
ZwQueryInformationFile function, though the complete list is documented on the
2.4 File Information Classes page in
the MS_FSCC document. Please note that not all of them can be used with IRP_MJ_QUERY_INFORMATION. However, for this post I'll only be focusing on those that can.
Pretty much each information class has an associated structure. For example, the information class of FileAlignmentInformation has the associated
FILE_ALIGNMENT_INFORMATION structure. Some of these structures have a fixed size so in that case all a filter has to do is to populate the information and complete the request:
switch ( Data->Iopb->Parameters.QueryFileInformation.FileInformationClass ) {
case FilePositionInformation:
{
PFILE_POSITION_INFORMATION positionInfo = Data->Iopb->Parameters.QueryFileInformation.InfoBuffer;
// retrieve the current position from the file object
positionInfo->CurrentByteOffset = FltObjects->FileObject->CurrentByteOffset;
// then complete the operation
Data->IoStatus.Status = STATUS_SUCCESS;
Data->IoStatus.Information = sizeof(positionInfo->CurrentByteOffset);
callbackStatus = FLT_PREOP_COMPLETE;
}
break;
....
}
...
return callbackStatus;
The filter can expect that a buffer of the appropriate size is received, otherwise the IO manager will reject the operation with STATUS_INFO_LENGTH_MISMATCH. This is done inside the IO manager, where the IO manager has an array of minimum sizes for each information class (can be seen in the debugger as nt!IopQueryOperationLength) and if the size is smaller than that then the request will fail with the error code I mentioned:
0: kd> u nt!NtQueryInformationFile+0x36 L0xB
nt!NtQueryInformationFile+0x36:
82aa87dc 8b4518 mov eax,dword ptr [ebp+18h]
82aa87df 83f838 cmp eax,38h
82aa87e2 7377 jae nt!NtQueryInformationFile+0xb5 (82aa885b)
82aa87e4 8a80d4a0a782 mov al,byte ptr nt!IopQueryOperationLength (82a7a0d4)[eax]
82aa87ea 84c0 test al,al
82aa87ec 746d je nt!NtQueryInformationFile+0xb5 (82aa885b)
82aa87ee 0fb6c0 movzx eax,al
82aa87f1 394514 cmp dword ptr [ebp+14h],eax
82aa87f4 730a jae nt!NtQueryInformationFile+0x5a (82aa8800)
82aa87f6 b8040000c0 mov eax,0C0000004h
82aa87fb e98e080000 jmp nt!NtQueryInformationFile+0x8e3 (82aa908e)
Now, there are certain information classes that have variable length. In general this happens with classes that contain names (for example, FileNameInformation) or that return certain lists (like FileStreamInformation that returns all the alternate data streams for the file). In these cases it's impossible for the IO manager to know what the required size might be without querying the file system and so file systems and file system filters will receive requests with what is possibly an insufficient buffer size (there are apps that allocate huge buffer just to avoid reissuing a request, but there are many callers that don't do that; I generally prefer to issue a request with enough buffer in cases where that's possible, for example most files don't have Alternate Data Streams so it's possible to calculate the size of the buffer). Those requests will be failed by the file system (or filter) with the STATUS_BUFFER_OVERFLOW status (which is actually a warning, not an error!). However, different information classes have different requirements about how the structure has to be populated in such cases. There are some structures (like FILE_NAME_INFORMATION) that have a field where the caller can fill in the required size so that the caller can just allocate a buffer of the appropriate size and resend the request. Other structures don't have any such field (like FILE_STREAM_INFORMATION) and so any caller must simply increase the size of the buffer without knowing in advance if it will be sufficient (I personally double the size of the buffer for each iteration but there are other ways of doing it) and then they must issue the request and see whether it was enough.
Finally, there are certain requirements for each information class about how to fill in the buffer even in the event when the request is going to be failed with STATUS_BUFFER_OVERFLOW. For example, for FileNameInformation the buffer must be filled with as many characters as will fit the buffer whereas the FILE_STREAM_INFORMATION must be filled only with complete records. These type of requirements make it necessary for whoever completes the request to be able to specify how much of the buffer actually contains real information. This can be achieved by setting the IoStatus.Information member to the number of bytes to return the caller. The IO manager will then copy the specified number of bytes to the caller's buffer. It is very important to get this right because returning a larger number here will result in the IO manager copying more bytes than were actually written by the file system or filter and is a security problem. For more details on this see Doron's post on
How to return the number of bytes required for a subsequent operation. Of course, the specifics of how the buffer must be set up for each class are documented in the pages for each information class or associated structure, so whenever dealing with this subject make sure to re-read those pages and pay great attention to the details.
I wanted to show some more code on how to do this, but there is plenty of code that shows how this is done in the file system sources that come with the WDK. I found the CDFS sources to be a bit more clear for this specific topic, where CdCommonQueryInfo pretty much does it all and shows exactly how to set things for most information classes. FastFat can be a bit more convoluted at times.
Finally, I want to say that whenever I have to deal with this I find it extremely useful to just use
FileTest and then query the information class I want while supplying different sized buffers (starting with 1 byte, then 2 bytes and so on) and make sure that I understand exactly what the file system returns without my filter present and how things look with the filter installed.
No comments:
Post a Comment