Skip to content

Commit

Permalink
Start implementing NtQueryObject.
Browse files Browse the repository at this point in the history
  • Loading branch information
Holt59 committed Jun 23, 2024
1 parent db45b83 commit 57a119d
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 19 deletions.
10 changes: 10 additions & 0 deletions src/shared/formatters.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ struct std::formatter<PCUNICODE_STRING, char> : std::formatter<std::string, char
}
};

template <>
struct std::formatter<UNICODE_STRING, char> : std::formatter<std::string, char>
{
template <class FmtContext>
FmtContext::iterator format(UNICODE_STRING v, FmtContext& ctx) const
{
return std::formatter<std::string, char>::format(usvfs::log::to_string(&v), ctx);
}
};

template <class Pointer>
requires (std::is_pointer_v<Pointer>
&& !std::is_same_v<Pointer, const void*>
Expand Down
15 changes: 11 additions & 4 deletions src/shared/ntdll_declarations.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,11 @@ typedef struct _FILE_REPARSE_POINT_INFORMATION {
} FILE_REPARSE_POINT_INFORMATION, *PFILE_REPARSE_POINT_INFORMATION;

// copied from ntstatus.h
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L)
#define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L)
#define STATUS_NO_SUCH_FILE ((NTSTATUS)0xC000000FL)
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L)
#define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define STATUS_NO_SUCH_FILE ((NTSTATUS)0xC000000FL)

#define SL_RESTART_SCAN 0x01
#define SL_RETURN_SINGLE_ENTRY 0x02
Expand Down Expand Up @@ -324,9 +325,15 @@ typedef struct _OBJECT_HANDLE_INFORMATION {
ACCESS_MASK GrantedAccess;
} OBJECT_HANDLE_INFORMATION, *POBJECT_HANDLE_INFORMATION;

typedef struct _OBJECT_NAME_INFORMATION
{
UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;

typedef enum _OBJECT_INFORMATION_CLASS
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2
} OBJECT_INFORMATION_CLASS;

Expand Down
73 changes: 68 additions & 5 deletions src/usvfs_dll/hooks/ntdll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1068,9 +1068,66 @@ DLLEXPORT NTSTATUS WINAPI usvfs::hook_NtQueryObject(
ObjectInformationLength, ReturnLength);
POST_REALCALL

LOG_CALL()
.addParam("path", ntdllHandleTracker.lookup(Handle))
.PARAM(ObjectInformationClass);
if (res == STATUS_SUCCESS && (ObjectInformationClass == ObjectNameInformation)) {
const auto trackerInfo = ntdllHandleTracker.lookup(Handle);
const auto redir = applyReroute(READ_CONTEXT(), callContext, trackerInfo);

OBJECT_NAME_INFORMATION* info =
reinterpret_cast<OBJECT_NAME_INFORMATION*>(ObjectInformation);

if (redir.redirected) {
// https://learn.microsoft.com/en-us/windows/win32/fileio/displaying-volume-paths
//

// TODO: is that always true?
// path should start with \??\X: - we need to replace this by device name
//
WCHAR deviceName[MAX_PATH];
std::wstring buffer(static_cast<LPCWSTR>(trackerInfo));
buffer[6] = L'\0';

const auto charCount = QueryDosDeviceW(buffer.data() + 4, deviceName, ARRAYSIZE(deviceName));

buffer =
std::wstring(deviceName) + L'\\' + std::wstring(buffer.data() + 7, buffer.size() - 7);

// TODO: check this...
if (ObjectInformationLength < buffer.size() * 2 + sizeof(OBJECT_NAME_INFORMATION)) {
res = STATUS_INFO_LENGTH_MISMATCH;

if (ReturnLength) {
*ReturnLength = buffer.size() * 2 + sizeof(OBJECT_NAME_INFORMATION);
}
} else {
// fill the object with 0 - not sure if mandatory
memset(ObjectInformation, L'\0', ObjectInformationLength);

// put the unicode buffer at the end of the object
const auto unicodeBufferLength =
ObjectInformationLength - sizeof(OBJECT_NAME_INFORMATION);
LPWSTR unicodeBuffer = reinterpret_cast<LPWSTR>(
static_cast<LPSTR>(ObjectInformation) + sizeof(OBJECT_NAME_INFORMATION));

// copy the path into the buffer
wmemcpy(unicodeBuffer, buffer.data(), buffer.size());

// update the actual unicode string
info->Name.Buffer = unicodeBuffer;
info->Name.Length = buffer.size() * 2;
info->Name.MaximumLength = unicodeBufferLength;
}
}

LOG_CALL()
.PARAMWRAP(res)
.PARAM(ObjectInformationLength)
.addParam("return_length", ReturnLength ? *ReturnLength : -1)
.addParam("tracker_path", trackerInfo)
.PARAM(ObjectInformationClass)
.PARAM(redir.redirected)
.PARAM(redir.path)
.addParam("name_info", info->Name);
}

