SwapBuffer doesn't seems to do memory mapping

Hi Folks,

I am trying to understand how to work with a minifilter driver in the context of memory-mapped files. According to Microsoft documentation, it is suggested to use the SwapBuffers technique.

To investigate this, I added debug print statements in all functions related to the SwapBuffer implementation. However, when I open or read a file using Notepad, I do not see any log or print output in the debugger.

Interestingly, when I write or save the file, I do see the appropriate logs being triggered.

So, my question is:

  1. I don't see any read log, do swapbuffer intercept this.
  2. If swapbuffer handles it which part exactly does this.

Any clarification or guidance would be appreciated.

the notepad using file mapping as possible as it can(so does many other apps)
read operations after mapping completed is all cache io, check your callbacks to see if you have filtered cached io out

Hi good to hear you,
But i have not filtered out any cached io out. This problem is there with my other codes also. Basically it works well when i use type command for reading file or use notepad for writing in file. But when i try to read using notepad it don't work.

For instance i have a code which do transparent encryption on fly whenever any user tries to access a file. Here in this code i did xor operation instead of encryption for simplicity. Here this code works well when reading through type command and writing using notepad. But when you access using notepad it give encrypted content.

#include <fltKernel.h>

#define MOUNT_PREFIX    L"\\Device\\HarddiskVolume3\\Mount"

PFLT_FILTER gFilterHandle;

FLT_PREOP_CALLBACK_STATUS  EncryptPreWrite(
    PFLT_CALLBACK_DATA Data,
    PCFLT_RELATED_OBJECTS FltObjects,
    PVOID* CompletionContext
);
FLT_PREOP_CALLBACK_STATUS EncryptPreRead(
    PFLT_CALLBACK_DATA Data,
    PCFLT_RELATED_OBJECTS FltObjects,
    PVOID CompletionContext
);
FLT_POSTOP_CALLBACK_STATUS EncryptPostRead(
    PFLT_CALLBACK_DATA Data,
    PCFLT_RELATED_OBJECTS FltObjects,
    PVOID CompletionContext,
    FLT_POST_OPERATION_FLAGS Flags
);
NTSTATUS EncryptUnload(FLT_FILTER_UNLOAD_FLAGS Flags);
FLT_POSTOP_CALLBACK_STATUS
EncryptPostCreate(
    PFLT_CALLBACK_DATA Data,
    PCFLT_RELATED_OBJECTS FltObjects,
    PVOID CompletionContext,
    FLT_POST_OPERATION_FLAGS Flags
);


// register only Write and Read
CONST FLT_OPERATION_REGISTRATION Callbacks[] = {
    { IRP_MJ_CREATE, 0, NULL, EncryptPostCreate },
    { IRP_MJ_WRITE, 0, EncryptPreWrite, NULL },
    { IRP_MJ_READ,  0, EncryptPreRead, EncryptPostRead },
    { IRP_MJ_OPERATION_END }
};

CONST FLT_REGISTRATION FilterRegistration = {
    sizeof(FLT_REGISTRATION), FLT_REGISTRATION_VERSION, 0, NULL,
    Callbacks, EncryptUnload, NULL, NULL, NULL, NULL
};

// simple XOR-by-2
static VOID XorBuffer(PUCHAR buf, ULONG len) {
    for (ULONG i = 0; i < len; i++) {
        buf[i] ^= 0x02;
    }
}


static
BOOLEAN
IsUnderMount(
    _In_ PFLT_CALLBACK_DATA Data
)
{
    PFLT_FILE_NAME_INFORMATION nameInfo;
    NTSTATUS status;
    BOOLEAN match = FALSE;
    UNICODE_STRING mountPrefix = RTL_CONSTANT_STRING(MOUNT_PREFIX);

    status = FltGetFileNameInformation(
        Data,
        FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT,
        &nameInfo);
    if (!NT_SUCCESS(status))
        return FALSE;

    status = FltParseFileNameInformation(nameInfo);
    if (NT_SUCCESS(status)) {
        if (RtlPrefixUnicodeString(&mountPrefix, &nameInfo->Name, TRUE)) {
            match = TRUE;
        }
    }
    FltReleaseFileNameInformation(nameInfo);
    return match;
}

