Skip to content

[PAC][libunwind] Unify signed return address handling logic #160110

@kovdan01

Description

@kovdan01

#143230 introduces libunwind hardening which could be used with pauth-enabled ABIs (such as arm64e for Apple and pauthtest for Linux). This includes handling of signed return addresses. Both mentioned ABIs implicitly enable -fptrauth-returns flag, which sets __has_feature(ptrauth_returns) to true and triggers RA signing in codegen via setting "ptrauth-returns" attribute for functions in IR.

The existing logic in libunwind already handles signed RA - such handling was introduced previously to support signed RA appearing when -mbranch-protection=pac-ret is used:

#if defined(_LIBUNWIND_TARGET_AARCH64)
// If the target is aarch64 then the return address may have been signed
// using the v8.3 pointer authentication extensions. The original
// return address needs to be authenticated before the return address is
// restored. autia1716 is used instead of autia as autia1716 assembles
// to a NOP on pre-v8.3a architectures.
if ((R::getArch() == REGISTERS_ARM64) &&
isReturnAddressSigned(addressSpace, registers, cfa, prolog) &&
returnAddress != 0) {
#if !defined(_LIBUNWIND_IS_NATIVE_ONLY)
return UNW_ECROSSRASIGNING;
#else
register unsigned long long x17 __asm("x17") = returnAddress;
register unsigned long long x16 __asm("x16") = cfa;
// We use the hint versions of the authentication instructions below to
// ensure they're assembled by the compiler even for targets with no
// FEAT_PAuth/FEAT_PAuth_LR support.
if (isReturnAddressSignedWithPC(addressSpace, registers, cfa, prolog)) {
register unsigned long long x15 __asm("x15") =
prolog.ptrAuthDiversifier;
if (cieInfo.addressesSignedWithBKey) {
asm("hint 0x27\n\t" // pacm
"hint 0xe"
: "+r"(x17)
: "r"(x16), "r"(x15)); // autib1716
} else {
asm("hint 0x27\n\t" // pacm
"hint 0xc"
: "+r"(x17)
: "r"(x16), "r"(x15)); // autia1716
}
} else {
if (cieInfo.addressesSignedWithBKey)
asm("hint 0xe" : "+r"(x17) : "r"(x16)); // autib1716
else
asm("hint 0xc" : "+r"(x17) : "r"(x16)); // autia1716
}
returnAddress = x17;
#endif
}
#endif

Note that existing implementation in libunwind supports different RA sign state in different stack frames. For example, we can build libunwind itself w/o pac-ret enabled, and build user code with pac-ret enabled, and unwinding would work fine since we look at RA sign state in each individual frame.

After #143230, signed RA handling looks as follows:

  1. If libunwind is built with pauth-enabled ABIs such as arm64e and pauthtest (which imply __has_feature(ptrauth_calls) && __has_feature(ptrauth_returns), we skip old signed RA handling logic and use signed RA handling logic from [runtimes][PAC] Harden unwinding when possible #143230.
  2. Otherwise, we use old signed RA handling logic.

Actual codegen is the same for -fptrauth-returns and -mbranch-protection=pac-ret, we just have different ways enabling that (see also #143230 (comment)).

We should unify logic in libunwind for these two cases as well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    hardeningIssues related to the hardening effortlibunwind

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions