Stream Context between multiple MiniFilter instances

Suppose the following scenario:

C -> Filter[0] -> FS0
D -> Filter[1] -> FS1

Filter[0] and Filter[1] are 2 instances of the same filter attached to
two different volumes.

If I set a stream context in Filter[0] post-create, then redirect the
request to Filter[1] by changing
FLT_IO_PARAMETER_BLOCK::TargetFileObject and
FLT_IO_PARAMETER_BLOCK::TargetInstance, future I/O request will go
directly to Filter[1]. This is not a problem IFF I get the same stream
context in Filter[1] that I set in post-create in Filter[0]. Is this
the case?

From my understanding this is not the case because contexts are
managed by file systems, and the file system instances are different?
If what I want is not possible, what do you recommend instead?

To give a bit of background. D is a virtual volume. I want D’s
namespace to be a subset of C’s namespace and my filter to work for
I/O done through D, but not for I/O done through C. That’s why I want
to set a context at post-create through D, but as files will reside
and be accesed on the real drive, C, I must be able to determine that
I/O initially originated through C or D.

Thanks,


Aram Hăvărneanu

>request to Filter[1] by changing

FLT_IO_PARAMETER_BLOCK::TargetFileObject and
FLT_IO_PARAMETER_BLOCK::TargetInstance

I think this causes STATUS_REPARSE, correct?

If yes - then sorry, no ways or preserving any context across reparse. Even the file object is killed, and then a new one is created.

The reparsed attempt will only see the MJ_CREATE parameters and the new reparsed pathname, but nothing from the previous attempt.


Maxim S. Shatskih
Windows DDK MVP
xxxxx@storagecraft.com
http://www.storagecraft.com

Well, I think Aram’s implementation does not involve returning STATUS_REPARSE. IIRC he is switching the device stack. However, in Vista and newer OSes, one can use ECPs to tag a CREATE even when STATUS_REPARSE is returned (so the new IRP_MJ_CREATE will have the ECP that the filter added attached to it)…

Now, on to the original question… Just so we’re all on the same page, I’ll be using Instance1 to refer to the instance of the filter where the original CREATE comes and Instance2 for the instance that Instance1 redirects the CREATE to.

The FltGetStreamContext api requires an instance which identifies which context to retrieve (the instance is the key). So if you set it in post-create on Instance1 and then query from Instance2 (meaning that you use Instance2 when calling FltGetStreamContext) you won’t get the same context back. This has nothing to do with the underlying file system because the same is true if you have two different instance on the same volume (at different altitudes)…

One thing you might want to try is to call FltSetStreamContext from the post-Create callback on Instance1 using Instance2 as the key (this should be pretty easy since Instance1 already knows about Instance2 because it redirected the IO to it). Then on Instance2 you should be able to see the right context when calling FltGetStreamContext with Instance2 as the key, which I think is what you wanted…

Does this make sense ?

Thanks,
Alex.

On Thu, Sep 9, 2010 at 6:39 PM, Alex Carp wrote:
> Well, I think Aram’s implementation does not involve returning STATUS_REPARSE. IIRC he is switching the device stack.

Correct.

> However, in Vista and newer OSes, one can use ECPs to tag a CREATE even when STATUS_REPARSE is returned (so the new IRP_MJ_CREATE will have the ECP that the filter added attached to it)…

Nice, however I need to support Windows XP.

> One thing you might want to try is to call FltSetStreamContext from the post-Create callback on Instance1 using Instance2 as the key (this should be pretty easy since Instance1 already knows about Instance2 because it redirected the IO to it). Then on Instance2 you should be able to see the right context when calling FltGetStreamContext with Instance2 as the key, which I think is what you wanted…

Wow, never thought about that. Yes, I will try this approach and post
the results.

> Does this make sense ?

Yes, very much. Thanks.


Aram Hăvărneanu

On Thu, Sep 9, 2010 at 6:39 PM, Alex Carp wrote:
> Well, I think Aram’s implementation does not involve returning STATUS_REPARSE. IIRC he is switching the device stack.

Another question. If I change TargetInstance from Instance1 to
Instance2 in the CREATE path subsequent I/O will go directly to
Instance2.

However, if I pass TargetInstance unchanged in the CREATE path and
only change it in READ and WRITE paths, what will happen?

Thanks,


Aram Hăvărneanu

“However, if I pass TargetInstance unchanged in the CREATE path and only change it in READ and WRITE paths, what will happen?”

I think you might bugcheck the machine because the FO will be initialized by the file system on volume1 and then you will use that FO on volume2 (so whether it is a bugcheck or simply a failure depends entirely on the FS… however I think NTFS doesn’t like it and bugchecks when trying to interpret some fields in the FCB that are not there at all or are bogus.).

