Arm64: [PAC-RET] Add Pointer Authentication support for Arm64#125436
Arm64: [PAC-RET] Add Pointer Authentication support for Arm64#125436SwapnilGaikwad wants to merge 166 commits into
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 |
|
/azp run runtime-nativeaot-outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
This PR covers the final subset of changes from #125436 related to NativeAOT as suggested in [comment](#125436 (comment)). It follows the previous work from- #127949, #127838 and #128147. More details on PAC and its role in software security can be found ([here](https://llsoftsec.github.io/llsoftsecbook/#sec:pointer-authentication)). --------- Co-authored-by: Jan Kotas <jkotas@microsoft.com>
As suggested in [comment](#125436 (comment)), this PR covers subset of changes from #125436 related to the JIT. 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`. - Changes are limited to JIT. **NOTE**: This PR adds part of the changes for PAC support to simply review process as discussed [here](#125436 (comment)). However it cannot pass the tests without the remaining parts. Kindly take a look at the CI status of #125436 to ensure correctness. --------- Co-authored-by: dhartglassMSFT <dhartglass+github@microsoft.com>
This PR covers subset of PAL-RET changes from #125436 related to the coreclr and VM. More details on PAC and its role in software security can be found ([here](https://llsoftsec.github.io/llsoftsecbook/#sec:pointer-authentication)). The feature is disabled by default so CI status of this PR won't reflect the impact of changes.
…ss (#129130) Fixes dotnet/dotnet#7079 Storing FP/LR along with the unwind info. It handle a scenario if unwinding takes place when `lr` is clobbered, as raised [here](#125436 (comment)). cc: @dotnet/arm64-contrib @a74nh @jkotas @dhartglassMSFT
This PR covers the final subset of changes from #125436 related to NativeAOT as suggested in [comment](#125436 (comment)). It follows the previous work from- #127949, #127838 and #128147. More details on PAC and its role in software security can be found ([here](https://llsoftsec.github.io/llsoftsecbook/#sec:pointer-authentication)). --------- Co-authored-by: Jan Kotas <jkotas@microsoft.com>
|
Workflow state for the Holistic Review Orchestrator. {
"version": 5,
"last_dispatched_commit": "c45d099ada75fe190e393ed2fb12a871fe0edf75",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "1da83b362354e3c2b75cf42153bcf1438c9fb4df",
"last_reviewed_commit": "c45d099ada75fe190e393ed2fb12a871fe0edf75",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "1da83b362354e3c2b75cf42153bcf1438c9fb4df",
"last_recorded_worker_run_id": "29680713400",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "c45d099ada75fe190e393ed2fb12a871fe0edf75",
"review_id": 4730565546
}
]
} |
There was a problem hiding this comment.
Holistic Review
Motivation: This PR adds Arm64 Pointer Authentication (PAC) support to mitigate ROP attacks by signing the return address (LR) in prologs (paciasp) and authenticating it in epilogs (autiasp). It contributes to #109457. This portion of the range wires the cDAC (managed data-contract reader) and diagnostics stack-walk paths to understand PAC-signed return addresses, replacing the previous NotImplementedException stubs with real PAC-stripping logic, and toggles the JitPacEnabled platform metadata flag through the runtime.
Approach: The change is well-structured and consistent. A single Arm64PtrAuthMask (0x0000FFFFFFFFFFFF) is introduced to strip the PAC bits from the top 16 bits of a return address, and a new CodePointerUtils.StripPtrAuthFromReturnAddress helper centralizes stripping with a null guard. Call sites across ARM64Unwinder, the frame handlers, FrameHelpers.GetReturnAddress, and StackWalk_1 are updated to normalize instruction pointers via CodePointerFromAddress/AddressFromCodePointer and to strip PAC from return addresses that originate from signed frames. cdacplatformmetadata.cpp now derives HasArm64PtrAuth from the JitPacEnabled CLRConfig value for all Arm64 (not just Apple), keeping the JIT and cDAC in agreement. The ARM64Unwinder threads an isReturnAddressSigned flag so PAC is only stripped when the unwind codes indicate the return address was actually signed, which is the correct, targeted behavior.
The cDAC/diagnostics changes look correct and internally consistent: the relaxed Debug.Assert((flags & ~CodePointerFlags.HasArm64PtrAuth) == 0) assertions appropriately tolerate the new flag, and the masking value matches the 48-bit VA space assumption used elsewhere. Reading-only code paths (which is what the cDAC does) can safely strip rather than authenticate.
Summary: The cDAC and diagnostics-reader changes are a solid, self-consistent implementation of PAC-aware return-address handling. My one significant concern is the default-on flip of JitPacEnabled (see inline comment on src/coreclr/jit/jitconfigvalues.h): it enables an incomplete feature by default and contradicts both the PR description and the PR's own "Disable PAC by default before merge" checklist item, with remaining ToDos (binary-size and performance analysis) still open. Assuming the default flip is unintentional (or is separately justified and the description updated), the rest of the diff looks correct. I could not build or run tests in this environment; assessment is based on reading the diff and existing CI.
Detailed Findings
-
Feature enabled by default (blocking concern):
JitPacEnableddefault changed from0to1injitconfigvalues.h(and mirrored as the default incdacplatformmetadata.cpp). See the inline comment for details. This appears to contradict the PR's stated intent. -
Minor style nit (non-blocking):
CodePointerUtils.csleaves a stray extra blank line before the closing brace of the class after the newStripPtrAuthFromReturnAddressmethod. Harmless, but worth tidying. -
Consistency (informational): The
Arm64PtrAuthMaskconstant is duplicated in bothCodePointerUtils.csandLegacy/ConversionExtensions.cs. This mirrors the existing duplication ofArm32ThumbBit, so it is consistent with current conventions; no change required, but a shared constant could reduce drift over time.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 67.9 AIC · ⌖ 10.9 AIC · ⊞ 10K
| CONFIG_INTEGER(JitMaxUncheckedOffset, "JitMaxUncheckedOffset", 8) | ||
| #if defined(TARGET_ARM64) | ||
| RELEASE_CONFIG_INTEGER(JitPacEnabled, "JitPacEnabled", 0) | ||
| RELEASE_CONFIG_INTEGER(JitPacEnabled, "JitPacEnabled", 1) |
There was a problem hiding this comment.
This flips the JitPacEnabled default from 0 to 1, enabling Pointer Authentication by default. This directly contradicts the PR description ("turned off by default") and the checked ToDo item "Disable PAC by default before merge." It also enables a still-in-progress feature whose remaining ToDos (binary-size impact, performance regression analysis) are unchecked. The matching cdacplatformmetadata.cpp change also defaults JitPacEnabled to 1, so the JIT and the cDAC agree, but both now default the feature on. If the intent is to keep PAC off by default until the feature is complete, this should remain 0 (and the cDAC config default likewise). If the default-on is intentional, please update the PR description and ToDo checklist to match.
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.ToDos
stpto store FP/LR.Contributes to #109457