Completing a failed Create in post callback

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.

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

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?

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

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.

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

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

IIRC EFS and reparse point related operations.

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

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 fffff88006b985a0 fffff880012eb1c5 nt!KiPageFault+0x422
0c fffff88006b98730 fffff880012edb6f Ntfs!NtfsCommonDirectoryControl+0x35
0d fffff88006b98770 fffff80002f35d56 Ntfs!NtfsFsdDirectoryControl+0x10f
0e fffff88006b987e0 fffff8800115b83f nt!IovCallDriver+0x566
0f fffff88006b98840 fffff8800115a6df fltmgr!FltpLegacyProcessingAfterPreCallbacksCompleted+0x24f
10 fffff88006b988d0 fffff80002f35d56 fltmgr!FltpDispatch+0xcf
11 fffff88006b98930 fffff80002d1b0da nt!IovCallDriver+0x566
12 fffff88006b98990 fffff80002acc9d3 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:
fffff880012eb190 48895c2408 mov qword ptr [rsp+8],rbx fffff880012eb195 4889742410 mov qword ptr [rsp+10h],rsi
fffff880012eb19a 57 push rdi fffff880012eb19b 4883ec30 sub rsp,30h
fffff880012eb19f 488b82b8000000 mov rax,qword ptr [rdx+0B8h] fffff880012eb1a6 488bfa mov rdi,rdx
fffff880012eb1a9 488bf1 mov rsi,rcx fffff880012eb1ac 4c8b4030 mov r8,qword ptr [rax+30h] <- r8 = FileObject
fffff880012eb1b0 4d8b4818 mov r9,qword ptr [r8+18h] \<- r9 = fffffa8033a1e950 = my phony FsContent fffff880012eb1b4 4d85c9 test r9,r9
fffff880012eb1b7 0f84a1c10600 je Ntfs! ?? ::NNGAKEGL::string’+0x15beb (fffff8800135735e) fffff880012eb1bd 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?

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

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.

Post your swapping code. Are you calling FltSetCallbackDataDirty?

-scott
OSR
@OSRDrivers

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.

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

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

  1. 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?

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

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).

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

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

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;
}

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 : 0xfffffa8032e6e9d0 _DEVICE_OBJECT +0x010 Vpb : 0xfffffa8032e6e290 _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 [0xfffffa80576b0450 - 0xfffffa80576b0450]
+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.

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

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?