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