From 972c8a97dc33245ce76ff8f9e0c7970e5ab3f71e Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 12 Aug 2025 00:23:52 +0100 Subject: [PATCH 1/6] ARM64EC: Disable syscall callbacks when handling OvercommitTracker AVs We don't need to be informed of our own mappings, avoids a deadlock that occurs if the ThreadCreationMutex lifetime is extended when creating a thread. --- Source/Windows/ARM64EC/Module.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 026ba821b..ff502a0d1 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -768,11 +768,18 @@ bool ResetToConsistentStateImpl(EXCEPTION_RECORD* Exception, CONTEXT* GuestConte } NTSTATUS ResetToConsistentState(EXCEPTION_RECORD* Exception, CONTEXT* GuestContext, ARM64_NT_CONTEXT* NativeContext) { + bool Cont {}; if (Exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { const auto FaultAddress = static_cast(Exception->ExceptionInformation[1]); - if (OvercommitTracker && OvercommitTracker->HandleAccessViolation(FaultAddress)) { - NtContinueNative(NativeContext, false); + if (OvercommitTracker) { + { + ScopedCallbackDisable guard; + Cont = OvercommitTracker->HandleAccessViolation(FaultAddress); + } + if (Cont) { + NtContinueNative(NativeContext, false); + } } } @@ -780,7 +787,6 @@ NTSTATUS ResetToConsistentState(EXCEPTION_RECORD* Exception, CONTEXT* GuestConte return STATUS_SUCCESS; } - bool Cont {}; { ScopedCallbackDisable guard; From 4df17abea7ecbef2bba8632a0d8af30dcf0a6ca8 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 12 Aug 2025 00:31:53 +0100 Subject: [PATCH 2/6] Windows: Avoid potential race between thread init and termination With the prior locking, we could try to terminate a thread while it was initializing and perhaps holding global locks leading to deadlock. --- Source/Windows/ARM64EC/Module.cpp | 24 ++++++++++++------------ Source/Windows/WOW64/Module.cpp | 26 +++++++++++++------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index ff502a0d1..efa9c147c 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -911,6 +911,7 @@ void BTCpu64NotifyMemoryDirty(void* Address, SIZE_T Size) { void BTCpu64NotifyReadFile(HANDLE Handle, void* Address, SIZE_T Size, BOOL After, NTSTATUS Status) {} NTSTATUS ThreadInit() { + std::scoped_lock Lock(ThreadCreationMutex); FEX::Windows::InitCRTThread(); static constexpr size_t EmulatorStackSize = 0x40000; const uint64_t EmulatorStack = reinterpret_cast(::VirtualAlloc(nullptr, EmulatorStackSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); @@ -945,7 +946,6 @@ NTSTATUS ThreadInit() { Exception::LoadStateFromECContext(Thread, CPUArea.ContextAmd64().AMD64_Context); { - std::scoped_lock Lock(ThreadCreationMutex); auto ThreadTID = GetCurrentThreadId(); Threads.emplace(ThreadTID, Thread); if (StatAllocHandler) { @@ -959,29 +959,29 @@ NTSTATUS ThreadInit() { } NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { - const auto [Err, CPUArea] = GetThreadCPUArea(Thread); - if (Err) { - return Err; - } - auto* OldThreadState = CPUArea.ThreadState(); - CPUArea.ThreadState() = nullptr; - THREAD_BASIC_INFORMATION Info; - if (NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { + if (auto Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return Err; } const auto ThreadTID = reinterpret_cast(Info.ClientId.UniqueThread); + bool Self = ThreadTID == GetCurrentThreadId(); + + const auto [Err, CPUArea] = GetThreadCPUArea(Thread); + if (Err) { + return Err; + } + { std::scoped_lock Lock(ThreadCreationMutex); Threads.erase(ThreadTID); if (StatAllocHandler) { - StatAllocHandler->DeallocateSlot(OldThreadState->ThreadStats); + StatAllocHandler->DeallocateSlot(CPUArea.ThreadState()->ThreadStats); } } - FEX::Windows::CallRetStack::DestroyThread(OldThreadState); - CTX->DestroyThread(OldThreadState); + FEX::Windows::CallRetStack::DestroyThread(CPUArea.ThreadState()); + CTX->DestroyThread(CPUArea.ThreadState()); ::VirtualFree(reinterpret_cast(CPUArea.EmulatorStackLimit()), 0, MEM_RELEASE); if (ThreadTID == GetCurrentThreadId()) { FEX::Windows::DeinitCRTThread(); diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index 04464326a..b8429aa58 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -559,6 +559,7 @@ void BTCpuProcessInit() { void BTCpuProcessTerm(HANDLE Handle, BOOL After, ULONG Status) {} void BTCpuThreadInit() { + std::scoped_lock Lock(ThreadCreationMutex); FEX::Windows::InitCRTThread(); auto* Thread = CTX->CreateThread(0, 0); @@ -567,7 +568,6 @@ void BTCpuThreadInit() { GetTLS().ThreadState() = Thread; GetTLS().ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed); - std::scoped_lock Lock(ThreadCreationMutex); auto ThreadTID = GetCurrentThreadId(); Threads.emplace(ThreadTID, Thread); if (StatAllocHandler) { @@ -576,30 +576,30 @@ void BTCpuThreadInit() { } void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) { - const auto [Err, TLS] = GetThreadTLS(Thread); - if (Err) { - return; - } - - auto* ThreadState = TLS.ThreadState(); - THREAD_BASIC_INFORMATION Info; - if (NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { + if (auto Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return; } const auto ThreadTID = reinterpret_cast(Info.ClientId.UniqueThread); + bool Self = ThreadTID == GetCurrentThreadId(); + + auto [Err, TLS] = GetThreadTLS(Thread); + if (Err) { + return; + } + { std::scoped_lock Lock(ThreadCreationMutex); Threads.erase(ThreadTID); if (StatAllocHandler) { - StatAllocHandler->DeallocateSlot(ThreadState->ThreadStats); + StatAllocHandler->DeallocateSlot(TLS.ThreadState()->ThreadStats); } } - FEX::Windows::CallRetStack::DestroyThread(ThreadState); - CTX->DestroyThread(ThreadState); - if (ThreadTID == GetCurrentThreadId()) { + FEX::Windows::CallRetStack::DestroyThread(TLS.ThreadState()); + CTX->DestroyThread(TLS.ThreadState()); + if (Self) { FEX::Windows::DeinitCRTThread(); } } From 8ad1ea499d028c28e412ea26622b801a07b3b897 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 12 Aug 2025 00:33:45 +0100 Subject: [PATCH 3/6] Windows: Avoid a double-free if a thread is terminated twice Unlikely to show up in any real code, but if an attempt is made to terminate a terminated thread we should handle this gracefully. --- Source/Windows/ARM64EC/Module.cpp | 8 +++++++- Source/Windows/WOW64/Module.cpp | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index efa9c147c..8dd677aa8 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -974,7 +974,13 @@ NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { { std::scoped_lock Lock(ThreadCreationMutex); - Threads.erase(ThreadTID); + auto it = Threads.find(ThreadTID); + if (it == Threads.end()) { + // Thread already terminated + return STATUS_SUCCESS; + } + + Threads.erase(it); if (StatAllocHandler) { StatAllocHandler->DeallocateSlot(CPUArea.ThreadState()->ThreadStats); } diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index b8429aa58..af499065e 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -591,7 +591,13 @@ void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) { { std::scoped_lock Lock(ThreadCreationMutex); - Threads.erase(ThreadTID); + auto it = Threads.find(ThreadTID); + if (it == Threads.end()) { + // Thread already terminated + return; + } + + Threads.erase(it); if (StatAllocHandler) { StatAllocHandler->DeallocateSlot(TLS.ThreadState()->ThreadStats); } From 6f5d4fbf30956e53c870c000a97c1e05c9512561 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 12 Aug 2025 00:37:21 +0100 Subject: [PATCH 4/6] Windows: Avoid potential deadlocks is a thread is terminated while in the JIT Try to suspend the thread before termination to ensure no important JIT locks are held that could be left locked if the thread is compiling code etc. --- Source/Windows/ARM64EC/Module.cpp | 7 +++++++ Source/Windows/WOW64/Module.cpp | 4 ++++ Source/Windows/include/winternl.h | 1 + 3 files changed, 12 insertions(+) diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 8dd677aa8..3c5a2fbc7 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -966,6 +966,13 @@ NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { const auto ThreadTID = reinterpret_cast(Info.ClientId.UniqueThread); bool Self = ThreadTID == GetCurrentThreadId(); + if (!Self) { + CONTEXT TmpContext; + // If we are suspending a thread that isn't ourselves, try to suspend it first so we know internal JIT locks aren't being held. + NtSuspendThread(Thread, NULL); + // This will wait for the thread to be suspended + NtGetContextThread(Thread, &TmpContext); + } const auto [Err, CPUArea] = GetThreadCPUArea(Thread); if (Err) { diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index af499065e..b5fea5f0e 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -583,6 +583,10 @@ void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) { const auto ThreadTID = reinterpret_cast(Info.ClientId.UniqueThread); bool Self = ThreadTID == GetCurrentThreadId(); + if (!Self) { + // If we are suspending a thread that isn't ourselves, try to suspend it first so we know internal JIT locks aren't being held. + RtlWow64SuspendThread(Thread, NULL); + } auto [Err, TLS] = GetThreadTLS(Thread); if (Err) { diff --git a/Source/Windows/include/winternl.h b/Source/Windows/include/winternl.h index 20fc97bc4..8d4c23e01 100644 --- a/Source/Windows/include/winternl.h +++ b/Source/Windows/include/winternl.h @@ -537,6 +537,7 @@ NTSTATUS WINAPI RtlWow64GetThreadContext(HANDLE, WOW64_CONTEXT*); NTSTATUS WINAPI RtlWow64SetThreadContext(HANDLE, const WOW64_CONTEXT*); void WINAPI Wow64ProcessPendingCrossProcessItems(void); NTSTATUS WINAPI Wow64SystemServiceEx(UINT, UINT*); +NTSTATUS WINAPI RtlWow64SuspendThread(HANDLE,ULONG*); #ifdef __cplusplus } From b2b5ccf69c6c3ad8f0156ec77ca1d804cc4fa108 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 12 Aug 2025 00:41:14 +0100 Subject: [PATCH 5/6] Windows: Validate any user handle permissions for syscall callbacks In the terminate case, if we don't return early then we will delete the FEX thread object but the actual terminate call that follows will fail, leaving the thread in a bad state. --- Source/Windows/ARM64EC/Module.cpp | 5 +++++ Source/Windows/Common/Handle.h | 16 ++++++++++++++++ Source/Windows/WOW64/Module.cpp | 9 +++++++++ Source/Windows/include/winternl.h | 2 +- 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 Source/Windows/Common/Handle.h diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 3c5a2fbc7..800ec3ee8 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -39,6 +39,7 @@ $end_info$ #include "Common/Module.h" #include "Common/CRT/CRT.h" #include "Common/PortabilityInfo.h" +#include "Common/Handle.h" #include "DummyHandlers.h" #include "BTInterface.h" #include "Windows/Common/SHMStats.h" @@ -959,6 +960,10 @@ NTSTATUS ThreadInit() { } NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { + if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_TERMINATE)) { + return STATUS_ACCESS_DENIED; + } + THREAD_BASIC_INFORMATION Info; if (auto Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return Err; diff --git a/Source/Windows/Common/Handle.h b/Source/Windows/Common/Handle.h new file mode 100644 index 000000000..20a2c2d57 --- /dev/null +++ b/Source/Windows/Common/Handle.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace FEX::Windows { + bool ValidateHandleAccess(HANDLE Handle, ACCESS_MASK Access) { + OBJECT_BASIC_INFORMATION Info; + + if (NtQueryObject(Handle, ObjectBasicInformation, &Info, sizeof(Info), nullptr)) { + return false; + } + + return (Info.GrantedAccess & Access) == Access; + } +} diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index b5fea5f0e..35870a1e3 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -39,6 +39,7 @@ $end_info$ #include "Common/Module.h" #include "Common/CRT/CRT.h" #include "Common/PortabilityInfo.h" +#include "Common/Handle.h" #include "DummyHandlers.h" #include "BTInterface.h" #include "Windows/Common/SHMStats.h" @@ -576,6 +577,10 @@ void BTCpuThreadInit() { } void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) { + if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_TERMINATE)) { + return; + } + THREAD_BASIC_INFORMATION Info; if (auto Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return; @@ -703,6 +708,10 @@ extern "C" void BTCpuSimulateImpl(CONTEXT *entry_context) { } NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG* Count) { + if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_SUSPEND_RESUME)) { + return STATUS_ACCESS_DENIED; + } + THREAD_BASIC_INFORMATION Info; if (NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return Err; diff --git a/Source/Windows/include/winternl.h b/Source/Windows/include/winternl.h index 8d4c23e01..8bf0cd48c 100644 --- a/Source/Windows/include/winternl.h +++ b/Source/Windows/include/winternl.h @@ -537,7 +537,7 @@ NTSTATUS WINAPI RtlWow64GetThreadContext(HANDLE, WOW64_CONTEXT*); NTSTATUS WINAPI RtlWow64SetThreadContext(HANDLE, const WOW64_CONTEXT*); void WINAPI Wow64ProcessPendingCrossProcessItems(void); NTSTATUS WINAPI Wow64SystemServiceEx(UINT, UINT*); -NTSTATUS WINAPI RtlWow64SuspendThread(HANDLE,ULONG*); +NTSTATUS WINAPI RtlWow64SuspendThread(HANDLE, ULONG*); #ifdef __cplusplus } From 1ac3d3c5a60ec2465c2e711d72391eec2aa43005 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Tue, 12 Aug 2025 00:43:52 +0100 Subject: [PATCH 6/6] Windows: Extend user handle access masks in callbacks where necessary While most applications will just create ALL_ACCESS handles which support the additional operations FEX needs over the regular windows syscall (usually just QUERY_INFORMATION to lookup the thread object), it is valid for them to pass in the minimal set required for each operation and windows/wine will reject any other operations (e.g. querying the TEB base on a handle with only the SUSPEND right). Windows permits duplicating handles to extend their permissions given the process itself has such permissions, so do that as necessary for the operations FEX needs. --- Source/Windows/ARM64EC/Module.cpp | 10 +++--- Source/Windows/Common/Handle.h | 47 +++++++++++++++++++++++----- Source/Windows/WOW64/Module.cpp | 51 +++++++++++++++++++------------ Source/Windows/include/winternl.h | 1 + 4 files changed, 79 insertions(+), 30 deletions(-) diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 800ec3ee8..2803110d6 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -964,8 +964,10 @@ NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { return STATUS_ACCESS_DENIED; } + auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME); + THREAD_BASIC_INFORMATION Info; - if (auto Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { + if (auto Err = NtQueryInformationThread(*ThreadDup, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return Err; } @@ -974,12 +976,12 @@ NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { if (!Self) { CONTEXT TmpContext; // If we are suspending a thread that isn't ourselves, try to suspend it first so we know internal JIT locks aren't being held. - NtSuspendThread(Thread, NULL); + NtSuspendThread(*ThreadDup, NULL); // This will wait for the thread to be suspended - NtGetContextThread(Thread, &TmpContext); + NtGetContextThread(*ThreadDup, &TmpContext); } - const auto [Err, CPUArea] = GetThreadCPUArea(Thread); + const auto [Err, CPUArea] = GetThreadCPUArea(*ThreadDup); if (Err) { return Err; } diff --git a/Source/Windows/Common/Handle.h b/Source/Windows/Common/Handle.h index 20a2c2d57..1f856ad3e 100644 --- a/Source/Windows/Common/Handle.h +++ b/Source/Windows/Common/Handle.h @@ -4,13 +4,46 @@ #include namespace FEX::Windows { - bool ValidateHandleAccess(HANDLE Handle, ACCESS_MASK Access) { - OBJECT_BASIC_INFORMATION Info; +class ScopedHandle final { +public: + explicit ScopedHandle(HANDLE Handle) + : Handle(Handle) {} - if (NtQueryObject(Handle, ObjectBasicInformation, &Info, sizeof(Info), nullptr)) { - return false; - } - - return (Info.GrantedAccess & Access) == Access; + // Move-only type + ScopedHandle(const ScopedHandle&) = delete; + ScopedHandle& operator=(ScopedHandle&) = delete; + ScopedHandle(ScopedHandle&& rhs) + : Handle(rhs.Handle) { + rhs.Handle = INVALID_HANDLE_VALUE; } + + ~ScopedHandle() { + if (Handle != INVALID_HANDLE_VALUE) { + NtClose(Handle); + } + } + + HANDLE operator*() const { + return Handle; + } +private: + HANDLE Handle; +}; + + +bool ValidateHandleAccess(HANDLE Handle, ACCESS_MASK Access) { + OBJECT_BASIC_INFORMATION Info; + + if (NtQueryObject(Handle, ObjectBasicInformation, &Info, sizeof(Info), nullptr)) { + return false; + } + + return (Info.GrantedAccess & Access) == Access; } + +ScopedHandle DupHandle(HANDLE Handle, ACCESS_MASK Access) { + HANDLE Duplicated = INVALID_HANDLE_VALUE; + NtDuplicateObject(NtCurrentProcess(), Handle, NtCurrentProcess(), &Duplicated, Access, 0, 0); + return ScopedHandle {Duplicated}; +} +} // namespace FEX::Windows diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index 35870a1e3..7bb731958 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -581,8 +581,10 @@ void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) { return; } + auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME); + THREAD_BASIC_INFORMATION Info; - if (auto Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { + if (auto Err = NtQueryInformationThread(*ThreadDup, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return; } @@ -590,10 +592,10 @@ void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) { bool Self = ThreadTID == GetCurrentThreadId(); if (!Self) { // If we are suspending a thread that isn't ourselves, try to suspend it first so we know internal JIT locks aren't being held. - RtlWow64SuspendThread(Thread, NULL); + RtlWow64SuspendThread(*ThreadDup, NULL); } - auto [Err, TLS] = GetThreadTLS(Thread); + auto [Err, TLS] = GetThreadTLS(*ThreadDup); if (Err) { return; } @@ -628,44 +630,53 @@ void* __wine_get_unix_opcode() { } NTSTATUS BTCpuGetContext(HANDLE Thread, HANDLE Process, void* Unknown, WOW64_CONTEXT* Context) { - auto [Err, TLS] = GetThreadTLS(Thread); + if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_GET_CONTEXT)) { + return STATUS_ACCESS_DENIED; + } + + auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT); + auto [Err, TLS] = GetThreadTLS(*ThreadDup); if (Err) { return Err; } if (!(TLS.ControlWord().load(std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) { - if (Err = Context::FlushThreadStateContext(Thread); Err) { + if (Err = Context::FlushThreadStateContext(*ThreadDup); Err) { return Err; } } - return RtlWow64GetThreadContext(Thread, Context); + return RtlWow64GetThreadContext(*ThreadDup, Context); } NTSTATUS BTCpuSetContext(HANDLE Thread, HANDLE Process, void* Unknown, WOW64_CONTEXT* Context) { - auto [Err, TLS] = GetThreadTLS(Thread); + if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_SET_CONTEXT)) { + return STATUS_ACCESS_DENIED; + } + + auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT); + auto [Err, TLS] = GetThreadTLS(*ThreadDup); if (Err) { return Err; } - // Back-up the input context incase we've been passed the CPU area (the flush below would wipe it out otherwise) WOW64_CONTEXT TmpContext = *Context; if (!(TLS.ControlWord().load(std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) { - if (Err = Context::FlushThreadStateContext(Thread); Err) { + if (Err = Context::FlushThreadStateContext(*ThreadDup); Err) { return Err; } } // Merge the input context into the CPU area then pass the full context into the JIT - if (Err = RtlWow64SetThreadContext(Thread, &TmpContext); Err) { + if (Err = RtlWow64SetThreadContext(*ThreadDup, &TmpContext); Err) { return Err; } TmpContext.ContextFlags = WOW64_CONTEXT_FULL | WOW64_CONTEXT_EXTENDED_REGISTERS; - if (Err = RtlWow64GetThreadContext(Thread, &TmpContext); Err) { + if (Err = RtlWow64GetThreadContext(*ThreadDup, &TmpContext); Err) { return Err; } @@ -712,8 +723,9 @@ NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG* Count) { return STATUS_ACCESS_DENIED; } + auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT); THREAD_BASIC_INFORMATION Info; - if (NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { + if (NTSTATUS Err = NtQueryInformationThread(*ThreadDup, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return Err; } @@ -723,17 +735,17 @@ NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG* Count) { // Mark the CPU area as dirty, to force the JIT context to be restored from it on entry as it may be changed using // SetThreadContext (which doesn't use the BTCpu API) if (!(GetTLS().ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) { - if (NTSTATUS Err = Context::FlushThreadStateContext(Thread); Err) { + if (NTSTATUS Err = Context::FlushThreadStateContext(*ThreadDup); Err) { return Err; } } - return NtSuspendThread(Thread, Count); + return NtSuspendThread(*ThreadDup, Count); } LogMan::Msg::DFmt("Suspending thread: {:X}", ThreadTID); - auto [Err, TLS] = GetThreadTLS(Thread); + auto [Err, TLS] = GetThreadTLS(*ThreadDup); if (Err) { return Err; } @@ -742,7 +754,8 @@ NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG* Count) { // If the thread hasn't yet been initialized, suspend it without special handling as it wont yet have entered the JIT if (!Threads.contains(ThreadTID)) { - return NtSuspendThread(Thread, Count); + LogMan::Msg::DFmt("Thread suspended: {:X}", ThreadTID); + return NtSuspendThread(*ThreadDup, Count); } // If CONTROL_IN_JIT is unset at this point, then it can never be set (and thus the JIT cannot be reentered) as @@ -762,7 +775,7 @@ NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG* Count) { ; // The JIT has now been interrupted and the context stored in the thread's CPU area is up-to-date - if (Err = NtSuspendThread(Thread, Count); Err) { + if (Err = NtSuspendThread(*ThreadDup, Count); Err) { TLS.ControlWord().fetch_and(~ControlBits::PAUSED, std::memory_order::relaxed); return Err; } @@ -773,12 +786,12 @@ NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG* Count) { // NtSuspendThread may return before the thread is actually suspended, so a sync operation like NtGetContextThread // needs to be called to ensure it is before we unset CONTROL_PAUSED - std::ignore = NtGetContextThread(Thread, &TmpContext); + std::ignore = NtGetContextThread(*ThreadDup, &TmpContext); // Mark the CPU area as dirty, to force the JIT context to be restored from it on entry as it may be changed using // SetThreadContext (which doesn't use the BTCpu API) if (!(TLS.ControlWord().fetch_or(ControlBits::WOW_CPU_AREA_DIRTY, std::memory_order::relaxed) & ControlBits::WOW_CPU_AREA_DIRTY)) { - if (Err = Context::FlushThreadStateContext(Thread); Err) { + if (Err = Context::FlushThreadStateContext(*ThreadDup); Err) { return Err; } } diff --git a/Source/Windows/include/winternl.h b/Source/Windows/include/winternl.h index 8bf0cd48c..10aaf91fc 100644 --- a/Source/Windows/include/winternl.h +++ b/Source/Windows/include/winternl.h @@ -490,6 +490,7 @@ NTSTATUS WINAPI NtAllocateVirtualMemoryEx(HANDLE, PVOID*, SIZE_T*, ULONG, ULONG, NTSTATUS WINAPI NtAllocateVirtualMemory(HANDLE, PVOID*, ULONG_PTR, SIZE_T*, ULONG, ULONG); NTSTATUS WINAPI NtContinue(PCONTEXT, BOOLEAN); NTSTATUS WINAPI NtCreateSection(HANDLE*, ACCESS_MASK, const OBJECT_ATTRIBUTES*, const LARGE_INTEGER*, ULONG, ULONG, HANDLE); +NTSTATUS WINAPI NtDuplicateObject(HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, ULONG, ULONG); NTSTATUS WINAPI NtFlushInstructionCache(HANDLE, LPCVOID, SIZE_T); NTSTATUS WINAPI NtFreeVirtualMemory(HANDLE, PVOID*, SIZE_T*, ULONG); NTSTATUS WINAPI NtGetContextThread(HANDLE, CONTEXT*);