Improvements to NtQueryDirectoryFileEx (#235)

This PR aims to:
- [Stub FileBasicInformation in
NtSetInformationFile](6d0ad1dd61)
- [Make sure a root `windows_path` have slash at the
end](02ed4fbb03),
this was necessary because `directory_iterator` fails when the path is
`C:` (without slash)..
- [Improve
NtQueryDirectoryFileEx](f6ec1fc9cc)
by adding filename filtering support, including more basic file
information in the struct, and fixing an issue that prevented the last
chunk of files from being enumerated correctly.
This commit is contained in:
Maurice Heumann
2025-04-25 08:32:47 +02:00
committed by GitHub
7 changed files with 204 additions and 26 deletions

View File

@@ -41,9 +41,7 @@ namespace utils
bool operator()(const std::string_view lhs, const std::string_view rhs) const
{
return std::ranges::equal(lhs, rhs, [](const char c1, const char c2) {
return string::char_to_lower(c1) == string::char_to_lower(c2);
});
return string::equals_ignore_case(lhs, rhs);
}
};

View File

@@ -149,4 +149,20 @@ namespace utils::string
return data;
}
template <class Elem, class Traits, class Alloc>
bool equals_ignore_case(const std::basic_string<Elem, Traits, Alloc>& lhs,
const std::basic_string<Elem, Traits, Alloc>& rhs)
{
return std::ranges::equal(lhs, rhs,
[](const auto c1, const auto c2) { return char_to_lower(c1) == char_to_lower(c2); });
}
template <class Elem, class Traits>
bool equals_ignore_case(const std::basic_string_view<Elem, Traits>& lhs,
const std::basic_string_view<Elem, Traits>& rhs)
{
return std::ranges::equal(lhs, rhs,
[](const auto c1, const auto c2) { return char_to_lower(c1) == char_to_lower(c2); });
}
}

View File

@@ -0,0 +1,99 @@
#pragma once
#include <string>
namespace utils::wildcard
{
inline bool is_wildcard(char16_t c)
{
return c == '*' || c == '?' || c == '>' || c == '<' || c == '\"';
}
inline bool has_wildcard(const std::u16string_view mask)
{
return std::ranges::any_of(mask, is_wildcard);
}
inline bool match_filename(std::u16string_view name, std::u16string_view mask)
{
if (mask.empty() || mask == u"*" || mask == u"*.*")
{
return true;
}
size_t name_pos = 0;
size_t mask_pos = 0;
size_t star_mask_pos = std::u16string_view::npos;
size_t star_name_pos = 0;
while (name_pos < name.size())
{
if (mask_pos < mask.size())
{
char16_t mask_char = mask[mask_pos];
char16_t name_char = name[name_pos];
bool char_matches = false;
if (mask_char == u'?' || mask_char == u'>')
{
char_matches = true;
}
else if (mask_char == u'"')
{
char_matches = name_char == u'.';
}
else
{
char_matches = string::char_to_lower(name_char) == string::char_to_lower(mask_char);
}
// Advance if current characters match
if (char_matches)
{
name_pos++;
mask_pos++;
continue;
}
// If this is a wildcard, skip all consecutive wildcards and save position for backtracking
if (mask[mask_pos] == u'*' || mask[mask_pos] == u'<')
{
mask_pos++;
while (mask_pos < mask.size() && (mask[mask_pos] == u'*' || mask[mask_pos] == u'<'))
{
mask_pos++;
}
if (mask_pos == mask.size())
{
// There is no need to continue because all that remained were star masks.
return true;
}
star_mask_pos = mask_pos;
star_name_pos = name_pos;
continue;
}
}
// The current characters didn't match...
// If we had a wildcard earlier, backtrack to it and try to match at the next position
if (star_mask_pos != std::u16string_view::npos)
{
mask_pos = star_mask_pos;
name_pos = ++star_name_pos;
continue;
}
return false;
}
// Skip any remaining wildcards in the mask
while (mask_pos < mask.size() && (mask[mask_pos] == u'*' || mask[mask_pos] == u'<'))
{
mask_pos++;
}
return mask_pos == mask.size();
}
}

View File

