Thursday, August 18, 2011

Handling Handles in Minifilters

For the purpose of this article I'm going to use the very basic definition that a handle is simply an index in a table of objects. This definition is accurate but not particularly useful. It doesn't explain why handles are necessary or anything else about them for that matter. However, it focused on what's important for this post. In addition, there are a couple of other things about handles that are important for this post:

  • Kernel components use handles only when they need to talk to other kernel components using a well defined interface. This means that if the registry (for example) wants to read a file from disk, it shouldn't need to care about the internal implementation of a FILE_OBJECT and its size. Likewise, any kernel component using tokens shouldn't need to care about token structures and their sizes and so on. For one, just think about what the included header list would look like if everything had to have definitions for all the structures of the other kernel components it might interface with.
  • Some handle tables are global (the kernel handle table) while some are not (process handle tables). Since the handle is simply an index (a number) it doesn't have any information about which table it belongs to; that is except for kernel handles which are marked in a special way. So just by looking at a user mode handle it's impossible to tell which table it belongs to and so a user handle can only be resolved to the right object in the right process context.
  • Even though they are numbers, handles are actually defined as pointers.

So now let's see what the implications of all these things are for file system writers. File systems (and file system filters) need handles to various other OS components (like tokens in the above example) but one thing they don't need handles for are the files they manage. The normal approach is that the NT layer converts the handle it receives into the actual object it refers to before sending the request to the appropriate component for processing. In fact, in some cases the NT layer doesn't even know what the appropriate component is until it resolves the handle to the actual object. So this means that a file system or a filter should never see a request from the IO manager that has a handle to a FILE_OBJECT, right ? Unfortunately this is not the case. There are some cases where a file system might indeed receive a handle to a FILE_OBJECT that it manages. Personally I'm not convinced it's necessary and I think it's pretty bad design but that's neither here nor there. A quick peek in the WDK (in ntifs.h) shows a couple of structures that are used by file systems and filters:

typedef struct _FILE_LINK_INFORMATION {
    BOOLEAN ReplaceIfExists;
    HANDLE RootDirectory;
    ULONG FileNameLength;
    WCHAR FileName[1];
} FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION;

typedef struct _FILE_MOVE_CLUSTER_INFORMATION {
    ULONG ClusterCount;
    HANDLE RootDirectory;
    ULONG FileNameLength;
    WCHAR FileName[1];
} FILE_MOVE_CLUSTER_INFORMATION, *PFILE_MOVE_CLUSTER_INFORMATION;

typedef struct _FILE_RENAME_INFORMATION {
    BOOLEAN ReplaceIfExists;
    HANDLE RootDirectory;
    ULONG FileNameLength;
    WCHAR FileName[1];
} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;

typedef struct _FILE_TRACKING_INFORMATION {
    HANDLE DestinationFile;
    ULONG ObjectInformationLength;
    CHAR ObjectInformation[1];
} FILE_TRACKING_INFORMATION, *PFILE_TRACKING_INFORMATION;

#if (_WIN32_WINNT >= 0x0400)
//
// Structure for FSCTL_MOVE_FILE
//

typedef struct {

    HANDLE FileHandle;
    LARGE_INTEGER StartingVcn;
    LARGE_INTEGER StartingLcn;
    ULONG ClusterCount;

} MOVE_FILE_DATA, *PMOVE_FILE_DATA;

typedef struct {

    HANDLE FileHandle;
    LARGE_INTEGER SourceFileRecord;
    LARGE_INTEGER TargetFileRecord;

} MOVE_FILE_RECORD_DATA, *PMOVE_FILE_RECORD_DATA;


#if defined(_WIN64)
//
//  32/64 Bit thunking support structure
//

typedef struct _MOVE_FILE_DATA32 {

    UINT32 FileHandle;
    LARGE_INTEGER StartingVcn;
    LARGE_INTEGER StartingLcn;
    ULONG ClusterCount;

} MOVE_FILE_DATA32, *PMOVE_FILE_DATA32;
#endif
#endif /* _WIN32_WINNT >= 0x0400 */


//
//  Structure for FSCTL_MARK_HANDLE
//

typedef struct {

    ULONG UsnSourceInfo;
    HANDLE VolumeHandle;
    ULONG HandleInfo;

} MARK_HANDLE_INFO, *PMARK_HANDLE_INFO;

#if defined(_WIN64)
//
//  32/64 Bit thunking support structure
//

typedef struct {

    ULONG UsnSourceInfo;
    UINT32 VolumeHandle;
    ULONG HandleInfo;

} MARK_HANDLE_INFO32, *PMARK_HANDLE_INFO32;
#endif

