Thursday, February 16, 2012

Reparsing to a Different Volume in Win7 and Win8

So last time I mentioned what I believe to be one of the most serious problems that STATUS_REPARSE introduces, namely the fact that FltCreateFile above a STATUS_REPARSE that reparses to a different volume fails with STATUS_MOUNT_POINT_NOT_RESOLVED. In this post I’d like to illustrate how to write a filter that can expose this problem as well as to show some steps that Microsoft seems to have take in Win8 to address this issue. The code also shows how to attach an ECP to an IRP_MJ_CREATE request issued by a filter.

So first let me describe my setup. There are two volumes, C: and E:. I have created the file E:\test.txt and then using mklink I have created a symlink from C:\myfile.txt to E:\test.txt. I have a minifilter based on the passthrough sample that whenever it intercepts a create for any file named "myfile.txt" (regardless of the path) tries to issue a FltCreateFileEx2(). I've added a DbgBreakPoint() right before the FltCreateFileEx2() call so that we can see the status and inspect the variables. The environments are Win7 (latest SP) and Win8 developer preview.

So this is the code I've used to illustrate the problem (code I've added to the passthrough sample):

    PT_DBG_PRINT( PTDBG_TRACE_ROUTINES,  
           ("PassThrough!PtPreOperationPassThrough: Entered\n") );  


    if (Data->Iopb->MajorFunction == IRP_MJ_CREATE) {

        UNICODE_STRING myFile = RTL_CONSTANT_STRING( L"myfile.txt" );
        OBJECT_ATTRIBUTES fileAttributes;
        HANDLE fileHandle = NULL;
        IO_STATUS_BLOCK ioStatus;
        IO_DRIVER_CREATE_CONTEXT driverContext;

        __try {

            //  
            // this is a preCreate, get the name.  
            //  
            status = FltGetFileNameInformation( Data,   
                                                FLT_FILE_NAME_OPENED | FLT_FILE_NAME_QUERY_DEFAULT,   
                                                &fileName );  

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            // we need to parse the name to get the final component
            //

            status = FltParseFileNameInformation( fileName );

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            //  Compare to see if this is a file we care about.
            //

            if (!RtlEqualUnicodeString( &fileName->FinalComponent,
                                        &myFile,
                                        TRUE )) {

                __leave;
            }

            //
            // initialize the driver context and the attributes. 
            //

            IoInitializeDriverCreateContext( &driverContext );

            InitializeObjectAttributes( &fileAttributes, 
                                        &fileName->Name,
                                        OBJ_KERNEL_HANDLE,
                                        NULL,
                                        NULL );

            //
            // finally issue the create.
            //

            status = FltCreateFileEx2( FltObjects->Filter,
                                       FltObjects->Instance,
                                       &fileHandle,
                                       NULL,
                                       FILE_READ_DATA | SYNCHRONIZE,
                                       &fileAttributes,
                                       &ioStatus,
                                       NULL,
                                       FILE_ATTRIBUTE_NORMAL,
                                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                       FILE_OPEN,
                                       FILE_SYNCHRONOUS_IO_ALERT,
                                       NULL,
                                       0,
                                       0,
                                       &driverContext ); 

            //
            // check the status.
            //

            DbgBreakPoint();

        } __finally {

            if (fileName != NULL) {

                FltReleaseFileNameInformation( fileName );
            }

            if (fileHandle != NULL) {

                ZwClose(fileHandle);
            }
        }
    }


    //
    //  See if this is an operation we would like the operation status
    //  for.  If so request it.

So as you can see we're not doing much here. As mentioned before, when trying to open c:\myfile.txt we will hit the breakpoint and the status will be STATUS_MOUNT_POINT_NOT_RESOLVED.

