My minifilter decrypts the file contents of one specific EXE file. The file is only allowed to be opened for read access, so no need to worry about anything write related in this thread.
First of all, here's my "flush" code, which works very well in itself:
BOOLEAN FlushFileObject(_In_ PFILE_OBJECT FileObject)
// flush the read and image caches of a specific FileObject
{
PAGED_CODE();
BOOLEAN result = TRUE;
// we only have to do anything if there's an FsContext and a FsContexte->Resource
if ((FileObject) && (FileObject->FsContext) && (((PFSRTL_COMMON_FCB_HEADER) (FileObject->FsContext))->Resource))
{
result = FALSE;
PERESOURCE fcbResource = ((PFSRTL_COMMON_FCB_HEADER) (FileObject->FsContext))->Resource;
PERESOURCE fcbPagingIoResource = ((PFSRTL_COMMON_FCB_HEADER) (FileObject->FsContext))->PagingIoResource;
for (int i1 = 0; (i1 < 20) && (!result); i1++)
{
if (i1)
{
// this is the 2nd (or 3rd or ...) iteration of the loop
// we couldn't manage to lock both resources, so let's wait 50ms, then try again
LARGE_INTEGER interval = {0};
interval.QuadPart = -50 * 10000LL; // 50 ms
KeDelayExecutionThread(KernelMode, FALSE, &interval);
}
KeEnterCriticalRegion();
if (ExAcquireResourceExclusiveLite(fcbResource, TRUE))
{
if ((!fcbPagingIoResource) || (ExAcquireResourceExclusiveLite(fcbPagingIoResource, FALSE)))
{
// we managed to lock both resources, so we can flush now
result = TRUE;
if (FileObject->SectionObjectPointer)
{
// there's actually something we can flush
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "before flush ImageSectionObject: %p, DataSectionObject: %p", FileObject->SectionObjectPointer->ImageSectionObject, FileObject->SectionObjectPointer->DataSectionObject);
if (FileObject->SectionObjectPointer->ImageSectionObject)
MmFlushImageSection(FileObject->SectionObjectPointer, MmFlushForWrite);
if (FileObject->SectionObjectPointer->DataSectionObject)
CcPurgeCacheSection(FileObject->SectionObjectPointer, NULL, 0, FALSE);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "after flush ImageSectionObject: %p, DataSectionObject: %p", FileObject->SectionObjectPointer->ImageSectionObject, FileObject->SectionObjectPointer->DataSectionObject);
}
if (fcbPagingIoResource)
ExReleaseResourceLite(fcbPagingIoResource);
}
ExReleaseResourceLite(fcbResource);
}
KeLeaveCriticalRegion();
}
}
return result;
}
Now whenever my minifilter is started or stopped, I need to flush the cache for the encrypted file, because (obviously) I don't want the OS to return the file half encrypted and half decrypted, due to outdated data in the cache.
As a first try, I opened the file both in "DriverEntry" and "Unload", and then flushed it. The flushing succeeds and is effective (the "after flush" debug prints confirm that). And it works perfectly in "Unload".
HOWEVER, and this is my key problem: Sometimes, even though flushing in DriverEntry succeeded (and I do that after filtering is already started), the EXE still gets partially encrypted data - and thus doesn't run properly. I don't really understand why this happens. It seems that the file cache is sometimes refilled after the successful flush from the original (still decrypted) file behind my minifilter's back. And yes, I also handle paging file I/O requests, so it's not that.
So as a workaround, I added an extra flush in the "PostCreate" callback. That fully solves the above problem. I never get encrypted data now.
But now I have a different problem: The flushing in PostCreate has the effect that sometimes read accesses have a few zeroed out bytes in them. I believe this happens when the PostCreate flushes the file at the same time when another thread is accessing the file through the cache.
So now I'm lost. Any ideas how to solve this mess properly?