llvm / llvm/llvm-project

[libunwind] `_Unwind_FindEnclosingFunction` fails on AArch64 with pointer authentication enabled

Open
#223,698 0 comments 0 reactions 0 assignees View on GitHub
libunwind
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

On AArch64 with pointer authentication enabled `_Unwind_FindEnclosingFunction` cannot succeed for an instruction pointer obtained from `_Unwind_GetIP`. This surfaced while adding `aarch64-unknown-linux-pauthtest` support to Rust's backtrace-rs: https://github.com/rust-lang/backtrace-rs/pull/755 ([the same Rust workaround](https://github.com/rust-lang/backtrace-rs/blob/master/src/backtrace/libunwind.rs#L81) - not calling into `_Unwind_FindEnclosingFunction` - is used for `arm64e`, so it's possible that it is also affected by this issue).

`_Unwind_GetIP` authenticates the stored PC and [returns an instruction pointer value associated with the original frame context](https://github.com/llvm/llvm-project/blob/main/libunwind/src/Registers.hpp#L1894), signed using the frame's SP as discriminator. That value is what a caller, like backtrace-rs, keeps as the frame's IP.

The failure occurs when `_Unwind_FindEnclosingFunction` is called and expected to [return the start address of the function enclosing a given PC](https://github.com/llvm/llvm-project/blob/main/libunwind/src/UnwindLevel1-gcc-ext.c#L85). However, the cursor is **not** the original frame that produced PC. It's a brand new unwind cursor initialized from the current thread context (wherever `_Unwind_FindEnclosingFunction` itself happens to be called from).

Because the instruction pointer returned by `_Unwind_GetIP` is discriminated with SP, its subsequent authentication depends on the original frame context, specifically the SP used for discrimination when signing. When `_Unwind_FindEnclosingFunction` imports this IP into its newly created cursor, it goes through `__unw_set_reg`, which [calls](https://github.com/llvm/llvm-project/blob/main/libunwind/src/Registers.hpp#L1905) `setIP`. And within it the SP belongs to the newly created cursor, not the SP belonging to the frame from which `_Unwind_GetIP` originally obtained the instruction pointer. So `setIP` attempts to authenticate an IP that belongs to the original frame using the SP of a different frame resulting in auth failure.

See: https://github.com/rust-lang/backtrace-rs/pull/755 for how this behaviour affects Rust's backtrace-rs.

Contributor guide

Open the contributing guide

Research direction

Start with libunwind/src/UnwindLevel1-gcc-ext.c at _Unwind_FindEnclosingFunction and libunwind/src/Registers.hpp around _Unwind_GetIP, __unw_set_reg, and setIP. Use the AArch64 pointer-authentication scenario described in the issue and the backtrace-rs pull request as a reproducer. Done means the enclosing function can be found for an IP returned from the original unwind frame, with regression coverage for this case.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, cpp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.