Get the tests running inside the WOW64 environment on pre-Win7 OSs
Prior to Windows 7, the undocumented NtQuerySystemInformation API did not return valid handle information when called by a WOW64 process. To work around this problem, the 32-bit test program connects to a 64-bit worker and uses the existing RPC mechanism, issuing LookupKernelObject commands, which call NtQuerySystemInformation in a native 64-bit environment. Even on Windows 7, the NtQuerySystemInformation API returns truncated kernel object pointers, so, AFAIK, we still need to do this work. Maybe not, though, if the lower 32 bits are guaranteed to be unique. On Windows 10, there is a CompareObjectHandles API that works in WOW64 mode.
This commit is contained in:
parent
72c5ea52fd
commit
7c12d90086
@ -4,6 +4,7 @@
|
||||
#include "Spawn.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
struct Command {
|
||||
enum Kind {
|
||||
@ -26,6 +27,8 @@ struct Command {
|
||||
GetStdin,
|
||||
GetStderr,
|
||||
GetStdout,
|
||||
Hello,
|
||||
LookupKernelObject,
|
||||
NewBuffer,
|
||||
OpenConin,
|
||||
OpenConout,
|
||||
@ -43,7 +46,17 @@ struct Command {
|
||||
WriteText,
|
||||
};
|
||||
|
||||
// These fields must appear first so that the LookupKernelObject RPC will
|
||||
// work. This RPC occurs from 32-bit test programs to a 64-bit worker.
|
||||
// In that case, most of this struct's fields do not have the same
|
||||
// addresses or sizes.
|
||||
Kind kind;
|
||||
struct {
|
||||
uint32_t pid;
|
||||
uint32_t handle[2];
|
||||
uint32_t kernelObject[2];
|
||||
} lookupKernelObject;
|
||||
|
||||
HANDLE handle;
|
||||
HANDLE targetProcess;
|
||||
DWORD dword;
|
||||
|
@ -58,3 +58,17 @@ std::vector<SYSTEM_HANDLE_ENTRY> queryNtHandles() {
|
||||
std::copy(info.Handle, info.Handle + info.Count, ret.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Get the ObjectPointer (underlying NT object) for the NT handle.
|
||||
void *ntHandlePointer(const std::vector<SYSTEM_HANDLE_ENTRY> &table,
|
||||
DWORD pid, HANDLE h) {
|
||||
HANDLE ret = nullptr;
|
||||
for (auto &entry : table) {
|
||||
if (entry.OwnerPid == pid &&
|
||||
entry.HandleValue == reinterpret_cast<uint64_t>(h)) {
|
||||
ASSERT(ret == nullptr);
|
||||
ret = entry.ObjectPointer;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -19,3 +19,5 @@ typedef struct _SYSTEM_HANDLE_INFORMATION {
|
||||
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
|
||||
|
||||
std::vector<SYSTEM_HANDLE_ENTRY> queryNtHandles();
|
||||
void *ntHandlePointer(const std::vector<SYSTEM_HANDLE_ENTRY> &table,
|
||||
DWORD pid, HANDLE h);
|
||||
|
@ -46,7 +46,7 @@ RemoteWorker::RemoteWorker(SpawnParams params) : RemoteWorker(DoNotSpawn) {
|
||||
m_valid = true;
|
||||
// Perform an RPC just to ensure that the worker process is ready, and
|
||||
// the console exists, before returning.
|
||||
rpc(Command::GetStdin);
|
||||
rpc(Command::Hello);
|
||||
}
|
||||
|
||||
RemoteWorker RemoteWorker::child(SpawnParams params) {
|
||||
@ -77,7 +77,7 @@ RemoteWorker RemoteWorker::tryChild(SpawnParams params, SpawnFailure *failure) {
|
||||
ret.m_valid = true;
|
||||
// Perform an RPC just to ensure that the worker process is ready, and
|
||||
// the console exists, before returning.
|
||||
ret.rpc(Command::GetStdin);
|
||||
ret.rpc(Command::Hello);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -148,6 +148,16 @@ std::vector<DWORD> RemoteWorker::consoleProcessList() {
|
||||
&cmd().u.processList[count]);
|
||||
}
|
||||
|
||||
uint64_t RemoteWorker::lookupKernelObject(DWORD pid, HANDLE h) {
|
||||
const uint64_t h64 = reinterpret_cast<uint64_t>(h);
|
||||
cmd().lookupKernelObject.pid = pid;
|
||||
memcpy(&cmd().lookupKernelObject.handle, &h64, sizeof(h64));
|
||||
rpc(Command::LookupKernelObject);
|
||||
uint64_t ret;
|
||||
memcpy(&ret, &cmd().lookupKernelObject.kernelObject, sizeof(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RemoteWorker::rpc(Command::Kind kind) {
|
||||
rpcImpl(kind);
|
||||
m_finishEvent.wait();
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
class RemoteWorker {
|
||||
friend class RemoteHandle;
|
||||
friend uint64_t wow64LookupKernelObject(DWORD pid, HANDLE handle);
|
||||
static DWORD dwDefaultCreationFlags;
|
||||
|
||||
public:
|
||||
@ -110,7 +111,10 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Command &cmd() { return m_parcel.value(); }
|
||||
uint64_t lookupKernelObject(DWORD pid, HANDLE h);
|
||||
|
||||
private:
|
||||
Command &cmd() { return m_parcel.value()[0]; }
|
||||
void rpc(Command::Kind kind);
|
||||
void rpcAsync(Command::Kind kind);
|
||||
void rpcImpl(Command::Kind kind);
|
||||
@ -118,7 +122,16 @@ private:
|
||||
private:
|
||||
bool m_valid = false;
|
||||
std::string m_name;
|
||||
ShmemParcelTyped<Command> m_parcel;
|
||||
|
||||
// HACK: Use Command[2] instead of Command. To accommodate WOW64, we need
|
||||
// to have a 32-bit test program communicate with a 64-bit worker to query
|
||||
// kernel handles. The sizes of the parcels will not match, but it's
|
||||
// mostly OK as long as the creation size is larger than the open size, and
|
||||
// the 32-bit program creates the parcel. In principle, a better fix might
|
||||
// be to use parcels of different sizes or make the Command struct's size
|
||||
// independent of architecture, but those changes are hard.
|
||||
ShmemParcelTyped<Command[2]> m_parcel;
|
||||
|
||||
Event m_startEvent;
|
||||
Event m_finishEvent;
|
||||
HANDLE m_process = NULL;
|
||||
|
@ -23,7 +23,11 @@ static std::vector<wchar_t> wstrToWVector(const std::wstring &str) {
|
||||
HANDLE spawn(const std::string &workerName,
|
||||
const SpawnParams ¶ms,
|
||||
SpawnFailure &error) {
|
||||
auto workerPath = pathDirName(getModuleFileName(NULL)) + "\\Worker.exe";
|
||||
auto exeBaseName = (isWow64() && params.nativeWorkerBitness)
|
||||
? "Worker64.exe" // always 64-bit binary, used to escape WOW64 environ
|
||||
: "Worker.exe"; // 32 or 64-bit binary, same arch as test program
|
||||
auto workerPath =
|
||||
pathDirName(getModuleFileName(NULL)) + "\\" + exeBaseName;
|
||||
const std::wstring workerPathWStr = widenString(workerPath);
|
||||
const std::string cmdLine = "\"" + workerPath + "\" " + workerName;
|
||||
auto cmdLineWVec = wstrToWVector(widenString(cmdLine));
|
||||
|
@ -13,6 +13,7 @@ struct SpawnParams {
|
||||
static const size_t NoInheritList = static_cast<size_t>(~0);
|
||||
size_t inheritCount = NoInheritList;
|
||||
std::array<HANDLE, 1024> inheritList = {};
|
||||
bool nativeWorkerBitness = false;
|
||||
|
||||
SpawnParams(bool bInheritHandles=false, DWORD dwCreationFlags=0) :
|
||||
bInheritHandles(bInheritHandles),
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "RemoteWorker.h"
|
||||
#include "TestUtil.h"
|
||||
#include "UnicodeConversions.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <DebugClient.h>
|
||||
#include <WinptyAssert.h>
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "RemoteHandle.h"
|
||||
#include "RemoteWorker.h"
|
||||
#include "UnicodeConversions.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <DebugClient.h>
|
||||
#include <OsModule.h>
|
||||
@ -39,21 +40,7 @@ RegistrationTable registeredTests() {
|
||||
return *g_testFunctions;
|
||||
}
|
||||
|
||||
// Get the ObjectPointer (underlying NT object) for the NT handle.
|
||||
void *ntHandlePointer(const std::vector<SYSTEM_HANDLE_ENTRY> &table,
|
||||
RemoteHandle h) {
|
||||
HANDLE ret = nullptr;
|
||||
for (auto &entry : table) {
|
||||
if (entry.OwnerPid == h.worker().pid() &&
|
||||
entry.HandleValue == h.uvalue()) {
|
||||
ASSERT(ret == nullptr);
|
||||
ret = entry.ObjectPointer;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool hasBuiltinCompareObjectHandles() {
|
||||
static bool hasBuiltinCompareObjectHandles() {
|
||||
static auto kernelbase = LoadLibraryW(L"KernelBase.dll");
|
||||
if (kernelbase != nullptr) {
|
||||
static auto proc = GetProcAddress(kernelbase, "CompareObjectHandles");
|
||||
@ -64,53 +51,79 @@ bool hasBuiltinCompareObjectHandles() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool compareObjectHandles(RemoteHandle h1, RemoteHandle h2) {
|
||||
if (hasBuiltinCompareObjectHandles()) {
|
||||
static OsModule kernelbase(L"KernelBase.dll");
|
||||
static auto comp =
|
||||
reinterpret_cast<BOOL(WINAPI*)(HANDLE,HANDLE)>(
|
||||
kernelbase.proc("CompareObjectHandles"));
|
||||
ASSERT(comp != nullptr);
|
||||
HANDLE h1local = nullptr;
|
||||
HANDLE h2local = nullptr;
|
||||
bool dup1 = DuplicateHandle(
|
||||
h1.worker().processHandle(),
|
||||
h1.value(),
|
||||
GetCurrentProcess(),
|
||||
&h1local,
|
||||
0, false, DUPLICATE_SAME_ACCESS);
|
||||
bool dup2 = DuplicateHandle(
|
||||
h2.worker().processHandle(),
|
||||
h2.value(),
|
||||
GetCurrentProcess(),
|
||||
&h2local,
|
||||
0, false, DUPLICATE_SAME_ACCESS);
|
||||
bool ret = dup1 && dup2 && comp(h1local, h2local);
|
||||
if (dup1) {
|
||||
CloseHandle(h1local);
|
||||
}
|
||||
if (dup2) {
|
||||
CloseHandle(h2local);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
auto table = queryNtHandles();
|
||||
return ntHandlePointer(table, h1) == ntHandlePointer(table, h2);
|
||||
static bool needsWow64HandleLookup() {
|
||||
// The Worker.exe and the test programs must always be the same bitness.
|
||||
// However, in WOW64 mode, prior to Windows 7 64-bit, the WOW64 version of
|
||||
// NtQuerySystemInformation returned almost no handle information. Even
|
||||
// in Windows 7, the pointers are truncated to 32-bits, so for maximum
|
||||
// reliability, use the RPC technique there too. Windows 10 has a proper
|
||||
// API.
|
||||
static bool value = isWow64();
|
||||
return value;
|
||||
}
|
||||
|
||||
static RemoteWorker makeLookupWorker() {
|
||||
SpawnParams sp(false, DETACHED_PROCESS);
|
||||
sp.nativeWorkerBitness = true;
|
||||
return RemoteWorker(sp);
|
||||
}
|
||||
|
||||
uint64_t wow64LookupKernelObject(DWORD pid, HANDLE handle) {
|
||||
static auto lookupWorker = makeLookupWorker();
|
||||
return lookupWorker.lookupKernelObject(pid, handle);
|
||||
}
|
||||
|
||||
static bool builtinCompareObjectHandles(RemoteHandle h1, RemoteHandle h2) {
|
||||
static OsModule kernelbase(L"KernelBase.dll");
|
||||
static auto comp =
|
||||
reinterpret_cast<BOOL(WINAPI*)(HANDLE,HANDLE)>(
|
||||
kernelbase.proc("CompareObjectHandles"));
|
||||
ASSERT(comp != nullptr);
|
||||
HANDLE h1local = nullptr;
|
||||
HANDLE h2local = nullptr;
|
||||
bool dup1 = DuplicateHandle(
|
||||
h1.worker().processHandle(),
|
||||
h1.value(),
|
||||
GetCurrentProcess(),
|
||||
&h1local,
|
||||
0, false, DUPLICATE_SAME_ACCESS);
|
||||
bool dup2 = DuplicateHandle(
|
||||
h2.worker().processHandle(),
|
||||
h2.value(),
|
||||
GetCurrentProcess(),
|
||||
&h2local,
|
||||
0, false, DUPLICATE_SAME_ACCESS);
|
||||
bool ret = dup1 && dup2 && comp(h1local, h2local);
|
||||
if (dup1) {
|
||||
CloseHandle(h1local);
|
||||
}
|
||||
if (dup2) {
|
||||
CloseHandle(h2local);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool compareObjectHandles(RemoteHandle h1, RemoteHandle h2) {
|
||||
ObjectSnap snap;
|
||||
return snap.eq(h1, h2);
|
||||
}
|
||||
|
||||
ObjectSnap::ObjectSnap() {
|
||||
if (!hasBuiltinCompareObjectHandles()) {
|
||||
if (!hasBuiltinCompareObjectHandles() && !needsWow64HandleLookup()) {
|
||||
m_table = queryNtHandles();
|
||||
m_hasTable = true;
|
||||
}
|
||||
}
|
||||
|
||||
void *ObjectSnap::object(RemoteHandle h) {
|
||||
uint64_t ObjectSnap::object(RemoteHandle h) {
|
||||
if (needsWow64HandleLookup()) {
|
||||
return wow64LookupKernelObject(h.worker().pid(), h.value());
|
||||
}
|
||||
if (!m_hasTable) {
|
||||
m_table = queryNtHandles();
|
||||
}
|
||||
return ntHandlePointer(m_table, h);
|
||||
return reinterpret_cast<uint64_t>(ntHandlePointer(
|
||||
m_table, h.worker().pid(), h.value()));
|
||||
}
|
||||
|
||||
bool ObjectSnap::eq(std::initializer_list<RemoteHandle> handles) {
|
||||
@ -119,14 +132,14 @@ bool ObjectSnap::eq(std::initializer_list<RemoteHandle> handles) {
|
||||
}
|
||||
if (hasBuiltinCompareObjectHandles()) {
|
||||
for (auto i = handles.begin() + 1; i < handles.end(); ++i) {
|
||||
if (!compareObjectHandles(*handles.begin(), *i)) {
|
||||
if (!builtinCompareObjectHandles(*handles.begin(), *i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
HANDLE first = ntHandlePointer(m_table, *handles.begin());
|
||||
auto first = object(*handles.begin());
|
||||
for (auto i = handles.begin() + 1; i < handles.end(); ++i) {
|
||||
if (first != ntHandlePointer(m_table, *i)) {
|
||||
if (first != object(*i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -53,17 +53,13 @@ using RegistrationTable = std::vector<std::tuple<std::string, bool(*)(), void(*)
|
||||
RegistrationTable registeredTests();
|
||||
inline bool always() { return true; }
|
||||
|
||||
// NT kernel handle query
|
||||
void *ntHandlePointer(const std::vector<SYSTEM_HANDLE_ENTRY> &table,
|
||||
RemoteHandle h);
|
||||
bool hasBuiltinCompareObjectHandles();
|
||||
bool compareObjectHandles(RemoteHandle h1, RemoteHandle h2);
|
||||
|
||||
// NT kernel handle->object snapshot
|
||||
class ObjectSnap {
|
||||
public:
|
||||
ObjectSnap();
|
||||
void *object(RemoteHandle h);
|
||||
uint64_t object(RemoteHandle h);
|
||||
bool eq(std::initializer_list<RemoteHandle> handles);
|
||||
bool eq(RemoteHandle h1, RemoteHandle h2) { return eq({h1, h2}); }
|
||||
private:
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include <sstream>
|
||||
|
||||
#include "UnicodeConversions.h"
|
||||
|
||||
#include <OsModule.h>
|
||||
#include <WinptyAssert.h>
|
||||
|
||||
std::string pathDirName(const std::string &path)
|
||||
@ -82,3 +84,18 @@ std::string errorString(DWORD errCode) {
|
||||
ss << ">";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool isWow64() {
|
||||
static bool valueInitialized = false;
|
||||
static bool value = false;
|
||||
if (!valueInitialized) {
|
||||
OsModule kernel32(L"kernel32.dll");
|
||||
auto proc = reinterpret_cast<decltype(IsWow64Process)*>(
|
||||
kernel32.proc("IsWow64Process"));
|
||||
BOOL isWow64 = FALSE;
|
||||
BOOL ret = proc(GetCurrentProcess(), &isWow64);
|
||||
value = ret && isWow64;
|
||||
valueInitialized = true;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -8,3 +8,4 @@
|
||||
std::string pathDirName(const std::string &path);
|
||||
std::string getModuleFileName(HMODULE module);
|
||||
std::string errorString(DWORD errCode);
|
||||
bool isWow64();
|
||||
|
@ -8,9 +8,11 @@
|
||||
|
||||
#include "Command.h"
|
||||
#include "Event.h"
|
||||
#include "NtHandleQuery.h"
|
||||
#include "OsVersion.h"
|
||||
#include "ShmemParcel.h"
|
||||
#include "Spawn.h"
|
||||
|
||||
#include <DebugClient.h>
|
||||
|
||||
static const char *g_prefix = "";
|
||||
@ -261,6 +263,26 @@ int main(int argc, char *argv[]) {
|
||||
case Command::GetStdout:
|
||||
cmd.handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
break;
|
||||
case Command::Hello:
|
||||
// NOOP for Worker startup synchronization.
|
||||
break;
|
||||
case Command::LookupKernelObject: {
|
||||
uint64_t h64;
|
||||
memcpy(&h64, &cmd.lookupKernelObject.handle, sizeof(h64));
|
||||
auto handles = queryNtHandles();
|
||||
uint64_t result =
|
||||
reinterpret_cast<uint64_t>(
|
||||
ntHandlePointer(
|
||||
handles, cmd.lookupKernelObject.pid,
|
||||
reinterpret_cast<HANDLE>(h64)));
|
||||
memcpy(&cmd.lookupKernelObject.kernelObject,
|
||||
&result, sizeof(result));
|
||||
trace("LOOKUP: p%d: 0x%I64x => 0x%I64x",
|
||||
(int)cmd.lookupKernelObject.pid,
|
||||
h64,
|
||||
result);
|
||||
break;
|
||||
}
|
||||
case Command::NewBuffer:
|
||||
cmd.handle = createBuffer(cmd.bInheritHandle);
|
||||
break;
|
||||
|
@ -21,6 +21,7 @@
|
||||
#ifndef OS_MODULE_H
|
||||
#define OS_MODULE_H
|
||||
|
||||
#include "DebugClient.h"
|
||||
#include "WinptyAssert.h"
|
||||
|
||||
class OsModule {
|
||||
|
Loading…
Reference in New Issue
Block a user