In general, a lot of the work in some types filters that either own FOs (SFO type filters and such) or that send requests from one volume to another (common in virtualization filters) is making sure that a FO never ends up on a file system that doesn’t own it (own = completed the IRP_MJ_CREATE). This is why, for example, such filters usually need to implement all possible operations (including name provider callbacks) even if they don’t care about the operation and they don’t change file names… Missing only one such path might result in the FO making its way to the wrong file system…

Thanks,
Alex.

Thanks for the valuable insights!


Aram Hăvărneanu

>> One thing you might want to try is to call FltSetStreamContext from the post-Create callback on Instance1 using Instance2 as the key (this should be pretty easy since Instance1 already knows about Instance2 because it redirected the IO to it). Then on Instance2 you should be able to see the right context when calling FltGetStreamContext with Instance2 as the key, which I think is what you wanted…

Wow, never thought about that. Yes, I will try this approach and post
the results.

As I need to create a new FILE_OBJECT in pre-create how can I make
sure I create the new FILE_OBJECT with the same access flags?


Aram Hăvărneanu

I’m not sure I follow you. Why do you need to create a new FILE_OBJECT in preCreate? I thought your minifilter was simply changing the TargetInstance to send it to a different volume…

Thanks,
Alex.

> I’m not sure I follow you. Why do you need to create a new FILE_OBJECT in preCreate? I thought your minifilter was simply changing the TargetInstance to send it to a different volume…

To quote from the docs: << A minifilter can change the value of the
TargetFileObject member. However, the new value must be a pointer to a
file object for a file that resides on the same volume as the instance
specified by the TargetInstance member. >>.

At pre-create the TargetFileObject is a pointer to a FILE_OBJECT that
references Instance1, instead of Instance2. From FILE_OBJECT
definition I see that it contains members such as DeviceObject which
references the wrong volume and Vpb which must be the volume parameter
block of the file on the target volume in order for caching to work
correctly. Because of this I understand I need to create a new
FILE_OBJECT that references the target volume on which Instance2
resides. Is my understanding wrong? Right now I do:

InitializeObjectAttributes(
&NewObjectAttributes,
&newFileName,
OBJ_OPENIF | OBJ_FORCE_ACCESS_CHECK,
NULL, NULL
);
status = FltCreateFileEx(
Globals.Filter, FltObjects->Instance,
&NewFileHandle, &NewFileObject,
Cbd->Iopb->Parameters.Create.SecurityContext->DesiredAccess,
&NewObjectAttributes,
&NewIoStatusBlock,
Cbd->Iopb->Parameters.Create.AllocationSize,
Cbd->Iopb->Parameters.Create.FileAttributes,
Cbd->Iopb->Parameters.Create.ShareAccess,
(Cbd->Iopb->Parameters.Create.Options & 0xFF000000) >> 24,
Cbd->Iopb->Parameters.Create.Options & 0x00FFFFFFFF,
Cbd->Iopb->Parameters.Create.EaBuffer,
Cbd->Iopb->Parameters.Create.EaLength,
IO_FORCE_ACCESS_CHECK
);

Thanks,


Aram Hăvărneanu

On Mon, Sep 20, 2010 at 3:11 AM, Aram Hăvărneanu wrote:
>> I’m not sure I follow you. Why do you need to create a new FILE_OBJECT in preCreate? I thought your minifilter was simply changing the TargetInstance to send it to a different volume…
>
> To quote from the docs: << A minifilter can change the value of the
> TargetFileObject member. However, the new value must be a pointer to a
> file object for a file that resides on the same volume as the instance
> specified by the TargetInstance member. >>.
>
> At pre-create the TargetFileObject is a pointer to a FILE_OBJECT that
> references Instance1, instead of Instance2. From FILE_OBJECT
> definition I see that it contains members such as DeviceObject which
> references the wrong volume and Vpb which must be the volume parameter
> block of the file on the target volume in order for caching to work
> correctly. Because of this I understand I need to create a new
> FILE_OBJECT that references the target volume on which Instance2
> resides. Is my understanding wrong? Right now I do:
>
> InitializeObjectAttributes(
> &NewObjectAttributes,
> &newFileName,
> OBJ_OPENIF | OBJ_FORCE_ACCESS_CHECK,
> NULL, NULL
> );
> status = FltCreateFileEx(
> Globals.Filter, FltObjects->Instance,
> &NewFileHandle, &NewFileObject,
> Cbd->Iopb->Parameters.Create.SecurityContext->DesiredAccess,
> &NewObjectAttributes,
> &NewIoStatusBlock,
> Cbd->Iopb->Parameters.Create.AllocationSize,
> Cbd->Iopb->Parameters.Create.FileAttributes,
> Cbd->Iopb->Parameters.Create.ShareAccess,
> (Cbd->Iopb->Parameters.Create.Options & 0xFF000000) >> 24,
> Cbd->Iopb->Parameters.Create.Options & 0x00FFFFFFFF,
> Cbd->Iopb->Parameters.Create.EaBuffer,
> Cbd->Iopb->Parameters.Create.EaLength,
> IO_FORCE_ACCESS_CHECK
> );