HOOK_END
return res;
Expand Down Expand Up @@ -1112,15 +1169,21 @@ DLLEXPORT NTSTATUS WINAPI usvfs::hook_NtQueryInformationFile(

if (redir.redirected)
{
SetInfoFilename(FileInformation, FileInformationClass, static_cast<LPCWSTR>(redir.path));
LPCWSTR filenameFixed = static_cast<LPCWSTR>(trackerInfo);
if (info->FileName[0] == L'\\') {
// strip the \??\X: prefix (X being the drive name)
filenameFixed = filenameFixed + 6;
}
SetInfoFilename(FileInformation, FileInformationClass, filenameFixed);
};

LOG_CALL()
.PARAMWRAP(res)
.addParam("tracker_path", trackerInfo)
.PARAM(FileInformationClass)
.PARAM(redir.redirected)
.PARAM(redir.path)
.addParam("name_info", std::wstring{info->FileName, info->FileNameLength});
.addParam("name_info", std::wstring{info->FileName, info->FileNameLength / sizeof(WCHAR)});

}

Expand Down
66 changes: 61 additions & 5 deletions test/tvfs_test/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,8 @@ HANDLE hooked_NtOpenFile(LPCWSTR path, ACCESS_MASK accessMask, ULONG shareAccess
attributes.ObjectName = &string;

HANDLE ret = INVALID_HANDLE_VALUE;
if (usvfs::hook_NtOpenFile(&ret, accessMask | SYNCHRONIZE,
&attributes, &statusBlock, shareAccess,
openOptions | FILE_SYNCHRONOUS_IO_NONALERT) != STATUS_SUCCESS)
if (usvfs::hook_NtOpenFile(&ret, accessMask, &attributes,
&statusBlock, shareAccess, openOptions) != STATUS_SUCCESS)
{
return INVALID_HANDLE_VALUE;
}
Expand All @@ -310,7 +309,7 @@ TEST_F(USVFSTest, NtQueryDirectoryFileRegularFile)
L"C:\\"
, FILE_GENERIC_READ
, FILE_SHARE_READ | FILE_SHARE_WRITE
, OPEN_EXISTING);
, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
ASSERT_NE(INVALID_HANDLE_VALUE, hdl);

IO_STATUS_BLOCK status;
Expand All @@ -329,6 +328,8 @@ TEST_F(USVFSTest, NtQueryDirectoryFileRegularFile)
, TRUE);

ASSERT_EQ(STATUS_SUCCESS, status.Status);

usvfs::hook_NtClose(hdl);
}

TEST_F(USVFSTest, NtQueryDirectoryFileFindsVirtualFile)
Expand All @@ -343,7 +344,7 @@ TEST_F(USVFSTest, NtQueryDirectoryFileFindsVirtualFile)
L"C:\\"
, FILE_GENERIC_READ
, FILE_SHARE_READ | FILE_SHARE_WRITE
, OPEN_EXISTING);
, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
ASSERT_NE(INVALID_HANDLE_VALUE, hdl);

IO_STATUS_BLOCK status;
Expand All @@ -366,8 +367,63 @@ TEST_F(USVFSTest, NtQueryDirectoryFileFindsVirtualFile)
FILE_DIRECTORY_INFORMATION *info = reinterpret_cast<FILE_DIRECTORY_INFORMATION*>(buffer);
ASSERT_EQ(STATUS_SUCCESS, status.Status);
ASSERT_EQ(0, wcscmp(info->FileName, L"np.exe"));

usvfs::hook_NtClose(hdl);
}

TEST_F(USVFSTest, NtQueryObjectVirtualFile)
{
auto params = defaultUsvfsParams();
std::unique_ptr<usvfs::HookContext> ctx(
usvfsCreateHookContext(*params, ::GetModuleHandle(nullptr)));
usvfs::RedirectionTreeContainer& tree = ctx->redirectionTable();

tree.addFile(L"C:\\np.exe", usvfs::RedirectionDataLocal(REAL_FILEA));

HANDLE hdl = hooked_NtOpenFile(L"C:\\np.exe"
, FILE_GENERIC_READ
, FILE_SHARE_READ | FILE_SHARE_WRITE
, FILE_NON_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT);
ASSERT_NE(INVALID_HANDLE_VALUE, hdl) << "last error=" << ::GetLastError();

{
char buffer[1024];
IO_STATUS_BLOCK status;
const auto res = usvfs::hook_NtQueryInformationFile(
hdl, &status, buffer, sizeof(buffer), FileNameInformation);
ASSERT_EQ(STATUS_SUCCESS, status.Status);

FILE_NAME_INFORMATION* fileNameInfo =
reinterpret_cast<FILE_NAME_INFORMATION*>(buffer);
ASSERT_EQ(0, wcscmp(fileNameInfo->FileName, L"\\np.exe"));
}

{
char buffer[1024];
IO_STATUS_BLOCK status;
const auto res = usvfs::hook_NtQueryInformationFile(
hdl, &status, buffer, sizeof(buffer), FileNormalizedNameInformation);
ASSERT_EQ(STATUS_SUCCESS, status.Status);

FILE_NAME_INFORMATION* fileNameInfo =
reinterpret_cast<FILE_NAME_INFORMATION*>(buffer);
ASSERT_EQ(0, wcscmp(fileNameInfo->FileName, L"\\np.exe"));
}

{
char buffer[2048];
const auto res = usvfs::hook_NtQueryObject(hdl, ObjectNameInformation, buffer,
sizeof(buffer), nullptr);
ASSERT_EQ(STATUS_SUCCESS, res);

OBJECT_NAME_INFORMATION *information = reinterpret_cast<OBJECT_NAME_INFORMATION*>(buffer);
ASSERT_EQ(L"\\Device\\HarddiskVolume3\\np.exe", std::wstring(information->Name.Buffer, information->Name.Length / sizeof(wchar_t)));
}

usvfs::hook_NtClose(hdl);
}