FLT_POSTOP_CALLBACK_STATUS
EncryptPostCreate(
    PFLT_CALLBACK_DATA Data,
    PCFLT_RELATED_OBJECTS FltObjects,
    PVOID CompletionContext,
    FLT_POST_OPERATION_FLAGS Flags
)
{
    UNREFERENCED_PARAMETER(CompletionContext);
    UNREFERENCED_PARAMETER(Flags);

    // only if create succeeded
    if (!NT_SUCCESS(Data->IoStatus.Status)) {
        return FLT_POSTOP_FINISHED_PROCESSING;
    }

    // get name
    PFLT_FILE_NAME_INFORMATION nameInfo;
    if (NT_SUCCESS(FltGetFileNameInformation(
        Data,
        FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT,
        &nameInfo)) &&
        NT_SUCCESS(FltParseFileNameInformation(nameInfo)))
    {
        UNICODE_STRING mountPrefix = RTL_CONSTANT_STRING(MOUNT_PREFIX);

        if (RtlPrefixUnicodeString(&mountPrefix, &nameInfo->Name, TRUE)) {
            // flush & purge caches for this file
            FltFlushBuffers(FltObjects->Instance,
                FltObjects->FileObject);

            // ask the I/O manager to re-try the create
            Data->IoStatus.Status = STATUS_SUCCESS;
            Data->IoStatus.Information = 0;
        }

        FltReleaseFileNameInformation(nameInfo);
    }

    return FLT_POSTOP_FINISHED_PROCESSING;
}

FLT_PREOP_CALLBACK_STATUS EncryptPreRead(
    PFLT_CALLBACK_DATA Data,
    PCFLT_RELATED_OBJECTS FltObjects,
    PVOID CompletionContext
) 
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);

    if (IsUnderMount(Data))
    {
        KdPrint(("PreRead Called"));
        return FLT_PREOP_SUCCESS_WITH_CALLBACK;
    }
    return FLT_PREOP_SUCCESS_NO_CALLBACK;
}


FLT_PREOP_CALLBACK_STATUS
EncryptPreWrite(
    PFLT_CALLBACK_DATA Data,
    PCFLT_RELATED_OBJECTS FltObjects,
    PVOID* CompletionContext
)
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);

    if (IsUnderMount(Data))
    {
        // lock user buffer into memory and get system-space pointer
        NTSTATUS sts = FltLockUserBuffer(Data);
        if (NT_SUCCESS(sts)) {
            PUCHAR buf = Data->Iopb->Parameters.Write.WriteBuffer;
            ULONG  len = Data->Iopb->Parameters.Write.Length;
            if (buf && len) {
                XorBuffer(buf, len);
            }
        }
    }

    return FLT_PREOP_SUCCESS_NO_CALLBACK;
}


FLT_POSTOP_CALLBACK_STATUS
EncryptPostRead(
    PFLT_CALLBACK_DATA Data,
    PCFLT_RELATED_OBJECTS FltObjects,
    PVOID CompletionContext,
    FLT_POST_OPERATION_FLAGS Flags
)
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);
    UNREFERENCED_PARAMETER(Flags);

    if (IsUnderMount(Data))
    {
        // lock user buffer into memory and get system-space pointer
        NTSTATUS sts = FltLockUserBuffer(Data);
        if (NT_SUCCESS(sts)) {
            PUCHAR buf = Data->Iopb->Parameters.Read.ReadBuffer;
            ULONG  len = Data->Iopb->Parameters.Read.Length;
            if (buf && len) {
                XorBuffer(buf, len);
            }
        }
    }

    return FLT_POSTOP_FINISHED_PROCESSING;
}

//------------------------------------------------------------------------------
//  Unload
//------------------------------------------------------------------------------
NTSTATUS
EncryptUnload(
    FLT_FILTER_UNLOAD_FLAGS Flags
)
{
    UNREFERENCED_PARAMETER(Flags);
    FltUnregisterFilter(gFilterHandle);
    return STATUS_SUCCESS;
}

