Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ARM64: Support for PAC-RET in .NET10 #109457

Open
14 tasks
a74nh opened this issue Nov 1, 2024 · 3 comments
Open
14 tasks

ARM64: Support for PAC-RET in .NET10 #109457

a74nh opened this issue Nov 1, 2024 · 3 comments
Assignees
Labels
arch-arm64 area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI User Story A single user-facing feature. Can be grouped under an epic.
Milestone

Comments

@a74nh
Copy link
Contributor

a74nh commented Nov 1, 2024

PAC-RET is a way of preventing ROP attacks on Arm64 using the PAC extension which was introduced in Arm 8.3. When enabled the stack pointer is encrypted before being stored to the stack and verified again when it is restored.

A detailed description of PAC-RET and the associated security issues can be found in Low-Level Software Security for Compiler Developers

Expand for another description....

The assumption here is that the attacker has gotten the ability the change writable memory in the process (possibly only the stack) and read executable memory. They do not have the ability to change readonly memory, change the control flow or change/access register contents. The goal of the attacker is to make the program execute arbitrary code. This can be done by editing the return addresses on the stack. When the program returns, it now jumps back to code the attacker wants to run. This in itself is not that useful as the attacker is limited to functions available and register contents. By looking through all executable memory they look for small groups of instructions directly proceeding a return instruction. These are "gadgets" which simply change a register or write a bit of memory. By chaining gadgets together using return addresses the attacker now can execute whatever they want. Tools exist to look at the executable code of a known program (or library) and build a library of gadgets (which is why I'm concerned about protection of CoreCLR code over jitted code).

PAC-RET works because the return address stays in a register LR (which the attacker cannot access) and only goes to memory when saved to the stack, which is encrypted before the store. When loading from the stack we unencrypt, and fault on an error. To modify the address, the hacker would need to know the secret per-process key. The hacker can't simply replace it with a different encrypted value as the location on the stack is used as a salt in the encryption, meaning every encrypted value is pinned to that location.

PAC-RET is self contained by function. When a function encrypts the return address, it will be the same function that decrypts it again before returning. Therefore, for standard programs, PAC can be enabled per function without interfering with other functionality. Issues arise when a program walks its own stack, rewrites it's stack, or jumps out of program order.

When run on systems without PAC, the PAC instructions are treated as NOPs. Therefore a PAC protected program can be run on a non-PAC system at a cost of a few NOPs per function.

Testing

There are a number of different scenarios that could be tested. To reduce testing size, only a few are required:

CoreCLR PAC feature OS system libraries Should be tested in CI
build with branch protection not in hardware or OS with PAC Yes
build with branch protection enabled in hardware + OS with PAC Yes
build with branch protection enabled in hardware + OS without PAC No
build without branch protection either either No

Assumptions

  • For now, only support Linux and Windows

Work items

  • Add PAC supported Linux hardware in the CI
    Using the scenarios above. This will likely be Cobalt 100. No other PAC work can be merged until this step is complete.

  • Build .NET using branch-protection flags Enable for PAC while compiling coreclr (not the jitted code) #108561
    This will ensure that the entire CoreCLR VM in protected via PAC. This will always be enabled for Linux builds. The expected cost is 1-2% slowdown in the VM and jit on PAC enabled machines. This code is static and is the most vulnerable to ROP attacks as an attacker will be able to use the code to build an a library of attack gadgets ahead of time. Building with branch-protection will prevent this

  • Protect assembly routines:
    This is only required if there are assembly routines in CoreCLR which save the return value to the stack. These should all be updated to encrypt/decrypt when saving/loading to/from the stack. Each routine could be implemented individually.

  • Fix up stack underwinders.
    CoreCLR contains two libunwinds. It may have other stack examiners. These should be updated to strip PAC from the return address (there is no requirement here to decrypt the value). The underwinders need to be able to handle both encrypted and unencrypted values.

  • Add PAC-RET support to the jit
    Once CoreCLR is protected, the next step is to protect code generated by CoreCLR. Enable via a config value. Ensure return values are encrypted/decrypted in prolog/epilog. Fix up any rewriting of the stack - for example return address hijacking in the GC. Suggested implementation order: 1) Encrypt the return address with a salt of 0 using the same key used by C++ code, decrypt by stripping - testing this will ensure all stack examiners/editors are found 2) decrypt fully 3) encrypt using the stack address as the salt. 4) Use a different key to C++.

  • Debugging and Diagnostics
    Along with stack unwinding, we need to ensure that debugger can decrypt the function addresses (if needed) and display the debug info correctly.

  • NativeAOT / R2R
    Lastly, we definitely should validate the working of PAC with NativeAOT and R2R to guarantee that it will work as expected.

Stretch items

  • Harden the config variable.
    An attacker could potentially overwrite the config variable and disable PAC. Either always enable PAC if the hardware supports it or ensure the config variables moved to read-only memory after startup.

  • Harden hijacking stub addresses
    The addresses used in the return address hijacking should be kept in memory as encrypted values. When rewriting the stack, the value is loaded from memory to register, decrypted, encrypted again, then stored to the stack.

Possible future work items

  • Support Windows
  • Support MacOS, once Arm64e is fully supported in MacOS.
  • Add BTI support
  • Encrypt more data pointers. Possibly all pointers that point to jitted code.
  • Support Arm64 GCS. This is similar to Intel's CET (see Epic: Support Intel CET #47309). GCS is not yet available in hardware.
@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Nov 1, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Nov 1, 2024
@a74nh a74nh added User Story A single user-facing feature. Can be grouped under an epic. and removed untriaged New issue has not been triaged by the area owner needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Nov 1, 2024
@a74nh a74nh self-assigned this Nov 1, 2024
@a74nh a74nh added arch-arm64 area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Nov 1, 2024
Copy link
Contributor

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

@a74nh a74nh added this to the 10.0.0 milestone Nov 1, 2024
@am11
Copy link
Member

am11 commented Nov 2, 2024

  • Support MacOS. This will involve full Arm64e support.

A simple hello world app fails to run on M1 system:

% cc -xc -target arm64e-apple-macos -o hello_arm_e - <<EOF
#include <stdio.h>

int main() {
#ifdef __arm64e__
    printf("Running on arm64e architecture\n");
#else
    printf("Running on arm64 without 'e'\n");
#endif

    return 0;
}
EOF

% ./hello_arm_e
zsh: killed     ./hello_arm_e

log show --info --last 1m | grep hello_arm_e shows stuff like:

2024-11-02 12:59:39.923157+0200 0x1d31bd   Default     0x0                  0      0    kernel: exec_mach_imgact: not running binary "hello_arm_e" built against preview arm64e ABI
2024-11-02 12:59:39.923401+0200 0x1d31be   Default     0x0                  0      0    kernel: (AppleSystemPolicy) ASP: Security policy would not allow process: 2291, /Users/am11/projects/hello_arm_e

"built against preview arm64e ABI" part suggests it's still (#34121) a preview feature?

@a74nh
Copy link
Contributor Author

a74nh commented Nov 4, 2024

  • Support MacOS. This will involve full Arm64e support.

A simple hello world app fails to run on M1 system:

Ok, definitely moves way into the future then. I'll leave it on the list so that it's tracked.

@kunalspathak kunalspathak changed the title ARM64: Support for PAC-RET on Linux in .NET10 ARM64: Support for PAC-RET in .NET10 Nov 7, 2024
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 User Story A single user-facing feature. Can be grouped under an epic.
Projects
None yet
Development

No branches or pull requests

3 participants