Skip to content

Arm64: [PAC-RET] Add Pointer Authentication support for Arm64#125436

Open
SwapnilGaikwad wants to merge 24 commits intodotnet:mainfrom
SwapnilGaikwad:github-add-pac
Open

Arm64: [PAC-RET] Add Pointer Authentication support for Arm64#125436
SwapnilGaikwad wants to merge 24 commits intodotnet:mainfrom
SwapnilGaikwad:github-add-pac

Conversation

@SwapnilGaikwad
Copy link
Copy Markdown
Contributor

@SwapnilGaikwad SwapnilGaikwad commented Mar 11, 2026

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).

  • 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

  • Disable PAC by default before merge.
  • 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.

Contributes to #109457

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.
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Mar 11, 2026
@jkotas jkotas added area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI arch-arm64 and removed area-NativeAOT-coreclr labels Mar 11, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@a74nh
Copy link
Copy Markdown
Contributor

a74nh commented Apr 1, 2026

How do we kick off a copilot review on this?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/autiasp and 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.

Comment on lines 79 to 114
@@ -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)
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
if (!FindMethodInfo(ControlPC, (MethodInfo*)&methodInfo))
return NULL;

PTR_uint8_t p = methodInfo.pLSDA;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-arm64 area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants