Use proper structure for FileFullDirectoryInformation in NTDLL hooks. (#84)

This commit is contained in:
Mikaël Capelle
2025-06-01 00:18:27 +02:00
committed by GitHub
parent eeee538780
commit de356fb2e4
3 changed files with 83 additions and 28 deletions
+28 -20
View File
@@ -41,9 +41,6 @@ DECLARE_HAS_FIELD(ShortName)
template <FILE_INFORMATION_CLASS fileInformationClass, class FileInformationClass>
struct FileInformationClassUtilsImpl
{
// minimum required size for the structure
std::size_t get_minimum_struct_size() { return sizeof(FileInformationClass); }
static void get_data(LPCVOID address, ULONG& offset, std::wstring& fileName)
{
const FileInformationClass* info =
@@ -101,7 +98,7 @@ struct FileInformationClassUtils<FileDirectoryInformation>
template <>
struct FileInformationClassUtils<FileFullDirectoryInformation>
: FileInformationClassUtilsImpl<FileFullDirectoryInformation,
FILE_ID_FULL_DIR_INFORMATION>
FILE_FULL_DIR_INFORMATION>
{};
template <>
struct FileInformationClassUtils<FileBothDirectoryInformation>
@@ -125,10 +122,6 @@ struct FileInformationClassUtils<FileNamesInformation>
: FileInformationClassUtilsImpl<FileNamesInformation, FILE_NAMES_INFORMATION>
{};
template <>
struct FileInformationClassUtils<FileAllInformation>
: FileInformationClassUtilsImpl<FileAllInformation, FILE_ALL_INFORMATION>
{};
template <>
struct FileInformationClassUtils<FileObjectIdInformation>
: FileInformationClassUtilsImpl<FileObjectIdInformation, FILE_OBJECTID_INFORMATION>
{};
@@ -183,6 +176,32 @@ struct FileInformationClassUtils<FileIdAllExtdBothDirectoryInformation>
FILE_ID_ALL_EXTD_BOTH_DIR_INFORMATION>
{};
// FILE_ALL_INFORMATION needs to be handled differently because it has a field that
// is itself a structure
template <>
struct FileInformationClassUtils<FileAllInformation>
{
static void get_data(LPCVOID address, ULONG& offset, std::wstring& fileName)
{
FileInformationClassUtils<FileNameInformation>::get_data(
&reinterpret_cast<const FILE_ALL_INFORMATION*>(address)->NameInformation,
offset, fileName);
}
static void set_offset(LPVOID address, ULONG offset)
{
// this is a no-op but it's consistent to do that everywhere
FileInformationClassUtils<FileNameInformation>::set_offset(
&reinterpret_cast<FILE_ALL_INFORMATION*>(address)->NameInformation, offset);
}
static void set_filename(LPVOID address, const std::wstring& fileName)
{
FileInformationClassUtils<FileNameInformation>::set_filename(
&reinterpret_cast<FILE_ALL_INFORMATION*>(address)->NameInformation, fileName);
}
};
} // namespace usvfs::details
#define _APP_FINFO_CASE(clazz, fn, ...) \
@@ -190,6 +209,7 @@ struct FileInformationClassUtils<FileIdAllExtdBothDirectoryInformation>
return usvfs::details::FileInformationClassUtils<clazz>::fn(__VA_ARGS__);
#define _APPLY_FILEINFO_FN(fn, ...) \
_APP_FINFO_CASE(FileAllInformation, fn, __VA_ARGS__) \
_APP_FINFO_CASE(FileDirectoryInformation, fn, __VA_ARGS__) \
_APP_FINFO_CASE(FileFullDirectoryInformation, fn, __VA_ARGS__) \
_APP_FINFO_CASE(FileBothDirectoryInformation, fn, __VA_ARGS__) \
@@ -197,7 +217,6 @@ struct FileInformationClassUtils<FileIdAllExtdBothDirectoryInformation>
_APP_FINFO_CASE(FileNameInformation, fn, __VA_ARGS__) \
_APP_FINFO_CASE(FileRenameInformation, fn, __VA_ARGS__) \
_APP_FINFO_CASE(FileNamesInformation, fn, __VA_ARGS__) \
_APP_FINFO_CASE(FileAllInformation, fn, __VA_ARGS__) \
_APP_FINFO_CASE(FileObjectIdInformation, fn, __VA_ARGS__) \
_APP_FINFO_CASE(FileReparsePointInformation, fn, __VA_ARGS__) \
_APP_FINFO_CASE(FileIdBothDirectoryInformation, fn, __VA_ARGS__) \
@@ -210,17 +229,6 @@ struct FileInformationClassUtils<FileIdAllExtdBothDirectoryInformation>
_APP_FINFO_CASE(FileIdAllExtdDirectoryInformation, fn, __VA_ARGS__) \
_APP_FINFO_CASE(FileIdAllExtdBothDirectoryInformation, fn, __VA_ARGS__)
// minimum required size for the structure
// std::size_t
// get_file_information_minimum_struct_size(FILE_INFORMATION_CLASS fileInformationClass)
// {
// switch (fileInformationClass) {
// _APPLY_FILEINFO_FN(get_minimum_struct_size, );
// default:
// return 0;
// }
// }
void GetFileInformationData(FILE_INFORMATION_CLASS fileInformationClass,
LPCVOID address, ULONG& offset, std::wstring& fileName)
{
+1 -1
View File
@@ -4,7 +4,7 @@ find_package(GTest CONFIG REQUIRED)
add_executable(tvfs_test main.cpp)
usvfs_set_test_properties(tvfs_test)
target_link_libraries(tvfs_test PRIVATE test_utils usvfs_helper GTest::gtest GTest::gtest_main)
target_link_libraries(tvfs_test PRIVATE test_utils usvfs_helper GTest::gtest GTest::gmock GTest::gtest_main)
usvfs_target_link_usvfs(tvfs_test)
# tvfs_test uses a private USVFS header so we need to include it manually
+54 -7
View File
@@ -25,12 +25,8 @@ along with usvfs. If not, see <http://www.gnu.org/licenses/>.
#include <test_helpers.h>
#pragma warning(push, 3)
#include <gtest/gtest.h>
#include <iostream>
#include <fstream>
#pragma warning(pop)
#include <iostream>
#include <inject.h>
#include <stringutils.h>
@@ -47,6 +43,9 @@ along with usvfs. If not, see <http://www.gnu.org/licenses/>.
#include <unicodestring.h>
#include <usvfs.h>
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
namespace spd = spdlog;
namespace ush = usvfs::shared;
@@ -299,7 +298,7 @@ HANDLE hooked_NtOpenFile(LPCWSTR path, ACCESS_MASK accessMask, ULONG shareAccess
string.Buffer = stringBuffer;
lstrcpyW(stringBuffer, L"\\??\\");
lstrcatW(stringBuffer, path);
string.Length = lstrlenW(stringBuffer) * 2;
string.Length = static_cast<USHORT>(lstrlenW(stringBuffer) * 2);
string.MaximumLength = BUFFER_SIZE;
attributes.ObjectName = &string;
@@ -365,6 +364,53 @@ TEST_F(USVFSTest, NtQueryDirectoryFileFindsVirtualFile)
usvfs::hook_NtClose(hdl);
}
TEST_F(USVFSTest, NtQueryDirectoryFileExVirtualFile)
{
auto params = defaultUsvfsParams();
std::unique_ptr<usvfs::HookContext> ctx(
usvfsCreateHookContext(*params, ::GetModuleHandle(nullptr)));
usvfs::RedirectionTreeContainer& tree = ctx->redirectionTable();
tree.addFile(L"C:\\0123456789.txt", usvfs::RedirectionDataLocal(REAL_FILEA));
tree.addFile(L"C:\\123456", usvfs::RedirectionDataLocal(REAL_FILEA));
tree.addFile(L"C:\\abcdef", usvfs::RedirectionDataLocal(REAL_FILEA));
tree.addFile(L"C:\\abcdefghijklmnopqrstuvwxyz.txt",
usvfs::RedirectionDataLocal(REAL_FILEA));
HANDLE hdl =
hooked_NtOpenFile(L"C:\\", FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
ASSERT_NE(INVALID_HANDLE_VALUE, hdl);
IO_STATUS_BLOCK status;
constexpr size_t BUFFER_SIZE = 2048;
char buffer[BUFFER_SIZE];
std::vector<std::wstring> foundFiles;
while (usvfs::hook_NtQueryDirectoryFileEx(
hdl, nullptr, nullptr, nullptr, &status, buffer, BUFFER_SIZE,
FileFullDirectoryInformation, 0, nullptr) == STATUS_SUCCESS) {
std::size_t offset = 0;
while (offset < BUFFER_SIZE) {
const auto* info = reinterpret_cast<FILE_FULL_DIR_INFORMATION*>(buffer + offset);
foundFiles.emplace_back(info->FileName, info->FileNameLength / sizeof(wchar_t));
if (info->NextEntryOffset == 0) {
break; // no more entries
}
offset += info->NextEntryOffset;
}
}
ASSERT_THAT(foundFiles,
::testing::IsSupersetOf({L"0123456789.txt", L"123456", L"abcdef",
L"abcdefghijklmnopqrstuvwxyz.txt"}));
usvfs::hook_NtClose(hdl);
}
TEST_F(USVFSTest, NtQueryObjectVirtualFile)
{
std::wstring c_drive_device;
@@ -405,6 +451,7 @@ TEST_F(USVFSTest, NtQueryObjectVirtualFile)
IO_STATUS_BLOCK status;
const auto res = usvfs::hook_NtQueryInformationFile(
hdl, &status, buffer, sizeof(buffer), FileNameInformation);
ASSERT_EQ(STATUS_SUCCESS, res);
ASSERT_EQ(STATUS_SUCCESS, status.Status);
FILE_NAME_INFORMATION* fileNameInfo =
@@ -471,7 +518,7 @@ TEST_F(USVFSTest, NtQueryObjectVirtualFile)
{
// expected length is sizeof struct + size of path (in bytes), including the
// null-character
const ULONG expectedLength =
const auto expectedLength =
sizeof(OBJECT_NAME_INFORMATION) + c_drive_device.size() * 2 + 12 + 2;
ULONG requiredLength;
NTSTATUS res;