Simon Goodman
Simon Goodman

Reputation: 1204

How to tell if a path is a file or a directory without elevated privileges

I have an process without elevated privileges, and because of that, I cannot get the File attribute of certains files/directories

const auto attr = GetFileAttributesW(path);

or

auto *pwfd = new WIN32_FIND_DATAW;
const auto handle = FindFirstFileW(path, pwfd);

In both cases it makes sense that I cannot access the file attributes, (because I do not have elevated privileges)

But all I need to know is if the path is a file or directory.

Is there a way of knowing if a valid path is a file or a directory without elevated privileges?

Edit It is not a duplicate of How can I tell if a given path is a directory or a file? (C/C++), (for example), I already know how to get file attributes.

I am asking about knowing if the path is a file/directory without elevated privileges.

Upvotes: 0

Views: 785

Answers (1)

RbMm
RbMm

Reputation: 33804

first of all ERROR_ACCESS_DENIED returned by GetLastError not always mean access denied error. usual (but not always) source of error - is NTSTATUS code, returned by native api and then is converted to win32 error code via RtlNtStatusToDosError. but this conversion not injective. many different NTSTATUS codes converted to the same ERROR_ACCESS_DENIED, not only STATUS_ACCESS_DENIED, as result frequently exist confusion about real reason of error. in case GetFileAttributes, FindFirstFileExW fail better call

extern "C" NTSYSAPI ULONG NTAPI RtlGetLastNtStatus();

api, instead GetLastError(). this is undocumented, but very helpful in some cases.

now about concrete GetFileAttributesW - we need have FILE_READ_ATTRIBUTES access to the file, for not fail with access denied. but file systems grant FILE_READ_ATTRIBUTES to caller or if file security descriptor grant it, of if parent folder grant FILE_LIST_DIRECTORY for caller. of course if we have not both FILE_LIST_DIRECTORY for parent folder and FILE_READ_ATTRIBUTES for file and caller have not SeBackupPrivilege (This privilege causes the system to grant all read access control to any file, regardless of the access control list (ACL) specified for the file.) - here nothing can be done.

but in concrete case i guess that op really got not STATUS_ACCESS_DENIED but STATUS_DELETE_PENDING error. this error will be if somebody call DeleteFile but still exist open handles on file:

The DeleteFile function marks a file for deletion on close. Therefore, the file deletion does not occur until the last handle to the file is closed. Subsequent calls to CreateFile to open the file fail with ERROR_ACCESS_DENIED.

(really of course NTSTATUS code will be STATUS_DELETE_PENDING in this case but RtlNtStatusToDosError convert it to ERROR_ACCESS_DENIED)

so possible reason, why GetFileAttributes can fail, is that file marked for deletion on close. but if file still not deleted, possible get it attributes via FindFirstFileExW. note - FindFirstFileExW return information about file attributes in parent folder. for call this api we need only FILE_LIST_DIRECTORY access to the parent folder. this is reason why file systems grant FILE_READ_ATTRIBUTES for file if we have FILE_LIST_DIRECTORY for parent folder. also GetFileAttributes can fail with error STATUS_SHARING_VIOLATION if call it on page file (of course very special case). if we have not FILE_LIST_DIRECTORY access to parent folder (and no backup privilege enabled) - FindFirstFileExW fail too and here nothing can be done.

so possible solution, for not fail, when file marked for deletion on close - call FindFirstFileExW in this case. note that FindFirstFileExW much more expensive call compare GetFileAttributes, from another side situation when we query file marked for deletion very rare. so always better first call GetFileAttributes and only if it fail with code STATUS_DELETE_PENDING try FindFirstFileExW call.

NTSTATUS GetFileAttributesEx(PCWSTR FileName, DWORD* pdwFileAttributes)
{
    DWORD dwFileAttributes = GetFileAttributes(FileName);

    if (INVALID_FILE_ATTRIBUTES != dwFileAttributes)
    {
        *pdwFileAttributes = dwFileAttributes;
        return STATUS_SUCCESS;
    }

    NTSTATUS status = RtlGetLastNtStatus();

    switch (status)
    {
    case STATUS_SHARING_VIOLATION: // this is only for pagefile i think can be
    case STATUS_DELETE_PENDING:
        WIN32_FIND_DATAW fd;
        HANDLE hFile = FindFirstFileExW(FileName, FindExInfoBasic, &fd, FindExSearchNameMatch, 0, 0);

        if (hFile != INVALID_HANDLE_VALUE)
        {
            *pdwFileAttributes = fd.dwFileAttributes;
            FindClose(hFile);
            status = STATUS_SUCCESS;
        }
        else
        {
            status = RtlGetLastNtStatus();
        }
        break;
    }

    return status;
}

demo code for test this:

ULONG cb = 0, rcb = 0x80;
static volatile UCHAR guz = 0;
PVOID stack = alloca(guz);
PWSTR FileName = 0;
do 
{
    if (cb < rcb)
    {
        cb = (ULONG)((PWSTR)stack - (FileName = (PWSTR)alloca((rcb - cb)* sizeof(WCHAR))));
    }

    rcb = ExpandEnvironmentStringsW(L"%tmp%/test.tmp", FileName, cb);
} while (cb < rcb);

if (rcb)
{
    HANDLE hFile = CreateFile(FileName, DELETE, 0, 0, CREATE_NEW, 
        FILE_ATTRIBUTE_TEMPORARY|FILE_ATTRIBUTE_HIDDEN|FILE_FLAG_DELETE_ON_CLOSE, 0);

    if (hFile != INVALID_HANDLE_VALUE)
    {
        ULONG dwFileAttributes;
        GetFileAttributesEx(FileName, &dwFileAttributes);

        static FILE_DISPOSITION_INFO fdi = { TRUE };
        SetFileInformationByHandle(hFile, FileDispositionInfo, &fdi, sizeof(fdi));

        GetFileAttributesEx(FileName, &dwFileAttributes);

        CloseHandle(hFile);

        GetFileAttributesEx(FileName, &dwFileAttributes);
    }
}

Upvotes: 1

Related Questions