1

My minifilter driver uses the post-create callback to communicate the path of the file being opened to a user-mode process. The minifilter uses a reparse point to identify which files need to be processed.

PFLT_FILE_NAME_INFORMATION pFNI = NULL;

DWORD FNIFlags = FLT_FILE_NAME_OPENED | 
                 FLT_FILE_NAME_QUERY_ALWAYS_ALLOW_CACHE_LOOKUP |
                 FLT_FILE_NAME_ALLOW_QUERY_ON_REPARSE;

NTSTATUS Status = FltGetFileNameInformation(Data, FNIFlags, &pFNI);
if (!NT_SUCCESS(Status))
{
    DBG_PRINT_ERROR("FltGetFileNameInformation failed: %#x", Status);
    __leave;
}

DBG_PRINT_INFO("Realize '%wZ'", pFNI->Name);

This works fine, except when the file is opened through a NTFS junction like so:

mkdir c:\a
mkdir c:\b
echo "hello world" >c:\b\b.txt
# ...set reparse point metadata on b.txt
mklink /j c:\a\b c:\b
type c:\a\b\b.txt

When type openes the file, my driver prints this output:

Realize '\Device\HarddiskVolume2\b\b.txt'

So, it's seeing the file's true path, after the junction is evaluated, not the path that the file was opened from.

The problem is, my program depends on finding a configuration file in one of the parent directories of the file being opened, and this problem with junctions makes my program miss the configuration file.

MSDN says that FLT_FILE_NAME_OPENED returns "The name that was used when the handle was opened to this file". I believe that it should therefore give me \Device\HarddiskVolume2\a\b\b.txt instead of \Device\HarddiskVolume2\b\b.txt. What am I missing?

