Windows System Software -- Consulting, Training, Development -- Unique Expertise, Guaranteed Results

Before Posting... Please check out the Community Guidelines in the
Announcements and Administration Category, below.

Completing a failed Create in post callback

IanMIanM Posts: 24
Hi,

I have a minifilter driver and I'm experimenting with 'fixing up' a failed IRP_MJ_CREATE in the post callback where certain creates have failed because of permissions (i.e. the file does actually exist). I am using FltCreateFile to create a new file object and cherry-picking FsContext, FsContext2, SectionObjectPointer and PrivateCacheMap out of it:


FLT_POSTOP_CALLBACK_STATUS PostCreate(_Inout_ PFLT_CALLBACK_DATA pData, _In_ PCFLT_RELATED_OBJECTS pFltObjects, _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags)
{
:
PFILE_OBJECT pFileObject;
HANDLE hFile;
IO_STATUS_BLOCK IoStatusBlock;
status = FltCreateFileEx(
pFltObjects->Filter,
pFltObjects->Instance,
&hFile,
&pFileObject,
GENERIC_READ ,
&ObjectAttributes,
&IoStatusBlock,
0,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
0,
NULL,
0,
IO_IGNORE_SHARE_ACCESS_CHECK);

if (NT_SUCCESS(status))
{
pData->Iopb->TargetFileObject->FsContext = pFileObject->FsContext;
pData->Iopb->TargetFileObject->FsContext2 = pFileObject->FsContext2;
pData->Iopb->TargetFileObject->SectionObjectPointer = pFileObject->SectionObjectPointer;
pData->Iopb->TargetFileObject->PrivateCacheMap = pFileObject->PrivateCacheMap;

pData->IoStatus.Status = STATUS_SUCCESS;

return FLT_POSTOP_FINISHED_PROCESSING;
}
:
}

Superficially this seems to work but my questions are: first, is this an acceptable thing to do and if not, why not and is there an alternative; second, I am currently deliberately leaking pFileObject - how can I manage the lifetime of this object give that I've copied parts of it?

Thanks,

Ian.