At the moment this doesn’t seem to work, the new file is opened in my
filter and I swap the FILE_OBJECT, but applications fail opening the
file in user mode. I use a modified simrep to do stack switching
instead of reparse and my debug output says things should work:

FsRdr: FsRdrPreCreate -> Processing create for file
\Device\HarddiskVolume2\x\y\ (Cbd = 86794F5C, FileObject = 867B3028)
FsRdr: FsRdrPreCreate -> Setting the callback data dirty flag.
FsRdr: FsRdrPreCreate -> Changed target for file \x\y. (Cbd =
86794F5C, NewFileObject = 86AF3288)
OpenedFileName = \Device\HarddiskVolume2\x\y<br> NewName = \Device\HarddiskVolume3\a\b<br>
As you can see, I have a new, different FILE_OBJECT and the new path
is the correct path (otherwise I couldn’t have opened the file in
kernel mode to obtain my FILE_OBJECT anyway). Userspace applications
fail:

W:&gt;dir x\y<br> Volume in drive W is Test
Volume Serial Number is 7496-0619

Directory of W:\x\y

File Not Found

Or Explorer:
W:\x\y refers to a location that is unavailable. It could be on a hard
drive on this computer, or on a network. Check to make sure that the
disk is properly inserted, or that you are connected to the Internet
or your network, and then try again. If it still cannot be located,
the information might have been moved to a different location.

What should I investigate further?

Thanks,


Aram Hăvărneanu

Yeah, I think you got it wrong… The FILE_OBJECT doesn’t yet reside on any volume. That happens when the FS receives the IRP_MJ_CREATE and finds the stream that the FILE_OBJECT is describing (via the FileName and RelatedFileObject members) and associates an SCB with it. So since you’re in preCreate this hasn’t yet happened.

Also, please consider what would happen if you actually went through with this. The FS on that volume would see two IRP_MJ_CREATEs with the same FILE_OBJECT, the second one with a FILE_OBJECT that’s actually in use… not sure what would happen, but I’m pretty sure it would be bad.

I’m sure I’ve asked this before, but why not use STATUS_REPARSE ? This model of redirecting creates using the TargetInstance is pretty flawed anyway…

Thanks,
Alex.

On Mon, Sep 20, 2010 at 4:28 AM, Alex Carp wrote:
> I’m sure I’ve asked this before, but why not use STATUS_REPARSE ? This model of redirecting creates using the TargetInstance is pretty flawed anyway…

Because I can’t track context with STATUS_REPARSE in pre-Vista OSes.


Aram Hăvărneanu

On Mon, Sep 20, 2010 at 4:28 AM, Alex Carp wrote:
> Yeah, I think you got it wrong… The FILE_OBJECT doesn’t yet reside on any volume. That happens when the FS receives the IRP_MJ_CREATE and finds the stream that the FILE_OBJECT is describing (via the FileName and RelatedFileObject members) and associates an SCB with it. So since you’re in preCreate this hasn’t yet happened.

That makes sense. I have tried this approach first, but I got the
exact same errors (see previous email) I get with my second approach.
Stuff seems to be happening correctly in the driver, but usermode
applications can’t open the file.


Aram Hăvărneanu

On Mon, Sep 20, 2010 at 4:35 AM, Aram Hăvărneanu wrote:
> On Mon, Sep 20, 2010 at 4:28 AM, Alex Carp wrote:
>> Yeah, I think you got it wrong… The FILE_OBJECT doesn’t yet reside on any volume. That happens when the FS receives the IRP_MJ_CREATE and finds the stream that the FILE_OBJECT is describing (via the FileName and RelatedFileObject members) and associates an SCB with it. So since you’re in preCreate this hasn’t yet happened.
>
> That makes sense. I have tried this approach first, but I got the
> exact same errors (see previous email) I get with my second approach.
> Stuff seems to be happening correctly in the driver, but usermode
> applications can’t open the file.

