Yeah, that’s not going to work for Notepad (or executable files) because it uses memory mapped I/O. To implement this properly you’ll need to go the path of Isolation which is doable but significantly more complicated. This usually comes up in the context of encryption so you can read more than you ever wanted about this by Googling:
Didn’t dig into your example code, but have you considered using STATUS_REPARSE in IRP_MJ_CREATE in order to redirect to a different file? This is not exactly the same as what you are describing obviously but it can be used to achieve a similar purpose (and it’s easy to implement compared to messing with buffers in READ ops)
@“Scott_Noone_(OSR)” said:
Yeah, that’s not going to work for Notepad (or executable files) because it uses memory mapped I/O. To implement this properly you’ll need to go the path of Isolation which is doable but significantly more complicated. This usually comes up in the context of encryption so you can read more than you ever wanted about this by Googling:
encryption notepad site:community.osr.com
The “encryption” route is what I’ve been going down, as it’s somewhat similar to what I’m trying to accomplish. I’ll browse through those posts. (I avoided posting this question because this “Notepad” question has been asked so many times.)
@0xrepnz said:
Didn’t dig into your example code, but have you considered using STATUS_REPARSE in IRP_MJ_CREATE in order to redirect to a different file? This is not exactly the same as what you are describing obviously but it can be used to achieve a similar purpose (and it’s easy to implement compared to messing with buffers in READ ops)
Would that require there to be a file on disk with the actual contents? The goal is to provide a virtual file. The file should exist on the disk; however, the contents should be ignored.
I think I might go the Fastfat route, if I don’t turn up anything more on this subject.
So, the best I’ve been able to do is use CcPrepareMdlWrite in SwapPostReadBuffersWhenSafe. This has two issues:
The initial read is the contents on disk. All subsequent reads are correct. (I could trigger the minifilter to read all monitored files on boot, but that’s not ideal.)
I can only “clear” the cache on FilterUnload, using CcCoherencyFlushAndPurgeCache. Pre/Post-Close seemed ideal, but FltObjects->FileObject->SectionObjectPointer is null. In any case, I have to keep a global of these pointers which isn’t ideal. (By “clearing” the cache, I wanted to prove the contents weren’t coming from the disk, and that I could toggle my filter at will. Unless someone corrects me, I assume I’m correct.)
Additionally, CcCoherencyFlushAndPurgeCache works on FilterUnload because I’m not calling CcMdlWriteComplete. Side-effect is being unable to write to the file (however these files aren’t meant to be written to and aren’t “real” files).
CcPrepareMdlWrite in SwapPostReadBuffersWhenSafe: Doesn’t work on initial read, works on all subsequent.
CcCoherencyFlushAndPurgeCache in FilterUnload: Allows original contents to be read, but requires I track section pointers in a global.
Not calling CcMdlWriteComplete: If I called this, it would prevents FilterUnload from “clearing the cache”. It also prevents writing to files (I don’t require it in my situation, so that’s a “good thing”). Ideally, I’d like to “clear the cache” immediately, but the section pointer is null on Pre/Post-Close.
No, Stream File Objects are a different thing than Shadow File Objects. Stream FOs let the file system "poof up" a File Object without having to perform an open. They're primarily used for allowing the file system to manage their metadata (e.g. the MFT) as file streams.
Basic idea for a Shadow File Object filter is that in PreCreate you need to call FltCreateFile to perform your own open in place of the requestor's (thus creating the "Shadow" File Object). You then return FLT_PREOP_COMPLETE for the PreCreate and now have two File Objects: the Upper File Object that you now "own" and the Shadow (i.e. Lower) File Object that the file system owns.
Once you do that you have to fill in all the fields of the Upper File Object that would normally be done by the file system (FsContext, SectionObjectPointers, Flags, etc.). You then need to handle every I/O request that arrives and act accordingly by either servicing the request yourself or swapping the File Objects in the request and passing it along. This ends up being a lot of arcane and annoying stuff because it's normally things only the file systems do and no one ever really cares about writing those.
This all starts to beg the obvious question of "why is this so annoying and do I really need to do all this?" It's a long answer but the short is, yes, you do need to do all this...The reasons why comes back to the original design of NT and the decision to deeply integrate the file system with the caching and memory management layers. Oh, and this quote from Showstopper! sums up the state of this part of the OS pretty well:
Perazzoli himself spawned "a whole stack of bugs'' when he wrongly assumed that Miller would synchronize the calls made between the file system and memory manager. Miller had assumed Perazzoli would do that. As a result, the fit between these two big pieces of NT was poor.
For more insight on the file system's role in Windows I highly recommend reading the Windows NT File System Internals book, which is incredibly old (NT 3.51!) but does give good background. Just be sure to ignore anything about file system filters because that's all been replaced by FltMgr: