fix: Strip PAC bits from return addresses in POSIX crash handler#315
fix: Strip PAC bits from return addresses in POSIX crash handler#315awforsythe wants to merge 1 commit into
Conversation
ARM64 has a security feature called Pointer Authentication Codes (PAC), where the CPU cryptographically signs address values, encoding the signature in the upper bits of the 64-bit value. It can then verify these addresses before jumping to them, to detect stack manipulation etc. at the hardware level. The SDK's in-process crash handler for POSIX reads return addresses as it walks the stack frame. When running arm64 Linux builds in Docker, I noticed that these addresses weren't being correctly resolved to loaded modules for shared libraries. As it turns out, module resolution was failing because the upper bits were set by PAC, causing the resolved address to land outside the load range of the module. Masking out these upper bits to preserve only the lower 48 bits resolved the issue in my tests.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7dac37e8c8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // bits, so we need to mask to 48-bit virtual address space to end up with the | ||
| // actual address within the relevant module. | ||
| uint64_t raw_ret = reinterpret_cast<uint64_t>(ret_addr); | ||
| raw_ret &= 0x0000ffffffffffff; |
There was a problem hiding this comment.
Preserve VA bits when stripping PAC
On AArch64 Linux processes that opt in to 52-bit user VAs (for example, executable mappings above 0x0000ffffffffffff), this hard-coded 48-bit mask truncates legitimate address bits before crash processing. write_modules() records full uintptr_t ranges from /proc/self/maps, so the truncated frame address will no longer fall inside the module range and symbolication will report the frame as unknown; use an architecture PAC-strip instruction/kernel PAC mask or derive the active VA width instead of assuming 48 bits.
Useful? React with 👍 / 👎.
ARM64 has a security feature called Pointer Authentication Codes (PAC), where the CPU cryptographically signs address values, encoding the signature in the upper bits of the 64-bit value. It can then verify these addresses before jumping to them, to detect stack manipulation etc. at the hardware level.
The SDK's in-process crash handler for POSIX reads return addresses as it walks the stack frame. When running arm64 Linux builds in Docker, I noticed that these addresses weren't being correctly resolved to loaded modules for shared libraries.
As it turns out, module resolution was failing because the upper bits were set by PAC, causing the resolved address to land outside the load range of the module. Masking out these upper bits to preserve only the lower 48 bits resolved the issue in my tests.