Actually I did another test, and in this first case the error would be
“The parameter is incorrect.” for both dir and explorer.exe.


Aram Hăvărneanu

>> I’m sure I’ve asked this before, but why not use STATUS_REPARSE  ? This model of redirecting creates using the TargetInstance is pretty flawed anyway…

Because I can’t track context with STATUS_REPARSE in pre-Vista OSes.

Since this doesn’t seem to work for some unknown reason and I need to
somehow mark' the redirected requests, I wonder if I can't add a stamp’ to the new file name I set when I return STATUS_REPARSE and
have a filter attached to the other volume remove this `stamp’. I’m
not sure how this works since the file name is cached and filters
attached to the other volume may get stamped requests. Can I
invalidate the cache somehow? I’m not sure if that’s safe because some
filters may be running in a context where it would not be safe to
re-enter the filesystem, though since I own the files I need to
process I guess that would not be a problem for me since I process
only regular files and skip paging I/O…

Is there a workaround?

Thanks,


Aram Hăvărneanu

I’ve been thinking about it as well and there doesn’t seem to be a good, reliable approach (which makes sense or MS wouldn’t have spent time working on ECPs if there was a better way).

The easiest thing is to do something to the file name that you can easily identify. However, any change to the actual name has a couple of serious issues:

  1. you might collide with someone else’s change depending on how you change the name (if you add a string to the end of the file for example then unless it is unique then someone else might use the same tag) - using a GUID for example would take care of this
  2. if you add a string then you might change the namespace… for example, if you add a GUID, then you will no longer allow for 32767 character paths, but rather 32759 ( sizeof(UUID) is 16, which is 8 unicode chars in windows ).
  3. if you add or change the string then filters above you might be unable to parse the path or to find it on disk or something like this, which might alter behavior of the IO stack…
  4. FltGetFileNameInformation would get confused…

Everything else seems just as bad.

I think this might be a good time to think about your requirements and see if there is a better solution… For example, it looks like you want to map a folder on a volume (V1) to a folder that lives on a different volume (V2) and not allow access to the folder through the volume it actually resides on (V2) but only through the folder on V1. Perhaps you could do something with ACLs? For example, one thing I think might work would be to restrict access to that folder and then before you reparse you could change the security context on the create (in Parameters.Create.SecurityContext) so that it can actually open the folder…

Would this work ?

Thanks,
Alex.

On Mon, Sep 20, 2010 at 10:17 PM, Alex Carp
wrote:
> 2. if you add a string then you might change the namespace… for example, if you add a GUID, then you will no longer allow for 32767 character paths, but rather 32759 ( sizeof(UUID) is 16, which is 8 unicode chars in windows ).

No problem, I won’t ADD a GUID to the file name, but simply USE a
GUID (generated randomly at runtime) for the whole file name and
manually maintain a mapping between GUIDs and real file names.

> 3. if you add or change the string then filters above you might be unable to parse the path or to find it on disk or something like this, which might alter behavior of the IO stack…

Absolutely correct.

> 4. FltGetFileNameInformation would get confused…

This is my main concern as well.

> I think this might be a good time to think about your requirements and see if there is a better solution… For example, it looks like you want to map a folder on a volume (V1) to a folder that lives on a different volume (V2) and not allow access to the folder through the volume it actually resides on (V2) but only through the folder on V1. Perhaps you could do something with ACLs? For example, one thing I think might work would be to restrict access to that folder and then before you reparse you could change the security context on the create (in Parameters.Create.SecurityContext) so that it can actually open the folder…
>
> Would this work ?

Wouldn’t work. The issue is not controlling access, but controlling
the operation of some filters that reside on V2. This filters do
logging and custom data processing that should not happen when the
file is accessed through V2, but only through V1. The file should be
accessible directly through V2, but the logging and the data
modification should not take place unless the access is through V1. I


Aram Hăvărneanu

Well, replacing the name with a GUID would work provided you also implement name provider callbacks that return the proper name. This might still impact legacy filters above yours though.

Thanks,
Alex.

On Mon, Sep 20, 2010 at 11:53 PM, Alex Carp
wrote:
> Well, replacing the name with a GUID would work provided you also implement name provider callbacks that return the proper name. This might still impact legacy filters above yours though.

Yes, the problem is minor (very unlikely to occur and would only have
impact if file is accessed through V1, while most access is through
V2) but I like to do things the proper way, in order to withstand the
test of time and not cause any problems for any filter.

Maybe I can create bogus temporary files on V2 so if a filter
intercepts a request and gets my unprocessed GUID would still have a
legal file to work with?


Aram Hăvărneanu