Thursday, May 12, 2011

STATUS_REPARSE and FILE_OPEN_BY_FILE_ID

Using STATUS_REPARSE is a pretty common operation in minifilters. However, I've seen a lot of minifilters misusing it in various ways. In this post I'd like to talk about how opens by ID interact with STATUS_REPARSE and how common it is to make a mistake in this path.

In one of my previous posts about the IRP_MJ_CREATE operation I mentioned that there is a structure, the _OPEN_PACKET (discussed in this post), that stores what the original IopCreateFile request looked like and I also mentioned how the IRP_MJ_CREATE IRP is populated based on that information. Of course, when STATUS_REPARSE is returned, the new IRP will have to take that into account as all as the contents of the _OPEN_PACKET. In particular the Options for the IRP come from the OPEN_PACKET while the file name comes from the buffer in FILE_OBJECT->FileName when STATUS_REPARSE was returned. In this case it's easy to make an error when the IRP_MJ_CREATE has the FILE_OPEN_BY_FILE_ID and the mini-filter blindly calls FltGetFileNameInformation() and gets a file name back. Behind the scenes FltMgr deals with the fact that the file is opened by ID, and this is transparent to the minifilter. So a minifilter might in fact be completely oblivious that the had the FILE_OPEN_BY_FILE_ID flag set and it will do what it normally does, calculate the new path based on the original file path, put it in the FILE_OBJECT->FileName and return STATUS_REPARSE. Unfortunately, when the IO manager issues the new IRP_MJ_CREATE, it will take the Options from the OPEN_PACKET (including the FILE_OPEN_BY_FILE_ID flag) and use the name supplied by the user and so the IRP_MJ_CREATE will now be broken and it will either fail or end up opening the wrong file if the new FileName is exactly of the right length and it happens to correspond to a file ID or object ID on the volume. This is rather unlikely but can happen and even worse, it can easily be exploited by an unprivileged user that only has to send a properly formatted name to NtCreateFile and trick a minifilter into opening the wrong file. Of course, the system will still perform ACL checks and so if the minifilter doesn't do break the NT security model then the user will not get access to files it doesn't already have access to, but this might be exploited to break out of a sandbox for example…

So now let's look at a code example of how this happens. There is a sample in the WDK that shows how to perform a reparse from a minifilter, SimRep. By default SimRep reparses any request for a path that starts with \x\y to a path that starts with \a\b. For my little experiment I've created two files:

  • C:\x\y\foo.txt
  • C:\a\b\foo.txt

Then before loading SimRep I used my favorite file system tool, FileTest (by the way, in my opinion this tool is definitely a must have for any windows file system and file system filter developer.) to open C:\x\y\foo.txt by ID. After I got the File ID and had the tool set to open C:\x\y\foo.txt by ID I loaded SimRep and attached it to C:. At that point trying to open the same File ID fails with STATUS_INVALID_PARAMETER (which is a result of FltGetFileNameInformation() failing). Of course, the interesting part is watching what happens in SimRep in this case. Here are some screenshots about how it all looks in FileTest:

Ok, so now that it's clear what the problem is, what can a developer do to work around this issue ? The solution is actually pretty straightforward, though it might be a bit complicated to implement… If FILE_OPEN_BY_FILE_ID is present then the minifilter must set the FileID or ObjectID of the target file instead of the name, so the minifilter might need to obtain that ID by either opening the file or the parent directory.