From e1e971b6a9942b454d8e50b96ce3f7b97dde6008 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Sat, 1 Aug 2026 21:17:07 -0400 Subject: [PATCH] [release/10.0] Fix GC hole when method return is hijacked for GC suspension Backport of #129714. Except on x86, no registers are scanned as part of the HijackFrame on top of the stack. The hijacked return value is only reported to the GC through the calling method's GC info. When the caller is CallDescrWorkerInternal, which is hand written assembly with no GC info, an objectref left in the return register at hijack time is neither reported nor updated, so a compacting GC leaves the caller holding a stale pointer and the heap gets corrupted. Fix this by declining to hijack when the return address is CallDescrWorkerInternal's. The upstream change also strips the PAC signature from the return address and handles returns into the interpreter. Neither applies here: release/10.0 has no PacStripPtr and no FEATURE_INTERPRETER, so only the CallDescrWorkerInternal early-out is taken. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9d15f64b-d86c-4a00-ba47-5d08f2b5f2cf --- src/coreclr/vm/threadsuspend.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index a060c121cb6874..671572fa1d6884 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -20,6 +20,8 @@ #include "exinfo.h" #endif +bool IsCallDescrWorkerInternalReturnAddress(PCODE pCode); + #define HIJACK_NONINTERRUPTIBLE_THREADS bool ThreadSuspend::s_fSuspendRuntimeInProgress = false; @@ -4597,6 +4599,19 @@ void Thread::HijackThread(ExecutionState *esb X86_ARG(ReturnKind returnKind) X86 // Remember the place that the return would have gone m_pvHJRetAddr = *esb->m_ppvRetAddrPtr; +#ifndef TARGET_X86 + // Except for x86, no registers are scanned as part of the HijackFrame on top of the stack. + // This still allows scanning of the return value because the registers in question are + // scanned as part of the calling method's roots. The problem arises if we are returning to + // CallDescrWorkerInternal, which is hand written assembly with no GC info, so a returned + // objectref would be neither reported nor updated by a GC. + if (IsCallDescrWorkerInternalReturnAddress((PCODE)(TADDR)m_pvHJRetAddr)) + { + STRESS_LOG2(LF_SYNC, LL_INFO100, "Thread::HijackThread(%p): Early out - return address %p is CallDescrWorkerInternal.\n", this, m_pvHJRetAddr); + return; + } +#endif // !TARGET_X86 + IS_VALID_CODE_PTR((FARPROC) (TADDR)m_pvHJRetAddr); // TODO [DAVBR]: For the full fix for VsWhidbey 450273, the below // may be uncommented once isLegalManagedCodeCaller works properly