SwapBuffer not returning updated content

Hi I am needing to write a minifilter driver for some on the fly decryption of word docs and other types. I understand that this is a non trivial task so I am trying to make a simple example to start with so I can get some of the basics tested in a very simple sample.

I have been looking at the swapbuffer example https://github.com/microsoft/Windows-driver-samples/blob/main/filesys/miniFilter/swapBuffers/swapBuffers.c. I made some modifications of this to make it easier to test with. I am only implementing the IRQ_MJ_READ. In the pre read callback I am looking at the first 4 bytes of the file and if they match a predefined header then I process the file otherwise I am returning FLT_PREOP_SUCCESS_NO_CALLBACK. This is just a simple way to not process every file that is called. Then I allocate and swap the buffer and set the new size of the buffer in Iopb.Paramaters.Read.Length. In the post read callback I copy the new data into the buffer and set the IoStatus.Information field to the number of bytes copied. I am just writing the bytes (“hello”) for a very simple test. My understanding is that when opening this file from a user mode application that it should render the “hello” instead of the original content. I understand that this may not work in a real application due to file chunking and other issues with files but it gives me a good staring point to test with.

In my pre read callback I do the following

<skip allocation for simplicity>

iopb->Parameters.Read.ReadBuffer = newBuf;
iopb->Parameters.Read.Length = readLen;
FltSetCallbackDataDirty(Data);

In my post read callback I do the following

UCHAR val[] = {0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x0};
ULONG sz = sizeof(val);

RtlCopyMemory(origBuf, val, sz);
Data->IoStatus.Information = sz;

I have verified in the debugger that the original buffer is indeed modified as I expect. Using Powershell’s Get-Content I see my modified content. However in other applications (wordpad, vi, notepad++, hex editors, etc.) I am still seeing the original content. What am I missing? I tried to use process monitor to look at the operations for both powershell as well as using type in the cmd.exe. I see that QueryStandardInformationFile is called but from my reading this does not seem to relevant. Can someone explain what i am missing here or if I need to handle some other IRQ besides just IRQ_MJ_READ to make this work as I expect.

… on the fly decryption of word docs …

If a Word document is encrypted by Word, then you can’t decrypt it without knowing the password. This is more than “non trivial”, it is impossible.

It is possible those other apps are mapping the file into memory directly.

The first 4 bytes of the file? Or the first 4 bytes of the portion of the file being read?

Notepad is well known to use a memory mapped section to read file data - hence its terrible performance for large files. And most ‘advanced’ text editors read files in chunks in various patterns. PDFs are specifically designed so that the last part of the file is the information needed first, and Word has a complex mechanism designed to prevent users from losing the document that they want - even when they do daft thinks like deleting it or corrupting it via bad media or another program

As Tim says, if the contents of a file are encrypted data, to do anything with it you need the key. If your goal is to transparently encrypt data from the app going to the disk and decrypt it on the way back up, enable bitlocker. If you are trying to do something else, you will get much better help if you describe it

I got the impression the OP wants on-the-fly encryption minifilter, not
some other task.

This is probably the most discussed topic on NTFSD.

It is trivial to do this if you don’t need different views for different
apps. If you do, you require shadow file objects.

There is another thread on that topic ongoing here now.

Sorry for the confusion. Our final goal is to have this working on the fly. We have our own encryption technology that we are using. So we should be able to encrypt/decrypt as needed. That being said I am trying to do something very simple as I am coming up to speed on this.

I am just wanting to replace the buffer that the application recieves with my own buffer which has different content and different sizes. I am aware of how the file may be read in chunks so to make this simple example I am making a very small file that should be able to be read in a single chuck. That being said it looks like it is being read as I would expect (in a single chunk) from the debugger. However the applications I tied to use seem to be displaying the original content not my updated changes.

I m only handling the IQR_MJ_READ but wanted to see this be displayed to the user. I did not use notepad since it was know to use the memory mapped files and this becomes more difficult.

My question is what needs to be done so that these applications (or some of them) will read this file with my new modified buffer. It seems like they are still seeing the original content not the swapped buffer.

Presumably, EFS or bitlocker aren’t good enough in some way?

Even very large files can be read in a single IOP. That doesn’t mean that applications will oblige. You have to handle the IO pattern that they actually use. Not the one that you think they should use. I suggest you create a trace of the calls that your filter receives so that you can understand that pattern.

Sorry for the newbe question but I debugged this more and I see that when the post read callback is called that IoStatus is failed.

(*((_IO_STATUS_BLOCK *)0xffffa88953561110)) [Type: _IO_STATUS_BLOCK]
[+0x000] Status : -1073741807 [Type: long]
[+0x000] Pointer : 0xc0000011 [Type: void *]
[+0x008] Information : 0x0 [Type: unsigned __int64]

Since Status is STATUS_BUFFER_TOO_SMALL I am not sure which buffer is too small. In the pre read callback I am only allocating the newBuffer of size iopb->Parameters.Read.Length. I am following the swapbuffer.c example. How do I know how big of an allocation to use or if this is a different buffer how can I figure out what is actually failing here.