//------------------------------------------------------------------------------
//  DriverEntry (register & start)
//------------------------------------------------------------------------------
NTSTATUS
DriverEntry(
    PDRIVER_OBJECT DriverObject,
    PUNICODE_STRING RegistryPath
)
{
    UNREFERENCED_PARAMETER(RegistryPath);

    NTSTATUS status = FltRegisterFilter(
        DriverObject,
        &FilterRegistration,
        &gFilterHandle
    );
    if (NT_SUCCESS(status)) {
        status = FltStartFiltering(gFilterHandle);
        if (!NT_SUCCESS(status)) {
            FltUnregisterFilter(gFilterHandle);
        }
    }
    return status;
}

Notepad memory maps for read but writes using cached I/O. So, your filter doesn’t properly account for/deal with memory mapped I/O.

This is a very common issue to have and is due to a lack of understanding of the underlying interactions between the I/O Manager, file system, Cache Manager, and Memory Manager. Given that it’s fundamental architectural stuff it’s not a simple matter of “oh, you need to just do XYZ.” You need to understand WHY this is happening so that you can design your filter accordingly.

Start by Googling “notepad encryption site:community.osr.com”. This problem has literally been discussed for at least 20 years (time flies!). The NT File System Internals book is good as well.

Hello Folks, I welcome you here
I am trying to manipulate file when some one is trying to access it (may be using notepad or type command) and again when some one is closing it (Some one can be any user or system process). I see that when accessed file using notepad there are multiple IRP_MJ_CREATE hit, so i can do manipulation in any one and use context to ignore others, but i don't see any callback hit when one close the file (in case of notepad). There are multiple IRP_MJ_CLEANUP called but i want to do this once and IRP_MJ_CLEANUP is also not a indicator the file is closed. IRP_MJ_CLOSE is also not triggered (I know the reason). How to perform some manipulation on file when it is closing. I even tried stream context cleanup callback but it is not working. I might be missing some thing or please suggest some alternative solution.

There are no easy answers to your questions. You need to go understand how the file system interfaces work so that you can understand what's possible.

Read The NT File System Internals book:

Windows NT File System Internals: A Developer's Guide: Nagar, Rajeev: 9781565922495: Amazon.com: Books

Play with FileTest:

ladislav-zezula/FileTest: Source code for File Test - Interactive File System Test Tool

And Process Monitor:

Process Monitor - Sysinternals | Microsoft Learn

Build FAT from source and set breakpoints in it:

Windows-driver-samples/filesys/fastfat at main · microsoft/Windows-driver-samples

Hi Folks
I have two more question

  1. Is is really possible to create on fly solution using minifilter or i need to make isolated driver is only the solution.
  2. I read this post Handling Paging IO Read change actual data on disk where Aditya has mentioned he changed his read operation to decrypt only if IrpFlags has IRP_NOCACHE or IRP_PAGING_IO or IRP_SYNCHRONOUS_PAGING_IO bit set. And he was able to decrypt encrypted data. But i was not able to do so. Below is my pseudo code for same, please suggest if this is what aditya has done or any changes needed.
