Arm64: [PAC-RET] Add Pointer Authentication support for Arm64#125436
Arm64: [PAC-RET] Add Pointer Authentication support for Arm64#125436SwapnilGaikwad wants to merge 24 commits intodotnet:mainfrom
Conversation
This PR adds support for Pointer Authentication (PAC) on Arm64. Pointer Authentication (PAC) is an Armv8.3+ security feature designed to mitigate Return-Oriented Programming (ROP) attacks by cryptographically signing return addresses. While using PAC, we store a signed return address, instead of the plain address, on the stack and later authenticate it before returning from a function. It ensures control flow returns to the intended caller. More details on PAC and its role in software security can be found ([here](https://llsoftsec.github.io/llsoftsecbook/#sec:pointer-authentication)). - The current implementation of PAC is turned off by default, but can be turned on by setting DOTNET_JitPacEnabled=1. - PAC protects link register (LR) by signing it in the prolog (using `paciasp`) before it is split, using the current SP as the modifier. It then authenticates the LR in the epilog (using `autiasp`) before the function returns. If the signature is invalid, the execution fails with `SIGILL`. - - When the runtime needs to read or overwrite a return address during hijacking for GC, it now strips the PAC (using `xpaclri`) and re-signs the new target address before storing it back. - To simply tracking the SP in return address hijacking, we avoid using the pre-indexed variant of storing FP/LR on stack (e.g., `stp fp,lr,[sp,-#framesz]! `) to simply tracking the SP in return address hijacking. We obtain the value of SP at the time of signing the LR from the location of the current FP. We can't use this approach when the pre-indexed `stp` is used because we don't know the`#framesz`. - The updated prolog/epilog sequences generated by the JIT now look like: // Prolog sub sp, sp, #framesz paciasp ; sign LR with A-key + SP stp fp, lr, [sp] // Epilog ldp fp, lr, [sp] autiasp ; authenticate LR add sp, sp, #framesz ret ToDos: [] Restore the original frame layout that used pre-indexed variant of `stp` to store FP/LR. [] Authenticate the return address instead of stripping in return address hijacking and unwinding. [] Identify increased binary size for System.*.dll [] Determine performance regressions using benchmarks such as OrchirdCMS.
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
|
How do we kick off a copilot review on this? |
There was a problem hiding this comment.
Pull request overview
This PR adds Arm64 Pointer Authentication (PAC-RET) support across the JIT, runtime stack-walking/hijacking paths, and NativeAOT tooling/runtime so that return addresses can be signed in prologs and authenticated in epilogs, with supporting unwind metadata updates.
Changes:
- Add JIT emission of
paciasp/autiaspand propagate PAC presence via unwind/CFI metadata. - Update CoreCLR hijacking/unwinding/debugger/tailcall helpers to strip and/or re-sign return addresses when reading/writing stack return slots.
- Extend NativeAOT metadata and code managers to detect PAC frames and compute SP modifiers needed to re-sign hijacked return addresses.
Reviewed changes
Copilot reviewed 42 out of 42 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/native/external/llvm-libunwind/src/DwarfInstructions.hpp | Adjust libunwind return-address handling for PAC by stripping when needed. |
| src/coreclr/vm/threadsuspend.cpp | Add SP tracking and PAC-aware return-address hijacking in CoreCLR thread suspension. |
| src/coreclr/vm/threads.h | Extend Thread state for PAC-aware hijacking (store SP modifier). |
| src/coreclr/vm/tailcallhelp.cpp | Strip PAC bits when reading tailcall return address metadata on Arm64. |
| src/coreclr/vm/excep.h | Add IsPacPresent declaration for hijack-related logic on Arm64. |
| src/coreclr/vm/excep.cpp | Implement IsPacPresent by scanning Arm64 unwind codes for PAC marker. |
| src/coreclr/vm/arm64/cgencpu.h | Note PAC handling requirements around IP extraction from context. |
| src/coreclr/vm/arm64/asmhelpers.S | Add PAC strip/sign helpers and strip LR before returning from hijack stub (Unix asm). |
| src/coreclr/vm/arm64/asmhelpers.asm | Add PAC strip/sign helpers and strip LR before returning from hijack stub (Windows asm). |
| src/coreclr/vm/arm64/asmconstants.h | Update asm constants due to Thread layout changes. |
| src/coreclr/unwinder/arm64/unwinder.cpp | Strip PAC from LR during runtime unwinding (non-DAC) via helper. |
| src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | Compute/record PAC hijack metadata from CFI blobs for Arm64 Linux. |
| src/coreclr/tools/Common/Compiler/DependencyAnalysis/INodeWithCodeInfo.cs | Introduce interface for nodes carrying Arm64 PAC hijack metadata. |
| src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs | Add CFI opcode for PAC (NEGATE_RA_STATE) and include it in compressed CFI. |
| src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs | Store/publish Arm64 PAC hijack info as associated method data. |
| src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Eabi/EabiUnwindConverter.cs | Ignore PAC CFI opcode during EABI unwind conversion. |
| src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfFde.cs | Emit DWARF DW_CFA_AARCH64_negate_ra_state for PAC frames. |
| src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectWriter/Dwarf/DwarfCfiOpcode.cs | Extend CFI opcode enum to include PAC negate RA state. |
| src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodAssociatedDataNode.cs | Add associated-data flag and payload for Arm64 PAC hijack SP delta. |
| src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.h | Extend hijack info API to return SP modifier and add PAC presence query. |
| src/coreclr/nativeaot/Runtime/windows/CoffNativeCodeManager.cpp | Implement PAC detection + SP reporting for Arm64 Windows NativeAOT. |
| src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.h | Extend hijack info API to return SP modifier; add PAC presence query for Arm64. |
| src/coreclr/nativeaot/Runtime/unix/UnixNativeCodeManager.cpp | Implement PAC detection from DWARF and compute SP modifier from associated data. |
| src/coreclr/nativeaot/Runtime/thread.cpp | Re-sign hijack target addresses for PAC frames during NativeAOT hijacking. |
| src/coreclr/nativeaot/Runtime/StackFrameIterator.cpp | Strip PAC from control PC/return addresses during NativeAOT stack iteration. |
| src/coreclr/nativeaot/Runtime/ICodeManager.h | Extend ICodeManager with PAC presence query + SP modifier for hijack signing. |
| src/coreclr/nativeaot/Runtime/arm64/MiscStubs.S | Add PAC strip/sign helpers for NativeAOT Arm64 (Unix asm). |
| src/coreclr/nativeaot/Runtime/arm64/MiscStubs.asm | Add PAC strip/sign helpers for NativeAOT Arm64 (Windows asm). |
| src/coreclr/nativeaot/Runtime/arm64/GcProbe.S | Strip PAC from restored LR in GC probe (Unix asm). |
| src/coreclr/nativeaot/Runtime/arm64/GcProbe.asm | Strip PAC from restored LR in GC probe (Windows asm). |
| src/coreclr/jit/unwindarm64.cpp | Add unwind opcode/CFI emission for PAC signing. |
| src/coreclr/jit/unwind.cpp | Add CFI dump support for CFI_NEGATE_RA_STATE. |
| src/coreclr/jit/jitconfigvalues.h | Introduce JitPacEnabled config knob (currently default enabled). |
| src/coreclr/jit/emitarm64.cpp | Add emitter helpers to insert PAC instructions in prolog/epilog. |
| src/coreclr/jit/emit.h | Declare PAC emitter helpers for Arm64. |
| src/coreclr/jit/compiler.h | Declare unwindPacSignLR helper. |
| src/coreclr/jit/codegenarmarch.cpp | Modify Arm64 prolog generation to avoid pre-indexed FP/LR stores when PAC enabled. |
| src/coreclr/jit/codegenarm64.cpp | Modify Arm64 epilog generation to integrate autiasp sequencing with SP adjustments. |
| src/coreclr/inc/gcinfodecoder.h | Strip PAC from IP when decoding GC info on Arm64. |
| src/coreclr/inc/clrconfigvalues.h | Add external config JitPacEnabled (currently default enabled). |
| src/coreclr/inc/cfi.h | Extend VM CFI opcode enum for PAC negate RA state. |
| src/coreclr/debug/ee/controller.cpp | Strip PAC from tailcall return addresses in debugger tailcall detection. |
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs
Show resolved
Hide resolved
| @@ -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) | |||
There was a problem hiding this comment.
GetIP() now unconditionally calls PacStripPtr on TARGET_ARM64, and declares it as an extern. This header is included by non-VM components built with GCINFODECODER_NO_EE (e.g., src/coreclr/gcdump/gcdumpnonx86.cpp and DAC code), which won’t link against vm/arm64/asmhelpers.*. This is likely to introduce unresolved symbol/link failures on arm64. Consider providing a header-local fallback (e.g., manual PAC bit masking) under GCINFODECODER_NO_EE / DACCESS_COMPILE, or otherwise avoiding a hard dependency on the PacStripPtr symbol from this shared header.
| if (!FindMethodInfo(ControlPC, (MethodInfo*)&methodInfo)) | ||
| return NULL; | ||
|
|
||
| PTR_uint8_t p = methodInfo.pLSDA; |
There was a problem hiding this comment.
Revert this change - no longer needed?
| else | ||
| asm("hint 0xc" : "+r"(x17) : "r"(x16)); // autia1716 | ||
| { | ||
| //TODO-PAC: Restore the authentication with A key when signing with SP is in place. |
This PR adds support for Pointer Authentication (PAC) on Arm64. Pointer Authentication (PAC) is an Armv8.3+ security feature designed to mitigate Return-Oriented Programming (ROP) attacks by cryptographically signing return addresses. While using PAC, we store a signed return address, instead of the plain address, on the stack and later authenticate it before returning from a function. It ensures control flow returns to the intended caller.
More details on PAC and its role in software security can be found (here).
paciasp) before it is split, using the current SP as the modifier. It then authenticates the LR in the epilog (usingautiasp) before the function returns. If the signature is invalid, the execution fails withSIGILL.xpaclri) and re-signs the new target address before storing it back.stp fp,lr,[sp,-#framesz]!) to simply tracking the SP in return address hijacking. We obtain the value of SP at the time of signing the LR from the location of the current FP. We can't use this approach when the pre-indexedstpis used because we don't know the#framesz.ToDos
stpto store FP/LR.Contributes to #109457