Thursday, July 12, 2012

The Flags of FILE_OBJECTs - Part V

I wasn't really planning on adding another part to the series but there were still a couple of things I wanted to mention.
First, as I'm sure you've noticed if you looked at the actual flag values in the wdm.h file, there seems to be a flag missing, with the value 0x00800000, which would put it between FO_VOLUME_OPEN and FO_REMOTE_ORIGIN. Well, the easiest way to solve this mystery is to look at an older DDK where you'll find the missing flag:
So what is this FO_FILE_OBJECT_HAS_EXTENSION and why is it gone from pretty much everything from documentation to the !fileobj extension (as I mentioned in my previous posts, an easy way to figure out flag values is to set them into a structure and use one of extension that knows how to parse that structure to see what name it displays; in this case however the !fileobj extension ignored that flag). As far as I can tell, this flag is no longer used since Vista, but before Vista this was used to indicate that there was some private data allocated past the end of the FILE_OBJECT (see this thread on NTFSD). From the thread it appears that the IO manager used this flag to store the targeting information associated with the FILE_OBJECT, which was also used by FltMgr (see this other thread on NTFSD as well). Since Vista this functionality is implemented using the FSRTL_PER_FILEOBJECT_CONTEXT structure. I've explored the way this works in my previous post on tracking a minifilter's ActiveOpens files.
Anyway, this FILE_OBJECT extension seems to have been used to implement the same functionality that FsRtlInsertPerFileObjectContext provides today. FltMgr's designers had a goal to make sure that everything that FltMgr does can be implemented by a 3rd party filter and so since they couldn't expect 3rd party filters to modify private OS structures they had exposed the same functionality through the FSRTL_PER_FILEOBJECT_CONTEXT structure and related functions (FsRtlInitPerFileObjectContext, FsRtlInsertPerFileObjectContext, FsRtlLookupPerFileObjectContext, FsRtlRemovePerFileObjectContext). Since this is implemented in a different way today it makes sense that this flag is obsolete for now. Please do note, however, that it's entirely possible that it will be reused at some point in the future.
Another thing I wanted to do was to take a closer look at how the FO_DISALLOW_EXCLUSIVE flag can be used, since as I mentioned in my previous post in the series I wasn't sure what it does. Well, after some investigation I have some idea what it does but I'm not sure why exactly this behavior would be desired. So first I'd like to mention that this flag doesn't seem to be used anywhere in the WDK, neither FastFat nor CDFS seem to need it. I couldn't find anything about it online. Naturally I searched for both FO_DISALLOW_EXCLUSIVE and FILE_DISALLOW_EXCLUSIVE but there was really no mention about what they might do or where they might be used. However, there was one bit of information in the WDK that helped a lot: the fact that FO_DISALLOW_EXCLUSIVE is the only flag defined for FO_FLAGS_VALID_ONLY_DURING_CREATE. So I decided to take a very close look at Ntfs' IRP_MJ_CREATE processing in the hope that I might be able to figure out where this flag might be used.
So after some debugging and using my favorite disassembler, IDA, I've been able to find a couple of places where this flag might be checked. In particular the Ntfs!NtfsOpenAttribute function looked very promising since it had code that checked for that exact value, and moreover it was using the same offset into a structure that the FILE_OBJECT->Flags are at:
1: kd> dt nt!_FILE_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x02c Flags            : Uint4B
   +0x07c FileObjectExtension : Ptr32 Void

1: kd> u 960e7c14 L1
960e7c14 f7402c00000002  test    dword ptr [eax+2Ch],2000000h

1: kd> u 960e7ded L1
960e7ded f7402c00000002  test    dword ptr [eax+2Ch],2000000h

So I figured I had a pretty good shot at finding out how this is used by looking at when these flags are checked (the path that leads to the checks). That involved a lot of debugging and backtracking, good thing VMWare makes it so easy to retry exactly the same code path over and over again :). Anyway, this is the piece of code that turned out to be relevant:
1: kd> u Ntfs!NtfsOpenAttribute+0x3af L0x10
960e7dd3 7328            jae     Ntfs!NtfsOpenAttribute+0x3da (960e7dfd)
960e7dd5 ff7518          push    dword ptr [ebp+18h]
960e7dd8 50              push    eax
960e7dd9 53              push    ebx
960e7dda e8e9d70100      call    Ntfs!NtfsWriteCheck (961055c8)
960e7ddf 8845e7          mov     byte ptr [ebp-19h],al
960e7de2 c645e601        mov     byte ptr [ebp-1Ah],1
960e7de6 84c0            test    al,al
960e7de8 7535            jne     Ntfs!NtfsOpenAttribute+0x3fc (960e7e1f)
960e7dea 8b4618          mov     eax,dword ptr [esi+18h]
960e7ded f7402c00000002  test    dword ptr [eax+2Ch],2000000h
960e7df4 7429            je      Ntfs!NtfsOpenAttribute+0x3fc (960e7e1f)
960e7df6 a0187c0996      mov     al,byte ptr [Ntfs!NtfsStatusDebugEnabled (96097c18)]
960e7dfb 84c0            test    al,al
960e7dfd 7414            je      Ntfs!NtfsOpenAttribute+0x3f0 (960e7e13)
960e7dff 681f3a0000      push    3A1Fh

1: kd> u 960e7e13
960e7e13 c745e0220000c0  mov     dword ptr [ebp-20h],0C0000022h
960e7e1a e98f040000      jmp     Ntfs!NtfsOpenAttribute+0x88a (960e82ae)
What this piece of code checks is that if the file is not writable and this flag is set and the user wants to open the file exclusively (doesn't share READ, WRITE and DELETE; this particular check happens a bit further up in the code, not pictured here…) then the IRP_MJ_CREATE will fail with error 0xC0000022, STATUS_ACCESS_DENIED. I've been able to validate that this is the case using FileTest (during debugging I resorted to nasty tricks to get the thread to take that path I wanted, so I had to validate that this can be done by regular creates too), by creating a file with a Deny Write DACL for EVERYONE (so that Ntfs!NtfsWriteCheck would fail) and then opening it exclusively. It works fine if I don't specify FILE_DISALLOW_EXCLUSIVE but fails with STATUS_ACCESS_DENIED when I add FILE_DISALLOW_EXCLUSIVE to the options.
As I said before I'm not sure why this is useful, so if you know a scenario where this might be useful please add a comment and enlighten me :). Anyway, this is what I've been to figure out about this flag so I hope it helps...