Comments

  • Scott_NooneScott_Noone Posts: 2,989
    I'm not sure how you solve the lifetime tracking problem in this model.
    Specifically, FsContext2 is allocated as a *per File Object* value in the
    file system. You now have two File Objects with the same FsContext2 value.
    You either need to leak one (very bad) or somehow trick the file system into
    not crashing when the second one is freed (also very bad).

    There are lots of options in terms of how to proceed, it just depends on
    what exactly you're trying to do. Here's a few with some caveats (I don't
    intend this list or the problems/benefits to be exhaustive):

    1. There's a NameChanger WDK sample that statically knows which paths will
    be redirected. It then does the redirection by replacing the file name in
    the File Object during PreCreate processing. Doing this in PreCreate isn't
    problem free (see the sample) but it's possible. Note that this only works
    if the redirection target is on the same volume (not sure if that matters
    for you)

    2. You could FltReissueSynchronousIo in PostCreate with the new path. This
    also has the problem of only working if the redirection is on the same
    volume. You also need to make sure that you implement a Name Provider
    (amongst other things) because you're hiding the real path of the file from
    filters above you.

    3. You could change the failure status to STATUS_REPARSE and let the I/O
    Manager deal with re-issuing the I/O request. This is conceptually simpler
    than the other two because you're not lying to the layers above you about
    the actual destination of the open (e.g. you do not need to be a Name
    Provider). It also works if the redirection target is another volume.

    In all cases, if you need to retroactively create paths that don'texist, be
    sure you test on NTFS and account for the security descriptor that ends up
    on the directories you create. Your FltCreateFile call is likely being made
    as a kernel mode request, which might create a directory that is
    inaccessible to the application that you're trying to fake out.

    If you can provide a bit more detail about what you're trying to accomplish
    we might be able to give more concrete advice.

    -scott
    OSR
    @OSRDrivers
  • IanMIanM Posts: 24
    Thanks Scott.

    What I'm trying to do is to catch failed creates in the post callback and, based on some rules, make the create operation succeed. The idea is to give processes running with limited rights access to certain files according to a set of dynamic rules (which precludes just statically changing the ACLs on the files). The rationale behind using the post-callback is to avoid doing costly rule processing in the pre callback by just handling failures. Doing the processing in the pre callback would also mean I'd have to work out whether the create is going to fail in order to intervene.

    Would it be possible to modify and reissue the IRP in a way that would allow it to succeed?
  • Scott_NooneScott_Noone Posts: 2,989
    Are you trying to bypass discretionary access checking or share access checking? Or both?

    And, just to be clear, these files/directories *exist*, it?s just a matter of the application not being able to access them for some reason. Correct?

    Also, presumably this is local only, correct?

    Sorry to just ask a bunch of questions, but it helps to have a complete picture.

    -scott
    OSR
    @OSRDrivers
  • IanMIanM Posts: 24
    Not at all. Sorry if I wasn't clear. Yes to both - I want to bypass the discretionary access checking in favour of my own (or maybe impersonate to achieve the same end). Yes, they're local files which exist. The application has failed to access the file because it doesn't have sufficient rights but on certain occasions I want to allow it.
  • Scott_NooneScott_Noone Posts: 2,989
    OK, got it.

    It seems like this should be simple, but it's more complicated than you might expect given that the system is designed to NOT work this way. Also, solving the discretionary access checking problem and the share access checking problem are orthogonal.

    Going through these as a mental exercise:

    One could, in theory, solve the discretionary access checking problem by changing the RequestorMode to KernelMode. However, this isn't supported by Filter Manager (rightfully so, it's a bit cavalier). This also wouldn't do anything for share access checking.

    Another option might be to change the desired access of the open the way down so that the file system sees very little access (e.g. read attributes) but the I/O Manager above you sees the original access (e.g. read). After open, access checking is mostly handled by the I/O Manager above you, so in theory if you get the bits right the application will be able to use their handle for more than the file system expects. This also probably solves the share access checking problem because we only share access check on read/write/delete access. The down side is that this really only works by way of implementation and not by way of architecture. This is confirmed by the fact that NTFS will fail a limited number of operations if it didn?t see data access on the open.

    You might be able to solve this purely with impersonation, though that would probably involve changing the AccessState parameter to the create operation. The docs say don?t do this and I?ve never tried. It feels a bit icky to me to go digging in there and changing the SubjectSecurityContext. Even if you got this working it would again be by way of implementation.

    Probably the best approach is what we refer to as ?shadowing?. This is like Isolation, but you don?t take over the management of the file system data cache. It basically looks a lot like what you?ve already done. For a create that fails, you submit an equivalent FltCreateFile in its place. You then complete the original create request with success.

    However, YOU need to implement FsContext and FsContext2. As operations arrive at your filter, you note which ones arrive for your FsContext/FsContext2. If the operation is for you, you swap the TargetFileObject and pass it along.

    This takes a bit more to get going than you might expect. Challenges include:
    - Performing an equivalent FltCreateFile call to an incoming IRP_MJ_CREATE callback data. The arguments are scattered all over the place so it just sucks if you want the new IRP_MJ_CREATE to look as much like the old one as possible
    - There are a LOT of operations that require swapping the File Object. Expect a lot of boilerplate code
    - You need to set up your FsContext as if you're a file system (i.e an FSRTL_ADVANCED_FCB_HEADER). Not super complicated, but just something else to manage

    The shadow approach is probably more code than you were expecting, but it?s a reasonably well worn path at this point. That?s how I would approach this for a prototype so I could see what kind of problems I encountered.

    Aside #1: You definitely want to run the IFS Tests with something like this, they?re usually pretty good at finding unexpected interactions with the system.

    Aside #2: I do feel obligated to note that whatever you come up with will likely also have the effect of bypassing system access checking (i.e. auditing). So, if someone is using the security log to know when someone has successfully accessed a file then they might be unhappy (which is a nice way of saying "screwed").

    -scott
    OSR
    @OSRDrivers
  • IanMIanM Posts: 24
    Thanks Scott, that's really interesting, and very useful.

    I have quickly tried tweaking the desired access and it seems to work. I will be testing it fully, but do you have any thoughts on which NTFS operations are likely to fail?

    Also, I (clearly incorrectly) thought that access checking was delegated to the file system. This is definitely not the case then?

    The shadowing approach looks the best way to go although it's going to take me longer to get a prototype together.

    Thanks again,

    Ian
  • Scott_NooneScott_Noone Posts: 2,989
    <QUOTE>
    I will be testing it fully, but do you have any thoughts on which NTFS
    operations are likely to fail?
    </QUOTE>

    IIRC EFS and reparse point related operations.

    <QUOTE>
    Also, I (clearly incorrectly) thought that access checking was delegated to
    the file system. This is definitely not the case then?
    </QUOTE>

    Access checking is performed by the file system at time of open. The result
    is then cached in the HANDLE table and enforced by the I/O Manager on
    subsequent HANDLE operations (i.e. ReadFile/WriteFile).

    -scott
    OSR
    @OSRDrivers
  • IanMIanM Posts: 24
    Right, I've been trying the Shadowing approach. In the post-create callback I create the phony FsContent and FILE_OBJECT plus I set my FsContext into the TargetFileObject and change the IoStatus. In the other callbacks I change the TargetFileObject if the phony FsContext if present. Unfortunately it seems that my phony FsContext is being used in Ntfs functions called by FltpLegacyProcessingAfterPreCallbacksCompleted:

    :
    0b fffff880`06b985a0 fffff880`012eb1c5 nt!KiPageFault+0x422
    0c fffff880`06b98730 fffff880`012edb6f Ntfs!NtfsCommonDirectoryControl+0x35
    0d fffff880`06b98770 fffff800`02f35d56 Ntfs!NtfsFsdDirectoryControl+0x10f
    0e fffff880`06b987e0 fffff880`0115b83f nt!IovCallDriver+0x566
    0f fffff880`06b98840 fffff880`0115a6df fltmgr!FltpLegacyProcessingAfterPreCallbacksCompleted+0x24f
    10 fffff880`06b988d0 fffff800`02f35d56 fltmgr!FltpDispatch+0xcf
    11 fffff880`06b98930 fffff800`02d1b0da nt!IovCallDriver+0x566
    12 fffff880`06b98990 fffff800`02acc9d3 nt!NtQueryDirectoryFile+0x1aa
    :

    In this example NtfsCommonDirectoryControl is accessing some member at FsContext+78h which is beyond the end of FSRTL_ADVANCED_FCB_HEADER so i think it assumes it has a complete FCB.

    Ntfs!NtfsCommonDirectoryControl:
    fffff880`012eb190 48895c2408 mov qword ptr [rsp+8],rbx
    fffff880`012eb195 4889742410 mov qword ptr [rsp+10h],rsi
    fffff880`012eb19a 57 push rdi
    fffff880`012eb19b 4883ec30 sub rsp,30h
    fffff880`012eb19f 488b82b8000000 mov rax,qword ptr [rdx+0B8h]
    fffff880`012eb1a6 488bfa mov rdi,rdx
    fffff880`012eb1a9 488bf1 mov rsi,rcx
    fffff880`012eb1ac 4c8b4030 mov r8,qword ptr [rax+30h] <- r8 = FileObject
    fffff880`012eb1b0 4d8b4818 mov r9,qword ptr [r8+18h] <- r9 = fffffa8033a1e950 = my phony FsContent
    fffff880`012eb1b4 4d85c9 test r9,r9
    fffff880`012eb1b7 0f84a1c10600 je Ntfs! ?? ::NNGAKEGL::`string'+0x15beb (fffff880`0135735e)
    fffff880`012eb1bd 498b5978 mov rbx,qword ptr [r9+78h] <- Page Fault

    It also happens with other IRPs like IRP_MJ_QUERY_SECURITY. Am I doing something wrong? Should I somehow suppress this legacy processing or maybe construct a more complete FCB?
  • Scott_NooneScott_Noone Posts: 2,989
    Your file objects are leaking to the lower file system. You need to handle
    every possible IRP_MJ_XXX code and make sure you always swap the file
    objects. If you miss one the lower file system will crash.

    Are you handling IRP_MJ_DIRECTORY_CONTROL?

    -scott
    OSR
    @OSRDrivers
  • IanMIanM Posts: 24
    Thanks Scott. Yes, I am handling IRP_MJ_DIRECTORY_CONTROL. Here's the Debug output from the same run.

    Found [\Device\HarddiskVolume1\Users\Ian]
    g_pShadowFileObject FFFFFA8033E747F0
    Creating FsContext FFFFFA8033A1E950
    Setting FsContext FFFFFA8033A1E950
    Pre IRP_MJ_FILE_SYSTEM_CONTROL. Replacing FFFFFA80363574E0 with FFFFFA8033E747F0
    Pre IRP_MJ_DIRECTORY_CONTROL. Replacing FFFFFA80363574E0 with FFFFFA8033E747F0

    *** Fatal System Error: 0x00000024
    (0x00000000001904FB,0xFFFFF88006B984F8,0xFFFFF88006B97D60,0xFFFFF880012EB1C5)

    Break instruction exception - code 80000003 (first chance)

    A fatal system error has occurred.
    Debugger entered on first try; Bugcheck callbacks have not been invoked.

    A fatal system error has occurred.


    After my pre handler has swapped the TargetFileObject the filter manager is offering the IRP for 'LegacyProcessing' by FltpLegacyProcessingAfterPreCallbacksCompleted after the minifilter callbacks have been processed.
  • Scott_NooneScott_Noone Posts: 2,989
    Post your swapping code. Are you calling FltSetCallbackDataDirty?

    -scott
    OSR
    @OSRDrivers
  • IanMIanM Posts: 24
    Doh. Of course. No, I wasn't. Thanks, that's got me a lot further. I now have it working although my code for managing the phony FsContent is a bit shonky. For each unique file path I have a single FsContext and use a refcounting mechanism to manage its lifetime, but I was wondering if there's a better way of doing this? I believe the FLT_STREAM_CONTEXT's lifetime is the same as the FCB so could I use that? Or is there some mechanism whereby I can be notified that the FsContext needs to be destroyed?

    Thanks again.
  • Scott_NooneScott_Noone Posts: 2,989
    It's your FCB, so you need to figure out the right time to delete it.

    A file system would normally use a reference count (see usage of
    FatDeleteFcb in the fastfat source). However, with shadowing/isolation you
    can leverage the fact that the lower FCB and the upper (i.e. your) FCB have
    a matching lifetime. So, put a stream context on the lower file and you can
    delete the upper FCB when your stream context goes away.

    -scott
    OSR
    @OSRDrivers
  • IanMIanM Posts: 24
    I actually ended up putting a context on the upper file but the lower one makes more sense - I had to make sure my FCB was properly initialised, in particular Flags2 needed FSRTL_FLAG2_SUPPORTS_FILTER_CONTEXTS set. However, I still have a couple of issues:

    1) I think my fake SECTION_OBJECT_POINTERS structure or its contents is finding its way into the cache manager. If I open a file with notepad using this approach I get a crash in CcFlushCache after the final IRP_MJ_CLOSE has been seen. Looking at the disassembly I think that CcFlushCache is referencing a NULL SharedCacheMap. I see no sign of the SECTION_OBJECT_POINTER structure that I allocated for my 'upper' file object though. Should I be doing more than creating an empty SOP:

    pSectionObjectPointer = ExAllocatePoolWithTag(NonPagedPool, sizeof(SECTION_OBJECT_POINTERS), FSFILTER_TAG);
    RtlZeroMemory(pSectionObjectPointer, sizeof(SECTION_OBJECT_POINTERS));
    pData->Iopb->TargetFileObject->SectionObjectPointer = pSectionObjectPointer;
    :
    FltSetCallbackDataDirty(pData);

    Here's the stack:

    nt!KiBugCheckDispatch+0x69
    nt!KiPageFault+0x448 (TrapFrame @ fffff880`05c4f780)
    nt!CcFlushCache+0x103
    nt!CcWriteBehind+0x1c6
    nt!CcWorkerThread+0x1c8
    nt!ExpWorkerThread+0x111
    nt!PspSystemThreadStartup+0x194
    nt!KxStartSystemThread+0x16


    2) Also, with procmon in the equation I get a crash:

    nt!KiPageFault+0x422
    Ntfs!NtfsDecodeFileObject+0x48
    Ntfs!NtfsCommonQueryInformation+0x98
    Ntfs!NtfsFsdDispatchSwitch+0x106
    Ntfs!NtfsFsdDispatchWait+0x14
    nt!IovCallDriver+0x566
    fltmgr!FltpLegacyProcessingAfterPreCallbacksCompleted+0x24f
    fltmgr!FltPerformSynchronousIo+0x2ca
    fltmgr!FltpQueryInformationFileFromInstance+0x74
    fltmgr!QueryStandardLinkInformationFromInstance+0x4c
    fltmgr! ?? ::NNGAKEGL::`string'+0x2477
    fltmgr!FltpGetFileNameInformation+0x1fb
    fltmgr!FltGetFileNameInformation+0x187
    PROCMON23+0x2a12
    fltmgr!FltpPerformPreCallbacks+0x50b
    fltmgr!FltpPassThrough+0x2f7
    fltmgr!FltpDispatch+0xb7
    nt!IovCallDriver+0x566
    nt!IopSynchronousServiceTail+0xfa
    nt!NtWriteFile+0x80a
    nt!KiSystemServiceCopyEnd+0x13

    I am handling every IRP so how I don't understand how ntfs has got hold of my 'upper' file object. I notice it's getting file name information. Does my filter driver need to be a name provider?

    As I've been Googling around these problems I keep finding references to isolation drivers and how long they take to get right. Am I getting sucked into implementing something huge or is the basic shadowing I'm trying to accomplish be more easily achievable?
  • Scott_NooneScott_Noone Posts: 2,989
    <QUOTE>
    I actually ended up putting a context on the upper file but the lower one
    makes more sense - I had to make sure my FCB was properly initialised, in
    particular Flags2 needed FSRTL_FLAG2_SUPPORTS_FILTER_CONTEXTS set. However,
    I still have a couple of issues:
    </QUOTE>

    There's an API for initializing the FCB header: FsRtlSetupAdvancedHeaderEx.
    You also want to call FsRtlTeardownPerStreamContexts as part of tearing it
    down.

    <QUOTE>
    1) I think my fake SECTION_OBJECT_POINTERS structure or its contents is
    finding its way into the cache manager.
    </QUOTE>

    You don't want your own SOP in this case. You can "borrow" the lower SOP and
    point your file object to it. This is a major difference between what you're
    doing and Isolation (in Isolation your own the SOP, thus you own all the
    Cc/Mm integration).

    <QUOTE>
    2) Also, with procmon in the equation I get a crash:
    ...
    I am handling every IRP so how I don't understand how ntfs has got hold of
    my 'upper' file object. I notice it's getting file name information. Does my
    filter driver need to be a name provider?
    </QUOTE>

    Yes, you need a "do nothing" name provider. It's annoying boiler plate that
    you can lift pretty much verbatim from the SimRep sample:

    https://github.com/Microsoft/Windows-driver-samples/blob/master/filesys/miniFilter/simrep/simrep.c#L2404

    <QUOTE>
    As I've been Googling around these problems I keep finding references to
    isolation drivers and how long they take to get right. Am I getting sucked
    into implementing something huge or is the basic shadowing I'm trying to
    accomplish be more easily achievable?
    </QUOTE>

    Not as complicated as Isolate because you're not going to change the
    presentation of the data from its on disk format. As I mentioned previously
    though, lots of annoying boilerplate code.

    -scott
    OSR
    @OSRDrivers
  • IanMIanM Posts: 24
    Thanks again, Scott. The name provider has fixed the issue with ProcMon.

    Unfortunately, even after changing my code to steal the SOP from the lower file object, I am still getting the same crash in CcFlushCache after the the file has been written to and the last IRP_MJ_CLOSE has been seen. I think it's the SharedCacheMap member of the SOP although that doesn't make sense now that I'm using the lower FO's SOP. There must be something wrong with the upper FILE_OBJECT but I can't see what. From what I have read, the links between the FILE_OBJECT and the Cache Manager are the SOP and PrivateCacheMap, both of which I initialise. My PostCreate code is below. Is there anything obvious I'm doing wrong? Any thoughts about the best way to go about diagnosing this?



    FLT_POSTOP_CALLBACK_STATUS PostCreate(_Inout_ PFLT_CALLBACK_DATA pData, _In_ PCFLT_RELATED_OBJECTS pFltObjects, _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags)
    {
    NTSTATUS status = STATUS_SUCCESS;
    FLT_POSTOP_CALLBACK_STATUS returnStatus = FLT_POSTOP_FINISHED_PROCESSING;
    UNICODE_STRING strFullPath = { 0, 0, 0 };
    UNICODE_STRING strSid = { 0, 0, 0 };

    UNREFERENCED_PARAMETER(CompletionContext);
    UNREFERENCED_PARAMETER(Flags);

    // Ignore kernel activity
    if (ExGetPreviousMode() == KernelMode || pData->RequestorMode == KernelMode)
    {
    return FLT_POSTOP_FINISHED_PROCESSING;
    }

    // Unconfigured?
    if (!gFsFilterData.bConfigured)
    {
    return FLT_POSTOP_FINISHED_PROCESSING;
    }

    // Unsuccessful create attempt?
    if (!NT_SUCCESS(pData->IoStatus.Status))
    {
    status = ConstructFullPath(pData, pFltObjects->Volume, &(pData->Iopb->TargetFileObject->FileName), NULL, &strFullPath);
    if (!NT_SUCCESS(status))
    {
    return FLT_POSTOP_FINISHED_PROCESSING;
    }

    status = GetRequestSid(pData->Iopb->Parameters.Create.SecurityContext, &strSid);
    if (NT_SUCCESS(status))
    {
    DbgPrint("Status 0x%08x\n", status);
    goto cleanup;
    }

    // Match Rule
    if (Rule_Find(&strSid, &strFullPath, AllowList, bDirectory, pData->Iopb->Parameters.Create.SecurityContext->DesiredAccess))
    {
    DbgPrint("Post IRP_MJ_CREATE [%wZ] FO:%p FsC:%p\n", &(pData->Iopb->TargetFileObject->FileName), pData->Iopb->TargetFileObject, pData->Iopb->TargetFileObject->FsContext);

    ULONG createDisposition = (pData->Iopb->Parameters.Create.Options & 0xff000000) >> 24;
    ULONG createOptions = pData->Iopb->Parameters.Create.Options & 0x00ffffff;

    ULONG attributes = OBJ_KERNEL_HANDLE;
    ULONG createFileFlags = IO_IGNORE_SHARE_ACCESS_CHECK;

    if (createDisposition & FILE_OPEN_IF)
    {
    attributes |= OBJ_OPENIF;
    }
    if (!(pData->Iopb->OperationFlags & SL_CASE_SENSITIVE))
    {
    attributes |= OBJ_CASE_INSENSITIVE;
    }

    DbgPrint("Disposition:0x%08x Options:0x%08x OperationFlags:0x%08x\n", createDisposition, createOptions, (pData->Iopb->OperationFlags));

    OBJECT_ATTRIBUTES ObjectAttributes;
    InitializeObjectAttributes(&ObjectAttributes, &strFullPath, attributes, NULL, NULL);

    HANDLE hFile;
    IO_STATUS_BLOCK IoStatusBlock;
    PFILE_OBJECT pShadowFileObject;
    status = FltCreateFileEx(
    pFltObjects->Filter,
    pFltObjects->Instance,
    &hFile,
    &pShadowFileObject,
    pData->Iopb->Parameters.Create.SecurityContext->DesiredAccess,
    &ObjectAttributes,
    &IoStatusBlock,
    &(pData->Iopb->Parameters.Create.AllocationSize),
    pData->Iopb->Parameters.Create.FileAttributes,
    pData->Iopb->Parameters.Create.ShareAccess,
    createDisposition,
    createOptions,
    pData->Iopb->Parameters.Create.EaBuffer,
    pData->Iopb->Parameters.Create.EaLength,
    createFileFlags);

    if (NT_SUCCESS(status))
    {
    DbgPrint("pShadowFileObject %p\n", pShadowFileObject);

    PFSRTL_COMMON_FCB_HEADER pFsContext = (PFSRTL_COMMON_FCB_HEADER)pShadowFileObject->FsContext;

    PFSRTL_ADVANCED_FCB_HEADER pShadowFsContext = NULL;
    PSECTION_OBJECT_POINTERS pSectionObjectPointer = NULL;
    PSTREAM_CONTEXT pStreamContext = NULL;
    BOOLEAN bCreateContext = !NT_SUCCESS(FltGetStreamContext(pFltObjects->Instance, pFltObjects->FileObject, &pStreamContext));

    // Create a context if necessary
    if (bCreateContext)
    {
    pShadowFsContext = ExAllocatePoolWithTag(NonPagedPool, sizeof(FSRTL_ADVANCED_FCB_HEADER), FSFILTER_TAG);
    RtlZeroMemory(pShadowFsContext, sizeof(FSRTL_ADVANCED_FCB_HEADER));

    PFAST_MUTEX pMutext = ExAllocatePoolWithTag(NonPagedPool, sizeof(FAST_MUTEX), FSFILTER_TAG);
    ExInitializeFastMutex(pMutext);
    PVOID pFileContextSupportPointer = NULL;

    FsRtlSetupAdvancedHeaderEx(pShadowFsContext, pMutext, pFileContextSupportPointer);

    DbgPrint("Creating FsContext %p\n", pShadowFsContext);

    pShadowFsContext->Resource = ExAllocatePoolWithTag(NonPagedPool, sizeof(ERESOURCE), FSFILTER_TAG);
    pShadowFsContext->PagingIoResource = ExAllocatePoolWithTag(NonPagedPool, sizeof(ERESOURCE), FSFILTER_TAG);
    ExInitializeResourceLite(pShadowFsContext->Resource);
    ExInitializeResourceLite(pShadowFsContext->PagingIoResource);

    pShadowFsContext->AllocationSize = pFsContext->AllocationSize;
    pShadowFsContext->FileSize = pFsContext->FileSize;
    pShadowFsContext->ValidDataLength = pFsContext->ValidDataLength;

    pSectionObjectPointer = pShadowFileObject->SectionObjectPointer;
    }
    else
    {
    pShadowFsContext = pStreamContext->pShadowFsContext;
    pSectionObjectPointer = pStreamContext->pSectionObjectPointer;
    FltReleaseContext(pStreamContext);
    }

    DbgPrint("Setting FsContext %p\n", pShadowFsContext);
    DbgPrint("SectionObjectPointers %p\n", pSectionObjectPointer);

    pData->Iopb->TargetFileObject->FsContext = pShadowFsContext;
    pData->Iopb->TargetFileObject->SectionObjectPointer = pSectionObjectPointer;
    pData->Iopb->TargetFileObject->Flags = pShadowFileObject->Flags;

    pData->Iopb->TargetFileObject->PrivateCacheMap = NULL;

    pData->IoStatus = IoStatusBlock;

    FltSetCallbackDataDirty(pData);

    // Add a STREAMHANDLE context to the patched up FILE_OBJECT so we can clean up the new FILE_OBJECT when it is deleted
    PSTREAM_HANDLE_CONTEXT pStreamHandleContext;
    status = FltAllocateContext(pFltObjects->Filter, FLT_STREAMHANDLE_CONTEXT, sizeof(STREAM_HANDLE_CONTEXT), NonPagedPool, &pStreamHandleContext);
    if (NT_SUCCESS(status))
    {
    // Set the context on the file object
    status = FltSetStreamHandleContext(pFltObjects->Instance, pFltObjects->FileObject, FLT_SET_CONTEXT_KEEP_IF_EXISTS, pStreamHandleContext, NULL);
    if (NT_SUCCESS(status))
    {
    // Initialise the context
    pStreamHandleContext->pShadowFileObject = pShadowFileObject;
    pStreamHandleContext->hFile = hFile;
    }

    FltReleaseContext(pStreamHandleContext);
    }

    if (bCreateContext)
    {
    // Add a STREAM context to the patched up FILE_OBJECT so we can clean up the new FsContent when the last reference is deleted
    status = FltAllocateContext(pFltObjects->Filter, FLT_STREAM_CONTEXT, sizeof(STREAM_CONTEXT), NonPagedPool, &pStreamContext);
    if (NT_SUCCESS(status))
    {
    // Set the context on the file object
    status = FltSetStreamContext(pFltObjects->Instance, pFltObjects->FileObject, FLT_SET_CONTEXT_KEEP_IF_EXISTS, pStreamContext, NULL);
    if (NT_SUCCESS(status))
    {
    // Initialise the context
    pStreamContext->pShadowFsContext = pShadowFsContext;
    pStreamContext->pSectionObjectPointer = pSectionObjectPointer;
    }

    FltReleaseContext(pStreamContext);
    }
    }
    }
    else
    {
    DbgPrint("Status 0x%08x\n", status);
    pData->IoStatus.Status = status;
    }
    }
    }

    cleanup:
    if (strFullPath.Buffer)
    {
    ExFreePoolWithTag(strFullPath.Buffer, FSFILTER_TAG);
    }

    RtlFreeUnicodeString(&strSid);

    return returnStatus;
    }
  • IanMIanM Posts: 24
    If it helps, I've done a bit of disassembling and I think CcWriteBehind is using my lower file object, created in the PostCreate above which it gets from SharedCacheMap.FileObjectFastRef. The SectionObjectPointer that it retrieves from this FILE_OBJECT is NULL even though at the point where I create the FO, it has valid SOP. This is what it looks like at the point of the crash:

    0: kd> dt (_FILE_OBJECT)0xfffffa80576b0390
    ntdll!_FILE_OBJECT
    +0x000 Type : 5
    +0x002 Size : 0xd8
    +0x008 DeviceObject : 0xfffffa80`32e6e9d0 _DEVICE_OBJECT
    +0x010 Vpb : 0xfffffa80`32e6e290 _VPB
    +0x018 FsContext : (null)
    +0x020 FsContext2 : (null)
    +0x028 SectionObjectPointer : (null)
    +0x030 PrivateCacheMap : (null)
    +0x038 FinalStatus : 0
    +0x040 RelatedFileObject : (null)
    +0x048 LockOperation : 0 ''
    +0x049 DeletePending : 0 ''
    +0x04a ReadAccess : 0x1 ''
    +0x04b WriteAccess : 0x1 ''
    +0x04c DeleteAccess : 0 ''
    +0x04d SharedRead : 0x1 ''
    +0x04e SharedWrite : 0 ''
    +0x04f SharedDelete : 0 ''
    +0x050 Flags : 0x44042
    +0x058 FileName : _UNICODE_STRING "\Windows\System32\drivers\etc\hosts"
    +0x068 CurrentByteOffset : _LARGE_INTEGER 0x4
    +0x070 Waiters : 0
    +0x074 Busy : 0
    +0x078 LastLock : (null)
    +0x080 Lock : _KEVENT
    +0x098 Event : _KEVENT
    +0x0b0 CompletionContext : (null)
    +0x0b8 IrpListLock : 0
    +0x0c0 IrpList : _LIST_ENTRY [ 0xfffffa80`576b0450 - 0xfffffa80`576b0450 ]
    +0x0d0 FileObjectExtension : 0xfffffa80`576b02b0 Void

    So I guess my questions are, why is it still being used after it's been closed and what NULLs out the SOP?

    Thanks again.
  • Scott_NooneScott_Noone Posts: 2,989
    The file object looks bogus. You shouldn't have a NULL FsContext and SOP.

    It DOES have the FO_CLEANUP_COMPLETE bit set though, are you zeroing down
    the fields of the file object on cleanup or something??

    -scott
    OSR
    @OSRDrivers
  • IanMIanM Posts: 24
    First of all, yes the FO was bogus because I was treating the CLOSE and CLEANUP IRPs like all the others and swapping the FO and passing them down. This had the effect of getting the file system to cleanup the FO (and presumably NULLing out the SOP) where nothing above me (like the cache manager) knew what had happened and was still using it. I now close the lower FO in the PreClose for the upper one. Does that seem OK?

    On a related note I had a problem with notepad which turned out to be its use of SetEndOfFile(). Because the CurrentFileOffset is updated on the lower FO after a write, say, it needs to be transferred onto the upper one in the Post callback to keep the two in step. While I've fixed this I need to work out a complete set of which FO fields need updating in which IRP callbacks. Is there a deterministic way of going about this or is it a case of test and fix?
  • Scott_NooneScott_Noone Posts: 2,989
    CurrentByteOffset is special because it's updated by the file system on on
    each (successful) synchronous read and write. It also is updated as a result
    of IRP_MJ_SET_INFORMATION/FilePositionInformation.

    You should also sync some other fields in PostCreate:

    UserFileObject->DeletePending = LowerFileObject->DeletePending;
    UserFileObject->ReadAccess = LowerFileObject->ReadAccess;
    UserFileObject->WriteAccess = LowerFileObject->WriteAccess;
    UserFileObject->DeleteAccess = LowerFileObject->DeleteAccess;
    UserFileObject->SharedRead = LowerFileObject->SharedRead;
    UserFileObject->SharedWrite = LowerFileObject->SharedWrite;
    UserFileObject->SharedDelete = LowerFileObject->SharedDelete;

    Sometimes higher filters look at these fields to check for data access. Note
    that they shouldn't change after PostCreate (though some filters will muck
    with them for their own twisted reasons).

    -scott
    OSR
    @OSRDrivers
Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!