FLT_PREOP_CALLBACK_STATUS PreReadCallback(
    _Inout_ PFLT_CALLBACK_DATA Data,
    _In_ PCFLT_RELATED_OBJECTS FltObjects,
    _In_opt_ PVOID CompletionContext
) 
{
    ULONG flag = Data->Iopb->IrpFlags;
    KdPrint(("Pre Read"));
    if (FlagOn(IRP_NOCACHE, flag)) {
        return FLT_PREOP_SUCCESS_WITH_CALLBACK;
    }
    if (FlagOn(IRP_PAGING_IO, flag)) {
        return FLT_PREOP_SUCCESS_WITH_CALLBACK;
    }
    if(FlagOn(IRP_SYNCHRONOUS_PAGING_IO, flag)) {
        return FLT_PREOP_SUCCESS_WITH_CALLBACK;
    }
    return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

FLT_POSTOP_CALLBACK_STATUS
PostReadCallback(
    _Inout_    PFLT_CALLBACK_DATA       Data,
    _In_       PCFLT_RELATED_OBJECTS    FltObjects,
    _In_opt_   PVOID                   CompletionContext,
    _In_       FLT_POST_OPERATION_FLAGS Flags
)
{
    UNREFERENCED_PARAMETER(Flags);
    UNREFERENCED_PARAMETER(CompletionContext);
    UNREFERENCED_PARAMETER(FltObjects);

    if (!NT_SUCCESS(Data->IoStatus.Status) || Data->IoStatus.Information == 0 || Data->Iopb->Parameters.Read.ReadBuffer == NULL) {
        return FLT_POSTOP_FINISHED_PROCESSING;
    }

    PUCHAR buffer = (PUCHAR)Data->Iopb->Parameters.Read.ReadBuffer;
    ULONG  bytesRead = (ULONG)Data->IoStatus.Information;

    CHAR dummy[] = "This is dummy content";
    SIZE_T toCopy = min(sizeof(dummy) - 1, bytesRead);
    RtlCopyMemory(buffer, dummy, toCopy);

    Data->IoStatus.Information = (ULONG)toCopy;

    return FLT_POSTOP_FINISHED_PROCESSING;
}

I know this is not the code to decrypt but at least it should change the content of file but it is not able to that also.

Hello Everyone

FLT_PREOP_CALLBACK_STATUS PreReadCallback(
    _Inout_ PFLT_CALLBACK_DATA Data,
    _In_ PCFLT_RELATED_OBJECTS FltObjects,
    _In_opt_ PVOID CompletionContext
) 
{
    ULONG flag = Data->Iopb->IrpFlags;
    KdPrint(("Pre Read"));
    if (FlagOn(IRP_NOCACHE, flag)) {
        return FLT_PREOP_SUCCESS_WITH_CALLBACK;
    }
    if (FlagOn(IRP_PAGING_IO, flag)) {
        return FLT_PREOP_SUCCESS_WITH_CALLBACK;
    }
    if(FlagOn(IRP_SYNCHRONOUS_PAGING_IO, flag)) {
        return FLT_PREOP_SUCCESS_WITH_CALLBACK;
    }
    return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

FLT_POSTOP_CALLBACK_STATUS
PostReadCallback(
    _Inout_    PFLT_CALLBACK_DATA       Data,
    _In_       PCFLT_RELATED_OBJECTS    FltObjects,
    _In_opt_   PVOID                   CompletionContext,
    _In_       FLT_POST_OPERATION_FLAGS Flags
)
{
    UNREFERENCED_PARAMETER(Flags);
    UNREFERENCED_PARAMETER(CompletionContext);
    UNREFERENCED_PARAMETER(FltObjects);

    if (!NT_SUCCESS(Data->IoStatus.Status) || Data->IoStatus.Information == 0 || Data->Iopb->Parameters.Read.ReadBuffer == NULL) {
        return FLT_POSTOP_FINISHED_PROCESSING;
    }

    PUCHAR buffer = (PUCHAR)Data->Iopb->Parameters.Read.ReadBuffer;
    ULONG  bytesRead = (ULONG)Data->IoStatus.Information;

    CHAR dummy[] = "This is dummy content";
    SIZE_T toCopy = min(sizeof(dummy) - 1, bytesRead);
    RtlCopyMemory(buffer, dummy, toCopy);

    Data->IoStatus.Information = (ULONG)toCopy;

    return FLT_POSTOP_FINISHED_PROCESSING;
}

Sorry for asking this repeatedly but i saw some inactivity.
Is this the way (Code) Aditya mentioned Handling Paging IO Read change actual data on disk in this post, to handle memory mapped file. While this is not working for me, can you give suggestion or any pseudo code.

Posting the same question repeatedly isn't going to help get an answer.

You need to read the resources I mentioned. Format a secondary drive with FAT, build your own copy from source, and set breakpoints. Put a single text file on the drive and open it with Notepad. What do you see?

  • When is the read entry point called?

  • When do you see IRP_NOCACHE set?

  • When do you see IRP_PAGING_IO set?

  • What process is the IRP_MJ_READ sent from? Notepad? Defender? Search Indexer?

  • What happens when you open the file with Notepad the first time? What about the second time?

  • When do you see the FLT_CALLBACK_DATA have a ReadBuffer? When don't you?

I highly recommend dismounting the volume after each time you watch this otherwise you're just watching the effects of caching...My trick is usually to do a forced chkdsk:

chkdsk /f f:

Assume that you're a good 6-8 months out from being confident in understanding what's happening. At that point you should understand why things don't work the way you expected and can start to design a solution (YMMV of course depending on how much past kernel development experience you have on Windows or other platforms).