TEST_F(USVFSTestAuto, CannotCreateLinkToFileInNonexistantDirectory)
{
ASSERT_EQ(FALSE, usvfsVirtualLinkFile(REAL_FILEW, L"c:/this_directory_shouldnt_exist/np.exe", FALSE));
Expand Down
43 changes: 43 additions & 0 deletions test/usvfs_global_test/usvfs_global_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,28 @@ class
const auto& data() const { return m_data; }
} parameters;

// simple guard for handle
class HandleGuard
{
HANDLE m_handle = INVALID_HANDLE_VALUE;

public:
HandleGuard() = default;
HandleGuard(HANDLE handle) : m_handle{handle} {}

~HandleGuard() { close(); }

operator HANDLE() { return m_handle; }

void close()
{
if (m_handle != INVALID_HANDLE_VALUE) {
::CloseHandle(m_handle);
m_handle = INVALID_HANDLE_VALUE;
}
}
};

// simple function to write content to a specified path
void write_content(const std::filesystem::path& path, const std::string_view content)
{
Expand All @@ -55,6 +77,27 @@ TEST(BasicTest, SimpleTest)
ASSERT_TRUE(exists(data / "info.txt"));
remove(data / "info.txt");
ASSERT_FALSE(exists(data / "info.txt"));

{
const auto doc_txt = data / "docs" / "doc.txt";
HandleGuard hdl = CreateFileW(doc_txt.c_str(), FILE_READ_ATTRIBUTES, 0, nullptr,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, (HANDLE)hdl);

WCHAR filepath[1024];
const auto length = GetFinalPathNameByHandleW(
hdl, filepath, sizeof(filepath) / sizeof(WCHAR), FILE_NAME_NORMALIZED);
const auto lastError = ::GetLastError();
ASSERT_NE(0, length) << "last error=" << ::GetLastError();

// we need to construct a new path because the format returned by
// GetFinalPathNameByHandleW is not really standardized (or is it?)

// TODO: more tests for this

ASSERT_EQ(data / "docs" / "doc.txt",
canonical(std::filesystem::path(std::wstring(filepath, length))));
}
}

// see https://github.com/ModOrganizer2/modorganizer/issues/2039 for context
Expand Down
9 changes: 8 additions & 1 deletion test/usvfs_global_test_runner/usvfs_global_test_fixture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ DWORD spawn_usvfs_hooked_process(
return exit;
}

bool UsvfsGlobalTest::m_force_usvfs_logs = false;

void UsvfsGlobalTest::ForceUsvfsLogs()
{
m_force_usvfs_logs = true;
}

class UsvfsGlobalTest::UsvfsGuard
{
public:
Expand Down Expand Up @@ -183,7 +190,7 @@ int UsvfsGlobalTest::Run() const
L"--gtest_brief=1", m_data_folder.native()});

// TODO: try to do this with gtest itself?
if (res != 0) {
if (m_force_usvfs_logs || res != 0) {
const auto log_path = test::path_of_test_bin(m_group + L".log");
std::ofstream os{log_path};
std::string buffer(1024, '\0');
Expand Down
14 changes: 10 additions & 4 deletions test/usvfs_global_test_runner/usvfs_global_test_fixture.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@

class UsvfsGlobalTest : public testing::Test
{
public:
// enable log mode - this will generate USVFS log file for all tests regardless of
// success or failure (default is to only generate for failure)
//
static void ForceUsvfsLogs();

public:
UsvfsGlobalTest();

void SetUp() override
{
PrepareFileSystem();
}
void SetUp() override { PrepareFileSystem(); }

void TearDown() override { CleanUp(); }

Expand Down Expand Up @@ -42,6 +45,9 @@ class UsvfsGlobalTest : public testing::Test
private:
class UsvfsGuard;

// always generate usvfs logs
static bool m_force_usvfs_logs;

// prepare the filesystem by copying files and folders from the relevant fixtures
// folder to the temporary folder
//
Expand Down
2 changes: 2 additions & 0 deletions test/usvfs_global_test_runner/usvfs_global_test_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ int main(int argc, char* argv[])

testing::InitGoogleTest(&argc, argv);

UsvfsGlobalTest::ForceUsvfsLogs();

usvfsInitLogging(false);

return RUN_ALL_TESTS();
Expand Down

0 comments on commit 57a119d

Please sign in to comment.