IO_IGNORE_SHARE_ACCESS_CHECK

Documented Behavior

The IO_IGNORE_SHARE_ACCESS_CHECK flag provides a way for Windows kernel components to bypass sharing checks while opening local files. NTFSD has lots of posts on the subject — see here, here, here, etc…

MSDN discusses the flag as well but the wording is somewhat ambiguous. For example, the documentation for FltCreateFileEx2 mentions IO_IGNORE_SHARE_ACCESS_CHECK in a couple of places:

Options flags Meaning
IO_IGNORE_SHARE_ACCESS_CHECK Indicates that the I/O manager should not perform share-access checks on the file object after it is created. However, the file system might still perform these checks.

And later, a similarly ambiguous note:

Note If IO_IGNORE_SHARE_ACCESS_CHECK is specified in the Flags parameter, the I/O manager ignores the ShareAccess parameter. However, the file system might still perform access checks. Thus, it is important to specify the sharing mode you would like for the ShareAccess parameter, even when using the IO_IGNORE_SHARE_ACCESS_CHECK flag.

I’ve been using this flag for years, and I thought I understood the ambiguity in the documentation.

  1. The documentation fails to mention that this flag has no effect on files opened across the network.
  2. My understanding was that this flag *might* not be honored on third-party file systems since they may perform sharing access checks without the help of the IO manager.

I was slightly off on the second point — it turns out that even MS file systems perform internal sharing checks in some cases — see the Bonus section at the end.

On top of this, using IO_IGNORE_SHARE_ACCESS_CHECK results in outright undocumented behavior when subsequently opening the same file.

Note: I am testing/disassembling on Windows 10 RS3.

Undocumented Behavior

On a couple of occasions, we’ve been surprised at the outcome of the following test:

  1. Open an existing file via FltCreateFileEx2 (or similar) with:
    • DesiredAccess = FILE_READ_DATA
    • ShareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE
    • Flags = IO_IGNORE_SHARE_ACCESS_CHECK
  2. While the file handle is still open, open the same file with:
    • DesiredAccess = FILE_WRITE_DATA
    • ShareAccess = 0
    • Flags = 0

We expect the second open to fail with STATUS_SHARING_VIOLATION, since the file is already open for READ and the sharing flags are 0.

However, the second open succeeds. We find the same regardless of the desired access of the second open — FILE_READ_DATA and DELETE also work. We’ve reproduced this on Windows 7 SP1 as well as Windows 10 SP3.

When I finally decided to post my puzzlement to NTFSD, the site was down due to an OSR office move. So, I dove in. I later found someone else asking about this behavior but I did not come across an explanation for it in the archives.

IoCheckShareAccess

The IO Manager provides a set of functions to make it easier for file systems to handle share access checking. In particular, IoCheckShareAccess and IoCheckShareAccessEx can be used to check share access permissions when opening a file.

Local Microsoft file systems use these functions (you can see this by examining fastfat!FatCheckShareAccess or by disassembling NTFS!NtfsCheckShareAccess). Here’s the signature for IoCheckShareAccess:

 NTKERNELAPI NTSTATUS IoCheckShareAccess(
  ACCESS_MASK   DesiredAccess,
  ULONG         DesiredShareAccess,
  PFILE_OBJECT  FileObject,
  PSHARE_ACCESS ShareAccess,
  BOOLEAN       Update
);

MSDN explains the parameters and touches on the relevance of ShareAccess — this is a struct that is stored in the File/Directory/Stream Control Block (FCB/DCB/SCB) of a file object and is therefore common to all open instances of the file/directory/stream. Here’s the definition from a recent WDK:

typedef struct _SHARE_ACCESS {
    ULONG OpenCount;
    ULONG Readers;
    ULONG Writers;
    ULONG Deleters;
    ULONG SharedRead;
    ULONG SharedWrite;
    ULONG SharedDelete;
} SHARE_ACCESS, *PSHARE_ACCESS;

And, here’s a description of what each field should contain:

OpenCount The number of open file objects with (FILE_READ_DATA | FILE_EXECUTE | FILE_WRITE_DATA | FILE_APPEND_DATA | DELETE) access (for the same file/directory/stream)
Readers The number of open file objects with (FILE_READ_DATA | FILE_EXECUTE) access
Writers The number of open file objects with (FILE_WRITE_DATA | FILE_APPEND_DATA) access
Deleters The number of open file objects with DELETE access
SharedRead The number of open file objects with FILE_SHARE_READ
SharedWrite The number of open file objects with FILE_SHARE_WRITE
SharedDelete The number of open file objects with FILE_SHARE_DELETE

IoCheckShareAccess is not a huge function and so I disassembled it to see what it does (actually, I disassembled IoCheckLinkShareAccess since that’s the underlying function). IoCheckLinkAccess does the following:

  1. Set the ReadAccess, WriteAccess, and DeleteAccess bits in the FILE_OBJECT, based on the DesiredAccess parameter. If all of these are 0, return with STATUS_SUCCESS.
  2. Set the SharedRead, SharedWrite, and SharedDelete bits in the FILE_OBJECT based on the ShareAccess parameter. At this point, if the IO_IGNORE_SHARE_ACCESS_CHECK flag is present, return with STATUS_SUCCESS.
  3. Check that DesiredAccess does not collide with the share access of existing opens. If there is a collision, return STATUS_SHARING_VIOLATION.
  4. Check that DesiredSharedAccess does not collide with the access permissions of existing opens. If there is a collision, return STATUS_SHARING_VIOLATION.
  5. Finally, if the share access check passes and Update is non-zero, update the relevant counts in the common ShareAccess struct.

