Completing a failed Create in post callback

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?

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

Thanks Scott. All seems OK except a problem I have hit with renames: both the parent FO (C:\Windows\System32\drivers\etc) and target FO (C:\Windows\System32\drivers\etc\hosts) are being ‘shadowed’ because the user is a standard user and MoveFileW requires FILE_ADD_FILE access for the parent and DELETE for the file itself. I positioned FileSpy below my driver and this is the output:

Process Request IRP Flags FileObject FO Flags Path Status More info

1 MoveHostsBak.exe IRP_MJ_CREATE 00000884 FFFFFA803422D610 00000002 C:\Windows\System32\drivers\etc\hosts STATUS_ACCESS_DENIED FILE_OPEN CreOpts: 00200020 Access: 00110080 Share: 00000007 Attrib: 0
2 MoveHostsBak.exe IRP_MJ_CREATE 00000884 FFFFFA8034B0BD60 00000002 C:\Windows\System32\drivers\etc\hosts STATUS_SUCCESS FILE_OPEN CreOpts: 00200020 Access: 00110080 Share: 00000007 Attrib: 0 Result: FILE_OPENED
3 MoveHostsBak.exe IRP_MJ_QUERY_INFORMATION 00060870 FFFFFA8034B0BD60 00040042 C:\Windows\System32\drivers\etc\hosts STATUS_SUCCESS FileAttributeTagInformation
4 MoveHostsBak.exe IRP_MJ_QUERY_INFORMATION 00000010 FFFFFA8034B0BD60 00040042 C:\Windows\System32\drivers\etc\hosts STATUS_BUFFER_OVERFLOW FileNameInformation
5 MoveHostsBak.exe IRP_MJ_QUERY_INFORMATION 00000010 FFFFFA8034B0BD60 00040042 C:\Windows\System32\drivers\etc\hosts STATUS_SUCCESS FileNameInformation
6 MoveHostsBak.exe IRP_MJ_QUERY_INFORMATION 00000000 FFFFFA8034B0BD60 00040042 C:\Windows\System32\drivers\etc\hosts STATUS_SUCCESS FileBasicInformation Attrib: 00000020
7 MoveHostsBak.exe IRP_MJ_CREATE 00000884 FFFFFA8031213730 00000000 C:\Windows\System32\drivers\etc STATUS_ACCESS_DENIED FILE_OPEN CreOpts: 0 Access: 00100002 Share: 00000003 Attrib: 0
8 MoveHostsBak.exe IRP_MJ_CREATE 00000884 FFFFFA8031696670 00000000 C:\Windows\System32\drivers\etc STATUS_SUCCESS FILE_OPEN CreOpts: 0 Access: 00100002 Share: 00000003 Attrib: 0 Result: FILE_OPENED
9 MoveHostsBak.exe IRP_MJ_QUERY_INFORMATION 00000000 FFFFFA8034B0BD60 00040042 C:\Windows\System32\drivers\etc\hosts STATUS_SUCCESS 00000036
10 MoveHostsBak.exe IRP_MJ_SET_INFORMATION 00060830 FFFFFA8034B0BD60 00040042 C:\Windows\System32\drivers\etc\hosts STATUS_ACCESS_DENIED FileRenameInformation
11 MoveHostsBak.exe IRP_MJ_CLEANUP 00000404 FFFFFA8031696670 00040000 C:\Windows\System32\drivers\etc STATUS_SUCCESS
12 MoveHostsBak.exe IRP_MJ_CLEANUP 00000404 FFFFFA8034B0BD60 00040042 C:\Windows\System32\drivers\etc\hosts STATUS_SUCCESS

You can see the file and its parent being shadowed in #2 and #8 respectively (FFFFFA803422D610 is shadowed by FFFFFA8034B0BD60 and FFFFFA8031213730 is shadowed by FFFFFA8031696670)
In my driver I see #8 come through with the path […]etc\hosts.bak and the SL_OPEN_TARGET_DIRECTORY flag set which I believe is what I should expect and I pass it to FltCreateFileEx in this form.
In my IRP_MJ_SET_INFORMATION pre-handler, I replace Data->Iopb->TargetFileObject with its ‘lower’ FO and Data->Iopb->Parameters.SetFileInformation.ParentOfTarget with its ‘lower’ FO then FltSetCallbackDataDirty.
The root directory in the rename info is NULL.

The result in #10 is that I still get access denied, even though my parent and child objects were successfully created and swapped in when the FileRenameInformation came through.

So my question is why is the rename still returning access denied when the file objects appear to have been successfully created and swapped over?
Is there another check below my driver that maybe causing this? Do I need to impersonate? Or is there something about the FileRenameInformation that I don’t understand?

Thanks again,

Ian

Nothing obvious to me, if you’re performing equivalent opens and swapping the file object it should “just work.”

Can you reproduce this over FAT? That makes life easier because you can just look up where the error is coming from. If not, NTFS has status debugging that will at least help pinpoint the check that’s failing. Which version of the OS is running on the target?

Thanks - I saw your post too. It’s Windows 7 and when I turn on the NTFS debugging it breaks here

Ntfs!NtfsStatusDebug+0x99
Ntfs!NtfsCheckIndexForAddOrDelete+0x124
Ntfs!NtfsSetRenameInfo+0x145a
Ntfs!NtfsCommonSetInformation+0x7d5
Ntfs!NtfsFsdSetInformation+0x124
fltmgr!FltpLegacyProcessingAfterPreCallbacksCompleted+0x24f
fltmgr!FltpDispatch+0xcf

and it looks like the underlying access check is performed by SeAccessCheckWithHint (called by TxfAccessCheck) CheckIndexForAddOrDelete suggests that it’s checking access to the parent directory. Is there any way of finding out what the parameters are for these functions? The prototype for SeAccessCheck is out there, but not the newer ones.

I’m not sure I can reproduce the issue on FAT because I’m fixing up an ACCESS_DENIED.

Is there a way to get more logging from NTFS or SeAccessCheckWithHint so I can see which permissions it thinks I don’t have?

I’ve had a bit more of a look and NtfsCheckIndexForAddOrDelete ends up calling SepAccessCheck:

nt!SepAccessCheck+5f
nt!SeAccessCheckWithHint+178 (perf)
Ntfs!TxfAccessCheck+123
Ntfs!NtfsCheckIndexForAddOrDelete+e7
Ntfs!NtfsSetRenameInfo+145a

The parameters appear to contain an SD and a Token. The SD is from the parent directory which is OK but the Token looks like its from the standard user who I am trying to allow access to. Why would the filesystem need to perform another access check when access to the two file objects has already been checked and granted?