diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index 55f9fcb4601755..baa191a34e4b1f 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -19,6 +19,10 @@ #include "../../vm/methoditer.h" #include "../../vm/tailcallhelp.h" +#if defined(TARGET_ARM64) +extern "C" void* PacStripPtr(void* ptr); +#endif // TARGET_ARM64 + const char *GetTType( TraceType tt); #define IsSingleStep(exception) ((exception) == EXCEPTION_SINGLE_STEP) @@ -6198,6 +6202,12 @@ static bool IsTailCall(const BYTE * ip, ControllerStackInfo* info, TailCallFunct TailCallTls* tls = GetThread()->GetTailCallTls(); LPVOID tailCallAwareRetAddr = tls->GetFrame()->TailCallAwareReturnAddress; +#if defined(TARGET_ARM64) + //TODO-PAC: Authenticate instead of stripping the return addresses. + retAddr = PacStripPtr(retAddr); + tailCallAwareRetAddr = PacStripPtr(tailCallAwareRetAddr); +#endif // TARGET_ARM64 + LOG((LF_CORDB,LL_INFO1000, "ITCTR: ret addr is %p, tailcall aware ret addr is %p\n", retAddr, tailCallAwareRetAddr)); diff --git a/src/coreclr/inc/cfi.h b/src/coreclr/inc/cfi.h index 3d7ec0f4cc11f8..a0f4c246a4b0b7 100644 --- a/src/coreclr/inc/cfi.h +++ b/src/coreclr/inc/cfi.h @@ -9,7 +9,9 @@ enum CFI_OPCODE { CFI_ADJUST_CFA_OFFSET, // Offset is adjusted relative to the current one. CFI_DEF_CFA_REGISTER, // New register is used to compute CFA - CFI_REL_OFFSET // Register is saved at offset from the current CFA + CFI_REL_OFFSET, // Register is saved at offset from the current CFA + CFI_DEF_CFA, // Take address from register and add offset to it + CFI_NEGATE_RA_STATE, // Sign the return address in lr with paciasp }; struct CFI_CODE diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index 7767eb65a9b9c4..ed944ba9f29246 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -715,6 +715,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64Sm4, W("EnableArm64Sm RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64SveAes, W("EnableArm64SveAes"), 1, "Allows Arm64 SveAes+ hardware intrinsics to be disabled") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64SveSha3, W("EnableArm64SveSha3"), 1, "Allows Arm64 SveSha3+ hardware intrinsics to be disabled") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableArm64SveSm4, W("EnableArm64SveSm4"), 1, "Allows Arm64 SveSm4+ hardware intrinsics to be disabled") +RETAIL_CONFIG_DWORD_INFO(EXTERNAL_JitPacEnabled, W("JitPacEnabled"), 1, "Allows Arm64 Pointer Authentication (PAC) to be disabled") #elif defined(TARGET_RISCV64) RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zba, W("EnableRiscV64Zba"), 1, "Allows RiscV64 Zba hardware intrinsics to be disabled") RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbb, W("EnableRiscV64Zbb"), 1, "Allows RiscV64 Zbb hardware intrinsics to be disabled") diff --git a/src/coreclr/inc/gcinfodecoder.h b/src/coreclr/inc/gcinfodecoder.h index 0836dfd0c54b68..ba9de788c29f52 100644 --- a/src/coreclr/inc/gcinfodecoder.h +++ b/src/coreclr/inc/gcinfodecoder.h @@ -76,6 +76,10 @@ typedef void * OBJECTREF; #ifndef __cgencpu_h__ +#if defined(TARGET_ARM64) +extern "C" void* PacStripPtr(void* ptr); +#endif // TARGET_ARM64 + inline void SetIP(T_CONTEXT* context, PCODE rip) { _ASSERTE(!"don't call this"); @@ -105,7 +109,8 @@ inline PCODE GetIP(T_CONTEXT* context) #elif defined(TARGET_ARM) return (PCODE)context->Pc; #elif defined(TARGET_ARM64) - return (PCODE)context->Pc; + //TODO-PAC: Authenticate instead of stripping the return address. + return (PCODE) PacStripPtr((void *)context->Pc); #elif defined(TARGET_LOONGARCH64) return (PCODE)context->Pc; #elif defined(TARGET_RISCV64) diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 47ecfbea7dc7de..67063b537c36de 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -251,6 +251,11 @@ void CodeGen::genPopCalleeSavedRegistersAndFreeLclFrame(bool jmpEpilog) } } + if (JitConfig.JitPacEnabled() != 0) + { + GetEmitter()->emitPacInEpilog(); + } + // For OSR, we must also adjust the SP to remove the Tier0 frame. // if (m_compiler->opts.IsOSR()) @@ -487,12 +492,13 @@ void CodeGen::genPrologSaveRegPair(regNumber reg1, if ((spOffset == 0) && (spDelta >= -512)) { - // We can use pre-indexed addressing. + // We can use pre-indexed addressing when the stack adjustment fits in the instruction. + // Generate: // stp REG, REG + 1, [SP, #spDelta]! // 64-bit STP offset range: -512 to 504, multiple of 8. + assert(reg1 != REG_LR); GetEmitter()->emitIns_R_R_R_I(INS_stp, EA_PTRSIZE, reg1, reg2, REG_SPBASE, spDelta, INS_OPTS_PRE_INDEX); m_compiler->unwindSaveRegPairPreindexed(reg1, reg2, spDelta); - needToSaveRegs = false; } else // (spOffset != 0) || (spDelta < -512) @@ -511,6 +517,8 @@ void CodeGen::genPrologSaveRegPair(regNumber reg1, // 64-bit STP offset range: -512 to 504, multiple of 8. assert(spOffset <= 504); assert((spOffset % 8) == 0); + assert(reg1 != REG_LR); + GetEmitter()->emitIns_R_R_R_I(INS_stp, EA_PTRSIZE, reg1, reg2, REG_SPBASE, spOffset); if (TargetOS::IsUnix && m_compiler->generateCFIUnwindCodes()) @@ -622,6 +630,7 @@ void CodeGen::genRestoreRegPair(regNumber reg1, assert((spDelta % 16) == 0); // SP changes must be 16-byte aligned assert(genIsValidFloatReg(reg1) == genIsValidFloatReg(reg2)); // registers must be both general-purpose, or both // FP/SIMD + assert(reg1 != REG_LR); if (spDelta != 0) { @@ -1384,6 +1393,11 @@ void CodeGen::genFuncletProlog(BasicBlock* block) m_compiler->unwindBegProlog(); + if (JitConfig.JitPacEnabled() != 0) + { + GetEmitter()->emitPacInProlog(); + } + regMaskTP maskSaveRegsFloat = genFuncletInfo.fiSaveRegs & RBM_ALLFLOAT; regMaskTP maskSaveRegsInt = genFuncletInfo.fiSaveRegs & ~maskSaveRegsFloat; @@ -1590,7 +1604,6 @@ void CodeGen::genFuncletEpilog(BasicBlock* /* block */) { GetEmitter()->emitIns_R_R_R_I(INS_ldp, EA_PTRSIZE, REG_FP, REG_LR, REG_SPBASE, 0); m_compiler->unwindSaveRegPair(REG_FP, REG_LR, 0); - genStackPointerAdjustment(-genFuncletInfo.fiSpDelta1, REG_SCRATCH, nullptr, /* reportUnwindData */ true); } else @@ -1621,7 +1634,6 @@ void CodeGen::genFuncletEpilog(BasicBlock* /* block */) else if (genFuncletInfo.fiFrameType == 3) { // With OSR we may see large values for fiSpDelta1 - // if (m_compiler->opts.IsOSR()) { GetEmitter()->emitIns_R_R_R_I(INS_ldp, EA_PTRSIZE, REG_FP, REG_LR, REG_SPBASE, 0); @@ -1669,6 +1681,11 @@ void CodeGen::genFuncletEpilog(BasicBlock* /* block */) } } + if (JitConfig.JitPacEnabled() != 0) + { + GetEmitter()->emitPacInEpilog(); + } + inst_RV(INS_ret, REG_LR, TYP_I_IMPL); m_compiler->unwindReturn(REG_LR); @@ -5675,6 +5692,18 @@ void CodeGen::genOSRHandleTier0CalleeSavedRegistersAndFrame() genRestoreRegPair(REG_FP, REG_LR, REG_FP, 0, 0, false, REG_IP1, nullptr, /* reportUnwindData */ false); + if (JitConfig.JitPacEnabled() != 0) + { + // Tier0 signed LR with the Tier0 caller SP before allocating its frame. + // Recreate that SP from the current Tier0 body SP so we can authenticate + // LR before the OSR prolog later re-signs it with the OSR SP via PACIASP. + genInstrWithConstant(INS_add, EA_PTRSIZE, REG_IP0, REG_SPBASE, patchpointInfo->TotalFrameSize(), REG_IP0, + /* inUnwindRegion */ false); + GetEmitter()->emitIns_Mov(INS_mov, EA_PTRSIZE, REG_IP1, REG_LR, /* canSkip */ false); + GetEmitter()->emitIns(INS_autia1716); + GetEmitter()->emitIns_Mov(INS_mov, EA_PTRSIZE, REG_LR, REG_IP1, /* canSkip */ false); + } + // Emit phantom unwind data for the tier0 frame. m_compiler->unwindAllocStack(patchpointInfo->TotalFrameSize()); // Emit nops to make the prolog 1:1 in unwind codes to instructions. This diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 74d8148f720653..192c305b9e2583 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -4476,6 +4476,11 @@ void CodeGen::genPushCalleeSavedRegisters(regNumber initReg, bool* pInitRegZeroe } #endif // DEBUG + if (JitConfig.JitPacEnabled() != 0) + { + GetEmitter()->emitPacInProlog(); + } + // The frameType number is arbitrary, is defined below, and corresponds to one of the frame styles we // generate based on various sizes. int frameType = 0; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 35be79978cc0e3..0c528b2591b369 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -9558,6 +9558,7 @@ class Compiler void unwindSaveRegPair(regNumber reg1, regNumber reg2, int offset); // stp reg1, reg2, [sp, #offset] void unwindSaveRegPairPreindexed(regNumber reg1, regNumber reg2, int offset); // stp reg1, reg2, [sp, #offset]! void unwindSaveNext(); // unwind code: save_next + void unwindPacSignLR(); // unwind code: pac_sign_lr void unwindReturn(regNumber reg); // ret lr #endif // defined(TARGET_ARM64) diff --git a/src/coreclr/jit/emit.h b/src/coreclr/jit/emit.h index 28e61029a4c06e..54344ce9111e43 100644 --- a/src/coreclr/jit/emit.h +++ b/src/coreclr/jit/emit.h @@ -3337,6 +3337,11 @@ class emitter instrDescAlign* emitNewInstrAlign(); #endif +#if defined(TARGET_ARM64) + void emitPacInProlog(); + void emitPacInEpilog(); +#endif + instrDesc* emitNewInstrSmall(emitAttr attr); instrDesc* emitNewInstr(emitAttr attr = EA_4BYTE); instrDesc* emitNewInstrSC(emitAttr attr, cnsval_ssize_t cns); diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index 254a81fe57ec22..78e2521b3aef08 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -1431,6 +1431,32 @@ static const char * const bRegNames[] = // clang-format on +//------------------------------------------------------------------------ +// emitPacInProlog: Sign LR as part of Pointer Authentication (PAC) support +// +void emitter::emitPacInProlog() +{ + if (JitConfig.JitPacEnabled() == 0) + { + return; + } + emitIns(INS_paciasp); + m_compiler->unwindPacSignLR(); +} + +//------------------------------------------------------------------------ +// emitPacInEpilog: unsign LR as part of Pointer Authentication (PAC) support +// +void emitter::emitPacInEpilog() +{ + if (JitConfig.JitPacEnabled() == 0) + { + return; + } + emitIns(INS_autiasp); + m_compiler->unwindPacSignLR(); +} + //------------------------------------------------------------------------ // emitRegName: Returns a general-purpose register name or SIMD and floating-point scalar register name. // diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 41f52190955dab..de4ffb17a39ebe 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -126,6 +126,7 @@ CONFIG_STRING(JitInlineMethodsWithEHRange, "JitInlineMethodsWithEHRange") CONFIG_INTEGER(JitLongAddress, "JitLongAddress", 0) // Force using the large pseudo instruction form for long address CONFIG_INTEGER(JitMaxUncheckedOffset, "JitMaxUncheckedOffset", 8) +RELEASE_CONFIG_INTEGER(JitPacEnabled, "JitPacEnabled", 1) // Enable devirtualization for generic virtual methods RELEASE_CONFIG_INTEGER(JitEnableGenericVirtualDevirtualization, "JitEnableGenericVirtualDevirtualization", 1) diff --git a/src/coreclr/jit/unwind.cpp b/src/coreclr/jit/unwind.cpp index 1b4d454e142326..97d819e04cb379 100644 --- a/src/coreclr/jit/unwind.cpp +++ b/src/coreclr/jit/unwind.cpp @@ -398,6 +398,11 @@ void Compiler::DumpCfiInfo(bool isHotCode, assert(dwarfReg == DWARF_REG_ILLEGAL); printf(" CodeOffset: 0x%02X Op: AdjustCfaOffset Offset:0x%X\n", codeOffset, offset); break; + case CFI_NEGATE_RA_STATE: + assert(dwarfReg == DWARF_REG_ILLEGAL); + assert(offset == 0); + printf(" CodeOffset: 0x%02X Op: NegateRAState\n", codeOffset); + break; default: printf(" Unrecognized CFI_CODE: 0x%llX\n", *(UINT64*)pCode); break; diff --git a/src/coreclr/jit/unwindarm64.cpp b/src/coreclr/jit/unwindarm64.cpp index f842737171c0b4..f5696b1aa841b4 100644 --- a/src/coreclr/jit/unwindarm64.cpp +++ b/src/coreclr/jit/unwindarm64.cpp @@ -635,6 +635,33 @@ void Compiler::unwindSaveNext() pu->AddCode(0xE6); } +void Compiler::unwindPacSignLR() +{ + if (JitConfig.JitPacEnabled() == 0) + { + return; + } +#if defined(FEATURE_CFI_SUPPORT) + if (generateCFIUnwindCodes()) + { + // Emit NEGATE_RA_STATE opcode in prologs. + if (!compGeneratingProlog) + { + return; + } + FuncInfoDsc* func = funCurrentFunc(); + UNATIVE_OFFSET cbProlog = unwindGetCurrentOffset(func); + // Maps to DW_CFA_AARCH64_negate_ra_state + createCfiCode(func, cbProlog, CFI_NEGATE_RA_STATE, DWARF_REG_ILLEGAL); + + return; + } +#endif // FEATURE_CFI_SUPPORT + + // pac_sign_lr: 11111100: sign the return address in lr with paciasp + funCurrentFunc()->uwi.AddCode(0xFC); +} + void Compiler::unwindReturn(regNumber reg) { // Nothing to do; we will always have at least one trailing "end" opcode in our padding. @@ -1081,6 +1108,12 @@ void DumpUnwindInfo(Compiler* comp, printf(" %02X save_next\n", b1); } + else if (b1 == 0xFC) + { + // pac_sign_lr: 11111100 : sign the return address in lr with paciasp. + + printf(" %02X pac_sign_lr\n", b1); + } else { // Unknown / reserved unwind code diff --git a/src/coreclr/nativeaot/Runtime/ICodeManager.h b/src/coreclr/nativeaot/Runtime/ICodeManager.h index a508aad49f769e..52765d516444c7 100644 --- a/src/coreclr/nativeaot/Runtime/ICodeManager.h +++ b/src/coreclr/nativeaot/Runtime/ICodeManager.h @@ -163,7 +163,8 @@ class ICodeManager virtual bool GetReturnAddressHijackInfo(MethodInfo * pMethodInfo, REGDISPLAY * pRegisterSet, // in - PTR_PTR_VOID * ppvRetAddrLocation // out + PTR_PTR_VOID * ppvRetAddrLocation, // out + uintptr_t * pSpForArm64PacSign // out ) PURE_VIRTUAL #ifdef TARGET_X86 diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp index 928e16439e5bad..dd2395e57b2134 100644 --- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp +++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp @@ -64,6 +64,10 @@ EXTERN_C CODE_LOCATION RhpRethrow2; #define FAILFAST_OR_DAC_FAIL_UNCONDITIONALLY(msg) { ASSERT_UNCONDITIONALLY(msg); RhFailFast(); } #endif +#if defined(TARGET_ARM64) +extern "C" void* PacStripPtr(void* ptr); +#endif // TARGET_ARM64 + StackFrameIterator::StackFrameIterator(Thread * pThreadToWalk, PInvokeTransitionFrame* pInitialTransitionFrame) { STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ GC ]\n"); @@ -1854,7 +1858,12 @@ void StackFrameIterator::NextInternal() // if the thread is safe to walk, it better not have a hijack in place. ASSERT(!m_pThread->IsHijacked()); +#if defined(TARGET_ARM64) + // TODO-PAC: Authenticate instead of stripping the return address. + SetControlPC(PacStripPtr(dac_cast(PCODEToPINSTR(m_RegDisplay.GetIP())))); +#else SetControlPC(dac_cast(PCODEToPINSTR(m_RegDisplay.GetIP()))); +#endif // TARGET_ARM64 PTR_VOID collapsingTargetFrame = NULL; @@ -2185,6 +2194,11 @@ void StackFrameIterator::CalculateCurrentMethodState() return; } +#if defined(TARGET_ARM64) + //TODO-PAC: Authenticate instead of stripping the return addresses. + m_ControlPC = PacStripPtr(m_ControlPC); +#endif // TARGET_ARM64 + // Assume that the caller is likely to be in the same module if (m_pCodeManager == NULL || !m_pCodeManager->FindMethodInfo(m_ControlPC, &m_methodInfo)) { diff --git a/src/coreclr/nativeaot/Runtime/arm64/GcProbe.S b/src/coreclr/nativeaot/Runtime/arm64/GcProbe.S index c327e304b8e290..144e2688adf61a 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/GcProbe.S +++ b/src/coreclr/nativeaot/Runtime/arm64/GcProbe.S @@ -153,6 +153,7 @@ // Fix the stack by restoring the original return address // ldr lr, [x9, #OFFSETOF__Thread__m_pvHijackedReturnAddress] + xpaclri // // Clear hijack state diff --git a/src/coreclr/nativeaot/Runtime/arm64/GcProbe.asm b/src/coreclr/nativeaot/Runtime/arm64/GcProbe.asm index bd44842a8a8be6..12c5d76b2f0d94 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/GcProbe.asm +++ b/src/coreclr/nativeaot/Runtime/arm64/GcProbe.asm @@ -124,6 +124,7 @@ PROBE_FRAME_SIZE field 0 ;; Fix the stack by restoring the original return address ;; ldr lr, [x9, #OFFSETOF__Thread__m_pvHijackedReturnAddress] + DCD 0xD50320FF ;; xpaclri instruction in binary to avoid error while compiling with non-PAC enabled compilers ;; ;; Clear hijack state diff --git a/src/coreclr/nativeaot/Runtime/arm64/MiscStubs.S b/src/coreclr/nativeaot/Runtime/arm64/MiscStubs.S index ea5d91a1a1c1f9..b0c1e8da846c6f 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/MiscStubs.S +++ b/src/coreclr/nativeaot/Runtime/arm64/MiscStubs.S @@ -3,3 +3,28 @@ #include #include "AsmOffsets.inc" + +// void* PacStripPtr(void *); +// This function strips the pointer of PAC info that is passed as an agrument. +// To avoid failing on non-PAC enabled machines, we use xpaclri (instead of xpaci) which strips lr explicitly. +// Thus we move need to move input in lr, strip it and copy it back to the result register. +.arch_extension pauth + LEAF_ENTRY PacStripPtr, _TEXT + mov x9, lr + mov lr, x0 + xpaclri + mov x0, lr + ret x9 + LEAF_END PacStripPtr, _TEXT + +// void* PacSignPtr(void *, void *); +// This function sign the input pointer using zero as salt. +// Thus we need to move input in lr, sign it and then copy it back to the result register. +.arch_extension pauth + LEAF_ENTRY PacSignPtr, _TEXT + mov x17, x0 + mov x16, x1 + pacia1716 + mov x0, x17 + ret + LEAF_END PacSignPtr, _TEXT \ No newline at end of file diff --git a/src/coreclr/nativeaot/Runtime/arm64/MiscStubs.asm b/src/coreclr/nativeaot/Runtime/arm64/MiscStubs.asm index 49baea4977259b..fae95d519db4d4 100644 --- a/src/coreclr/nativeaot/Runtime/arm64/MiscStubs.asm +++ b/src/coreclr/nativeaot/Runtime/arm64/MiscStubs.asm @@ -5,4 +5,27 @@ TEXTAREA +; void* PacStripPtr(void *); +; This function strips the pointer of PAC info that is passed as an agrument. +; To avoid failing on non-PAC enabled machines, we use xpaclri (instead of xpaci) which strips lr explicitly. +; Thus we move need to move input in lr, strip it and copy it back to the result register. + LEAF_ENTRY PacStripPtr + mov x9, lr + mov lr, x0 + DCD 0xD50320FF ; xpaclri instruction in binary to avoid error while compiling with non-PAC enabled compilers + mov x0, lr + ret x9 + LEAF_END PacStripPtr + +; void* PacSignPtr(void *, void *); +; This function sign the input pointer using zero as salt. +; Thus we need to move input in lr, sign it and then copy it back to the result register. + LEAF_ENTRY PacSignPtr + mov x17, x0 + mov x16, x1 + DCD 0xD503211F ; pacia1716 instruction in binary to avoid error while compiling with non-PAC enabled compilers + mov x0, x17 + ret + LEAF_END PacSignPtr + end diff --git a/src/coreclr/nativeaot/Runtime/thread.cpp b/src/coreclr/nativeaot/Runtime/thread.cpp index 3e002ce0775934..4f2db0358ac578 100644 --- a/src/coreclr/nativeaot/Runtime/thread.cpp +++ b/src/coreclr/nativeaot/Runtime/thread.cpp @@ -37,6 +37,11 @@ static Thread* g_RuntimeInitializingThread; #endif //!DACCESS_COMPILE +#if defined(TARGET_ARM64) +extern "C" void* PacSignPtr(void* ptr, void* sp); +extern "C" void* PacStripPtr(void* ptr); +#endif // TARGET_ARM64 + ee_alloc_context::PerThreadRandom::PerThreadRandom() { minipal_xoshiro128pp_init(&random_state, (uint32_t)minipal_hires_ticks()); @@ -795,11 +800,14 @@ void Thread::HijackReturnAddress(NATIVE_CONTEXT* pSuspendCtx, HijackFunc* pfnHij void Thread::HijackReturnAddressWorker(StackFrameIterator* frameIterator, HijackFunc* pfnHijackFunction) { void** ppvRetAddrLocation; + uintptr_t spForPacSign = 0; frameIterator->CalculateCurrentMethodState(); + if (frameIterator->GetCodeManager()->GetReturnAddressHijackInfo(frameIterator->GetMethodInfo(), frameIterator->GetRegisterSet(), - &ppvRetAddrLocation)) + &ppvRetAddrLocation, + &spForPacSign)) { ASSERT(ppvRetAddrLocation != NULL); @@ -811,8 +819,15 @@ void Thread::HijackReturnAddressWorker(StackFrameIterator* frameIterator, Hijack CrossThreadUnhijack(); void* pvRetAddr = *ppvRetAddrLocation; + ASSERT(pvRetAddr != NULL); + +#if defined(TARGET_ARM64) + //TODO-PAC: Authenticate instead of stripping the return addresses. + ASSERT(StackFrameIterator::IsValidReturnAddress(PacStripPtr(pvRetAddr))); +#else ASSERT(StackFrameIterator::IsValidReturnAddress(pvRetAddr)); +#endif // TARGET_ARM64 m_ppvHijackedReturnAddressLocation = ppvRetAddrLocation; m_pvHijackedReturnAddress = pvRetAddr; @@ -822,7 +837,14 @@ void Thread::HijackReturnAddressWorker(StackFrameIterator* frameIterator, Hijack frameIterator->GetRegisterSet())); #endif - *ppvRetAddrLocation = (void*)pfnHijackFunction; + void* pvHijackedAddr = (void*)pfnHijackFunction; +#if defined(TARGET_ARM64) + if (spForPacSign != 0) + { + pvHijackedAddr = PacSignPtr(pvHijackedAddr, (void*)spForPacSign); + } +#endif // TARGET_ARM64 + *ppvRetAddrLocation = pvHijackedAddr; STRESS_LOG2(LF_STACKWALK, LL_INFO10000, "InternalHijack: TgtThread = %llx, IP = %p\n", GetOSThreadId(), frameIterator->GetRegisterSet()->GetIP()); diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp index 266b56bd1f6e4e..9a547d0a45f3d6 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp @@ -64,6 +64,197 @@ UnixNativeCodeManager::~UnixNativeCodeManager() { } +#if defined(TARGET_ARM64) +static size_t readULEB(const uint8_t *&p, const uint8_t *end) +{ + size_t result = 0; + unsigned shift = 0; + while (p < end) + { + uint8_t byte = *p++; + result |= size_t(byte & 0x7F) << shift; + if ((byte & 0x80) == 0) // clear top bit indicates the last by of the value + break; + shift += 7; + } + return result; +} + +static ssize_t readSLEB(const uint8_t *&p, const uint8_t *end) +{ + ssize_t result = 0; + unsigned shift = 0; + uint8_t byte = 0; + + while (p < end) + { + byte = *p++; + result |= ssize_t(byte & 0x7F) << shift; + shift += 7; + if ((byte & 0x80) == 0) // clear top bit indicates the last by of the value + { + break; + } + } + + if ((shift < (sizeof(result) * 8)) && ((byte & 0x40) != 0)) + { + result |= -((ssize_t)1 << shift); + } + + return result; +} + +struct PacFrameInfo +{ + bool hasPac; + int cfaOffset; + int lrOffset; +}; + +static bool TryGetPacFrameInfo(UnixNativeMethodInfo *pNativeMethodInfo, + PacFrameInfo *pPacFrameInfo) +{ + const uint8_t* p = (const uint8_t*)pNativeMethodInfo->unwind_info; + uint32_t fdeLength = *dac_cast((uint8_t*)p); + const uint8_t* end = p + fdeLength; + p += sizeof(uint32_t); // FDE length + + if (*dac_cast((uint8_t*)p) == 0) + return false; + + p += sizeof(uint32_t); // CIE pointer + p += sizeof(uint32_t); // PC start + p += sizeof(uint32_t); // function length + + size_t augmentationLength = readULEB(p, end); + if ((size_t)(end - p) < augmentationLength) + return false; + p += augmentationLength; + + constexpr int DataAlignFactor = -4; + constexpr uint8_t ReturnAddressRegister = 30; + + int cfaOffset = 0; + int lrOffset = INT_MIN; + bool hasPac = false; + + while (p < end) + { + uint8_t op = *p++; + + if (op == DW_CFA_AARCH64_negate_ra_state) + { + hasPac = true; + continue; + } + + if ((op & 0xC0) == DW_CFA_advance_loc) + { + continue; + } + + if ((op & 0xC0) == DW_CFA_offset) + { + uint8_t dwarfReg = op & 0x3F; + ssize_t offsetFactor = (ssize_t)readULEB(p, end); + if (dwarfReg == ReturnAddressRegister) + { + lrOffset = cfaOffset + (int)(offsetFactor * DataAlignFactor); + } + continue; + } + + switch (op) + { + case DW_CFA_nop: + break; + + case DW_CFA_advance_loc1: + p += sizeof(uint8_t); + break; + + case DW_CFA_advance_loc2: + p += sizeof(uint16_t); + break; + + case DW_CFA_advance_loc4: + p += sizeof(uint32_t); + break; + + case DW_CFA_offset_extended: + { + uint8_t dwarfReg = (uint8_t)readULEB(p, end); + ssize_t offsetFactor = (ssize_t)readULEB(p, end); + if (dwarfReg == ReturnAddressRegister) + { + lrOffset = cfaOffset + (int)(offsetFactor * DataAlignFactor); + } + break; + } + + case DW_CFA_offset_extended_sf: + { + uint8_t dwarfReg = (uint8_t)readULEB(p, end); + ssize_t offsetFactor = readSLEB(p, end); + if (dwarfReg == ReturnAddressRegister) + { + lrOffset = cfaOffset + (int)(offsetFactor * DataAlignFactor); + } + break; + } + + case DW_CFA_def_cfa: + readULEB(p, end); // register + cfaOffset = (int)readULEB(p, end); + break; + + case DW_CFA_def_cfa_register: + readULEB(p, end); // register + break; + + case DW_CFA_def_cfa_offset: + cfaOffset = (int)readULEB(p, end); + break; + + case DW_CFA_def_cfa_sf: + readULEB(p, end); // register + cfaOffset = (int)(readSLEB(p, end) * DataAlignFactor); + break; + + case DW_CFA_def_cfa_offset_sf: + cfaOffset = (int)(readSLEB(p, end) * DataAlignFactor); + break; + + default: + return false; + } + } + + pPacFrameInfo->hasPac = hasPac; + pPacFrameInfo->cfaOffset = cfaOffset; + pPacFrameInfo->lrOffset = lrOffset; + return true; +} + +static bool TryGetSpForPacSigning(const PacFrameInfo& pacFrameInfo, + PTR_PTR_VOID ppvRetAddrLocation, + uintptr_t *pSpForPacSign) +{ + if (!pacFrameInfo.hasPac) + { + *pSpForPacSign = 0; + return true; + } + + if (ppvRetAddrLocation == NULL || pacFrameInfo.lrOffset == INT_MIN || pacFrameInfo.cfaOffset < pacFrameInfo.lrOffset) + return false; + + *pSpForPacSign = dac_cast(ppvRetAddrLocation) + (pacFrameInfo.cfaOffset - pacFrameInfo.lrOffset); + return true; +} +#endif // TARGET_ARM64 + // Virtually unwind stack to the caller of the context specified by the REGDISPLAY bool UnixNativeCodeManager::VirtualUnwind(MethodInfo* pMethodInfo, REGDISPLAY* pRegisterSet) { @@ -381,7 +572,7 @@ bool UnixNativeCodeManager::IsUnwindable(PTR_VOID pvAddress) pMethodInfo = &methodInfo; #endif -#if (defined(TARGET_APPLE) && defined(TARGET_ARM64)) || defined(TARGET_ARM) +#if defined(TARGET_ARM64) || defined(TARGET_ARM) // VirtualUnwind can't unwind epilogues and some prologues. return TrailingEpilogueInstructionsCount(pMethodInfo, pvAddress) == 0 && IsInProlog(pMethodInfo, pvAddress) != 1; #else @@ -501,7 +692,7 @@ static bool IsArmPrologInstruction(uint16_t* pInstr) #endif -#if (defined(TARGET_APPLE) && defined(TARGET_ARM64)) || defined(TARGET_ARM) +#if defined(TARGET_ARM64) || defined(TARGET_ARM) // checks for known prolog instructions generated by ILC and returns // 1 - in prolog // 0 - not in prolog @@ -870,6 +1061,16 @@ int UnixNativeCodeManager::TrailingEpilogueInstructionsCount(MethodInfo * pMetho #define LDP_BITS2 0x28400000 #define LDP_MASK2 0x7E400000 +// add sp, sp, #imm +// 1001 0001 0xxx xxxx xxxx xx11 1111 1111 +#define ADD_SP_SP_BITS 0x910003FF +#define ADD_SP_SP_MASK 0xFF8003FF + +// sub sp, fp, #imm +// 1101 0001 0xxx xxxx xxxx xx11 1011 1111 +#define SUB_SP_FP_BITS 0xD10003BF +#define SUB_SP_FP_MASK 0xFF8003FF + // Branches, Exception Generating and System instruction group // xxx1 01xx xxxx xxxx xxxx xxxx xxxx xxxx #define BEGS_BITS 0x14000000 @@ -924,6 +1125,26 @@ int UnixNativeCodeManager::TrailingEpilogueInstructionsCount(MethodInfo * pMetho return -1; } } + + // Post-index restore sequences such as "ldp x19, x20, [sp], #0x10" also adjust SP + // before the final AUTIASP/RET. We avoid signing with a partially-restored SP. + int baseRegister = (instr >> 5) & 0x1f; + if (baseRegister == 31) + { + if ((instr & LDP_MASK2) == LDP_BITS2 || + (instr & LDR_MASK2) == LDR_BITS2) + { + return -1; + } + } + + // Stack pointer adjustments can happen before AUTIASP/RET in some epilog layouts, + // so treat them as being in the epilog as well. + if ((instr & ADD_SP_SP_MASK) == ADD_SP_SP_BITS || + (instr & SUB_SP_FP_MASK) == SUB_SP_FP_BITS) + { + return -1; + } } #elif defined(TARGET_ARM) @@ -1147,7 +1368,8 @@ int UnixNativeCodeManager::TrailingEpilogueInstructionsCount(MethodInfo * pMetho bool UnixNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodInfo, REGDISPLAY * pRegisterSet, // in - PTR_PTR_VOID * ppvRetAddrLocation) // out + PTR_PTR_VOID * ppvRetAddrLocation, // out + uintptr_t * pSpForArm64PacSign) // out { UnixNativeMethodInfo* pNativeMethodInfo = (UnixNativeMethodInfo*)pMethodInfo; @@ -1164,6 +1386,22 @@ bool UnixNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn if ((unwindBlockFlags & UBF_FUNC_REVERSE_PINVOKE) != 0) return false; +#if defined(TARGET_ARM64) + PacFrameInfo pacFrameInfo = {}; + bool hasPacFrameInfo = TryGetPacFrameInfo(pNativeMethodInfo, &pacFrameInfo); + bool pacPresent = hasPacFrameInfo && pacFrameInfo.hasPac; + if (pacPresent) + { + // For PAC frames we only hijack locations where the current frame state is + // unambiguous. Partial prologs can save FP/LR before FP is established, and some + // epilog layouts adjust SP before the final AUTIASP/RET sequence. + if (IsInProlog(pMethodInfo, (PTR_VOID)pRegisterSet->IP) == 1) + { + return false; + } + } +#endif + #if defined(TARGET_ARM) // Ensure that PC doesn't have the Thumb bit set. Prolog and epilog // checks depend on it. @@ -1176,9 +1414,22 @@ bool UnixNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn // can't figure, possibly a breakpoint instruction return false; } - else if (epilogueInstructions > 0) + +#if defined(TARGET_ARM64) + if (pacPresent && epilogueInstructions != 0) { + return false; + } +#endif + + if (epilogueInstructions > 0) + { + *pSpForArm64PacSign = 0; *ppvRetAddrLocation = (PTR_PTR_VOID)(pRegisterSet->GetSP() + (sizeof(TADDR) * (epilogueInstructions - 1))); +#if defined(TARGET_ARM64) + if (!TryGetSpForPacSigning(pacFrameInfo, *ppvRetAddrLocation, pSpForArm64PacSign)) + return false; +#endif return true; } @@ -1201,6 +1452,7 @@ bool UnixNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn // Unwind the current method context to the caller's context to get its stack pointer // and obtain the location of the return address on the stack #if defined(TARGET_AMD64) + *pSpForArm64PacSign = 0; if (!VirtualUnwind(pMethodInfo, pRegisterSet)) { @@ -1211,6 +1463,7 @@ bool UnixNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn return true; #elif defined(TARGET_ARM64) || defined(TARGET_ARM) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) + *pSpForArm64PacSign = 0; if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) != 0) p += sizeof(int32_t); @@ -1235,6 +1488,17 @@ bool UnixNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn return false; } +#if defined(TARGET_ARM64) + if (pacPresent) + { + // We hijack the caller frame later. To retrieve signing SP for correct PAC + // processing, we need to pacFrameInfo for the caller frame. Currently bail + // out of hijacking in this case. + // ToDo-PAC: Enable hijacking caller frame + return false; + } +#endif + PTR_uintptr_t oldLocation = pRegisterSet->GetReturnAddressRegisterLocation(); if (!VirtualUnwind(pMethodInfo, pRegisterSet)) { diff --git a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.h b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.h index ca3f3f2272bde1..7d0969d85dedc2 100644 --- a/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.h +++ b/src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.h @@ -63,7 +63,7 @@ class UnixNativeCodeManager : public ICodeManager bool IsUnwindable(PTR_VOID pvAddress); -#if (defined(TARGET_APPLE) && defined(TARGET_ARM64)) || defined(TARGET_ARM) +#if defined(TARGET_ARM64) || defined(TARGET_ARM) int IsInProlog(MethodInfo * pMethodInfo, PTR_VOID pvAddress); #endif @@ -71,7 +71,8 @@ class UnixNativeCodeManager : public ICodeManager bool GetReturnAddressHijackInfo(MethodInfo * pMethodInfo, REGDISPLAY * pRegisterSet, // in - PTR_PTR_VOID * ppvRetAddrLocation); // out + PTR_PTR_VOID * ppvRetAddrLocation, // out + uintptr_t * pSpForArm64PacSign);// out PTR_VOID RemapHardwareFaultToGCSafePoint(MethodInfo * pMethodInfo, PTR_VOID controlPC); diff --git a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp index b2a85e88d588e5..04bd83ea1f9d42 100644 --- a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp +++ b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp @@ -830,9 +830,52 @@ bool CoffNativeCodeManager::IsUnwindable(PTR_VOID pvAddress) return true; } +#if defined(TARGET_ARM64) +static bool HasPacInUnwindInfo(PTR_VOID pUnwindDataBlob, size_t unwindDataBlobSize) +{ + PTR_uint8_t UnwindCodePtr = dac_cast(pUnwindDataBlob); + PTR_uint8_t UnwindCodesEndPtr = dac_cast(pUnwindDataBlob) + unwindDataBlobSize; + + while (UnwindCodePtr < UnwindCodesEndPtr) + { + uint8_t CurCode = *UnwindCodePtr; + if ((CurCode & 0xfe) == 0xe4) // The last unwind code + { + break; + } + + if (CurCode == 0xFC) // Unwind code for PAC (pac_sign_lr) + { + return true; + } + + if (CurCode < 0xC0) + { + UnwindCodePtr += 1; + } + else if (CurCode < 0xE0) + { + UnwindCodePtr += 2; + } + else + { + static const BYTE UnwindCodeSizeTable[32] = + { + 4,1,2,1,1,1,1,3, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 2,3,4,5,1,1,1,1 + }; + + UnwindCodePtr += UnwindCodeSizeTable[CurCode - 0xE0]; + } + } + + return false; +} +#endif //TARGET_ARM64 + bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodInfo, REGDISPLAY * pRegisterSet, // in - PTR_PTR_VOID * ppvRetAddrLocation) // out + PTR_PTR_VOID * ppvRetAddrLocation, // out + uintptr_t * pSpForArm64PacSign) // out { CoffNativeMethodInfo * pNativeMethodInfo = (CoffNativeMethodInfo *)pMethodInfo; @@ -863,6 +906,7 @@ bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn #endif #if defined(TARGET_AMD64) + *pSpForArm64PacSign = 0; context.Rsp = pRegisterSet->GetSP(); context.Rbp = pRegisterSet->GetFP(); context.Rip = pRegisterSet->GetIP(); @@ -879,6 +923,7 @@ bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn *ppvRetAddrLocation = (PTR_PTR_VOID)(context.Rsp - sizeof (PVOID)); return true; #elif defined(TARGET_ARM64) + *pSpForArm64PacSign = 0; if ((unwindBlockFlags & UBF_FUNC_HAS_ASSOCIATED_DATA) != 0) p += sizeof(int32_t); @@ -903,6 +948,11 @@ bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn return false; } + if (HasPacInUnwindInfo(pUnwindDataBlob, unwindDataBlobSize)) + { + *pSpForArm64PacSign = pRegisterSet->GetSP(); + } + context.Sp = pRegisterSet->GetSP(); context.Fp = pRegisterSet->GetFP(); context.Pc = pRegisterSet->GetIP(); @@ -936,6 +986,7 @@ bool CoffNativeCodeManager::GetReturnAddressHijackInfo(MethodInfo * pMethodIn *ppvRetAddrLocation = (PTR_PTR_VOID)contextPointers.Lr; return true; #else + *pSpForArm64PacSign = 0; EstablisherFrame = 0; HandlerData = NULL; return false; diff --git a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.h b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.h index c85f5250967793..dbeb3956d483ea 100644 --- a/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.h +++ b/src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.h @@ -92,7 +92,8 @@ class CoffNativeCodeManager : public ICodeManager bool GetReturnAddressHijackInfo(MethodInfo * pMethodInfo, REGDISPLAY * pRegisterSet, // in - PTR_PTR_VOID * ppvRetAddrLocation); // out + PTR_PTR_VOID * ppvRetAddrLocation, // out + uintptr_t * pSpForArm64PacSign); // out #ifdef TARGET_X86 GCRefKind GetReturnValueKind(MethodInfo * pMethodInfo, diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs index 25d81deb39aabc..e79f859da5efce 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs @@ -11,6 +11,7 @@ internal enum CFI_OPCODE CFI_ADJUST_CFA_OFFSET, // Offset is adjusted relative to the current one. CFI_DEF_CFA_REGISTER, // New register is used to compute CFA CFI_REL_OFFSET, // Register is saved at offset from the current CFA - CFI_DEF_CFA // Take address from register and add offset to it. + CFI_DEF_CFA, // Take address from register and add offset to it. + CFI_NEGATE_RA_STATE, // Sign the return address in lr with paciasp } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFde.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFde.cs index 4c1444c179a701..4a8b2076872590 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFde.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFde.cs @@ -116,6 +116,10 @@ private static byte[] CfiCodeToInstructions(DwarfCie cie, byte[] blobData) cfaOffset = cfiOffset; cfiCodeOffset += DwarfHelper.WriteULEB128(cfiCode.AsSpan(cfiCodeOffset), (uint)cfaOffset); break; + + case CFI_OPCODE.CFI_NEGATE_RA_STATE: + cfiCode[cfiCodeOffset++] = DW_CFA_AARCH64_negate_ra_state; + break; } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs index 5db4b2cfeb7e25..f5d6372535a9bd 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs @@ -121,6 +121,10 @@ public static byte[] ConvertCFIToEabi(byte[] blobData) EmitSpAdjustment(cfiOffset); } break; + + case CFI_OPCODE.CFI_NEGATE_RA_STATE: + // Do nothing here. + break; } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.Aot.cs index 6cb3f71f117d88..840405a4350d22 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.Aot.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/MachObjectWriter.Aot.cs @@ -160,6 +160,10 @@ private static uint GetArm64CompactUnwindCode(byte[] blobData) switch (opcode) { + case CFI_OPCODE.CFI_NEGATE_RA_STATE: + // Fall back to DWARF so the AArch64 negate_ra_state opcode is preserved for libunwind. + return UNWIND_ARM64_MODE_DWARF; + case CFI_OPCODE.CFI_DEF_CFA_REGISTER: cfaRegister = dwarfReg; diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 141ca0c60720b6..35dc6604a9f9e5 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -101,7 +101,8 @@ private enum CFI_OPCODE CFI_ADJUST_CFA_OFFSET, // Offset is adjusted relative to the current one. CFI_DEF_CFA_REGISTER, // New register is used to compute CFA CFI_REL_OFFSET, // Register is saved at offset from the current CFA - CFI_DEF_CFA // Take address from register and add offset to it. + CFI_DEF_CFA, // Take address from register and add offset to it. + CFI_NEGATE_RA_STATE, // Sign the return address in lr with paciasp } // Get the CFI data in the same shape as clang/LLVM generated one. This improves the compatibility with libunwind and other unwind solutions @@ -132,6 +133,7 @@ private static byte[] CompressARM64CFI(byte[] blobData) } int offset = 0; + bool shouldAddPACOpCode = false; while (offset < blobData.Length) { codeOffset = Math.Max(codeOffset, blobData[offset++]); @@ -185,6 +187,10 @@ private static byte[] CompressARM64CFI(byte[] blobData) } } break; + + case CFI_OPCODE.CFI_NEGATE_RA_STATE: + shouldAddPACOpCode = true; + break; } } @@ -194,6 +200,14 @@ private static byte[] CompressARM64CFI(byte[] blobData) using (BinaryWriter cfiWriter = new BinaryWriter(cfiStream)) { + if (shouldAddPACOpCode) + { + cfiWriter.Write((byte)codeOffset); + cfiWriter.Write((byte)CFI_OPCODE.CFI_NEGATE_RA_STATE); + cfiWriter.Write((short)-1); + cfiWriter.Write(0); + } + if (cfaRegister != -1) { cfiWriter.Write((byte)codeOffset); diff --git a/src/coreclr/unwinder/arm64/unwinder.cpp b/src/coreclr/unwinder/arm64/unwinder.cpp index f76a97c67f88ec..8b6f49e2ba09b7 100644 --- a/src/coreclr/unwinder/arm64/unwinder.cpp +++ b/src/coreclr/unwinder/arm64/unwinder.cpp @@ -31,6 +31,10 @@ #define FIELD_OFFSET(type, field) ((LONG)__builtin_offsetof(type, field)) #endif +#if !defined(DACCESS_COMPILE) && !defined(FEATURE_CDAC_UNWINDER) +extern "C" void* PacAuthPtr(void* ptr, void* sp); +#endif // !defined(DACCESS_COMPILE) && !defined(FEATURE_CDAC_UNWINDER) + #ifdef HOST_UNIX #define RtlZeroMemory ZeroMemory @@ -251,16 +255,75 @@ do { #endif // !defined(DEBUGGER_UNWIND) -// // Macros for stripping pointer authentication (PAC) bits. -// +#if !defined(DACCESS_COMPILE) && !defined(FEATURE_CDAC_UNWINDER) -#if !defined(DEBUGGER_STRIP_PAC) +#define HANDLE_PAC(pointer, sp) RtlStripPacOnline(pointer, sp) -// NOTE: Pointer authentication is not used by .NET, so the implementation does nothing -#define STRIP_PAC(Params, pointer) +FORCEINLINE +VOID RtlStripPacOnline(_Inout_ PULONG64 Pointer, _In_ ULONG64 Sp) -#endif +/*++ + +Routine Description: + + This routine authenticates an ARM64 pointer authenticated with PACIASP + using the supplied stack pointer as the modifier. Hence this should only + be called when authenticating a pointer at runtime (not debugger). + +Arguments: + + Pointer - Supplies a pointer to the pointer whose PAC will be authenticated. + + Sp - Supplies the stack pointer value that was used as the PAC modifier. + +Return Value: + + None. + +--*/ + +{ + *Pointer = (ULONG64)PacAuthPtr((void *)(*Pointer), (void *)Sp); +} +#else + +#define HANDLE_PAC(pointer, sp) RtlStripPacManual(pointer, sp) + +FORCEINLINE +VOID +RtlStripPacManual( + _Inout_ PULONG64 Pointer, + _In_ ULONG64 Sp + ) +/*++ + +Routine Description: + + This routine manually strips the ARM64 Pointer Authentication Code (PAC) + from a pointer. This is functionally similar to the XPAC family of + instructions. + + N.B. Even though PAC is only supported on ARM64, this routine is available + on all architectures to conveniently enable scenarios such as the + Debugger. + +Arguments: + + Pointer - Supplies a pointer to the pointer whose PAC will be stripped. + +Return Value: + + None. + +--*/ +{ + UNREFERENCED_PARAMETER(Sp); + *Pointer &= 0x0000FFFFFFFFFFFF; + return; +} + +#endif // !defined(DACCESS_COMPILE) && !defined(FEATURE_CDAC_UNWINDER) // // Macros to clarify opcode parsing @@ -2343,7 +2406,8 @@ Return Value: return STATUS_UNWIND_INVALID_SEQUENCE; } - STRIP_PAC(UnwindParams, &ContextRecord->Lr); + // TODO-PAC: Authenticate instead of stripping the return address. + HANDLE_PAC(&ContextRecord->Lr, ContextRecord->Sp); // // TODO: Implement support for UnwindFlags RTL_VIRTUAL_UNWIND2_VALIDATE_PAC. diff --git a/src/coreclr/vm/arm64/asmhelpers.S b/src/coreclr/vm/arm64/asmhelpers.S index b60f2a118be155..c592d44596f15b 100644 --- a/src/coreclr/vm/arm64/asmhelpers.S +++ b/src/coreclr/vm/arm64/asmhelpers.S @@ -147,11 +147,51 @@ NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler EPILOG_RESTORE_REG_PAIR x25, x26, 64 EPILOG_RESTORE_REG_PAIR x27, x28, 80 EPILOG_RESTORE_REG_PAIR_INDEXED fp, lr, 192 + xpaclri EPILOG_RETURN NESTED_END OnHijackTripThread, _TEXT #endif // FEATURE_HIJACK +// void* PacStripPtr(void *); +// This function strips the pointer of PAC info that is passed as an argument. +// To avoid failing on non-PAC enabled machines, we use xpaclri (instead of xpaci) which strips lr explicitly. +// Thus we move need to move input in lr, strip it and copy it back to the result register. +.arch_extension pauth + LEAF_ENTRY PacStripPtr, _TEXT + mov x9, lr + mov lr, x0 + xpaclri + mov x0, lr + ret x9 + LEAF_END PacStripPtr, _TEXT + +// void* PacSignPtr(void *, void *); +// This function sign the input pointer using SP as salt. +// To avoid failing on non-PAC enabled machines, we use pacia1716 which signs lr explicitly. +// Thus we need to move input in lr, sign it and then copy it back to the result register. +.arch_extension pauth + LEAF_ENTRY PacSignPtr, _TEXT + mov x17, x0 + mov x16, x1 + pacia1716 + mov x0, x17 + ret + LEAF_END PacSignPtr, _TEXT + +// void* PacAuthPtr(void *, void *); +// This function authenticates the input signed-pointer using x1 as salt. +// To avoid failing on non-PAC enabled machines, we use pacia1716 which authenticates lr explicitly. +// Thus we need to move input in lr, authenticate it and then copy it back to the result register. +.arch_extension pauth + LEAF_ENTRY PacAuthPtr, _TEXT + mov x17, x0 + mov x16, x1 + autia1716 + mov x0, x17 + ret + LEAF_END PacAuthPtr, _TEXT + // ------------------------------------------------------------------ // Redirection Stub for GC in fully interruptible method //GenerateRedirectedHandledJITCaseStub GCThreadControl diff --git a/src/coreclr/vm/arm64/asmhelpers.asm b/src/coreclr/vm/arm64/asmhelpers.asm index e7f6f8083b6d58..32381b97e4a28b 100644 --- a/src/coreclr/vm/arm64/asmhelpers.asm +++ b/src/coreclr/vm/arm64/asmhelpers.asm @@ -311,11 +311,49 @@ NoFloatingPointRetVal EPILOG_RESTORE_REG_PAIR x25, x26, #64 EPILOG_RESTORE_REG_PAIR x27, x28, #80 EPILOG_RESTORE_REG_PAIR fp, lr, #192! + + DCD 0xD50320FF ; xpaclri instruction in binary to avoid error while compiling with non-PAC enabled compilers EPILOG_RETURN NESTED_END #endif ; FEATURE_HIJACK +; void* PacStripPtr(void *); +; This function strips the pointer of PAC info that is passed as an agrument. +; To avoid failing on non-PAC enabled machines, we use xpaclri (instead of xpaci) which strips lr explicitly. +; Thus we move need to move input in lr, strip it and copy it back to the result register. + LEAF_ENTRY PacStripPtr + mov x9, lr + mov lr, x0 + DCD 0xD50320FF ; xpaclri instruction in binary to avoid error while compiling with non-PAC enabled compilers + mov x0, lr + ret x9 + LEAF_END PacStripPtr + +; void* PacSignPtr(void *, void *); +; This function sign the input pointer using zero as salt. +; To avoid failing on non-PAC enabled machines, we use pacia1716 which signs lr explicitly. +; Thus we need to move input in lr, sign it and then copy it back to the result register. + LEAF_ENTRY PacSignPtr + mov x17, x0 + mov x16, x1 + DCD 0xD503211F ; pacia1716 instruction in binary to avoid error while compiling with non-PAC enabled compilers + mov x0, x17 + ret + LEAF_END PacSignPtr + +; void* PacAuthPtr(void *, void *); +; This function authenticates the input signed-pointer using x1 as salt. +; To avoid failing on non-PAC enabled machines, we use pacia1716 which authenticates lr explicitly. +; Thus we need to move input in lr, authenticate it and then copy it back to the result register. + LEAF_ENTRY PacAuthPtr + mov x17, x0 + mov x16, x1 + DCD 0xD503219F ; autia1716 instruction in binary to avoid error while compiling with non-PAC enabled compilers + mov x0, x17 + ret + LEAF_END PacAuthPtr + ;; ------------------------------------------------------------------ ;; Redirection Stub for GC in fully interruptible method GenerateRedirectedHandledJITCaseStub GCThreadControl diff --git a/src/coreclr/vm/arm64/cgencpu.h b/src/coreclr/vm/arm64/cgencpu.h index 55e327dff3beda..281bdcfbdfa2a1 100644 --- a/src/coreclr/vm/arm64/cgencpu.h +++ b/src/coreclr/vm/arm64/cgencpu.h @@ -208,7 +208,8 @@ typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA inline PCODE GetIP(const T_CONTEXT * context) { LIMITED_METHOD_DAC_CONTRACT; - return context->Pc; + //TODO-PAC: Strip/Authenticate while populating the context. + return (PCODE) context->Pc; } inline void SetIP(T_CONTEXT *context, PCODE eip) { diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index 49b323075cda1b..f39cc3e25441ec 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -6346,6 +6346,293 @@ bool IsIPInEpilog(PTR_CONTEXT pContextToCheck, EECodeInfo *pCodeInfo, BOOL *pSaf return fIsInEpilog; } +#if defined(TARGET_ARM64) +// Read the PAC state for a managed ARM64 frame and, when PAC is enabled, recover the +// SP value that was live when PACIASP signed the return address in LR. +bool GetPacSignInfo(PTR_CONTEXT pContextToCheck, EECodeInfo *pCodeInfo, TADDR retAddrLocation, TADDR *pSpForPacSign) +{ + _ASSERTE(pContextToCheck != nullptr); + _ASSERTE(pCodeInfo->IsValid()); + _ASSERTE(pSpForPacSign != nullptr); + + *pSpForPacSign = 0; + + // In prolog or epilog while the current frame is still being established or torn down we cannot retrieve correct SP reliably. + if (IsIPInProlog(pCodeInfo)) + { + return false; + } + + BOOL unused = TRUE; + if (IsIPInEpilog(pContextToCheck, pCodeInfo, &unused)) + { + return false; + } + + // Lookup the function entry for the IP + PTR_RUNTIME_FUNCTION FunctionEntry = pCodeInfo->GetFunctionEntry(); + + // We should always get a function entry for a managed method + _ASSERTE(FunctionEntry != NULL); + DWORD_PTR ImageBase = pCodeInfo->GetModuleBase(); + + _ASSERTE((FunctionEntry->UnwindData & 3) == 0); // Packed unwind data are not used with managed code + ULONG_PTR UnwindDataPtr = (ULONG_PTR)(ImageBase + FunctionEntry->UnwindData); + + // For unwind info layout details refer https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#arm64-exception-handling-information + // Read the header word. + DWORD HeaderWord = *(DWORD*)UnwindDataPtr; + UnwindDataPtr += 4; + + _ASSERTE(((HeaderWord >> 18) & 3) == 0); // Version 0 is the only supported version. + + ULONG UnwindWords = (HeaderWord >> 27) & 31; + ULONG EpilogScopeCount = (HeaderWord >> 22) & 31; + if (EpilogScopeCount == 0 && UnwindWords == 0) + { + EpilogScopeCount = *(DWORD*)UnwindDataPtr; + UnwindDataPtr += 4; + UnwindWords = (EpilogScopeCount >> 16) & 0xFF; + EpilogScopeCount &= 0xFFFF; + } + + if ((HeaderWord & (1 << 21)) != 0) + { + EpilogScopeCount = 0; + } + + ULONG_PTR UnwindCodePtr = UnwindDataPtr + 4 * EpilogScopeCount; + ULONG_PTR UnwindCodesEndPtr = UnwindCodePtr + 4 * UnwindWords; + + auto GetUnwindOpSize = [](BYTE unwindCode) -> SIZE_T + { + if (unwindCode < 0xC0) + { + return 1; + } + else if (unwindCode < 0xE0) + { + return 2; + } + else + { + static const BYTE UnwindCodeSizeTable[32] = + { + 4,1,2,1,1,1,1,3, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 2,3,4,5,1,1,1,1 + }; + + return UnwindCodeSizeTable[unwindCode - 0xE0]; + } + }; + + ULONG unwindOpCount = 0; + for (ULONG_PTR unwindOpPtr = UnwindCodePtr; unwindOpPtr < UnwindCodesEndPtr;) + { + BYTE curCode = *(BYTE*)unwindOpPtr; + if ((curCode & 0xFE) == 0xE4) // end, end_c + { + break; + } + + SIZE_T unwindOpSize = GetUnwindOpSize(curCode); + if ((unwindOpPtr + unwindOpSize) > UnwindCodesEndPtr) + { + return false; + } + + unwindOpCount++; + unwindOpPtr += unwindOpSize; + } + + ULONG_PTR* unwindOpStarts = (ULONG_PTR*)_alloca(unwindOpCount * sizeof(ULONG_PTR)); + ULONG unwindOpIndex = 0; + for (ULONG_PTR unwindOpPtr = UnwindCodePtr; unwindOpPtr < UnwindCodesEndPtr;) + { + BYTE curCode = *(BYTE*)unwindOpPtr; + if ((curCode & 0xFE) == 0xE4) // end, end_c + { + break; + } + + SIZE_T unwindOpSize = GetUnwindOpSize(curCode); + if ((unwindOpPtr + unwindOpSize) > UnwindCodesEndPtr) + { + return false; + } + + unwindOpStarts[unwindOpIndex++] = unwindOpPtr; + unwindOpPtr += unwindOpSize; + } + + SSIZE_T currentSpOffset = 0; + SSIZE_T pacSpOffset = SSIZE_T_MIN; + SSIZE_T lrSlotOffset = SSIZE_T_MIN; + constexpr SSIZE_T PtrSize = 8; + + // ARM64 prolog unwind codes are stored in reverse prolog order. Replay them in prolog order so + // PACIASP captures the SP that was live when LR was originally signed. + while (unwindOpIndex != 0) + { + UnwindCodePtr = unwindOpStarts[--unwindOpIndex]; + ULONG CurCode = *(BYTE*)UnwindCodePtr; + + if ((CurCode & 0xE0) == 0x00) // alloc_s + { + currentSpOffset -= (CurCode & 0x1F) * 16; + continue; + } + + if ((CurCode & 0xE0) == 0x20) // save_r19r20_x + { + currentSpOffset -= (CurCode & 0x1F) * 8; + continue; + } + + if ((CurCode & 0xC0) == 0x40) // save_fplr + { + lrSlotOffset = currentSpOffset + ((CurCode & 0x3F) * 8) + PtrSize; + continue; + } + + if ((CurCode & 0xC0) == 0x80) // save_fplr_x + { + currentSpOffset -= ((CurCode & 0x3F) + 1) * 8; + lrSlotOffset = currentSpOffset + PtrSize; + continue; + } + + if ((CurCode & 0xF8) == 0xC0) // alloc_m + { + ULONG x = ((CurCode & 0x7) << 8) | *(BYTE*)(UnwindCodePtr + 1); + currentSpOffset -= x * 16; + continue; + } + + if ((CurCode & 0xFC) == 0xC8) // save_regp + { + continue; + } + + if ((CurCode & 0xFC) == 0xCC) // save_regp_x + { + ULONG z = *(BYTE*)(UnwindCodePtr + 1) & 0x3F; + currentSpOffset -= (z + 1) * 8; + continue; + } + + if ((CurCode & 0xFC) == 0xD0) // save_reg + { + BYTE nextCode = *(BYTE*)(UnwindCodePtr + 1); + ULONG x = ((CurCode & 0x3) << 2) | (nextCode >> 6); + ULONG z = nextCode & 0x3F; + if (x == 11) // R30 / LR is the 12th GP register in the save_reg encodings + { + lrSlotOffset = currentSpOffset + z * 8; + } + + continue; + } + + if ((CurCode & 0xFE) == 0xD4) // save_reg_x + { + BYTE nextCode = *(BYTE*)(UnwindCodePtr + 1); + ULONG x = ((CurCode & 0x1) << 3) | (nextCode >> 5); + currentSpOffset -= ((nextCode & 0x1F) + 1) * 8; + if (x == 11) // R30 / LR is the 12th GP register in the save_reg encodings + { + lrSlotOffset = currentSpOffset; + } + + continue; + } + + if ((CurCode & 0xFE) == 0xD6) // save_lrpair + { + ULONG z = *(BYTE*)(UnwindCodePtr + 1) & 0x3F; + lrSlotOffset = currentSpOffset + z * 8 + PtrSize; + continue; + } + + if ((CurCode & 0xFE) == 0xD8) // save_fregp + { + continue; + } + + if ((CurCode & 0xFE) == 0xDA) // save_fregp_x + { + ULONG z = *(BYTE*)(UnwindCodePtr + 1) & 0x3F; + currentSpOffset -= (z + 1) * 8; + continue; + } + + if ((CurCode & 0xFE) == 0xDC) // save_freg + { + continue; + } + + if (CurCode == 0xDE) // save_freg_x + { + ULONG z = *(BYTE*)(UnwindCodePtr + 1) & 0x1F; + currentSpOffset -= (z + 1) * 8; + continue; + } + + if (CurCode == 0xE0) // alloc_l + { + ULONG x = (*(BYTE*)(UnwindCodePtr + 1) << 16) | (*(BYTE*)(UnwindCodePtr + 2) << 8) | *(BYTE*)(UnwindCodePtr + 3); + currentSpOffset -= x * 16; + continue; + } + + if (CurCode == 0xE1) // set_fp + { + continue; + } + + if (CurCode == 0xE2) // add_fp + { + continue; + } + + if (CurCode == 0xE3) // nop + { + continue; + } + + if (CurCode == 0xE6) // save_next + { + continue; + } + + if (CurCode == 0xFC) // pac_sign_lr + { + if (pacSpOffset == SSIZE_T_MIN) + { + // Snapshot the SP delta for the PACIASP in prolog. + pacSpOffset = currentSpOffset; + } + + continue; + } + + return false; + } + + if (pacSpOffset == SSIZE_T_MIN) + { + return true; + } + + if (lrSlotOffset == SSIZE_T_MIN) + { + return false; + } + + *pSpForPacSign = (TADDR)((SSIZE_T)retAddrLocation + pacSpOffset - lrSlotOffset); + return true; +} +#endif // TARGET_ARM64 + #endif // FEATURE_HIJACK && (!TARGET_X86 || TARGET_UNIX) #define EXCEPTION_VISUALCPP_DEBUGGER ((DWORD) (1<<30 | 0x6D<<16 | 5000)) diff --git a/src/coreclr/vm/excep.h b/src/coreclr/vm/excep.h index 33f7444ae220a7..17564920f33e78 100644 --- a/src/coreclr/vm/excep.h +++ b/src/coreclr/vm/excep.h @@ -29,7 +29,9 @@ BOOL AdjustContextForJITHelpers(EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pCo // General purpose functions for use on an IP in jitted code. bool IsIPInProlog(EECodeInfo *pCodeInfo); bool IsIPInEpilog(PTR_CONTEXT pContextToCheck, EECodeInfo *pCodeInfo, BOOL *pSafeToInjectThreadAbort); - +#if defined(TARGET_ARM64) +bool GetPacSignInfo(PTR_CONTEXT pContextToCheck, EECodeInfo *pCodeInfo, TADDR retAddrLocation, TADDR *pSpForPacSign); +#endif // TARGET_ARM64 #endif // FEATURE_HIJACK && (!TARGET_X86 || TARGET_UNIX) // Enums diff --git a/src/coreclr/vm/tailcallhelp.cpp b/src/coreclr/vm/tailcallhelp.cpp index da2dcca7d1da13..fa8ea22a9b4ad6 100644 --- a/src/coreclr/vm/tailcallhelp.cpp +++ b/src/coreclr/vm/tailcallhelp.cpp @@ -10,6 +10,9 @@ #include "gcrefmap.h" #include "threads.h" +#if defined(TARGET_ARM64) +extern "C" void* PacStripPtr(void* ptr); +#endif // TARGET_ARM64 FCIMPL0(void*, TailCallHelp::GetTailCallArgBuffer) { @@ -39,7 +42,14 @@ FCIMPL2(void*, TailCallHelp::GetTailCallInfo, void** retAddrSlot, void** retAddr Thread* thread = GetThread(); - *retAddr = thread->GetReturnAddress(retAddrSlot); + void* retAddrFromSlot = thread->GetReturnAddress(retAddrSlot); + +#if defined(TARGET_ARM64) + //TODO-PAC: Authenticate instead of stripping the return address. + retAddrFromSlot = PacStripPtr(retAddrFromSlot); +#endif // TARGET_ARM64 + *retAddr = retAddrFromSlot; + return thread->GetTailCallTls(); } FCIMPLEND diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 18aad8dcd2c5e3..60f28815a2e7ea 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -2507,6 +2507,9 @@ class Thread void HijackThread(ExecutionState *esb X86_ARG(ReturnKind returnKind) X86_ARG(bool hasAsyncRet)); VOID *m_pvHJRetAddr; // original return address (before hijack) +#ifdef TARGET_ARM64 + VOID *m_pSpForPacSign; // stack pointer value that was used to sign LR with PACIASP +#endif VOID **m_ppvHJRetAddrPtr; // place we bashed a new return address MethodDesc *m_HijackedFunction; // remember what we hijacked diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index f265094ce9876a..b7b133af1d860c 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -22,6 +22,11 @@ #define HIJACK_NONINTERRUPTIBLE_THREADS +#if defined(TARGET_ARM64) +extern "C" void* PacSignPtr(void* ptr, void* sp); +extern "C" void* PacAuthPtr(void* ptr, void* sp); +#endif // TARGET_ARM64 + bool ThreadSuspend::s_fSuspendRuntimeInProgress = false; bool ThreadSuspend::s_fSuspended = false; @@ -4466,6 +4471,9 @@ struct ExecutionState bool m_IsInterruptible; // is this code interruptible? MethodDesc *m_pFD; // current function/method we're executing VOID **m_ppvRetAddrPtr; // pointer to return address in frame +#if defined(TARGET_ARM64) + VOID *m_pSpForPacSign; // stack pointer value that was used to sign LR with PACIASP +#endif DWORD m_RelOffset; // relative offset at which we're currently executing in this fcn IJitManager *m_pJitManager; METHODTOKEN m_MethodToken; @@ -4473,8 +4481,10 @@ struct ExecutionState ExecutionState() { LIMITED_METHOD_CONTRACT; -#ifdef TARGET_X86 +#if defined(TARGET_X86) m_FirstPass = true; +#elif defined(TARGET_ARM64) + m_pSpForPacSign = nullptr; #endif } }; @@ -4537,6 +4547,10 @@ void Thread::HijackThread(ExecutionState *esb X86_ARG(ReturnKind returnKind) X86 // Remember the place that the return would have gone m_pvHJRetAddr = *esb->m_ppvRetAddrPtr; +#if defined(TARGET_ARM64) + m_pSpForPacSign = esb->m_pSpForPacSign; +#endif + 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 @@ -4548,6 +4562,13 @@ void Thread::HijackThread(ExecutionState *esb X86_ARG(ReturnKind returnKind) X86 m_HijackedFunction = esb->m_pFD; // Bash the stack to return to one of our stubs +#if defined(TARGET_ARM64) + if (m_pSpForPacSign != nullptr) + { + pvHijackAddr = PacSignPtr(pvHijackAddr, m_pSpForPacSign); + } +#endif // TARGET_ARM64 + *esb->m_ppvRetAddrPtr = pvHijackAddr; SetThreadState(TS_Hijacked); } @@ -4627,6 +4648,9 @@ StackWalkAction SWCB_GetExecutionState(CrawlFrame *pCF, VOID *pData) pES->m_pFD = pCF->GetFunction(); pES->m_MethodToken = pCF->GetMethodToken(); pES->m_ppvRetAddrPtr = 0; +#if defined(TARGET_ARM64) + pES->m_pSpForPacSign = nullptr; +#endif pES->m_IsInterruptible = pCF->IsGcSafe(); pES->m_RelOffset = pCF->GetRelOffset(); pES->m_pJitManager = pCF->GetJitManager(); @@ -4804,12 +4828,24 @@ void STDCALL OnHijackWorker(HijackArgs * pArgs) thread->ResetThreadState(Thread::TS_Hijacked); - // Fix up our caller's stack, so it can resume from the hijack correctly + // Keep the actual resume address in the saved LR slot. HijackFrame needs a + // canonical managed PC for stackwalk/GC, but OnHijackTripThread will later + // return via the saved LR in HijackArgs. pArgs->ReturnAddress = (size_t)thread->m_pvHJRetAddr; +#if defined(TARGET_ARM64) + void* hijackFrameReturnAddress = thread->m_pvHJRetAddr; + if (thread->m_pSpForPacSign != nullptr) + { + hijackFrameReturnAddress = PacAuthPtr(hijackFrameReturnAddress, thread->m_pSpForPacSign); + } +#else + void* hijackFrameReturnAddress = thread->m_pvHJRetAddr; +#endif // TARGET_ARM64 + // Build a frame so that stack crawling can proceed from here back to where // we will resume execution. - HijackFrame frame((void *)pArgs->ReturnAddress, thread, pArgs); + HijackFrame frame(hijackFrameReturnAddress, thread, pArgs); #ifdef _DEBUG BOOL GCOnTransition = FALSE; @@ -5248,8 +5284,17 @@ BOOL Thread::HandledJITCase() X86_ONLY(ReturnKind returnKind;) X86_ONLY(bool hasAsyncRet;) + ARM64_ONLY(TADDR spForPacSign = 0;) if (GetReturnAddressHijackInfo(&codeInfo X86_ARG(&returnKind) X86_ARG(&hasAsyncRet))) { +#if defined(TARGET_ARM64) + if (!GetPacSignInfo(&ctx, &codeInfo, dac_cast(esb.m_ppvRetAddrPtr), &spForPacSign)) + { + return FALSE; + } + + esb.m_pSpForPacSign = (PVOID)spForPacSign; +#endif // TARGET_ARM64 HijackThread(&esb X86_ARG(returnKind) X86_ARG(hasAsyncRet)); } } @@ -5800,6 +5845,15 @@ void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext) StackWalkerWalkingThreadHolder threadStackWalking(pThread); // Hijack the return address to point to the appropriate routine based on the method's return type. + ARM64_ONLY(TADDR spForPacSign = 0;) +#if defined(TARGET_ARM64) + if (!GetPacSignInfo(interruptedContext, &codeInfo, dac_cast(executionState.m_ppvRetAddrPtr), &spForPacSign)) + { + return; + } + + executionState.m_pSpForPacSign = (PVOID)spForPacSign; +#endif // TARGET_ARM64 pThread->HijackThread(&executionState X86_ARG(returnKind) X86_ARG(hasAsyncRet)); } }