c++winapiwindows-kernelnt-native-api

How to use MAXIMUM_ALLOWED properly?


I have created a small framework that provides a unified API to multiple file systems/APIs (namely Win32, Posix, NFS). Said API is somewhat similar to Posix -- to access a file you need to "open" it providing a hint for intended purpose (r, w or rw). Something like open_file("/abc/log.txt", access::rw).

Supporting Win32 API in this framework gives me a headache due to "declarative" nature of Win32 -- you are supposed to know upfront which operations you plan to perform on given handle and pass related dwDesiredAccess into related (Nt)CreateFile() call. Unfortunately framework has no idea what operation client is going to perform (i.e. change-owner, write-attributes, etc) besides generic r/w/rw hint. And I am not willing to let Win32 concepts to leak into my framework (i.e. I don't like adding dwDesiredAccess equivalent into my open_file()).

Here is what I've tried:

1. MAXIMUM_ALLOWED

Idea: Open related handles with MAXIMUM_ALLOWED -- I'll get everything I could and if some right is missing, related operation (e.g. set_mtime()) will simply fail with access denied.

Problems:

2. Reopen object when necessary

Idea: Represent r/w/rw though GENERIC_READ and GENERIC_WRITE and for all operations that require additional access (e.g. delete() requires DELETE) reopen the object with required access.

Problems:

3. Duplicate handle instead of reopening object

Idea: same as in previous section, but instead of reopening object -- duplicate original handle (asking for new access):

HANDLE h;
HANDLE hp = GetCurrentProcess();
CHECK_WIN32( DuplicateHandle(hp, hFile, hp, &h, FILE_WRITE_ATTRIBUTES|SYNCHRONIZE, FALSE, 0) );

Problems:

... otherwise approach seem to be working.

Bottomline:

I am looking for a way to address MAXIMUM_ALLOWED issues. (Or suggestions for alternative approach, maybe?)


Solution

  • Edit: Here is another reason why reopening file is not a good idea.

    There is no way to use MAXIMUM_ALLOWED reliably -- R/O files and volumes cause it to error. Poorly designed feature.

    Another approach is to get minimum access and "expand" it as required (by re-opening file with new dwAccessRequired flag). This does not work:

    Alas, DuplicateHandle() can't grant new access -- so this won't help either.

    Basically, all I need is a thread-safe BOOL ExtendAccess(HANDLE h, DWORD dwAdditionalAccess) function. It looks like you can't have it even via NT API -- possible only in kernel mode.

    Luckily this framework is always used under privileged account, which means I can enable SE_BACKUP_NAME, use FILE_OPEN_FOR_BACKUP_INTENT, over-request access (with minimal fallback in case of read-only volume) and avoid dealing with restrictive DACLs. Ah, and yes, deal with ReadOnly attribute in delete(). Still haven't decided what to do if user wants to open read-only file for writing...

    I ended up with this:

    W32Handle open_file_(HANDLE hparent, UNICODE_STRING zwpath, access a, disposition d, truncate t)
    {
        ...
        ACCESS_MASK access = [a]() -> ACCESS_MASK {
            switch(a)
            {
            case access::r  : return GENERIC_READ;
            case access::w  : [[fallthrough]];                      // MSDN suggests to use GENERIC_READ with GENERIC_WRITE over network (performance reasons)
            case access::rw : return GENERIC_READ|GENERIC_WRITE;
            }
            UNREACHEABLE;
        }();
    
        constexpr DWORD write_access = FILE_WRITE_ATTRIBUTES|DELETE|WRITE_OWNER;    // we want to always have these (for apply, unlink, chown, etc)
    
        access |= write_access;
        access |= SYNCHRONIZE|READ_CONTROL|ACCESS_SYSTEM_SECURITY;                  // add "read DACL/SACL" rights (for full_metadata)
    
        ULONG flags = FILE_SYNCHRONOUS_IO_NONALERT|FILE_NON_DIRECTORY_FILE|FILE_OPEN_FOR_BACKUP_INTENT;
    
        OBJECT_ATTRIBUTES oa;
        InitializeObjectAttributes(&oa, &zwpath, 0, hparent, NULL);
    
        HANDLE h;
        IO_STATUS_BLOCK io;
        NTSTATUS r = ZwCreateFile(&h, access, &oa, &io, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_VALID_FLAGS, disposition, flags, NULL, 0);
        if (r == STATUS_SUCCESS) return W32Handle(h);
    
        if (r == STATUS_MEDIA_WRITE_PROTECTED)      // try again without write flags
        {
            access &= ~write_access;
            r = ZwCreateFile(&h, access, &oa, &io, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_VALID_FLAGS, disposition, flags, NULL, 0);
            if (r == STATUS_SUCCESS) return W32Handle(h);
        }
    
        HR_THROW_(HRESULT_FROM_NT(r), "%s: Failed to open file", __func__);
    }
    

    Overall terrible API, a spaghetti of special cases. I wish I had my own SMB client.