I haven't yet been able to figure out what the FILE_TRACKING_INFORMATION structure is used for so I'm going to ignore it for now.

So why and when should a minifilter care about these operations ? Well, it should care about them whenever the handle resolution in the file system might lead to incorrect results. For example, if a minifilter pends some operations and then reissues them in the context of a different process then the file system (or any filter below) might do the resolution in the wrong process context and either fail or it would get the wrong object. The same goes for minifilters that might replace a FILE_OBJECT with a different FILE_OBJECT in some circumstances. In this case the file system (or any filter below) might get a different FILE_OBJECT than the one the caller intended.

I've discussed how minifilters process FILE_RENAME_INFORMATION in a previous post. As far as I know, everything in that post applies to FILE_MOVE_CLUSTER_INFORMATION and FILE_LINK_INFORMATION as well. Since the handle in the structure is created through IopOpenLinkOrRenameTarget(), it is always a kernel handle. This means that a minifilter doesn't need to worry about the process context.

There is yet another problem that minifilters must deal with, as hinted by the 32bit/64bit structures. The problem is that since HANDLE is a pointer, it has different sizes between 32bit and 64bit OSes and binaries. In most cases this doesn’t really cause any issues but on a 64bit machine someone might run a 32bit process, which is naturally using 32bit handles. The OS manager normally handles converting the 32bit handles (WOW64), but there isn't anything it can do about handles that are passed as data structures in general since it doesn't know the internal structure of that information. It could know it for OS defined structures like MOVE_FILE_DATA and MARK_HANDLE_INFO but it doesn't. So a minifilter that needs to handle those must take the size of the handle into account. Fortunately the FastFat sample shows how to deal with this. The main idea is to see whether the data in the IRP originated in a 32bit or a 64bit user mode process and then use the appropriate size (please note that minifilters have their very own FltIs32bitProcess() function):

#if defined(_WIN64)
    if (IoIs32bitProcess( Irp )) {

        if (InputBuffer == NULL || InputBufferLength < sizeof(MOVE_FILE_DATA32)) {

            FatCompleteRequest( IrpContext, Irp, STATUS_BUFFER_TOO_SMALL );
            return STATUS_BUFFER_TOO_SMALL;
        }

        MoveFileData32 = (PMOVE_FILE_DATA32) InputBuffer;

        LocalMoveFileData.FileHandle = (HANDLE) LongToHandle( MoveFileData32->FileHandle );
        LocalMoveFileData.StartingVcn = MoveFileData32->StartingVcn;
        LocalMoveFileData.StartingLcn = MoveFileData32->StartingLcn;
        LocalMoveFileData.ClusterCount = MoveFileData32->ClusterCount;

        InputBuffer = &LocalMoveFileData;

    } else {
#endif
        if (InputBuffer == NULL || InputBufferLength < sizeof(MOVE_FILE_DATA)) {

            FatCompleteRequest( IrpContext, Irp, STATUS_BUFFER_TOO_SMALL );
            return STATUS_BUFFER_TOO_SMALL;
        }
#if defined(_WIN64)
    }
#endif

This code fragment shows how to use the data, which is fine for file systems (or the consumer of the data), but things are a lot trickier for filters. A filter that issues the request directly instead of calling the Zw function (ZwSetInformationFile()) to set the previous mode properly might end up using a kernel handle (which is always 64bit) while IoIs32bitProcess() might return TRUE and so the file system (or underlying filters) would tread the handle as 32bit and the structure as being the 32bit version of the structure. Please note that issuing the request directly is that same as calling FltSetInformationFile() in this case since FltSetInformationFile() doesn't change the previous mode.

In conclusion, what a minifilter should do to properly handle these operations is:

  • If a filter changes the process context it must allocate a new handle in the table of the process where the request will be sent down or replace the handle with a kernel handle.
  • If the request comes from a 32bit process and the filter needs to change it, it should use the 32bit sized version of the structure.
  • If a filter needs to issue its own request then it must take into account the process context and the PreviousMode and use the appropriate sized structure.

1 comment:

  1. Great post as always. That HANDLE in the MOVE_FILE_DATA has ruined my life at least once...

    One thing to note is that it's not always sufficient to simply replace the HANDLE in the structure with a kernel handle. The trouble is, again, the previous mode, which may be User and thus would cause the operation to fail. And you don't want to just change the previous mode to be Kernel because then you're potentially bypassing other checks :) What a mess!

    -scott

    ReplyDelete