update wil to 0b2d6c2d822bb301e7558a14ee66d567c14f5dc7

This commit is contained in:
Shawn Hoffman
2023-02-22 13:12:56 -08:00
parent 95ce41ac56
commit 69c335ca8c
66 changed files with 14776 additions and 2507 deletions

View File

@ -31,6 +31,7 @@ namespace wil
return wcsncmp(path, L"\\\\?\\", 4) == 0;
}
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
//! Find the last segment of a path. Matches the behavior of shlwapi!PathFindFileNameW()
//! note, does not support streams being specified like PathFindFileNameW(), is that a bug or a feature?
inline PCWSTR find_last_path_segment(_In_ PCWSTR path)
@ -51,6 +52,7 @@ namespace wil
}
return result;
}
#endif
//! Determine if the file name is one of the special "." or ".." names.
inline bool path_is_dot_or_dotdot(_In_ PCWSTR fileName)
@ -83,7 +85,7 @@ namespace wil
return false;
}
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
// PathCch.h APIs are only in desktop API for now.
@ -111,7 +113,7 @@ namespace wil
{
if (::CreateDirectoryW(path, nullptr) == FALSE)
{
DWORD const lastError = ::GetLastError();
DWORD lastError = ::GetLastError();
if (lastError == ERROR_PATH_NOT_FOUND)
{
size_t parentLength;
@ -120,9 +122,16 @@ namespace wil
wistd::unique_ptr<wchar_t[]> parent(new (std::nothrow) wchar_t[parentLength + 1]);
RETURN_IF_NULL_ALLOC(parent.get());
RETURN_IF_FAILED(StringCchCopyNW(parent.get(), parentLength + 1, path, parentLength));
CreateDirectoryDeepNoThrow(parent.get()); // recurs
RETURN_IF_FAILED(CreateDirectoryDeepNoThrow(parent.get())); // recurs
}
if (::CreateDirectoryW(path, nullptr) == FALSE)
{
lastError = ::GetLastError();
if (lastError != ERROR_ALREADY_EXISTS)
{
RETURN_WIN32(lastError);
}
}
RETURN_IF_WIN32_BOOL_FALSE(::CreateDirectoryW(path, nullptr));
}
else if (lastError != ERROR_ALREADY_EXISTS)
{
@ -183,13 +192,53 @@ namespace wil
enum class RemoveDirectoryOptions
{
None = 0,
KeepRootDirectory = 0x1
KeepRootDirectory = 0x1,
RemoveReadOnly = 0x2,
};
DEFINE_ENUM_FLAG_OPERATORS(RemoveDirectoryOptions);
namespace details
{
// Reparse points should not be traversed in most recursive walks of the file system,
// unless allowed through the appropriate reparse tag.
inline bool CanRecurseIntoDirectory(const FILE_ATTRIBUTE_TAG_INFO& info)
{
return (WI_IsFlagSet(info.FileAttributes, FILE_ATTRIBUTE_DIRECTORY) &&
(WI_IsFlagClear(info.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT) ||
(IsReparseTagDirectory(info.ReparseTag) || (info.ReparseTag == IO_REPARSE_TAG_WCI))));
}
}
// Retrieve a handle to a directory only if it is safe to recurse into.
inline wil::unique_hfile TryCreateFileCanRecurseIntoDirectory(PCWSTR path, PWIN32_FIND_DATAW fileFindData, DWORD access = GENERIC_READ | /*DELETE*/ 0x00010000L, DWORD share = FILE_SHARE_READ)
{
wil::unique_hfile result(CreateFileW(path, access, share,
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
if (result)
{
FILE_ATTRIBUTE_TAG_INFO fati;
if (GetFileInformationByHandleEx(result.get(), FileAttributeTagInfo, &fati, sizeof(fati)) &&
details::CanRecurseIntoDirectory(fati))
{
if (fileFindData)
{
// Refresh the found file's data now that we have secured the directory from external manipulation.
fileFindData->dwFileAttributes = fati.FileAttributes;
fileFindData->dwReserved0 = fati.ReparseTag;
}
}
else
{
result.reset();
}
}
return result;
}
// If inputPath is a non-normalized name be sure to pass an extended length form to ensure
// it can be addressed and deleted.
inline HRESULT RemoveDirectoryRecursiveNoThrow(PCWSTR inputPath, RemoveDirectoryOptions options = RemoveDirectoryOptions::None) WI_NOEXCEPT
inline HRESULT RemoveDirectoryRecursiveNoThrow(PCWSTR inputPath, RemoveDirectoryOptions options = RemoveDirectoryOptions::None, HANDLE deleteHandle = INVALID_HANDLE_VALUE) WI_NOEXCEPT
{
wil::unique_hlocal_string path;
PATHCCH_OPTIONS combineOptions = PATHCCH_NONE;
@ -228,14 +277,50 @@ namespace wil
PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH | PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, &pathToDelete));
if (WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY))
{
RemoveDirectoryOptions localOptions = options;
RETURN_IF_FAILED(RemoveDirectoryRecursiveNoThrow(pathToDelete.get(), WI_ClearFlag(localOptions, RemoveDirectoryOptions::KeepRootDirectory)));
// Get a handle to the directory to delete, preventing it from being replaced to prevent writes which could be used
// to bypass permission checks, and verify that it is not a name surrogate (e.g. symlink, mount point, etc).
wil::unique_hfile recursivelyDeletableDirectoryHandle = TryCreateFileCanRecurseIntoDirectory(pathToDelete.get(), &fd);
if (recursivelyDeletableDirectoryHandle)
{
RemoveDirectoryOptions localOptions = options;
RETURN_IF_FAILED(RemoveDirectoryRecursiveNoThrow(pathToDelete.get(), WI_ClearFlag(localOptions, RemoveDirectoryOptions::KeepRootDirectory), recursivelyDeletableDirectoryHandle.get()));
}
else if (WI_IsFlagSet(fd.dwFileAttributes, FILE_ATTRIBUTE_REPARSE_POINT))
{
// This is a directory reparse point that should not be recursed. Delete it without traversing into it.
RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(pathToDelete.get()));
}
else
{
// Failed to grab a handle to the file or to read its attributes. This is not safe to recurse.
RETURN_WIN32(::GetLastError());
}
}
else
{
// note: if pathToDelete is read-only this will fail, consider adding
// RemoveDirectoryOptions::RemoveReadOnly to enable this behavior.
RETURN_IF_WIN32_BOOL_FALSE(::DeleteFileW(pathToDelete.get()));
// Try a DeleteFile. Some errors may be recoverable.
if (!::DeleteFileW(pathToDelete.get()))
{
// Fail for anything other than ERROR_ACCESS_DENIED with option to RemoveReadOnly available
bool potentiallyFixableReadOnlyProblem =
WI_IsFlagSet(options, RemoveDirectoryOptions::RemoveReadOnly) && ::GetLastError() == ERROR_ACCESS_DENIED;
RETURN_LAST_ERROR_IF(!potentiallyFixableReadOnlyProblem);
// Fail if the file does not have read-only set, likely just an ACL problem
DWORD fileAttr = ::GetFileAttributesW(pathToDelete.get());
RETURN_LAST_ERROR_IF(!WI_IsFlagSet(fileAttr, FILE_ATTRIBUTE_READONLY));
// Remove read-only flag, setting to NORMAL if completely empty
WI_ClearFlag(fileAttr, FILE_ATTRIBUTE_READONLY);
if (fileAttr == 0)
{
fileAttr = FILE_ATTRIBUTE_NORMAL;
}
// Set the new attributes and try to delete the file again, returning any failure
::SetFileAttributesW(pathToDelete.get(), fileAttr);
RETURN_IF_WIN32_BOOL_FALSE(::DeleteFileW(pathToDelete.get()));
}
}
}
@ -252,7 +337,35 @@ namespace wil
if (WI_IsFlagClear(options, RemoveDirectoryOptions::KeepRootDirectory))
{
RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(path.get()));
if (deleteHandle != INVALID_HANDLE_VALUE)
{
#if (NTDDI_VERSION >= NTDDI_WIN10_RS1)
// DeleteFile and RemoveDirectory use POSIX delete, falling back to non-POSIX on most errors. Do the same here.
FILE_DISPOSITION_INFO_EX fileInfoEx{};
fileInfoEx.Flags = FILE_DISPOSITION_FLAG_DELETE | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS;
if (!SetFileInformationByHandle(deleteHandle, FileDispositionInfoEx, &fileInfoEx, sizeof(fileInfoEx)))
{
auto const err = ::GetLastError();
// The real error we're looking for is STATUS_CANNOT_DELETE, but that's mapped to ERROR_ACCESS_DENIED.
if (err != ERROR_ACCESS_DENIED)
{
#endif
FILE_DISPOSITION_INFO fileInfo{};
fileInfo.DeleteFile = TRUE;
RETURN_IF_WIN32_BOOL_FALSE(SetFileInformationByHandle(deleteHandle, FileDispositionInfo, &fileInfo, sizeof(fileInfo)));
#if (NTDDI_VERSION >= NTDDI_WIN10_RS1)
}
else
{
RETURN_WIN32(err);
}
}
#endif
}
else
{
RETURN_IF_WIN32_BOOL_FALSE(::RemoveDirectoryW(path.get()));
}
}
return S_OK;
}
@ -552,7 +665,7 @@ namespace wil
OVERLAPPED m_overlapped{};
TP_IO *m_tpIo = __nullptr;
srwlock m_cancelLock;
char m_readBuffer[4096]; // Consider alternative buffer sizes. With 512 byte buffer i was not able to observe overflow.
unsigned char m_readBuffer[4096]; // Consider alternative buffer sizes. With 512 byte buffer i was not able to observe overflow.
};
inline void delete_folder_change_reader_state(_In_opt_ folder_change_reader_state *storage) { delete storage; }
@ -596,7 +709,6 @@ namespace wil
auto readerState = static_cast<details::folder_change_reader_state *>(context);
// WI_ASSERT(overlapped == &readerState->m_overlapped);
bool requeue = true;
if (result == ERROR_SUCCESS)
{
for (auto const& info : create_next_entry_offset_iterator(reinterpret_cast<FILE_NOTIFY_INFORMATION *>(readerState->m_readBuffer)))
@ -613,19 +725,17 @@ namespace wil
}
else
{
requeue = false;
// No need to requeue
return;
}
if (requeue)
// If the lock is held non-shared or the TP IO is nullptr, this
// structure is being torn down. Otherwise, monitor for further
// changes.
auto autoLock = readerState->m_cancelLock.try_lock_shared();
if (autoLock && readerState->m_tpIo)
{
// If the lock is held non-shared or the TP IO is nullptr, this
// structure is being torn down. Otherwise, monitor for further
// changes.
auto autoLock = readerState->m_cancelLock.try_lock_shared();
if (autoLock && readerState->m_tpIo)
{
readerState->StartIo(); // ignoring failure here
}
readerState->StartIo(); // ignoring failure here
}
}
@ -798,7 +908,7 @@ namespace wil
// Type unsafe version used in the implementation to avoid template bloat.
inline HRESULT GetFileInfo(HANDLE fileHandle, FILE_INFO_BY_HANDLE_CLASS infoClass, size_t allocationSize,
_Outptr_result_nullonfailure_ void **result)
_Outptr_result_maybenull_ void **result)
{
*result = nullptr;
@ -875,6 +985,36 @@ namespace wil
return S_OK;
}
// Verifies that the given file path is not a hard or a soft link. If the file is present at the path, returns
// a handle to it without delete permissions to block an attacker from swapping the file.
inline HRESULT CreateFileAndEnsureNotLinked(PCWSTR path, wil::unique_hfile& fileHandle)
{
// Open handles to the original path and to the final path and compare each file's information
// to verify they are the same file. If they are different, the file is a soft link.
fileHandle.reset(CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
RETURN_LAST_ERROR_IF(!fileHandle);
BY_HANDLE_FILE_INFORMATION fileInfo;
RETURN_IF_WIN32_BOOL_FALSE(GetFileInformationByHandle(fileHandle.get(), &fileInfo));
// Open a handle without the reparse point flag to get the final path in case it is a soft link.
wil::unique_hfile finalPathHandle(CreateFileW(path, 0, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr));
RETURN_LAST_ERROR_IF(!finalPathHandle);
BY_HANDLE_FILE_INFORMATION finalFileInfo;
RETURN_IF_WIN32_BOOL_FALSE(GetFileInformationByHandle(finalPathHandle.get(), &finalFileInfo));
finalPathHandle.reset();
// The low and high indices and volume serial number uniquely identify a file. These must match if they are the same file.
const bool isSoftLink =
((fileInfo.nFileIndexLow != finalFileInfo.nFileIndexLow) ||
(fileInfo.nFileIndexHigh != finalFileInfo.nFileIndexHigh) ||
(fileInfo.dwVolumeSerialNumber != finalFileInfo.dwVolumeSerialNumber));
// Return failure if it is a soft link or a hard link (number of links greater than 1).
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME), (isSoftLink || fileInfo.nNumberOfLinks > 1));
return S_OK;
}
#ifdef _CPPUNWIND
/** Get file information for a fixed sized structure, throws on failure.
~~~
@ -902,7 +1042,7 @@ namespace wil
return result;
}
#endif // _CPPUNWIND
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
#endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && (_WIN32_WINNT >= _WIN32_WINNT_WIN7)
}
#endif // __WIL_FILESYSTEM_INCLUDED