New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Windows] Inconsistent os.stat behavior for directory with Access Denied #83003
Comments
After upgrading some scripts from Python 2.7 to 3.7 in Windows 10, I got different behavior that seems to be caused by inconsistent behavior for os.stat in Python 3.7. Python 2.7:
>>> os.stat("D:\\System Volume Information")
nt.stat_result ...
>>> os.stat("D:\\System Volume Information\\")
nt.stat_result ... (same as previous call)
Python 3.7:
>>> os.stat("D:\\System Volume Information")
os.stat_result ...
>>> os.stat("D:\\System Volume Information\\")
Traceback ...
PermissionError: [WinError 5] Access is denied: 'D:\\System Volume Information\\'
What I really do is calling:
>>> os.path.exists("D:\\System Volume Information\\")
False (Unexpected and inconsistent. I expect the return value to be True.)
Behavior for other calls:
>>> os.path.exists("D:\\System Volume Information")
True (OK)
>>> os.path.isdir("D:\\System Volume Information\\")
True (OK, but according to the documentation "Return True if path is an existing directory." where 'existing' links to os.path.exists, which returns False) The closest issue I could find was bpo-28075 which has already been fixed. |
I haven't debugged it, but I'm guessing we're not handling the trailing slash properly when falling back to asking the parent directory for information. Looking at the actual stat result for "C:\System Volume Information" vs. "C:\Windows", most of the information is missing in the first case, which should mean that we can't access it but we know it's a directory because the entry in C:\ said so. |
In attributes_from_dir() in Modules/posixmodule.c, a trailing backslash or slash should be stripped from the lpFileName parameter of FindFirstFileW. Otherwise the call opens the directory via NtOpenFile, instead of opening its parent directory. Even if opening the directory is successful, which we don't expect in this case, FindFirstFileW forcibly fails the call with ERROR_FILE_NOT_FOUND (2) because it expects a filename filter (e.g. "*") for the internal NtQueryDirectoryFile[Ex] system call. Care needs to be taken to not strip the trailing slash of the root directory of a DOS drive because that creates a drive-relative path (e.g. "C:"). It is expected that FindFirstFileW will fail for the root of a DOS drive or UNC share, since there's no parent directory to open. ---- "System Volume Information" explicitly grants access only to the SYSTEM account. Implicitly we have read-attributes access to this directory because we have read-data (i.e. list-directory) access to the root directory. Great, but even for 0 desired access, CreateFileW requests both read-attributes and synchronize access, even for overlapped I/O (i.e. kernel File objects created by CreateFileW can always be waited on). So even an elevated administrator normally can't open this directory to query information. However, backup and restore privileges are in effect when an open requests backup semantics, which we already do. We could extend os.stat to temporarily enable SeBackupPrivilege if the caller has it. It's also possible to open the directory with a native NtOpenFile or NtCreateFile system call, without the FILE_SYNCHRONOUS_IO_NONALERT option and without requesting SYNCHRONIZE access -- i.e. the File object will be asynchronous and not waitable. |
The problem exists in Python 3.8 as well, with the difference that Tested with Python 3.8.2 and 3.8.7. |
The problem exists in Python 3.9 as well, and it behaves the same as Python 3.8. Tested with Python 3.9.1. |
Here's an implementation of attributes_from_dir() that strips trailing slashes (e.g. "C:/spam///" -> "C:/spam"), which entails copying the const string up to the last character that's not a slash. Rooted paths and drive paths that reference a root directory, such as "/" and "C:////", are special cased to avoid creating an empty path or a drive-relative path (e.g. "C:", which expands to the current working directory on drive C:). static BOOL
attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info,
ULONG *reparse_tag)
{
BOOL result = TRUE;
HANDLE hFind;
WIN32_FIND_DATAW fileData;
LPWSTR filename = (LPWSTR)pszFile;
size_t len, n;
/* Issue 38822: trailing slashes must be removed. */
/* Get the length without trailing slashes. */
n = len = wcslen(filename);
while (n && (filename[n - 1] == L'\\' || filename[n - 1] == L'/')) {
n--;
}
if (n != len) {
if (n == 0 || (n == 2 && filename[1] == L':')) {
/* A root directory such as \ or C:\ has no parent to query. */
result = FALSE;
goto cleanup;
}
/* Copy the string without trailing slashes. */
filename = malloc((n + 1) * sizeof(WCHAR));
if (!filename || wcsncpy_s(filename, n + 1, pszFile, n)) {
result = FALSE;
goto cleanup;
}
}
hFind = FindFirstFileW(filename, &fileData);
if (hFind == INVALID_HANDLE_VALUE) {
result = FALSE;
goto cleanup;
}
FindClose(hFind);
find_data_to_file_info(&fileData, info, reparse_tag);
cleanup:
if (filename && filename != pszFile) {
free(filename);
}
return result;
} |
Steve, your PR checks |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: