diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 026ba821b..2803110d6 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" @@ -768,11 +769,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 +788,6 @@ NTSTATUS ResetToConsistentState(EXCEPTION_RECORD* Exception, CONTEXT* GuestConte return STATUS_SUCCESS; } - bool Cont {}; { ScopedCallbackDisable guard; @@ -905,6 +912,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)); @@ -939,7 +947,6 @@ NTSTATUS ThreadInit() { Exception::LoadStateFromECContext(Thread, CPUArea.ContextAmd64().AMD64_Context); { - std::scoped_lock Lock(ThreadCreationMutex); auto ThreadTID = GetCurrentThreadId(); Threads.emplace(ThreadTID, Thread); if (StatAllocHandler) { @@ -953,29 +960,48 @@ NTSTATUS ThreadInit() { } NTSTATUS ThreadTerm(HANDLE Thread, LONG ExitCode) { - const auto [Err, CPUArea] = GetThreadCPUArea(Thread); - if (Err) { - return Err; + if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_TERMINATE)) { + return STATUS_ACCESS_DENIED; } - auto* OldThreadState = CPUArea.ThreadState(); - CPUArea.ThreadState() = nullptr; + + auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME); THREAD_BASIC_INFORMATION Info; - if (NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { + if (auto Err = NtQueryInformationThread(*ThreadDup, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return Err; } 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(*ThreadDup, NULL); + // This will wait for the thread to be suspended + NtGetContextThread(*ThreadDup, &TmpContext); + } + + const auto [Err, CPUArea] = GetThreadCPUArea(*ThreadDup); + if (Err) { + return Err; + } + { 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(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/Common/Handle.h b/Source/Windows/Common/Handle.h new file mode 100644 index 000000000..1f856ad3e --- /dev/null +++ b/Source/Windows/Common/Handle.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace FEX::Windows { +class ScopedHandle final { +public: + explicit ScopedHandle(HANDLE Handle) + : Handle(Handle) {} + + // 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 04464326a..7bb731958 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" @@ -559,6 +560,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 +569,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 +577,46 @@ void BTCpuThreadInit() { } void BTCpuThreadTerm(HANDLE Thread, LONG ExitCode) { - const auto [Err, TLS] = GetThreadTLS(Thread); - if (Err) { + if (!FEX::Windows::ValidateHandleAccess(Thread, THREAD_TERMINATE)) { return; } - auto* ThreadState = TLS.ThreadState(); + auto ThreadDup = FEX::Windows::DupHandle(Thread, THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME); THREAD_BASIC_INFORMATION Info; - if (NTSTATUS Err = NtQueryInformationThread(Thread, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { + if (auto Err = NtQueryInformationThread(*ThreadDup, ThreadBasicInformation, &Info, sizeof(Info), nullptr); Err) { return; } 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(*ThreadDup, NULL); + } + + auto [Err, TLS] = GetThreadTLS(*ThreadDup); + if (Err) { + return; + } + { 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(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(); } } @@ -613,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; } @@ -693,8 +719,13 @@ 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; + } + + 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; } @@ -704,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; } @@ -723,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 @@ -743,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; } @@ -754,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 20fc97bc4..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*); @@ -537,6 +538,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 }