diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.targets
index 18c3c1f017a9f..8035484bf04a5 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Windows.targets
@@ -99,6 +99,12 @@ The .NET Foundation licenses this file to you under the MIT license.
+
+
+
+
+
+
diff --git a/src/coreclr/nativeaot/Runtime/EHHelpers.cpp b/src/coreclr/nativeaot/Runtime/EHHelpers.cpp
index 325128c4e01fc..dc0e6fe1b75be 100644
--- a/src/coreclr/nativeaot/Runtime/EHHelpers.cpp
+++ b/src/coreclr/nativeaot/Runtime/EHHelpers.cpp
@@ -536,6 +536,61 @@ int32_t __stdcall RhpVectoredExceptionHandler(PEXCEPTION_POINTERS pExPtrs)
return EXCEPTION_CONTINUE_SEARCH;
}
+ // the following would work on ARM64 as well, but there is no way to test right now.
+#ifdef TARGET_AMD64
+
+#ifndef STATUS_RETURN_ADDRESS_HIJACK_ATTEMPT
+#define STATUS_RETURN_ADDRESS_HIJACK_ATTEMPT ((uintptr_t)0x80000033L)
+#endif
+
+ if (faultCode == STATUS_RETURN_ADDRESS_HIJACK_ATTEMPT)
+ {
+ Thread * pThread = ThreadStore::GetCurrentThreadIfAvailable();
+ if (pThread == NULL || !pThread->IsCurrentThreadInCooperativeMode())
+ {
+ // if we are not in coop mode, this cannot be our hijack
+ // Perhaps some other runtime is responsible.
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ // Sanity check.
+ if (!pThread->IsHijacked())
+ {
+ _ASSERTE(!"The thread should be hijacked by us.");
+ RhFailFast();
+ }
+
+ PCONTEXT interruptedContext = pExPtrs->ContextRecord;
+ bool areShadowStacksEnabled = PalAreShadowStacksEnabled();
+ if (areShadowStacksEnabled)
+ {
+ // OS should have fixed the SP value to the same as we`ve stashed for the hijacked thread
+ _ASSERTE(*(size_t *)interruptedContext->GetSp() == (uintptr_t)pThread->GetHijackedReturnAddress());
+
+ // When the CET is enabled, the interruption happens on the ret instruction in the calee.
+ // We need to "pop" rsp to the caller, as if the ret has consumed it.
+ interruptedContext->SetSp(interruptedContext->GetSp() + 8);
+ }
+
+ // Change the IP to be at the original return site, as if we have returned to the caller.
+ // That IP is an interruptible safe point, so we can suspend right there.
+ uintptr_t origIp = interruptedContext->GetIp();
+ interruptedContext->SetIp((uintptr_t)pThread->GetHijackedReturnAddress());
+
+ pThread->InlineSuspend(interruptedContext);
+
+ if (areShadowStacksEnabled)
+ {
+ // Undo the "pop", so that the ret could now succeed.
+ interruptedContext->SetSp(interruptedContext->GetSp() - 8);
+ interruptedContext->SetIp(origIp);
+ }
+
+ ASSERT(!pThread->IsHijacked());
+ return EXCEPTION_CONTINUE_EXECUTION;
+ }
+#endif // TARGET_AMD64 (support for STATUS_RETURN_ADDRESS_HIJACK_ATTEMPT)
+
uintptr_t faultingIP = pExPtrs->ContextRecord->GetIp();
ICodeManager * pCodeManager = GetRuntimeInstance()->GetCodeManagerForAddress((PTR_VOID)faultingIP);
diff --git a/src/coreclr/nativeaot/Runtime/PalRedhawk.h b/src/coreclr/nativeaot/Runtime/PalRedhawk.h
index 9257324bd1589..56bb1fb66c4eb 100644
--- a/src/coreclr/nativeaot/Runtime/PalRedhawk.h
+++ b/src/coreclr/nativeaot/Runtime/PalRedhawk.h
@@ -436,6 +436,7 @@ typedef struct DECLSPEC_ALIGN(16) _CONTEXT {
uintptr_t GetIp() { return Pc; }
uintptr_t GetLr() { return Lr; }
uintptr_t GetSp() { return Sp; }
+ void SetSp(uintptr_t sp) { Sp = sp; }
template
void ForEachPossibleObjectRef(F lambda)
@@ -665,6 +666,7 @@ REDHAWK_PALIMPORT UInt32_BOOL REDHAWK_PALAPI PalVirtualProtect(_In_ void* pAddre
REDHAWK_PALIMPORT void PalFlushInstructionCache(_In_ void* pAddress, size_t size);
REDHAWK_PALIMPORT void REDHAWK_PALAPI PalSleep(uint32_t milliseconds);
REDHAWK_PALIMPORT UInt32_BOOL REDHAWK_PALAPI PalSwitchToThread();
+REDHAWK_PALIMPORT UInt32_BOOL REDHAWK_PALAPI PalAreShadowStacksEnabled();
REDHAWK_PALIMPORT HANDLE REDHAWK_PALAPI PalCreateEventW(_In_opt_ LPSECURITY_ATTRIBUTES pEventAttributes, UInt32_BOOL manualReset, UInt32_BOOL initialState, _In_opt_z_ LPCWSTR pName);
REDHAWK_PALIMPORT uint64_t REDHAWK_PALAPI PalGetTickCount64();
REDHAWK_PALIMPORT HANDLE REDHAWK_PALAPI PalGetModuleHandleFromPointer(_In_ void* pointer);
@@ -692,6 +694,7 @@ REDHAWK_PALIMPORT bool REDHAWK_PALAPI PalStartEventPipeHelperThread(_In_ Backgro
typedef void (*PalHijackCallback)(_In_ NATIVE_CONTEXT* pThreadContext, _In_opt_ void* pThreadToHijack);
REDHAWK_PALIMPORT void REDHAWK_PALAPI PalHijack(HANDLE hThread, _In_opt_ void* pThreadToHijack);
REDHAWK_PALIMPORT UInt32_BOOL REDHAWK_PALAPI PalRegisterHijackCallback(_In_ PalHijackCallback callback);
+REDHAWK_PALIMPORT HijackFunc* REDHAWK_PALAPI PalGetHijackTarget(_In_ HijackFunc* defaultHijackTarget);
#endif
REDHAWK_PALIMPORT UInt32_BOOL REDHAWK_PALAPI PalAllocateThunksFromTemplate(_In_ HANDLE hTemplateModule, uint32_t templateRva, size_t templateSize, _Outptr_result_bytebuffer_(templateSize) void** newThunksOut);
diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
index ae073e57c7ecd..2574ea39a1663 100644
--- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
+++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp
@@ -277,28 +277,8 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, PInvokeTransitionF
#endif // defined(USE_PORTABLE_HELPERS)
- // This function guarantees that the final initialized context will refer to a managed
- // frame. In the rare case where the PC does not refer to managed code (and refers to an
- // assembly thunk instead), unwind through the thunk sequence to find the nearest managed
- // frame.
- // NOTE: When thunks are present, the thunk sequence may report a conservative GC reporting
- // lower bound that must be applied when processing the managed frame.
-
- ReturnAddressCategory category = CategorizeUnadjustedReturnAddress(m_ControlPC);
-
- if (category == InManagedCode)
- {
- ASSERT(m_pInstance->IsManaged(m_ControlPC));
- }
- else if (IsNonEHThunk(category))
- {
- UnwindNonEHThunkSequence();
- ASSERT(m_pInstance->IsManaged(m_ControlPC));
- }
- else
- {
- FAILFAST_OR_DAC_FAIL_UNCONDITIONALLY("PInvokeTransitionFrame PC points to an unexpected assembly thunk kind.");
- }
+ // adjust for thunks, if needed
+ EnsureInitializedToManagedFrame();
STRESS_LOG1(LF_STACKWALK, LL_INFO10000, " %p\n", m_ControlPC);
}
@@ -484,7 +464,13 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, PTR_PAL_LIMITED_CO
}
// Prepare to start a stack walk from the context listed in the supplied NATIVE_CONTEXT.
-// The supplied context can describe a location in managed code.
+// NOTE: When a return address hijack is executed, the PC in the NATIVE_CONTEXT
+// matches the hijacked return address. This PC is not guaranteed to be in managed code
+// since the hijacked return address may refer to a location where an assembly thunk called
+// into managed code.
+// NOTE: When the PC is in an assembly thunk, this function will unwind to the next managed
+// frame and may publish a conservative stack range (if and only if any of the unwound
+// thunks report a conservative range).
void StackFrameIterator::InternalInit(Thread * pThreadToWalk, NATIVE_CONTEXT* pCtx, uint32_t dwFlags)
{
ASSERT((dwFlags & MethodStateCalculated) == 0);
@@ -498,8 +484,9 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, NATIVE_CONTEXT* pC
// properly walk it in parallel.
ResetNextExInfoForSP(pCtx->GetSp());
- // This codepath is used by the hijack stackwalk. The IP must be in managed code.
- ASSERT(m_pInstance->IsManaged(dac_cast(pCtx->GetIp())));
+ // This codepath is used by the hijack stackwalk. The IP must be in managed code
+ // or in a conservatively reported assembly thunk.
+ ASSERT(IsValidReturnAddress((void*)pCtx->GetIp()));
//
// control state
@@ -616,6 +603,35 @@ void StackFrameIterator::InternalInit(Thread * pThreadToWalk, NATIVE_CONTEXT* pC
#endif // TARGET_ARM
#undef PTR_TO_REG
+
+ // adjust for thunks, if needed
+ EnsureInitializedToManagedFrame();
+}
+
+void StackFrameIterator::EnsureInitializedToManagedFrame()
+{
+ // This function guarantees that the final initialized context will refer to a managed
+ // frame. In the rare case where the PC does not refer to managed code (and refers to an
+ // assembly thunk instead), unwind through the thunk sequence to find the nearest managed
+ // frame.
+ // NOTE: When thunks are present, the thunk sequence may report a conservative GC reporting
+ // lower bound that must be applied when processing the managed frame.
+
+ ReturnAddressCategory category = CategorizeUnadjustedReturnAddress(m_ControlPC);
+
+ if (category == InManagedCode)
+ {
+ ASSERT(m_pInstance->IsManaged(m_ControlPC));
+ }
+ else if (IsNonEHThunk(category))
+ {
+ UnwindNonEHThunkSequence();
+ ASSERT(m_pInstance->IsManaged(m_ControlPC));
+ }
+ else
+ {
+ FAILFAST_OR_DAC_FAIL_UNCONDITIONALLY("Unadjusted initial PC points to an unexpected assembly thunk kind.");
+ }
}
PTR_VOID StackFrameIterator::HandleExCollide(PTR_ExInfo pExInfo)
diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.h b/src/coreclr/nativeaot/Runtime/StackFrameIterator.h
index cf7f524de8dbb..77cef2133b5c1 100644
--- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.h
+++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.h
@@ -86,6 +86,7 @@ class StackFrameIterator
void InternalInit(Thread * pThreadToWalk, PTR_PInvokeTransitionFrame pFrame, uint32_t dwFlags); // GC stackwalk
void InternalInit(Thread * pThreadToWalk, PTR_PAL_LIMITED_CONTEXT pCtx, uint32_t dwFlags); // EH and hijack stackwalk, and collided unwind
void InternalInit(Thread * pThreadToWalk, NATIVE_CONTEXT* pCtx, uint32_t dwFlags); // GC stackwalk of redirected thread
+ void EnsureInitializedToManagedFrame();
void InternalInitForEH(Thread * pThreadToWalk, PAL_LIMITED_CONTEXT * pCtx, bool instructionFault); // EH stackwalk
void InternalInitForStackTrace(); // Environment.StackTrace
diff --git a/src/coreclr/nativeaot/Runtime/amd64/AsmOffsetsCpu.h b/src/coreclr/nativeaot/Runtime/amd64/AsmOffsetsCpu.h
index 8dd52b3acfa85..fe98a2eafc7c3 100644
--- a/src/coreclr/nativeaot/Runtime/amd64/AsmOffsetsCpu.h
+++ b/src/coreclr/nativeaot/Runtime/amd64/AsmOffsetsCpu.h
@@ -58,6 +58,7 @@ PLAT_ASM_OFFSET(0f0, PAL_LIMITED_CONTEXT, Xmm15)
PLAT_ASM_SIZEOF(130, REGDISPLAY)
PLAT_ASM_OFFSET(78, REGDISPLAY, SP)
+PLAT_ASM_OFFSET(80, REGDISPLAY, IP)
PLAT_ASM_OFFSET(18, REGDISPLAY, pRbx)
PLAT_ASM_OFFSET(20, REGDISPLAY, pRbp)
diff --git a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm
index 86c3f408665b6..facd8e983e679 100644
--- a/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm
+++ b/src/coreclr/nativeaot/Runtime/amd64/ExceptionHandling.asm
@@ -15,6 +15,10 @@ include asmmacros.inc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
NESTED_ENTRY RhpThrowHwEx, _TEXT
+ALTERNATE_ENTRY RhpThrowHwExGEHCONT ; this needs to be an EHCONT target since we'll be context-jumping here.
+
+.GEHCONT RhpThrowHwExGEHCONT
+
SIZEOF_XmmSaves equ SIZEOF__PAL_LIMITED_CONTEXT - OFFSETOF__PAL_LIMITED_CONTEXT__Xmm6
STACKSIZEOF_ExInfo equ ((SIZEOF__ExInfo + 15) AND (NOT 15))
@@ -486,8 +490,9 @@ endif
INLINE_THREAD_UNHIJACK rdx, rcx, r9 ;; Thread in rdx, trashes rcx and r9
mov rcx, [rsp + rsp_offsetof_arguments + 18h] ;; rcx <- current ExInfo *
+ mov r10, [r8 + OFFSETOF__REGDISPLAY__IP] ;; r10 <- original IP value
mov r8, [r8 + OFFSETOF__REGDISPLAY__SP] ;; r8 <- resume SP value
- xor r9d, r9d ;; r9 <- 0
+ xor r9, r9 ;; r9 <- 0
@@: mov rcx, [rcx + OFFSETOF__ExInfo__m_pPrevExInfo] ;; rcx <- next ExInfo
cmp rcx, r9
@@ -497,6 +502,20 @@ endif
@@: mov [rdx + OFFSETOF__Thread__m_pExInfoStackHead], rcx ;; store the new head on the Thread
+ ;; Sanity check: if we have shadow stack, it should agree with what we have in rsp
+ LOCAL_STACK_USE equ 118h
+ ifdef _DEBUG
+ rdsspq r9
+ test r9, r9
+ jz @f
+ mov r9, [r9]
+ cmp [rsp + LOCAL_STACK_USE], r9
+ je @f
+ int 3
+ @@:
+ xor r9, r9 ;; r9 <- 0
+ endif
+
test [RhpTrapThreads], TrapThreadsFlags_AbortInProgress
jz @f
@@ -507,12 +526,28 @@ endif
;; It was the ThreadAbortException, so rethrow it
mov rcx, STATUS_REDHAWK_THREAD_ABORT
mov rdx, rax ;; rdx <- continuation address as exception RIP
- mov rsp, r8 ;; reset the SP to resume SP value
- jmp RhpThrowHwEx ;; Throw the ThreadAbortException as a special kind of hardware exception
+ mov rax, RhpThrowHwEx ;; Throw the ThreadAbortException as a special kind of hardware exception
- ;; reset RSP and jump to the continuation address
+ ;; reset RSP and jump to RAX
@@: mov rsp, r8 ;; reset the SP to resume SP value
- jmp rax
+
+ ;; if have shadow stack, then we need to reconcile it with the rsp change we have just made
+ rdsspq r9
+ test r9, r9
+ jz NoSSP
+
+ ;; Find the shadow stack pointer for the frame we are going to restore to.
+ ;; The SSP we search is pointing to the return address of the frame represented
+ ;; by the passed in context. So we search for the instruction pointer from
+ ;; the context and return one slot up from there.
+ ;; (Same logic as in GetSSPForFrameOnCurrentStack)
+ xor r11, r11
+ @@: inc r11
+ cmp [r9 + r11 * 8 - 8], r10
+ jne @b
+
+ incsspq r11
+NoSSP: jmp rax
NESTED_END RhpCallCatchFunclet, _TEXT
diff --git a/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm b/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm
index 7e2715d3dd768..fe09d2a73022a 100644
--- a/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm
+++ b/src/coreclr/nativeaot/Runtime/i386/GcProbe.asm
@@ -251,7 +251,7 @@ RhpGcStressProbe endp
endif ;; FEATURE_GC_STRESS
-FASTCALL_FUNC RhpGcProbeHijack, 0
+_RhpGcProbeHijack@0 proc public
HijackFixupProlog
test [RhpTrapThreads], TrapThreadsFlags_TrapThreads
jnz WaitForGC
@@ -261,18 +261,18 @@ WaitForGC:
or ecx, DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX
jmp RhpWaitForGC
-FASTCALL_ENDFUNC
+_RhpGcProbeHijack@0 endp
ifdef FEATURE_GC_STRESS
-FASTCALL_FUNC RhpGcStressHijack, 0
+_RhpGcStressHijack@0 proc public
HijackFixupProlog
or ecx, DEFAULT_PROBE_SAVE_FLAGS + PTFF_SAVE_RAX
jmp RhpGcStressProbe
-FASTCALL_ENDFUNC
+_RhpGcStressHijack@0 endp
-FASTCALL_FUNC RhpHijackForGcStress, 0
+_RhpHijackForGcStress@0 proc public
push ebp
mov ebp, esp
@@ -307,7 +307,7 @@ FASTCALL_FUNC RhpHijackForGcStress, 0
pop edx
pop ebp
ret
-FASTCALL_ENDFUNC
+_RhpHijackForGcStress@0 endp
endif ;; FEATURE_GC_STRESS
end
diff --git a/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h b/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h
index 71cd8fcd0bf2b..1b6e92b1788f2 100644
--- a/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h
+++ b/src/coreclr/nativeaot/Runtime/inc/CommonTypes.h
@@ -61,4 +61,8 @@ typedef struct _GUID {
} GUID;
#endif // FEATURE_EVENT_TRACE && !_INC_WINDOWS
+// Hijack funcs are not called, they are "returned to". And when done, they return to the actual caller.
+// Thus they cannot have any parameters or return anything.
+typedef void HijackFunc();
+
#endif // __COMMON_TYPES_H__
diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp
index c3a72d1846d95..b796b05218226 100644
--- a/src/coreclr/nativeaot/Runtime/thread.cpp
+++ b/src/coreclr/nativeaot/Runtime/thread.cpp
@@ -572,13 +572,13 @@ void Thread::GcScanRootsWorker(ScanFunc * pfnEnumCallback, ScanContext * pvCallb
#ifdef FEATURE_HIJACK
-EXTERN_C void FASTCALL RhpGcProbeHijack();
-EXTERN_C void FASTCALL RhpGcStressHijack();
+EXTERN_C void RhpGcProbeHijack();
+EXTERN_C void RhpGcStressHijack();
// static
bool Thread::IsHijackTarget(void* address)
{
- if (&RhpGcProbeHijack == address)
+ if (PalGetHijackTarget(/*defaultHijackTarget*/&RhpGcProbeHijack) == address)
return true;
#ifdef FEATURE_GC_STRESS
if (&RhpGcStressHijack == address)
@@ -697,7 +697,9 @@ void Thread::HijackCallback(NATIVE_CONTEXT* pThreadContext, void* pThreadToHijac
#endif //FEATURE_SUSPEND_REDIRECTION
}
- pThread->HijackReturnAddress(pThreadContext, &RhpGcProbeHijack);
+ pThread->HijackReturnAddress(
+ pThreadContext,
+ PalGetHijackTarget(/*defaultHijackTarget*/&RhpGcProbeHijack));
}
#ifdef FEATURE_GC_STRESS
diff --git a/src/coreclr/nativeaot/Runtime/thread.h b/src/coreclr/nativeaot/Runtime/thread.h
index f5a1c82e59697..4c0a21e9f9ab7 100644
--- a/src/coreclr/nativeaot/Runtime/thread.h
+++ b/src/coreclr/nativeaot/Runtime/thread.h
@@ -173,16 +173,9 @@ class Thread : private RuntimeThreadLocals
#ifdef FEATURE_HIJACK
static void HijackCallback(NATIVE_CONTEXT* pThreadContext, void* pThreadToHijack);
- //
- // Hijack funcs are not called, they are "returned to". And when done, they return to the actual caller.
- // Thus they cannot have any parameters or return anything.
- //
- typedef void FASTCALL HijackFunc();
-
void HijackReturnAddress(PAL_LIMITED_CONTEXT* pSuspendCtx, HijackFunc* pfnHijackFunction);
void HijackReturnAddress(NATIVE_CONTEXT* pSuspendCtx, HijackFunc* pfnHijackFunction);
void HijackReturnAddressWorker(StackFrameIterator* frameIterator, HijackFunc* pfnHijackFunction);
- bool InlineSuspend(NATIVE_CONTEXT* interruptedContext);
void CrossThreadUnhijack();
void UnhijackWorker();
#else // FEATURE_HIJACK
@@ -209,6 +202,7 @@ class Thread : private RuntimeThreadLocals
static uint64_t s_DeadThreadsNonAllocBytes;
public:
+ bool InlineSuspend(NATIVE_CONTEXT* interruptedContext);
static uint64_t GetDeadThreadsNonAllocBytes();
diff --git a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
index ac3dd24a26704..8b3c6af9fe48f 100644
--- a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
+++ b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
@@ -636,6 +636,11 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI __stdcall PalSwitchToThread()
return false;
}
+REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalAreShadowStacksEnabled()
+{
+ return false;
+}
+
extern "C" UInt32_BOOL CloseHandle(HANDLE handle)
{
if ((handle == NULL) || (handle == INVALID_HANDLE_VALUE))
@@ -1070,6 +1075,11 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalRegisterHijackCallback(_In_ PalH
return AddSignalHandler(INJECT_ACTIVATION_SIGNAL, ActivationHandler, &g_previousActivationHandler);
}
+REDHAWK_PALIMPORT HijackFunc* REDHAWK_PALAPI PalGetHijackTarget(HijackFunc* defaultHijackTarget)
+{
+ return defaultHijackTarget;
+}
+
REDHAWK_PALEXPORT void REDHAWK_PALAPI PalHijack(HANDLE hThread, _In_opt_ void* pThreadToHijack)
{
ThreadUnixHandle* threadHandle = (ThreadUnixHandle*)hThread;
diff --git a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp
index 94424b17562be..fec8c5fb7f10d 100644
--- a/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp
+++ b/src/coreclr/nativeaot/Runtime/windows/PalRedhawkMinWin.cpp
@@ -61,6 +61,11 @@ static HMODULE LoadKernel32dll()
return LoadLibraryExW(L"kernel32", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
}
+static HMODULE LoadNtdlldll()
+{
+ return LoadLibraryExW(L"ntdll.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+}
+
void InitializeCurrentProcessCpuCount()
{
DWORD count;
@@ -327,6 +332,20 @@ REDHAWK_PALEXPORT HANDLE REDHAWK_PALAPI PalCreateEventW(_In_opt_ LPSECURITY_ATTR
return CreateEventW(pEventAttributes, manualReset, initialState, pName);
}
+REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalAreShadowStacksEnabled()
+{
+#if defined(TARGET_AMD64)
+ // The SSP is null when CET shadow stacks are not enabled. On processors that don't support shadow stacks, this is a
+ // no-op and the intrinsic returns 0. CET shadow stacks are enabled or disabled for all threads, so the result is the
+ // same from any thread.
+ return _rdsspq() != 0;
+#else
+ // When implementing AreShadowStacksEnabled() on other architectures, review all the places where this is used.
+ return false;
+#endif
+}
+
+
#ifdef TARGET_X86
#define EXCEPTION_HIJACK 0xe0434f4e // 0xe0000000 | 'COM'+1
@@ -487,6 +506,10 @@ REDHAWK_PALEXPORT CONTEXT* PalAllocateCompleteOSContext(_Out_ uint8_t** contextB
context = context | CONTEXT_XSTATE;
}
+ // the context does not need XSTATE_MASK_CET_U because we should not be using
+ // redirection when CET is enabled and should not be here.
+ _ASSERTE(!PalAreShadowStacksEnabled());
+
// Retrieve contextSize by passing NULL for Buffer
DWORD contextSize = 0;
ULONG64 xStateCompactionMask = XSTATE_MASK_LEGACY | XSTATE_MASK_AVX | XSTATE_MASK_MPX | XSTATE_MASK_AVX512;
@@ -580,8 +603,6 @@ REDHAWK_PALIMPORT void REDHAWK_PALAPI PopulateControlSegmentRegisters(CONTEXT* p
static PalHijackCallback g_pHijackCallback;
-#ifdef FEATURE_SPECIAL_USER_MODE_APC
-
// These declarations are for a new special user-mode APC feature introduced in Windows. These are not yet available in Windows
// SDK headers, so some names below are prefixed with "CLONE_" to avoid conflicts in the future. Once the prefixed declarations
// become available in the Windows SDK headers, the prefixed declarations below can be removed in favor of the SDK ones.
@@ -611,6 +632,8 @@ static const CLONE_QUEUE_USER_APC_FLAGS SpecialUserModeApcWithContextFlags = (CL
(CLONE_QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC |
CLONE_QUEUE_USER_APC_CALLBACK_DATA_CONTEXT);
+static void* g_returnAddressHijackTarget = NULL;
+
static void NTAPI ActivationHandler(ULONG_PTR parameter)
{
CLONE_APC_CALLBACK_DATA* data = (CLONE_APC_CALLBACK_DATA*)parameter;
@@ -619,7 +642,6 @@ static void NTAPI ActivationHandler(ULONG_PTR parameter)
Thread* pThread = (Thread*)data->Parameter;
pThread->SetActivationPending(false);
}
-#endif
REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalRegisterHijackCallback(_In_ PalHijackCallback callback)
{
@@ -629,6 +651,55 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalRegisterHijackCallback(_In_ PalH
return true;
}
+void InitHijackingAPIs()
+{
+ HMODULE hKernel32 = LoadKernel32dll();
+
+#ifdef HOST_AMD64
+ typedef BOOL (WINAPI *IsWow64Process2Proc)(HANDLE hProcess, USHORT *pProcessMachine, USHORT *pNativeMachine);
+
+ IsWow64Process2Proc pfnIsWow64Process2Proc = (IsWow64Process2Proc)GetProcAddress(hKernel32, "IsWow64Process2");
+ USHORT processMachine, hostMachine;
+ if (pfnIsWow64Process2Proc != nullptr &&
+ (*pfnIsWow64Process2Proc)(GetCurrentProcess(), &processMachine, &hostMachine) &&
+ (hostMachine == IMAGE_FILE_MACHINE_ARM64) &&
+ !IsWindowsVersionOrGreater(10, 0, 26100))
+ {
+ // Special user-mode APCs are broken on WOW64 processes (x64 running on Arm64 machine) with Windows older than 11.0.26100 (24H2)
+ g_pfnQueueUserAPC2Proc = NULL;
+ }
+ else
+#endif // HOST_AMD64
+ {
+ g_pfnQueueUserAPC2Proc = (QueueUserAPC2Proc)GetProcAddress(hKernel32, "QueueUserAPC2");
+ }
+
+ if (PalAreShadowStacksEnabled())
+ {
+ // When shadow stacks are enabled, support for special user-mode APCs is required
+ _ASSERTE(g_pfnQueueUserAPC2Proc != NULL);
+
+ HMODULE hModNtdll = LoadNtdlldll();
+ typedef void* (*PFN_RtlGetReturnAddressHijackTarget)(void);
+
+ void* rtlGetReturnAddressHijackTarget = GetProcAddress(hModNtdll, "RtlGetReturnAddressHijackTarget");
+ if (rtlGetReturnAddressHijackTarget != NULL)
+ {
+ g_returnAddressHijackTarget = ((PFN_RtlGetReturnAddressHijackTarget)rtlGetReturnAddressHijackTarget)();
+ }
+
+ if (g_returnAddressHijackTarget == NULL)
+ {
+ _ASSERTE(!"RtlGetReturnAddressHijackTarget must provide a target when shadow stacks are enabled");
+ }
+ }
+}
+
+REDHAWK_PALIMPORT HijackFunc* REDHAWK_PALAPI PalGetHijackTarget(HijackFunc* defaultHijackTarget)
+{
+ return g_returnAddressHijackTarget ? (HijackFunc*)g_returnAddressHijackTarget : defaultHijackTarget;
+}
+
REDHAWK_PALEXPORT void REDHAWK_PALAPI PalHijack(HANDLE hThread, _In_opt_ void* pThreadToHijack)
{
_ASSERTE(hThread != INVALID_HANDLE_VALUE);
@@ -637,28 +708,10 @@ REDHAWK_PALEXPORT void REDHAWK_PALAPI PalHijack(HANDLE hThread, _In_opt_ void* p
// initialize g_pfnQueueUserAPC2Proc on demand.
// Note that only one thread at a time may perform suspension (guaranteed by the thread store lock)
- // so simple conditional assignment is ok.
+ // so simple condition check is ok.
if (g_pfnQueueUserAPC2Proc == QUEUE_USER_APC2_UNINITIALIZED)
{
- HMODULE hKernel32 = LoadKernel32dll();
-#ifdef HOST_AMD64
- typedef BOOL (WINAPI *IsWow64Process2Proc)(HANDLE hProcess, USHORT *pProcessMachine, USHORT *pNativeMachine);
-
- IsWow64Process2Proc pfnIsWow64Process2Proc = (IsWow64Process2Proc)GetProcAddress(hKernel32, "IsWow64Process2");
- USHORT processMachine, hostMachine;
- if (pfnIsWow64Process2Proc != nullptr &&
- (*pfnIsWow64Process2Proc)(GetCurrentProcess(), &processMachine, &hostMachine) &&
- (hostMachine == IMAGE_FILE_MACHINE_ARM64) &&
- !IsWindowsVersionOrGreater(10, 0, 26100))
- {
- // Special user-mode APCs are broken on WOW64 processes (x64 running on Arm64 machine) with Windows older than 11.0.26100 (24H2)
- g_pfnQueueUserAPC2Proc = NULL;
- }
- else
-#endif // HOST_AMD64
- {
- g_pfnQueueUserAPC2Proc = (QueueUserAPC2Proc)GetProcAddress(hKernel32, "QueueUserAPC2");
- }
+ InitHijackingAPIs();
}
if (g_pfnQueueUserAPC2Proc)
diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp
index 1183d1e825b58..942a8df13cdd6 100644
--- a/src/coreclr/vm/excep.cpp
+++ b/src/coreclr/vm/excep.cpp
@@ -6765,11 +6765,14 @@ VEH_ACTION WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo
{
if (pThread == NULL || !pThread->PreemptiveGCDisabled())
{
- // We are not running managed code, so this cannot be our hijack
+ // if we are not in coop mode, this cannot be our hijack
// Perhaps some other runtime is responsible.
return VEH_CONTINUE_SEARCH;
}
+ // Sanity check. The thread should be hijacked by us.
+ _ASSERTE_ALL_BUILDS(pThread->HasThreadState(Thread::TS_Hijacked));
+
PCONTEXT interruptedContext = pExceptionInfo->ContextRecord;
bool areShadowStacksEnabled = Thread::AreShadowStacksEnabled();
if (areShadowStacksEnabled)