Looking in the win8 ddk we can see some interesting definitions for an ECP with the GUID GUID_ECP_FLT_CREATEFILE_TARGET and for a structure with the name FLT_CREATEFILE_TARGET_ECP_CONTEXT. The comment states that "This GUID and structure define an extra create parameter used for passing back reparse information from FltCreateFileEx2 when the create is reparsed to a different device.". When an ECP is used to pass back information it means that the caller of the IRP_MJ_CREATE must set an ECP list into the DriverContext otherwise any ECP that is added to the IRP_MJ_CREATE will be discarded before IoCreateFileEx returns and so there is no way to get that ECP back. First thing I tried was to set an ECP list and see if the IO manager or the FS allocates an ECP with the information and passes it back to me, but I didn't get any. Next thing I tried was to allocate an EPC and add it to the ECP list and that seemed to do the trick, the ECP that I allocated was filled with data. But before we look at the debugger data, let me paste the code that I used for Win8. BTW, all I did was to copy the definitions from the header into the passThrough.c file and compiled with the Win7 WDK. This works because everything except the structure definition and the ECP GUID was available in Win7 anyway. So here is the code:
    PT_DBG_PRINT( PTDBG_TRACE_ROUTINES,  
           ("PassThrough!PtPreOperationPassThrough: Entered\n") );  


    if (Data->Iopb->MajorFunction == IRP_MJ_CREATE) {

        UNICODE_STRING myFile = RTL_CONSTANT_STRING( L"myfile.txt" );
        OBJECT_ATTRIBUTES fileAttributes;
        HANDLE fileHandle = NULL;
        IO_STATUS_BLOCK ioStatus;
        IO_DRIVER_CREATE_CONTEXT driverContext;
        PECP_LIST ecpList = NULL;
        PFLT_CREATEFILE_TARGET_ECP_CONTEXT targetEcp = NULL;

        NTSTATUS ecpFindStatus;
        ULONG targetEcpSize = 0;


        __try {

            //  
            // this is a preCreate, get the name.  
            //  

            status = FltGetFileNameInformation( Data,   
                                                FLT_FILE_NAME_OPENED | FLT_FILE_NAME_QUERY_DEFAULT,   
                                                &fileName );  
            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            // we need to parse the name to get the final component
            //

            status = FltParseFileNameInformation( fileName );

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            //  Compare to see if this is a file we care about.
            //

            if (!RtlEqualUnicodeString( &fileName->FinalComponent,
                                        &myFile,
                                        TRUE )) {

                __leave;
            }

            //
            // initialize the driver context.
            //

            IoInitializeDriverCreateContext( &driverContext );

            //
            // allocate the ECP list we're going to add to our create
            // 

            status = FltAllocateExtraCreateParameterList( FltObjects->Filter,
                                                         0,
                                                         &ecpList );

            if (!NT_SUCCESS(status)) {
                
                DbgBreakPoint();
                __leave;
            }

            //
            // allocate an ECP
            //

            status = FltAllocateExtraCreateParameter( FltObjects->Filter,
                                                      &GUID_ECP_FLT_CREATEFILE_TARGET,
                                                      sizeof(FLT_CREATEFILE_TARGET_ECP_CONTEXT),
                                                      0,
                                                      MyECPCleanupCallback,
                                                      'pcET',
                                                      &targetEcp );

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            // don't initialize anything, just zero out the ECP and add it to the ECP list.
            //

            RtlZeroMemory( targetEcp, sizeof(FLT_CREATEFILE_TARGET_ECP_CONTEXT));

            status = FltInsertExtraCreateParameter( FltObjects->Filter,
                                                    ecpList,
                                                    targetEcp );

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            // we make targetEcp NULL here so we don't manually free it. it will 
            // automatically go away when the list is freed.
            //

            targetEcp = NULL;

            //
            // add the ECP to the driverContex so that it gets attached to the IRP_MJ_CREATE.
            //

            driverContext.ExtraCreateParameter = ecpList;

            //
            // intialize the attributes and issue the create.
            //

            InitializeObjectAttributes( &fileAttributes, 
                                        &fileName->Name,
                                        OBJ_KERNEL_HANDLE,
                                        NULL,
                                        NULL );

            status = FltCreateFileEx2( FltObjects->Filter,
                                       FltObjects->Instance,
                                       &fileHandle,
                                       NULL,
                                       FILE_READ_DATA | SYNCHRONIZE,
                                       &fileAttributes,
                                       &ioStatus,
                                       NULL,
                                       FILE_ATTRIBUTE_NORMAL,
                                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                       FILE_OPEN,
                                       FILE_SYNCHRONOUS_IO_ALERT,
                                       NULL,
                                       0,
                                       0,
                                       &driverContext );

            //
            // and now we need to find the targetEcp. we shouldn't try to use the
            // address from before because it's possible that someone removed the ECP
            // and freed it during processing of the IRP_MJ_CREATE and so we
            // would be touching freed memory. so we're going to see if it's
            // still in the ECP list. at this time (after FltCreateFileEx2 returns)
            // we should be the only ones using the ECP list so it's safe to do this.
            //

            ecpFindStatus = FltFindExtraCreateParameter( FltObjects->Filter,
                                                         ecpList,
                                                         &GUID_ECP_FLT_CREATEFILE_TARGET,
                                                         &targetEcp,
                                                         &targetEcpSize );

            if (ecpFindStatus == STATUS_SUCCESS) {

                NT_ASSERT(targetEcpSize == sizeof(FLT_CREATEFILE_TARGET_ECP_CONTEXT));

                //
                // add a breakpoint here to investigate the targetEcp
                //

                DbgBreakPoint();
                
                //
                // NULL it here so we don't free it.
                //

                targetEcp = NULL; 
            }
             

            DbgBreakPoint();

        } __finally {

            if (fileName != NULL) {

                FltReleaseFileNameInformation( fileName );
            }

            if (fileHandle != NULL) {

                ZwClose(fileHandle);
            }

            if (ecpList != NULL) {

                FltFreeExtraCreateParameterList( FltObjects->Filter,
                                                 ecpList );
            }

            if (targetEcp != NULL) {

                FltFreeExtraCreateParameter( FltObjects->Filter,
                                             targetEcp );

            }
        }
    }


    //
    //  See if this is an operation we would like the operation status
    //  for.  If so request it.

