Message265040
The Windows API loses information when mapping kernel status values to Windows error codes. For example, the following status values are all mapped to ERROR_ACCESS_DENIED:
STATUS_INVALID_LOCK_SEQUENCE 0xc000001e
STATUS_INVALID_VIEW_SIZE 0xc000001f
STATUS_ALREADY_COMMITTED 0xc0000021
STATUS_ACCESS_DENIED 0xc0000022
STATUS_PORT_CONNECTION_REFUSED 0xc0000041
STATUS_THREAD_IS_TERMINATING 0xc000004b
STATUS_DELETE_PENDING 0xc0000056
STATUS_FILE_IS_A_DIRECTORY 0xc00000ba
STATUS_FILE_RENAMED 0xc00000d5
STATUS_PROCESS_IS_TERMINATING 0xc000010a
STATUS_CANNOT_DELETE 0xc0000121
STATUS_FILE_DELETED 0xc0000123
Encrypting File System
STATUS_ENCRYPTION_FAILED 0xc000028a
STATUS_DECRYPTION_FAILED 0xc000028b
STATUS_NO_RECOVERY_POLICY 0xc000028d
STATUS_NO_EFS 0xc000028e
STATUS_WRONG_EFS 0xc000028f
STATUS_NO_USER_KEYS 0xc0000290
STATUS_ACCESS_DENIED is from a failed NtAccessCheck. STATUS_FILE_IS_A_DIRECTORY is from trying to open a directory as a file, because NT doesn't allow accessing the anonymous data stream of a directory, such as "dirname::$DATA", which is the same as trying to open "dirname" as a file. It only allows creating a named data stream for a directory, such as "dirname:streamname:$DATA".
The original status value may still be available, but only by calling the undocumented runtime library function, RtlGetLastNtStatus, which was added in XP (NT 5.1). After a failed system call, the Windows base API calls BaseSetLastNTError, which calls RtlNtStatusToDosError to get the Win32/DOS error code for a given NT status value. This in turn caches the last NT status in the LastStatusValue field of the thread environment block (TEB). RtlGetLastNtStatus gets this value from the TEB.
Possibly PyErr_SetExcFromWindowsErrWithFilenameObjects could capture the most recent kernel status value from RtlGetLastNtStatus(), to add this as a new "ntstatus" attribute of OSError. This wouldn't always be meaningful, since the thread's LastErrorValue (returned by GetLastError) isn't always related to a failed system call, but it can help in cases such as this, to distinguish a genuine denial of access from some other failure, and without suffering from race conditions.
For example, I added a "testdir" subdirectory to a directory and then modified the DACL of the parent directory to deny write/append access for all users. The following experiment checks the NT status using ctypes:
STATUS_ACCESS_DENIED = ctypes.c_long(0xC0000022).value
STATUS_FILE_IS_A_DIRECTORY = ctypes.c_long(0xC00000BA).value
ntdll = ctypes.WinDLL('ntdll')
try: open('testdir', 'w')
except: status_dir = ntdll.RtlGetLastNtStatus()
try: open('test', 'w')
except: status_access = ntdll.RtlGetLastNtStatus()
>>> status_dir == STATUS_FILE_IS_A_DIRECTORY
True
>>> status_access == STATUS_ACCESS_DENIED
True
Obviously using ctypes isn't recommended, since the status value needs to be captured ASAP after the call fails, so here's an example in C:
#define UNICODE
#include <Windows.h>
#include <fcntl.h>
#include <stdio.h>
typedef NTSTATUS (NTAPI *RTLGETLASTNTSTATUS)(VOID);
int main()
{
HMODULE hNtdll = GetModuleHandle(L"ntdll");
RTLGETLASTNTSTATUS RtlGetLastNtStatus = (RTLGETLASTNTSTATUS)
GetProcAddress(hNtdll, "RtlGetLastNtStatus");
if (_open("testdir", _O_CREAT | _O_EXCL) == -1)
printf("status_dir: %#08x\n", RtlGetLastNtStatus());
if (_open("test", _O_CREAT | _O_EXCL) == -1)
printf("status_access: %#08x\n", RtlGetLastNtStatus());
return 0;
}
output:
status_dir: 0xc00000ba
status_access: 0xc0000022 |
|
Date |
User |
Action |
Args |
2016-05-07 03:50:01 | eryksun | set | recipients:
+ eryksun, georg.brandl, paul.moore, ncoghlan, rupole, tim.golden, python-dev, takluyver, zach.ware, serhiy.storchaka, steve.dower, Billy McCulloch |
2016-05-07 03:50:01 | eryksun | set | messageid: <1462593001.64.0.130303022272.issue22107@psf.upfronthosting.co.za> |
2016-05-07 03:50:01 | eryksun | link | issue22107 messages |
2016-05-07 03:49:59 | eryksun | create | |
|