It looks to me that the swapbuffer.c (https://github.com/microsoft/Windows-driver-samples/blob/b968cfbed5566a3a9597f5368334beb3b6dad4d2/filesys/miniFilter/swapBuffers/swapBuffers.c) sample which I have been working with is not working with fast io. As I understand this since this sample minifilter does not register for fast io, flt manager will fall back to IRP handling of the operation (IRP_MJ_READ in this case). When debugging this further in the post read callback when the Data->flag has FLTFL_CALLBACK_DATA_FAST_IO_OPERATION flag set, the IoStatus is failing with STATUS_BUFFER_TOO_SMALL.

(*((_IO_STATUS_BLOCK *)0xffffa88953561110)) [Type: _IO_STATUS_BLOCK]
[+0x000] Status : -1073741807 [Type: long]
[+0x000] Pointer : 0xc0000011 [Type: void *]
[+0x008] Information : 0x0 [Type: unsigned __int64]

The actual check that is failing is https://github.com/microsoft/Windows-driver-samples/blob/b968cfbed5566a3a9597f5368334beb3b6dad4d2/filesys/miniFilter/swapBuffers/swapBuffers.c#L1054

     if (!NT_SUCCESS(Data->IoStatus.Status) ||
            (Data->IoStatus.Information == 0)) {

I am still confused why this is failing with only in the fast io path and not with other paths. Can someone help me understand why this is happening only for the fast io path?

You cannot replace data per application without shadow file objects (which
is the most difficult filesystem thing to do, IMO).

You cannot just handle reads in general if all apps have the same view
either, but non-cached ones only.
On top of that, just FYI, for networked files you have to use shadow file
objects, non-cached handling will just fail miserably and even corrupt data.

Thanks @Dejan_Maksimovic this is super helpful. Do you know of any examples that I can look at for handling this?

Just filter out IRP MJ READ based on non-cached IO flag in SwapBuffers

Sorry for the newbe questions. I am just confused on what you are saying. Are you saying only process the MJ_READ when the IRP_NOCACHE flag is set?

My goal here is to replace the buffer with other content so that when the user mode application loads the file it renders the modified buffer. This works with powershell’s Get-Content or even if I type the file in cmd.exe. However if I open the file in wordpad (I am excluding notepad for now due to memory mapped files which is another complexity I don’t want to deal with yet) in the postread callback the IoStatus is failed as I described above. I did notice that this only happens when the Data->Flags has the FLTFL_CALLBACK_DATA_FAST_IO_OPERATION set.

If I ignore only process files when the IRP_NOCACHE is set I do not believe that I will handle these files when using wordpad or other similar tools. Can you please elaborate on what you are suggesting here.

Just when it’s set. That’s all there is.

I am clearly missing something here. If I add this check

if(!FlagOn(IRP_NO_CACHE, Data->Flags)) {
  return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

Then the post operation callback is not called so the original content is displayed. How do I handle this case so that the data can still be swapped in this case.

The point is to modify the non-cached reads, as those are what is going to populate the cached view(s) of the file data. The cached reads will read the cached data.

Ahh that makes sense. Thanks for the clarification.

What is the best way to test this? How can I either remove the cache or start my filter to be able to intercept the non cached io so that the cached will be filled from the non cache which my minifilter will populate? Will adding FLTFL_OPERATION_REGISTRATION_SKIP_CACHED_IO | FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO to the registration help here?

You may not safely remove the cache you do not control. This requires
shadow file objects.
As is you can only hope the data is not read yet! Ans modify it so.

Note that for simple documents, unsafe stuff might work. But if some backup
or network file shares come into play… you will get deadlocks without
shadow file objects.

Note also that if the folder is shared, files read over the network will be
cleartext just like locally, but you will only see System as the process.
This part you cannot control much.

Regards, Dejan.

Thanks Dejan. I have spent time reading about this design. I think I understand the high level idea of what needs to happen but the actual implementation is another issue all together. Reading through the other questions posted it seems like I am still missing some of the basics.

My understanding is that in IRP_MJ_CREATE I need to create the SFO which will be the upper FO by calling IoCreateStreamFileObjectEx(). Then I assign it to the Data->Iopb->TargetFileObject->FsContext2 so that I can access it in additional callbacks. The lower FO would be the original FltObjects->FileObject.

Since I now own the SFO I need to somehow swap this with the lower FO. Is there some example of how the swapping works? I started with the swapBuffer sample which basically just allocates the memory and sets the read buffer to the allocated buffer. If I uses this as an example I am still not sure how the SFO is used in this type of filter. Is there something else that I need to be doing to let the CacheManager know about the SFO?

To be honest proper way of allocating my own FO has always been a pitfall
for me, and I never did a commercial SFO design.
You swap the FOs in FltObjects->TargetFileObject and call
FltSetCallbackDataDirty.

But it is not just read/write, you have to work out which ones you need to
handle in your design.

Sorry I cannot help you more here. Also, this is likely the hardest driver
to make work even partially. So, expect a year before you can get anything
commercially viable, since you are starting from zero.

Regards, Dejan.

Thanks Dejan for your help this is super helpful.