Wade Brainerd
  • 103
  • 1
  • 6
  • because when junction in the name was really **2** requests to file system. in first request name will be `\a\b\b.txt` but file-system return `STATUS_REPARSE` to object manager ( `Irp->Tail.Overlay.AuxiliaryBuffer` will be pointer to `REPARSE_DATA_BUFFER` in this case usually). object manager change file name to `\b\b.txt` (based on `REPARSE_DATA_BUFFER` data) and call file-system again. and now when file is opened on second pass - you can view only `\b\b.txt` in file name - initial file path was lost at this point already – RbMm Jan 29 '18 at 16:56
  • are you got `\b\b.txt` on `STATUS_REPARSE` or on `STATUS_SUCCESS` ? also when you got `STATUS_REPARSE` which reparse tag in `Data->IoStatus.Information` ? `IO_REPARSE` (only in this case you must got `\b\b.txt`) or `IO_REPARSE_TAG_MOUNT_POINT` / `IO_REPARSE_TAG_SYMLINK` ? (with this tags you still need got `\a\b\b.txt` – RbMm Jan 30 '18 at 08:51

1 Answers1

1

Unless the file is cached in which case you will get the "resolved".
I would suggest to use only the FLT_FILE_NAME_OPENED getting rid of the other two flags.

Check out this article by Alex Carp regarding these flags.

Cheers,
Gabriel

Gabriel Bercea
  • 1,191
  • 1
  • 10
  • 21
  • Thanks for the response! My minifilter driver makes use of reparse points, which wasn't mentioned in the question. I'll update it since it now seems relevant. Passing only `FLT_FILE_NAME_OPENED` gives `STATUS_FLT_INVALID_NAME_REQUEST`, because the file in question (b.txt) has a reparse point that is being handled by my driver. Passing `FLT_FILE_NAME_OPENED` plus `FLT_FILE_NAME_ALLOW_QUERY_ON_REPARSE` returns the incorrect path. Adding `FLT_FILE_NAME_QUERY_FILESYSTEM_ONLY` to defeat caching also returns the incorrect path, so I don't believe it's caching-related. – Wade Brainerd Jan 29 '18 at 16:43
  • Interesting. You could simply then open the the file yourself using the **FILE_OPEN_REPARSE_POINT** flag and query the name there. One quicker alternative would be to check if the file being opened has reparse point. See FSCTL_GET_REPARSE_POINT and [ZwFsControlFile](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntfscontrolfile). Also in post-create you could query the file's attributes and check for **FILE_ATTRIBUTE_REPARSE_POINT**. If those are set then you could just look in the FileObject->FileName since this is still valid in Create. – Gabriel Bercea Jan 29 '18 at 18:03
  • 1
    Also make sure if you're using the **FLT_FILE_NAME_ALLOW_QUERY_ON_REPARSE** in post-create then look for the **STATUS_REPARSE** being returned **Data->IoStatus.Status**. Only then it makes sense to use this flag. – Gabriel Bercea Jan 29 '18 at 18:09
  • on `STATUS_REPARSE` with `IO_REPARSE_TAG_SYMLINK` or `IO_REPARSE_TAG_MOUNT_POINT` we already have this info (which `FSCTL_GET_REPARSE_POINT` return ) in `Data->TagData`. only in case some custom filter return `IO_REPARSE` will be no `TagData` but `FltObjects->FileObject->FileName` already will be changed (reparsed) in this point and no option get original name. but unclear (for me) are this problem exactly on `STATUS_REPARSE` or on next after reparse request finished with `STATUS_SUCCESS` – RbMm Jan 30 '18 at 08:59
  • Well again it depends where you are on the stack and which filter owns which reparse tags. If you happen to sit above the owner of a reparse point then you should be able to get the name before the STATUS_REPARSE is sent up. The reparse point owner, if it sees one of its tags then what it does is: 1) Replace the FileName in the FileObject structure. 2) Set the status to STATUS_REPARSE 3) Complete the operation - very important it does not send it down So you could **filter** on the STATUS_REPARSE and comapare the name before and after. Test that. – Gabriel Bercea Jan 30 '18 at 09:38
  • owner (if it *ntfs* itself) can instead of replace the `FileName` in the `FileObject` return pointer to `REPARSE_DATA_BUFFER` in `Tail.Overlay.AuxiliaryBuffer` (we access it in minifiler via `Data->TagData`). question here - what tag is in `IoStatus.Information` – RbMm Jan 30 '18 at 10:22
  • Well then if it is NTFS the task is trivial. If you see **STATUS_REPARSE** in Post-Create, that means the FileObject->FileName in Pre-Create has the name you are looking for. – Gabriel Bercea Jan 30 '18 at 19:50
  • in which `Pre-Create` ? with `STATUS_REPARSE` file system (and minifilter) called two time. for example on first request in post-create will be `\Documents and Settings\desktop.ini` as file name and (`STATUS_REPARSE`, `IO_REPARSE_TAG_MOUNT_POINT`). inside `Data->TagData` will be `\Users`. then io-subsystem replace name in file to `\Users\desktop.ini` and call device second time. at this point already impossible get original file name. even impossible know - are this is second request after `STATUS_REPARSE` or first original request – RbMm Jan 30 '18 at 23:32
  • only way - if filter on `STATUS_REPARSE` (and if it not `IO_REPARSE` - in this case original file name already lost) somehow remember original name and calling thread. and on every request check calling thread - we we have in DB reparse record for this thread - use it and delete. based on assumption that after `STATUS_REPARSE` current thread again call device with reparsed name. but this can sometime be false (circular reparse for example - 32 time and stop). – RbMm Jan 30 '18 at 23:39
  • i be say task - get original name in request, when file is opened - almost impossible. need use final normalized name. and not depend - are file open via reparse or by direct name – RbMm Jan 30 '18 at 23:39
  • Ofcourse it is possible to keep track of Pre-Post and know which Post corresponds to which Pre. One way to do it using the **CompletionContext** parameter. Define a custom structure where you store the the PreCreate name as opened, and then simply pass this structure to the PostCreate via the **CompletionContext**. Then in Post-Create check if your request returned **STATUS_REPARSE**. If yes, check the name you have stored from the PreCreate. In either way don't forget to discard the name and free the structure you used. – Gabriel Bercea Jan 31 '18 at 06:53
  • are you listen me ? will be 2 Pre and 2 Post create requests. problem not found Post corresponds to which Pre from the same request. problem understand that 2 different open request correspond each other. concrete example - in first request will be `\Documents and Settings\desktop.ini` file name - in both pre and post create. and `STATUS_REPARSE, IO_REPARSE_TAG_MOUNT_POINT` in post. after this will be **second** request with `\Users\desktop.ini` file name. and `Irp` (as result and minifilter structs) will be different and not point to first request – RbMm Jan 31 '18 at 09:15
  • so question no how bind Pre1 with Post1 from same Irp1 request(this is trivial). question how bind Post1 with Post2 from different Irp1 and Irp2 – RbMm Jan 31 '18 at 09:27
  • You are making a bunch of assumptions that are simply not true when it comes to filtering and filesystems. Have you checked the FileObject->FileName in the PostCreate of your so called first operation ? It should go down (Pre-Create) with: `\Documents and Settings\desktop.ini` and come up (PostCreate) with `\Users\desktop.ini` Then another request is being made. The operating system or whoever can chose not to do this second one. It is totally optional. – Gabriel Bercea Jan 31 '18 at 12:30
  • i not make a assumptions. i test this. and you mistake. on Post-Create of fist operation will be exactly `\Documents and Settings\desktop.ini` - the same name as in pre-create. (not `\Users\desktop.ini`). and on second operation the `\Users\desktop.ini` name will be already in Pre-Create. so in both operations names in Pre and Post Create will be the same (if ntfs handle reparse point). the ntfs not change file name on reparse. instead it return pointer to `REPARSE_DATA_BUFFER` in `Tail.Overlay.AuxiliaryBuffer`. – RbMm Jan 31 '18 at 13:46
  • ntfs use `IO_REPARSE_TAG_MOUNT_POINT` or `IO_REPARSE_TAG_SYMLINK` (based on type of reparse file). and in `IoStatus.Information` will be this reparse tags (but not `IO_REPARSE`) - in this case name of file will be changed later in `IopParseDevice` by call `IopDoNameTransmogrify`. all this i view in test from minifilter driver. this is not assumptions – RbMm Jan 31 '18 at 13:52
  • for example - [`PostCreate`](https://pastebin.com/8iJfzZ0P) code and [debug output](https://i.imgur.com/szVIgDX.png). on `\Documents and Settings\desktop.ini` open request. visible that 2 request in filter. and on first PostCreate name is still `\Documents and Settings\desktop.ini` and on second name will be `\Users\desktop.ini` already in pre-create. question how bind 2 independed io requests (but not pre and post create which is really trivial) – RbMm Jan 31 '18 at 14:14
  • Oh I see what you want now. First of all let's get all the confusions out of the way. You can see in your PostCreate, by parsing the reparse tag data that `\Documents and Settings\desktop.ini` points to/will reparse to `\Users\desktop.ini`. This is obvious from the code and the debug output. There is however no link between the two create requests. You cannot know by looking at the Create towards `\Users` that is was opened because of a reparse. There are several implications and reasons and why this is not possible. – Gabriel Bercea Feb 01 '18 at 09:17
  • To understand better I would recommend this blog post: http://fsfilters.blogspot.nl/2012/02/problems-with-statusreparse-part-i.html – Gabriel Bercea Feb 01 '18 at 09:18
  • 1
    *There is however no link between the two create requests.* - i wrote this at begin and thought so. but after read your link i decide check *ECP* and found something interesting. exactly after *ntfs* reparse (mount point or sym link) was *ECP* on following request with guid `{73d5118a-88ba-439f-92f4-46d38952d250}`. look like this is complete undocumented and unknown. are you listen before about this guid ? i [research and dump](https://pastebin.com/8iJfzZ0P) *ECP* structure for this guid. – RbMm Feb 01 '18 at 15:49
  • 1
    on `IO_REPARSE_TAG_MOUNT_POINT` in *ECP* data exist full original file path. even if we have multiple mount point in path (multiple requests) - on final request in post create - we anyway have full original file path - https://i.imgur.com/Lpsg99T.png. but in case `IO_REPARSE_TAG_SYMLINK` - in *ECP* by unknown reason exist already reparsed path - https://i.imgur.com/p8DAuKj.png. so we can know that was reparse before, but can not got original file name – RbMm Feb 01 '18 at 15:53
  • interesting are on http://www.osronline.com/index.cfm know about this. i can not found any links to `{73d5118a-88ba-439f-92f4-46d38952d250}` guid. very strange. can not that somebody before not view this – RbMm Feb 01 '18 at 15:56
  • Alex says that it would be nice if Msft would do it. That is not implemented or documented or reliable information. You could simply try heuristic approach and see if the same FileObjrct pointer or FltCallbackData pointer is used for both requests. Otherwise there is no "known good way" to do this. – Gabriel Bercea Feb 01 '18 at 16:28
  • Anyway, that being said, if there is an ECP that Msft uses for now but yet documented you could take.your chance and use it. Seems like great workaround. – Gabriel Bercea Feb 01 '18 at 16:32
  • how i say - i found in test that io-manager set `ECP` on next Irp after reparse with `IO_REPARSE_TAG_SYMLINK` or `IO_REPARSE_TAG_MOUNT_POINT` tags. the *ECP* guid `{73d5118a-88ba-439f-92f4-46d38952d250}`. i test this on win7, win8.1 - win10 - everywhere this exist – RbMm Feb 01 '18 at 16:34
  • however what is look very strange for me - in case `IO_REPARSE_TAG_MOUNT_POINT` we got type 3 with original file name here. but on `IO_REPARSE_TAG_SYMLINK` (more new tag) - we got type 2 and already reparsed file name. original file name is lost – RbMm Feb 01 '18 at 16:36
  • *if the same FileObject pointer* - object pointer formal be the same. but this nothing. file object can be deleted and the reallocated at same address. this is nothing prove and not useful. but ECP with `{73d5118a-88ba-439f-92f4-46d38952d250}` is fact. howver i can not believe that i first who view it (because it exist since win7 or may be even vista - here i not check) – RbMm Feb 01 '18 at 16:39
  • That is the thing with undocumented ECPs and strucutures. But it is good you have checked on all versions of the OSes. I'll tell you what. I will be at the Microsoft Plugest 30 and I will ask this for you when they plan to documented it and what all the structures mean. There will be guys from the file-systems team and filter manager team. Until then I say you have answered your own question. Simply use that ECP. Undocumented, but that's all you got. – Gabriel Bercea Feb 01 '18 at 16:40
  • That is the thing with undocumented ECPs and strucutures. But it is good you have checked on all versions of the OSes. I'll tell you what. I will be at the Microsoft Plugest 30 and I will ask this for you when they plan to documented it and what all the structures mean. This event will be at the end of this months. There will be guys from the file-systems team and filter manager team. Until then I say you have answered your own question. Simply use that ECP. Undocumented, but that's all you got. – Gabriel Bercea Feb 01 '18 at 16:41
  • sorry - but this is not my question - not i ask it. and for me not need all this at all now :) i simply research here just the structure for this ECP is `struct ECP_REPARSE_DATA { USHORT UnparsedNameLength; USHORT Tag; USHORT DeviceNameLength; USHORT Zero; ECP_REPARSE_DATA* Reparsed; UNICODE_STRING Name; };` may be will be intersting ask about this on osronline. probably somebody know it – RbMm Feb 01 '18 at 16:44
  • I will pbably ask somebody from OSR at the Plugfest event. I believe we should finish with this thread since it is already ridiculously long. If anything I said here helped you with your answer maybe you could award me the check mark. I would appreciate it. Until next time, good luck ! – Gabriel Bercea Feb 01 '18 at 16:47
  • thank you. i simply interesting in this topic because time from time write filters too. but not i ask this question and can not accept it. you confuse me with Wade Brainerd .) i can only upvote – RbMm Feb 01 '18 at 16:50
  • 1
    ok, last - most interesting case when both `IO_REPARSE_TAG_SYMLINK` and `IO_REPARSE_TAG_MOUNT_POINT` in path like `C:\Documents and Settings\All Users\Desktop\desktop.ini` - this is really something - https://i.imgur.com/j3y9w9t.png – RbMm Feb 01 '18 at 17:18
  • This thread seemed to diverge from my use case, but returning to it I can see that there is potential in the undocumented ECP technique. I look forward to checking whether the ECP solution applies in the case of my custom reparse tag. Thanks @RbMm for researching and Gabriel for your support and insight. – Wade Brainerd Mar 12 '18 at 19:20
  • @WadeBrainerd - *my custom reparse tag* - which reparse tag you use. system add `IopSymlinkECPGuid` only for `IO_REPARSE_TAG_MOUNT_POINT`, `IO_REPARSE_TAG_SYMLINK` and `IO_REPARSE_TAG_GLOBAL_REPARSE` – RbMm Mar 12 '18 at 20:16