dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.25k stars 4.73k forks source link

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

Open a74nh opened 15 hours ago

a74nh commented 15 hours ago

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: Test? CoreCLR PAC feature OS system libraries
yes build with branch protection not in hardware or OS with PAC
yes build with branch protection enabled in hardware + OS with PAC
no build with branch protection enabled in hardware + OS without PAC
no build without branch protection either either

Assumptions

Work items

Stretch items

Possible future work items

dotnet-policy-service[bot] commented 15 hours ago

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