@@ -55,7 +55,14 @@ namespace syscalls
emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block,
uint64_t file_information, uint32_t length, uint32_t info_class,
ULONG query_flags,
emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> /*file_name*/);
emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> file_name);
NTSTATUS handle_NtQueryDirectoryFile(const syscall_context& c, handle file_handle, handle event_handle,
emulator_pointer /*PIO_APC_ROUTINE*/ apc_routine, emulator_pointer apc_context,
emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block,
uint64_t file_information, uint32_t length, uint32_t info_class,
BOOLEAN return_single_entry,
emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> file_name,
BOOLEAN restart_scan);
NTSTATUS handle_NtQueryInformationFile(const syscall_context& c, handle file_handle,
emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block,
uint64_t file_information, uint32_t length, uint32_t info_class);
@@ -831,6 +838,7 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
add_handler(NtSetInformationKey);
add_handler(NtUserGetKeyboardLayout);
add_handler(NtQueryDirectoryFileEx);
add_handler(NtQueryDirectoryFile);
add_handler(NtUserSystemParametersInfo);
add_handler(NtGetContextThread);
add_handler(NtYieldExecution);

View File

@@ -5,6 +5,7 @@
#include <iostream>
#include <utils/finally.hpp>
#include <utils/wildcard.hpp>
#include <sys/stat.h>
@@ -21,6 +22,11 @@ namespace syscalls
return STATUS_INVALID_HANDLE;
}
if (info_class == FileBasicInformation)
{
return STATUS_NOT_SUPPORTED;
}
if (info_class == FilePositionInformation)
{
if (!f->handle)
@@ -99,17 +105,27 @@ namespace syscalls
}
}
std::vector<file_entry> scan_directory(const std::filesystem::path& dir)
std::vector<file_entry> scan_directory(const std::filesystem::path& dir, const std::u16string_view file_mask)
{
std::vector<file_entry> files{
{"."},
{".."},
};
std::vector<file_entry> files{};
if (file_mask.empty() || file_mask == u"*")
{
files.emplace_back(file_entry{.file_path = ".", .is_directory = true});
files.emplace_back(file_entry{.file_path = "..", .is_directory = true});
}
for (const auto& file : std::filesystem::directory_iterator(dir))
{
if (!file_mask.empty() && !utils::wildcard::match_filename(file.path().filename().u16string(), file_mask))
{
continue;
}
files.emplace_back(file_entry{
.file_path = file.path().filename(),
.file_size = file.file_size(),
.is_directory = file.is_directory(),
});
}
@@ -120,12 +136,13 @@ namespace syscalls
NTSTATUS handle_file_enumeration(const syscall_context& c,
const emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block,
const uint64_t file_information, const uint32_t length, const ULONG query_flags,
file* f)
const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> file_mask, file* f)
{
if (!f->enumeration_state || query_flags & SL_RESTART_SCAN)
{
f->enumeration_state.emplace(file_enumeration_state{});
f->enumeration_state->files = scan_directory(c.win_emu.file_sys.translate(f->name));
f->enumeration_state->files = scan_directory(c.win_emu.file_sys.translate(f->name),
file_mask ? read_unicode_string(c.emu, file_mask) : u"");
}
auto& enum_state = *f->enumeration_state;
@@ -135,13 +152,13 @@ namespace syscalls
size_t current_index = enum_state.current_index;
if (current_index >= enum_state.files.size())
{
return STATUS_NO_MORE_FILES;
}
do
{
if (current_index >= enum_state.files.size())
{
break;
}
const auto new_offset = align_up(current_offset, 8);
const auto& current_file = enum_state.files[current_index];
const auto file_name = current_file.file_path.u16string();
@@ -175,7 +192,12 @@ namespace syscalls
info.NextEntryOffset = 0;
info.FileIndex = static_cast<ULONG>(current_index);
info.FileAttributes = FILE_ATTRIBUTE_NORMAL;
if (current_file.is_directory)
{
info.FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
}
info.FileNameLength = static_cast<ULONG>(file_name.size() * 2);
info.EndOfFile.QuadPart = current_file.file_size;
object.set_address(file_information + new_offset);
object.write(info);
@@ -184,7 +206,7 @@ namespace syscalls
++current_index;
current_offset = end_offset;
} while ((query_flags & SL_RETURN_SINGLE_ENTRY) == 0);
} while ((query_flags & SL_RETURN_SINGLE_ENTRY) == 0 && current_index < enum_state.files.size());
if ((query_flags & SL_NO_CURSOR_UPDATE) == 0)
{
@@ -195,7 +217,7 @@ namespace syscalls
block.Information = current_offset;
io_status_block.write(block);
return current_index < enum_state.files.size() ? STATUS_SUCCESS : STATUS_NO_MORE_FILES;
return current_index <= enum_state.files.size() ? STATUS_SUCCESS : STATUS_NO_MORE_FILES;
}
NTSTATUS handle_NtQueryDirectoryFileEx(
@@ -203,7 +225,7 @@ namespace syscalls
const emulator_pointer /*PIO_APC_ROUTINE*/ /*apc_routine*/, const emulator_pointer /*apc_context*/,
const emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block, const uint64_t file_information,
const uint32_t length, const uint32_t info_class, const ULONG query_flags,
const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> /*file_name*/)
const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> file_name)
{
auto* f = c.proc.files.get(file_handle);
if (!f || !f->is_directory())
@@ -214,19 +236,19 @@ namespace syscalls
if (info_class == FileDirectoryInformation)
{
return handle_file_enumeration<FILE_DIRECTORY_INFORMATION>(c, io_status_block, file_information, length,
query_flags, f);
query_flags, file_name, f);
}
if (info_class == FileFullDirectoryInformation)
{
return handle_file_enumeration<FILE_FULL_DIR_INFORMATION>(c, io_status_block, file_information, length,
query_flags, f);
query_flags, file_name, f);
}
if (info_class == FileBothDirectoryInformation)
{
return handle_file_enumeration<FILE_BOTH_DIR_INFORMATION>(c, io_status_block, file_information, length,
query_flags, f);
query_flags, file_name, f);
}
c.win_emu.log.error("Unsupported query directory file info class: %X\n", info_class);
@@ -235,6 +257,28 @@ namespace syscalls
return STATUS_NOT_SUPPORTED;
}
NTSTATUS handle_NtQueryDirectoryFile(const syscall_context& c, const handle file_handle, const handle event_handle,
const emulator_pointer /*PIO_APC_ROUTINE*/ apc_routine,
const emulator_pointer apc_context,
const emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block,
const uint64_t file_information, const uint32_t length,
const uint32_t info_class, const BOOLEAN return_single_entry,
const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> file_name,
const BOOLEAN restart_scan)
{
ULONG query_flags = 0;
if (return_single_entry)
{
query_flags |= SL_RETURN_SINGLE_ENTRY;
}
if (restart_scan)
{
query_flags |= SL_RESTART_SCAN;
}
return handle_NtQueryDirectoryFileEx(c, file_handle, event_handle, apc_routine, apc_context, io_status_block,
file_information, length, info_class, query_flags, file_name);
}
NTSTATUS handle_NtQueryInformationFile(
const syscall_context& c, const handle file_handle,
const emulator_object<IO_STATUS_BLOCK<EmulatorTraits<Emu64>>> io_status_block, const uint64_t file_information,
@@ -599,21 +643,24 @@ namespace syscalls
printer.cancel();
if (f.name.ends_with(u"\\") || create_options & FILE_DIRECTORY_FILE)
const windows_path path = f.name;
const bool is_directory = std::filesystem::is_directory(c.win_emu.file_sys.translate(path));
if (is_directory || create_options & FILE_DIRECTORY_FILE)
{
c.win_emu.log.print(color::dark_gray, "--> Opening folder: %s\n", u16_to_u8(f.name).c_str());
if (create_disposition & FILE_CREATE)
{
std::error_code ec{};
create_directory(c.win_emu.file_sys.translate(f.name), ec);
create_directory(c.win_emu.file_sys.translate(path), ec);
if (ec)
{
return STATUS_ACCESS_DENIED;
}
}
else if (!is_directory(c.win_emu.file_sys.translate(f.name)))
else if (!is_directory)
{
return STATUS_OBJECT_NAME_NOT_FOUND;
}
@@ -626,7 +673,6 @@ namespace syscalls
c.win_emu.log.print(color::dark_gray, "--> Opening file: %s\n", u16_to_u8(f.name).c_str());
const windows_path path = f.name;
std::u16string mode = map_mode(desired_access, create_disposition);
if (mode.empty() || path.is_relative())

View File

@@ -94,15 +94,21 @@ struct mutant : ref_counted_object
struct file_entry
{
std::filesystem::path file_path{};
uint64_t file_size{};
bool is_directory{};
void serialize(utils::buffer_serializer& buffer) const
{
buffer.write(this->file_path);
buffer.write(this->file_size);
buffer.write(this->is_directory);
}
void deserialize(utils::buffer_deserializer& buffer)
{
buffer.read(this->file_path);
buffer.read(this->file_size);
buffer.read(this->is_directory);
}
};

View File

@@ -117,6 +117,11 @@ class windows_path
path.append(folder);
}
if (this->is_absolute() && this->folders_.empty())
{
path.push_back(u'\\');
}
return path;
}