Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

AArch64 Pointer Authentication using aarch64-unknown-linux-pauthtest target on Linux ELF platforms

Metadata
Point of contact@jchlanda
compiler championDavid Wood
StatusProposed
Other tracking issuehttps://github.com/rust-lang/rust/issues/148640
Zulip channelN/A
Teamscompiler
Task owners(none)

Summary

This goal aims to add Pointer Authentication Code (later on referred to as PAC, pauth) support to Rust compiler. PAC is a hardware security feature introduced by ARM in ARMv8.3. It works by adding a cryptographic signature to pointers and verifying that signature before the pointers are used. This mechanism helps prevent attacks that attempt to modify or substitute writable pointers, such as return addresses or function pointers.

The signature is stored directly within the pointer value. On AArch64 systems the usable virtual address space occupies fewer than 64 bits, leaving upper bits of the pointer available to store PAC data.

Motivation

Support for pointer authentication is already present in the LLVM ecosystem. The ABI and object-format extensions required for PAC on AArch64 ELF systems are specified in PAuth ABI Extension to ELF for the Arm 64-bit Architecture, which describes how pointer authentication interacts with the ELF format and the platform ABI. In addition, Clang Pointer Authentication Documentation describes the underlying concepts of pointer authentication, the language extensions provided by Clang for PAC-enabled targets, and the stable ABI used by C, C++, and Objective-C on platforms that support pointer authentication.

Adding PAC support to the Rust compiler is motivated by two primary goals:

  1. Enable interoperability with PAC-enabled C and C++ code. When C or C++ code is compiled with pointer authentication enabled, certain groups of pointers are signed according to the platform ABI and authenticated prior to their use. At present Rust code is incompatible with programs using PAC and will not be able to interop with them.

  2. Enable security hardening of Rust code on AArch64 Linux. Native support for PAC will allow Rust programs to benefit from hardware-assisted control-flow integrity protections. By signing and authenticating return addresses, function pointers, and relevant ELF entries (such as GOT and init/fini pointers), Rust binaries can gain additional protection against wide range of attacks.

Relation to existing branch protection mechanisms

Existing compiler options such as -mbranch-protection enable limited pointer authentication features, most notably signing return addresses (pac-ret) and enforcing Branch Target Identification (BTI). These mechanisms primarily protect control flow within a single compiled function by inserting authentication instructions around function returns and indirect branch targets. The work proposed in this goal extends beyond branch protection by enabling full ABI-level support for pointer authentication in Rust.

The status quo

The Rust compiler currently does not support pointer authentication on AArch64 Linux targets. This causes two issues: Rust cannot correctly interoperate with PAC-enabled C/C++ code, and Rust binaries cannot take advantage of the additional protections provided by the architecture.

Lack of support for PAC-enabled C and C++ means that Rust currently treats PAC signed pointers as ordinary pointers and does not perform authentication before use. This breaks the expected signing/authentication contract, leading to invalid execution of programs. Furthermore, using unsigned pointers generated by Rust in PAC-enabled C/C++ code will result in similar failures.

Addressing interoperability requires Rust to correctly handle PAC-protected pointers produced by other toolchains and matching the same expectation when generating externally accessible pointers.

Consider the following sample illustrating Rust code using a function pointer provided by C:

// Declaration of a C function.
extern "C" {
    fn increment(x: c_int) -> c_int;
}

// Rust function that accepts a C function pointer
fn call_callback(cb: unsafe extern "C" fn(c_int) -> c_int, value: c_int) -> c_int {
    // Rust calls the function pointer received from C.
    //
    // PAC relevance:
    // - If C code was compiled with PAC support, 'cb' will be cryptographically signed.
    // - The CPU must authenticate 'cb' at the call site using appropriate metadata.
    // - If Rust is unaware of PAC, it will call the raw pointer without authentication.
    //   This will cause a runtime fault, by accessing invalid memory location.
    unsafe {
        cb(value)
    }
}

fn main() {
    // Use the C function pointer via Rust wrapper.
    let result = call_callback(increment, 41);
    ...
}

The same mechanism is in place when C attempts to call functions provided by Rust, hence the above sample can be rewritten as:

// Declaration of a Rust provided function.
extern int increment(int x);

int call_callback(int (*cb)(int), int value) {
    // C calls the callback
    //
    // PAC relevance:
    // As above signed function pointer must be authenticated prior to the use.
    return cb(value);
}

int main() {
    // Pass Rust function pointer to a C wrapper.
    int result = call_callback(increment, 41);
    ...
}

What we propose to do about it

This goal can be logically divided into three stages:

  1. Support for PAC aware C interop. This involves both using signed C function pointers from within Rust as well as providing properly signed extern "C" Rust functions to be called from pauth-enabled C code. Additional security features such as signing return addresses, init/fini entries, GOT table entries would also be part of the task. It would require developing a signing scheme - as each combination of signing parameters introduces a risk of ABI breakage. The task is focusing on AArch64 Linux based systems using ELF file format.
  2. Support for C++ interop. While based on the same principle as C this will introduce more implementation details that are not present in C version, for example signing virtual table entries, or developing different PAC metadata (support for type discrimination). Like with C interop, the work will have to be reflected in a schema that describes supported features.
  3. As a last step we would like to develop pointer authentication ABI for Rust code. This task would likely involve a prior discussion with the community, in order to get the design correct.

The next 6 months

Add aarch64-unknown-linux-pauthtest target that uses PAC aware toolchain and standard library.

Add support for C interop that is capable of handling pointer authentication features.

Same as above but for C++ interop.

Provide pointer authentication support for native Rust code.

TaskOwner(s)Notes
aarch64-unknown-linux-pauthtest PAC target@jchlanda
PAC C interop@jchlanda
PAC C++ interop@jchlanda
PAC native Rust support@jchlanda

Team asks

TeamSupport levelNotes
compilerSmallDesign discussions, PR review

Frequently asked questions

None yet.