I've also added a function to cleanup the ECP (though please note that i'm just guessing at what needs to happen here, the documentation will explain what filters must release and when):


VOID MyECPCleanupCallback (
    __inout  PVOID EcpContext,
    __in     LPCGUID EcpType
    )
{
    PFLT_CREATEFILE_TARGET_ECP_CONTEXT targetEcp = NULL;
    
    targetEcp = (PFLT_CREATEFILE_TARGET_ECP_CONTEXT)EcpContext;

    if (targetEcp->FileNameInformation != NULL) {

        DbgBreakPoint();

        FltReleaseFileNameInformation( targetEcp->FileNameInformation );
    }

    if (targetEcp->Instance != NULL) {

        FltObjectDereference( targetEcp->Instance );
    }
}

Now, the same code works both under Win7 and Win8 (and Vista too). The FltCreateFile fails and under Win7 the targetEcp structure looks like this:

1: kd> !error @@(status)
Error code: (NTSTATUS) 0xc0000368 (3221226344) - The create operation failed because the name contained at least one mount point which resolves to a volume to which the specified device object is not attached.
1: kd> ?? targetEcp
struct _FLT_CREATEFILE_TARGET_ECP_CONTEXT * 0xd7e96f74
   +0x000 Instance         : (null) 
   +0x004 Volume           : (null) 
   +0x008 FileNameInformation : (null) 
   +0x00c Flags 

However, under Win8 we see this:

0: kd> !error @@(status)
Error code: (NTSTATUS) 0xc0000368 (3221226344) - The create operation failed because the name contained at least one mount point which resolves to a volume to which the specified device object is not attached.
0: kd> ?? targetEcp
struct _FLT_CREATEFILE_TARGET_ECP_CONTEXT * 0x9974cbfc
   +0x000 Instance         : 0x83572b18 _FLT_INSTANCE
   +0x004 Volume           : (null) 
   +0x008 FileNameInformation : 0x82144124 _FLT_FILE_NAME_INFORMATION
   +0x00c Flags            : 0
0: kd> dt 0x82144124 _FLT_FILE_NAME_INFORMATION
fltmgr!_FLT_FILE_NAME_INFORMATION
   +0x000 Size             : 0x40
   +0x002 NamesParsed      : 0
   +0x004 Format           : 2
   +0x008 Name             : _UNICODE_STRING "\Device\HarddiskVolume3\test.txt"
   +0x010 Volume           : _UNICODE_STRING "\Device\HarddiskVolume3"
   +0x018 Share            : _UNICODE_STRING ""
   +0x020 Extension        : _UNICODE_STRING ""
   +0x028 Stream           : _UNICODE_STRING ""
   +0x030 FinalComponent   : _UNICODE_STRING ""
   +0x038 ParentDir        : _UNICODE_STRING ""
0: kd> !fltkd.instance 0x83572b18 

FLT_INSTANCE: 83572b18 "PassThrough Instance" "370030"
   FLT_OBJECT: 83572b18  [01000000] Instance
      RundownRef               : 0x00000002 (1)
      PointerCount             : 0x00000001 
      PrimaryLink              : [84777bc4-83435b68] 
   OperationRundownRef      : 82c37ba8 
Could not read field "Number" of fltmgr!_EX_RUNDOWN_REF_CACHE_AWARE from address: 82c37ba8
   Flags                    : [00000000]
   Volume                   : 83435ae0 "\Device\HarddiskVolume3"
   Filter                   : 82db1a20 "PassThrough"
   TrackCompletionNodes     : 82db7588 
   ContextLock              : (83572b54)
   Context                  : 00000000 
   CallbackNodes            : (83572b64)
   VolumeLink               : [84777bc4-83435b68] 
   FilterLink               : [82db1a88-82d8403c] 

So as you can see the FileNameInformation member has the name of the target of the reparse and the Instance member points to the instance the current filter has on that volume. The documentation for FLT_CREATEFILE_TARGET_ECP_CONTEXT isn't up yet so I don't know exactly what the rules are for using this ECP. Who is supposed to release the FileNameInformation members ? When is the Volume member populated ? Is the Instance member always referenced (my research seems to indicate so but I'm not 100% sure) ?

This is a pretty big deal because all minifilters that use FltCreateFile() will need to use similar code whenever they make those calls to deal with the occasional STATUS_MOUNT_POINT_NOT_RESOLVED. This method also has the advantage that the same code can be used on any platform since Vista, and if the OS supports this functionality then the ECP will be populated. This should also make it easier in case MS decides to add support for this in Win7 SP2 or something like this.