win,fs: get (most) fstat when no permission (#4566)

Replaces: https://github.com/libuv/libuv/pull/4504
Fixes: https://github.com/libuv/libuv/issues/1980
Fixes: https://github.com/libuv/libuv/issues/3267

Co-authored-by: Hüseyin Açacak <huseyin@janeasystems.com>
This commit is contained in:
Jameson Nash 2024-12-12 15:05:53 -05:00 committed by GitHub
parent 16e6e84dcc
commit 72d9abccd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 269 additions and 8 deletions

View File

@ -58,6 +58,19 @@
#define FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE 0x0010
#endif /* FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE */
NTSTATUS uv__RtlUnicodeStringInit(
PUNICODE_STRING DestinationString,
PWSTR SourceString,
size_t SourceStringLen
) {
if (SourceStringLen > 0x7FFF)
return STATUS_INVALID_PARAMETER;
DestinationString->MaximumLength = DestinationString->Length =
SourceStringLen * sizeof(SourceString[0]);
DestinationString->Buffer = SourceString;
return STATUS_SUCCESS;
}
#define INIT(subtype) \
do { \
if (req == NULL) \
@ -1689,12 +1702,12 @@ INLINE static fs__stat_path_return_t fs__stat_path(WCHAR* path,
uv_stat_t* statbuf, int do_lstat) {
FILE_STAT_BASIC_INFORMATION stat_info;
// Check if the new fast API is available.
/* Check if the new fast API is available. */
if (!pGetFileInformationByName) {
return FS__STAT_PATH_TRY_SLOW;
}
// Check if the API call fails.
/* Check if the API call fails. */
if (!pGetFileInformationByName(path, FileStatBasicByNameInfo, &stat_info,
sizeof(stat_info))) {
switch(GetLastError()) {
@ -1708,7 +1721,7 @@ INLINE static fs__stat_path_return_t fs__stat_path(WCHAR* path,
return FS__STAT_PATH_TRY_SLOW;
}
// A file handle is needed to get st_size for links.
/* A file handle is needed to get st_size for links. */
if ((stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
return FS__STAT_PATH_TRY_SLOW;
}
@ -1802,7 +1815,6 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf,
* detect this failure and retry without do_lstat if appropriate.
*/
if (fs__readlink_handle(handle, NULL, &target_length) != 0) {
fs__stat_assign_statbuf(statbuf, stat_info, do_lstat);
return -1;
}
stat_info.EndOfFile.QuadPart = target_length;
@ -1941,6 +1953,179 @@ INLINE static void fs__stat_prepare_path(WCHAR* pathw) {
}
}
INLINE static DWORD fs__stat_directory(WCHAR* path, uv_stat_t* statbuf,
int do_lstat, DWORD ret_error) {
HANDLE handle = INVALID_HANDLE_VALUE;
FILE_STAT_BASIC_INFORMATION stat_info;
FILE_ID_FULL_DIR_INFORMATION dir_info;
FILE_FS_VOLUME_INFORMATION volume_info;
FILE_FS_DEVICE_INFORMATION device_info;
IO_STATUS_BLOCK io_status;
NTSTATUS nt_status;
WCHAR* path_dirpath = NULL;
WCHAR* path_filename = NULL;
UNICODE_STRING FileMask;
size_t len;
size_t split;
WCHAR splitchar;
int includes_name;
/* AKA strtok or wcscspn, in reverse. */
len = wcslen(path);
split = len;
includes_name = 0;
while (split > 0 && path[split - 1] != L'\\' && path[split - 1] != L'/' &&
path[split - 1] != L':') {
/* check if the path contains a character other than /,\,:,. */
if (path[split-1] != '.') {
includes_name = 1;
}
split--;
}
/* If the path is a relative path with a file name or a folder name */
if (split == 0 && includes_name) {
path_dirpath = L".";
/* If there is a slash or a backslash */
} else if (path[split - 1] == L'\\' || path[split - 1] == L'/') {
path_dirpath = path;
/* If there is no filename, consider it as a relative folder path */
if (!includes_name) {
split = len;
/* Else, split it */
} else {
splitchar = path[split - 1];
path[split - 1] = L'\0';
}
/* e.g. "..", "c:" */
} else {
path_dirpath = path;
split = len;
}
path_filename = &path[split];
len = 0;
while (1) {
if (path_filename[len] == L'\0')
break;
if (path_filename[len] == L'*' || path_filename[len] == L'?' ||
path_filename[len] == L'>' || path_filename[len] == L'<' ||
path_filename[len] == L'"') {
ret_error = ERROR_INVALID_NAME;
goto cleanup;
}
len++;
}
/* Get directory handle */
handle = CreateFileW(path_dirpath,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
ret_error = GetLastError();
goto cleanup;
}
/* Get files in the directory */
nt_status = uv__RtlUnicodeStringInit(&FileMask, path_filename, len);
if (!NT_SUCCESS(nt_status)) {
ret_error = pRtlNtStatusToDosError(nt_status);
goto cleanup;
}
nt_status = pNtQueryDirectoryFile(handle,
NULL,
NULL,
NULL,
&io_status,
&dir_info,
sizeof(dir_info),
FileIdFullDirectoryInformation,
TRUE,
&FileMask,
TRUE);
/* Buffer overflow (a warning status code) is expected here since there isn't
* enough space to store the FileName, and actually indicates success. */
if (!NT_SUCCESS(nt_status) && nt_status != STATUS_BUFFER_OVERFLOW) {
if (nt_status == STATUS_NO_MORE_FILES)
ret_error = ERROR_PATH_NOT_FOUND;
else
ret_error = pRtlNtStatusToDosError(nt_status);
goto cleanup;
}
/* Assign values to stat_info */
memset(&stat_info, 0, sizeof(FILE_STAT_BASIC_INFORMATION));
stat_info.FileAttributes = dir_info.FileAttributes;
stat_info.CreationTime.QuadPart = dir_info.CreationTime.QuadPart;
stat_info.LastAccessTime.QuadPart = dir_info.LastAccessTime.QuadPart;
stat_info.LastWriteTime.QuadPart = dir_info.LastWriteTime.QuadPart;
if (stat_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
/* A file handle is needed to get st_size for the link (from
* FSCTL_GET_REPARSE_POINT), which is required by posix, but we are here
* because getting the file handle failed. We could get just the
* ReparsePointTag by querying FILE_ID_EXTD_DIR_INFORMATION instead to make
* sure this really is a link before giving up here on the uv_fs_stat call,
* but that doesn't seem essential. */
if (!do_lstat)
goto cleanup;
stat_info.EndOfFile.QuadPart = 0;
stat_info.AllocationSize.QuadPart = 0;
} else {
stat_info.EndOfFile.QuadPart = dir_info.EndOfFile.QuadPart;
stat_info.AllocationSize.QuadPart = dir_info.AllocationSize.QuadPart;
}
stat_info.ChangeTime.QuadPart = dir_info.ChangeTime.QuadPart;
stat_info.FileId.QuadPart = dir_info.FileId.QuadPart;
/* Finish up by getting device info from the directory handle,
* since files presumably must live on their device. */
nt_status = pNtQueryVolumeInformationFile(handle,
&io_status,
&volume_info,
sizeof volume_info,
FileFsVolumeInformation);
/* Buffer overflow (a warning status code) is expected here. */
if (io_status.Status == STATUS_NOT_IMPLEMENTED) {
stat_info.VolumeSerialNumber.QuadPart = 0;
} else if (NT_ERROR(nt_status)) {
ret_error = pRtlNtStatusToDosError(nt_status);
goto cleanup;
} else {
stat_info.VolumeSerialNumber.QuadPart = volume_info.VolumeSerialNumber;
}
nt_status = pNtQueryVolumeInformationFile(handle,
&io_status,
&device_info,
sizeof device_info,
FileFsDeviceInformation);
/* Buffer overflow (a warning status code) is expected here. */
if (NT_ERROR(nt_status)) {
ret_error = pRtlNtStatusToDosError(nt_status);
goto cleanup;
}
stat_info.DeviceType = device_info.DeviceType;
stat_info.NumberOfLinks = 1; /* No way to recover this info. */
fs__stat_assign_statbuf(statbuf, stat_info, do_lstat);
ret_error = 0;
cleanup:
if (split != 0)
path[split - 1] = splitchar;
if (handle != INVALID_HANDLE_VALUE)
CloseHandle(handle);
return ret_error;
}
INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
int do_lstat,
@ -1949,7 +2134,7 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
DWORD flags;
DWORD ret;
// If new API exists, try to use it.
/* If new API exists, try to use it. */
switch (fs__stat_path(path, statbuf, do_lstat)) {
case FS__STAT_PATH_SUCCESS:
return 0;
@ -1959,7 +2144,7 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
break;
}
// If the new API does not exist, use the old API.
/* If the new API does not exist, use the old API. */
flags = FILE_FLAG_BACKUP_SEMANTICS;
if (do_lstat)
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
@ -1972,8 +2157,12 @@ INLINE static DWORD fs__stat_impl_from_path(WCHAR* path,
flags,
NULL);
if (handle == INVALID_HANDLE_VALUE)
return GetLastError();
if (handle == INVALID_HANDLE_VALUE) {
ret = GetLastError();
if (ret != ERROR_ACCESS_DENIED && ret != ERROR_SHARING_VIOLATION)
return ret;
return fs__stat_directory(path, statbuf, do_lstat, ret);
}
if (fs__stat_handle(handle, statbuf, do_lstat) != 0)
ret = GetLastError();

View File

@ -4287,6 +4287,22 @@ typedef struct _FILE_BOTH_DIR_INFORMATION {
WCHAR FileName[1];
} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;
typedef struct _FILE_ID_FULL_DIR_INFORMATION {
ULONG NextEntryOffset;
ULONG FileIndex;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaSize;
LARGE_INTEGER FileId;
WCHAR FileName[1];
} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION;
typedef struct _FILE_BASIC_INFORMATION {
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;

View File

@ -4507,6 +4507,60 @@ TEST_IMPL(fs_open_readonly_acl) {
MAKE_VALGRIND_HAPPY(loop);
return 0;
}
TEST_IMPL(fs_stat_no_permission) {
uv_passwd_t pwd;
uv_fs_t req;
int r;
char* filename = "test_file_no_permission.txt";
/* Setup - clear the ACL and remove the file */
loop = uv_default_loop();
r = uv_os_get_passwd(&pwd);
ASSERT_OK(r);
call_icacls("icacls %s /remove *S-1-1-0:(F)", filename);
unlink(filename);
/* Create the file */
r = uv_fs_open(loop,
&open_req1,
filename,
UV_FS_O_RDONLY | UV_FS_O_CREAT,
S_IRUSR,
NULL);
ASSERT_GE(r, 0);
ASSERT_GE(open_req1.result, 0);
uv_fs_req_cleanup(&open_req1);
r = uv_fs_close(NULL, &close_req, open_req1.result, NULL);
ASSERT_OK(r);
ASSERT_OK(close_req.result);
uv_fs_req_cleanup(&close_req);
/* Set up ACL */
r = call_icacls("icacls %s /deny *S-1-1-0:(F)", filename);
if (r != 0) {
goto acl_cleanup;
}
/* Read file stats */
r = uv_fs_stat(NULL, &req, filename, NULL);
if (r != 0) {
goto acl_cleanup;
}
uv_fs_req_cleanup(&req);
acl_cleanup:
/* Cleanup */
call_icacls("icacls %s /reset", filename);
uv_fs_unlink(NULL, &unlink_req, filename, NULL);
uv_fs_req_cleanup(&unlink_req);
unlink(filename);
uv_os_free_passwd(&pwd);
ASSERT_OK(r);
MAKE_VALGRIND_HAPPY(loop);
return 0;
}
#endif
#ifdef _WIN32

View File

@ -514,6 +514,7 @@ TEST_DECLARE (environment_creation)
TEST_DECLARE (listen_with_simultaneous_accepts)
TEST_DECLARE (listen_no_simultaneous_accepts)
TEST_DECLARE (fs_stat_root)
TEST_DECLARE (fs_stat_no_permission)
TEST_DECLARE (spawn_with_an_odd_path)
TEST_DECLARE (spawn_no_path)
TEST_DECLARE (spawn_no_ext)
@ -1043,6 +1044,7 @@ TASK_LIST_START
TEST_ENTRY (listen_with_simultaneous_accepts)
TEST_ENTRY (listen_no_simultaneous_accepts)
TEST_ENTRY (fs_stat_root)
TEST_ENTRY (fs_stat_no_permission)
TEST_ENTRY (spawn_with_an_odd_path)
TEST_ENTRY (spawn_no_path)
TEST_ENTRY (spawn_no_ext)