The key piece — if IO_IGNORE_SHARE_ACCESS_CHECK is passed in, IoCheckShareAccess skips steps 3-5. This means that the share access check will always pass for the current open, which is what we expect. Additionally, since the ShareAccess struct is not updated, share access checks for subsequent opens will not be affected by the current open!

Final Thoughts

The implementation of IO_IGNORE_SHARE_ACCESS_CHECK makes a lot of sense when you consider concurrency in the file system. After all, a call to FltCreateFileEx2 with IO_IGNORE_SHARE_ACCESS_CHECK and a a call to CreateFile can be issued at the same time. It would be horrendously frustrating if the CreateFile sometimes succeeded and sometimes failed, because of some inconsiderate kernel driver.

The documentation needs to be updated to make this behavior clear. I’ve submitted a doc issue to FltCreateFileEx2 via GitHub issues (this seems new).

Big thanks to MS for making the fastfat source available.

Bonus #1

Here’s another reason why the MSDN docs are ambiguous — even local Microsoft file systems can trigger a sharing violation when the IO_IGNORE_SHARE_ACCESS_CHECK flag is passed in.

When a file system sees IRP_MJ_CLEANUP for a file object, the counters in the associated _SHARE_ACCESS struct are decremented via IoRemoveShareAccess. However, the file object can still be used for memory-mapped IO and so the file system may perform additional sharing checks.

In particular, the fastfat FatCheckShareAccess function checks for writable sections before calling IoCheckShareAccess. Here’s the excerpt:

#if (NTDDI_VERSION >= NTDDI_VISTA)
    //
    //  Do an extra test for writeable user sections if the user did not allow
    //  write sharing - this is neccessary since a section may exist with no handles
    //  open to the file its based against.
    //

    if ((NodeType( FcbOrDcb ) == FAT_NTC_FCB) &&
        !FlagOn( ShareAccess, FILE_SHARE_WRITE ) &&
        FlagOn( *DesiredAccess, FILE_EXECUTE | FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA | DELETE | MAXIMUM_ALLOWED ) &&
        MmDoesFileHaveUserWritableReferences( &FcbOrDcb->NonPaged->SectionObjectPointers )) {

        return STATUS_SHARING_VIOLATION;
    }
#endif

It is reasonable to assume that NTFS does the same thing, but on a cursory disassembly of NTFS!NtfsCheckShareAccess I didn’t see any calls to MmDoesFileHaveUserWriteReferences.

Bonus #2 – Where is IO_IGNORE_SHARE_ACCESS_CHECK stored?

This is just a bit of trivia really; it appears that if you need to enable this behavior without the use of the IO_IGNORE_SHARE_ACCESS_CHECK flag, a minifilter can just call IoSetFileObjectIgnoreSharing on Windows 7+. However…

At present, when the IO Manager sees the IO_IGNORE_SHARE_ACCESS_CHECK flag, it sets bit 0 in the _FILE_OBJECT->FileObjectExtension.FoExtFlags field.

nt!_FILE_OBJECT
   ...
   +0x04a ReadAccess       : UChar
   +0x04b WriteAccess      : UChar
   +0x04c DeleteAccess     : UChar
   +0x04d SharedRead       : UChar
   +0x04e SharedWrite      : UChar
   +0x04f SharedDelete     : UChar
   ...
   +0x0d0 FileObjectExtension : Ptr64 Void

nt!_IOP_FILE_OBJECT_EXTENSION
+ 0x000 FoExtFlags : Uint4B
+ 0x008 FoExtPerTypeExtension : [9] Ptr64 Void
+ 0x050 FoIoPriorityHint : _IOP_PRIORITY_HINT

We can see this by looking at the disassembly for IoIsFileObjectIgnoringSharing, which “determines if a file object is set with the option to ignore file sharing access checks.”.

0: kd> uf IoIsFileObjectIgnoringSharing
nt!IoIsFileObjectIgnoringSharing:
// rax = FileObject->FileObjectExtension
fffff803`38865520 488b81d0000000  mov     rax,qword ptr [rcx+0D0h]
fffff803`38865527 4885c0          test    rax,rax
fffff803`3886552a 7505            jne     nt!IoIsFileObjectIgnoringSharing+0x11 (fffff803`38865531)  Branch

nt!IoIsFileObjectIgnoringSharing+0xc:
fffff803`3886552c 32c9            xor     cl,cl

nt!IoIsFileObjectIgnoringSharing+0xe:
fffff803`3886552e 8ac1            mov     al,cl
fffff803`38865530 c3              ret

nt!IoIsFileObjectIgnoringSharing+0x11:
// eax = FileObject->FileObjectExtension->FoExtFlags
fffff803`38865531 8b00            mov     eax,dword ptr [rax]
fffff803`38865533 b101            mov     cl,1
// test 1, (FileObject->FileObjectExtension->FoExtFlags & 0xf)
fffff803`38865535 84c1            test    cl,al
fffff803`38865537 74f3            je      nt!IoIsFileObjectIgnoringSharing+0xc (fffff803`3886552c)  Branch

nt!IoIsFileObjectIgnoringSharing+0x19:
fffff803`38865539 ebf3            jmp     nt!IoIsFileObjectIgnoringSharing+0xe (fffff803`3886552e)  Branch

 

Advertisements
This entry was posted in File System, Reversing. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s