Source-linked AI summary
PAC it up: Towards Pointer Integrity using ARM Pointer Authentication
Hans Liljestrand, Thomas Nyman, Kui Wang, Carlos Chinea Perez, Jan-Erik Ekberg, N. Asokan
TL;DR
Memory corruption in C and C++ enables control-flow and data-oriented attacks, while existing PA use has focused mainly on selected pointer corruptions. The paper extends PA through PARTS, an LLVM and GNU/Linux instrumentation framework, and reports broader protection with evaluated overheads. PARTS protects data pointers against currently known DOP attacks, while its current architecture does not directly handle some C++ features and future work includes other protection domains and memory-safety mechanisms.
Problem
PA has received little attention as a general-purpose defense against varied memory attacks despite existing protection for corrupted return addresses and function pointers.
Method
The paper develops PA-based pointer signing and run-time type safety and integrates them into LLVM and GNU/Linux through PARTS.
Results
PARTS protects all data pointers against currently known DOP attacks and reports 19.5% average data-pointer-signing overhead in nbench-byte.
Takeaways & Limitations
PA can provide practical pointer-integrity defenses for both control-flow and data-oriented attacks within the evaluated PARTS design.
Takeaways & Limitations
The current architecture does not directly handle some C++ features, and extending protection to the OS kernel or hypervisor requires higher-level key management.
Abstract
from arXiv · showhide
Run-time attacks against programs written in memory-unsafe programming languages (e.g., C and C++) remain a prominent threat against computer systems. The prevalence of techniques like return-oriented programming (ROP) in attacking real-world systems has prompted major processor manufacturers to design hardware-based countermeasures against specific classes of run-time attacks. An example is the recently added support for pointer authentication (PA) in the ARMv8-A processor architecture, commonly used in devices like smartphones. PA is a low-cost technique to authenticate pointers so as to resist memory vulnerabilities. It has been shown to enable practical protection against memory vulnerabilities that corrupt return addresses or function pointers. However, so far, PA has received very little attention as a general purpose protection mechanism to harden software against various classes of memory attacks. In this paper, we use PA to build novel defenses against various classes of run-time attacks, including the first PA-based mechanism for data pointer integrity. We present PARTS, an instrumentation framework that integrates our PA-based defenses into the LLVM compiler and the GNU/Linux operating system and show, via systematic evaluation, that PARTS provides better protection than current solutions at a reasonable performance overhead
1 Introduction
Memory corruption in C and C++ can redirect control flow or manipulate non-control data, while PA-based defenses have mainly addressed selected pointer corruptions. This work broadens PA into defenses for control-flow and data-oriented attacks through PARTS.
- Motivation: Memory corruption can overwrite code pointers, return addresses, or decision-making variables, enabling ROP and data-oriented attacks.These attacks can redirect execution, leak sensitive data, or escalate privileges.
- Goals and Contributions: The paper categorizes pointer reuse attacks and develops PA-based defenses against several run-time attack classes.The approach targets both control-flow and data-oriented attacks.
- Goals and Contributions: PARTS is an LLVM compiler instrumentation framework integrating pointer signing and run-time type safety with PA.The framework is implemented to realize protections for code pointers, data pointers, and return addresses.
- Goals and Contributions: PARTS combines pointer integrity for code and data pointers with run-time type safety to constrain pointer substitution attacks.The paper evaluates these mechanisms against memory corruption attacks.
- Evaluation: PARTS reports less than 0.5% average overhead for code-pointer and return-address signing and 19.5% for data-pointer signing in nbench-byte.The evaluation also reports better security guarantees than fully precise static CFI.
2 Background
Memory-unsafe programs face control-flow and data-oriented attacks, while ARMv8.3-A PA authenticates pointers using PACs stored within pointer bits. Existing return-address signing illustrates both PA’s protection and its implementation trade-offs.
- Attack Classes: Memory errors can corrupt control data to enable control-flow attacks or corrupt decision-making variables to enable data-oriented attacks.DEP has reduced the effectiveness of direct code injection, motivating these attack classes.
- ARM Pointer Authentication: ARMv8.3-A PA computes a pointer authentication code as a keyed MAC over the pointer value and a 64-bit modifier.Different key and modifier combinations provide domain separation among authenticated-pointer classes.
- Related Work: PA differs from CCFI by performing MAC comparison in hardware rather than requiring run-time software checks.CCFI uses hardware-accelerated AES, whereas PA uses QARMA or a manufacturer-specific MAC.
- ARM Pointer Authentication: The PAC occupies unused pointer bits, with its size depending on the virtual-address range and address-tagging configuration.On a default 39-bit-address AArch64 Linux configuration without tagging, the PAC is 24 bits; across configurations it ranges from 3 to 31 bits.
- ARM Pointer Authentication: PA provides separate code and data keys managed in hardware registers accessible only from a higher privilege level.Modifiers are supplied per instruction and describe the run-time context in which a pointer is created or authenticated.
- Return Address Signing: Return-address signing authenticates LR before return, binding the return address to the stack-pointer value used as its modifier.The instrumentation inserts PAC creation in the function prologue and authentication before return; the combined retaa instruction is not backward-compatible.
3 Attacks on Pointer Authentication
PA defenses must address more than pointer forgery: attackers may control PAC generation or reuse authenticated pointers. The paper therefore frames pointer protection around key and modifier separation and protected pointer contexts.
- Security Concerns: PA is intended to prevent pointer forgery, but pointer reuse and attacker-controlled PAC generation remain concerns for PA-based defenses.These concerns motivate separating authenticated-pointer classes by key and modifier.
- Assumptions: PA relies on confidential process-specific keys and immutable modifiers, while attackers may observe modifier generation and infer modifier values.The keys remain inaccessible through hardware-enforced controls, but modifier confidentiality is not assumed.
- Security Concerns: Attackers may brute-force PACs, exploit cryptanalysis or hardware side channels, or control PAC generation and reuse authenticated pointers.The paper leaves cryptographic-building-block scrutiny outside scope and treats guessing as a potential concern because PACs are limited in size.
- Malicious PAC Generation: Controlling the unauthenticated pointer, modifier, or both can produce authenticated pointers for attacker-chosen contexts.PA defenses must prevent attacker-controlled PAC inputs and constrain authentication instructions against use as gadgets.
- Reuse Attacks: Reuse attacks can roll an authenticated pointer back to a previous value or substitute another pointer using the same modifier.Observed authenticated pointers remain available for later reuse to the attacker.
- Reuse Attacks: Using the stack pointer as a modifier can permit return-address reuse when different function invocations share an SP value.The paper identifies partitioning authenticated pointers by distinct key and modifier pairs as a response.
4 Adversary Model and Requirements
The paper defines pointer integrity as preserving intended pointer values until use and evaluates PA defenses against a powerful memory attacker. Its requirements cover integrity, PA-attack resistance, compatibility, and performance.
- Pointer Integrity: Pointer integrity requires a pointer’s value at use to match the value when it was created or stored in program memory.The concept applies to both code and data pointers.
- Adversary Model: The adversary can arbitrarily read program memory and write to noncode segments, but cannot control higher privilege levels.This model is restricted by DEP and targets a user-space process when applicable.
- Requirements: The protection goal is to prevent attackers from forging pointers used by vulnerable programs in control-flow and data-oriented attacks.The requirements begin with detecting or preventing use of corrupted code and data pointers.
- Requirements: The design must resist PAC-generation control and pointer reuse attacks.This requirement addresses attacks that manipulate authenticated-pointer creation or substitute previously authenticated pointers.
- Requirements: The system must protect existing programs without interfering with normal operation while minimizing run-time and memory overhead.The performance requirement scales with the number of protected pointers and dereferences or calls.
5 Design
PARTS combines pointer signing, compartmentalization, and run-time type safety to defend code and data pointers against control-flow and data-oriented attacks. Its design instruments pointer creation, storage, loading, and use while accounting for pointer conversions and return-address reuse.
- Pointer integrity: PARTS emits PA instructions to sign return addresses, local, global, static, and C-structure pointers.The compiler enhancement instruments pointers as required to create and authenticate PACs.
- Pointer compartmentalization: Static PA modifiers separate code and data pointers, limiting reuse attacks through distinct key sets.PARTS uses separate key sets for data and code pointers as one compartmentalization mechanism.
- Run-time type safety: Run-time type safety encodes each pointer’s type-id in the PA modifier and checks it at dereferences or indirect calls.Compatible types receive equivalent deterministic type-ids, while incompatible types receive different identifiers.
- Return addresses: Return-address signing combines the stack-pointer value with a compile-time function-id to constrain reuse across function invocations.Binding only to the stack pointer is insufficient because the same stack-pointer value may recur for different invocations.
- Authentication strategy: PARTS authenticates data pointers on load and code pointers on use, matching their typical memory-access and indirect-branch behavior.The design relies on CFI to prevent attackers from diverting execution to standalone authentication or dereference instructions.
- Pointer conversions: PARTS handles legitimate data-pointer casts by authenticating with the source type-id on load and signing with the destination type-id on store.This preserves valid conversions such as casts between a structure pointer and its first field.
6 Implementation
PARTS extends LLVM with optimizer, backend, and frame-lowering changes that insert pointer-authentication instrumentation. The implementation signs data and code pointers at different points, signs return addresses with stack and function context, and initializes statically allocated pointers at startup.
- LLVM integration: PARTS modifies LLVM 6.0 optimizer passes, the AArch64 backend, and frame lowering to generate metadata and authentication instructions.The compiler also adds PARTS-specific intrinsics and compatibility wrappers for legacy code.
- LLVM integration: PARTS determines pointer type-ids during optimization from truncated 64-bit SHA-3 hashes of LLVM ElementType values.The same scheme applies to data pointers and function signatures for code pointers.
- Return-address signing: Return-address signing binds the PAC to the stack pointer and a unique function-id during function prologues and epilogues.The modifier combines the 16 least-significant bits of SP with a 48-bit function-specific identifier.
- Code pointer signing: Code pointers are signed once at creation with their type-id and authenticated during indirect calls using combined authenticate-and-branch instructions.Intermediate code-pointer loads and stores are not instrumented.
- Data pointer signing: Data pointers receive PACs before stores and are authenticated after loads, with backend passes attaching or recovering type-ids for instrumented operations.The approach avoids invasive changes such as modifying register allocation.
- Static initialization: PARTS initializes PACs for statically allocated pointers through compiler-generated functions invoked by the PARTS runtime library at process startup.The proof-of-concept explicitly invokes the bootstrap when entering main.
- Static initialization: The current static-pointer initialization approach relies on link-time optimization, while constructor ordering can interfere with programs already using C constructors.The authors identify constructor-based initialization as an alternative with ordering constraints across translation units.
- Runtime behavior: PARTS uses inline instrumentation without separate runtime metadata, and failed authentication causes a later memory translation fault.Except for bootstrap processing, the original code structure remains largely unchanged.
7 Evaluation
PARTS protects control-flow and data-oriented attacks through authenticated code, return, and data pointers, while its overhead depends on instrumentation and workload characteristics. Evaluation reports practical attack prevention, low return-address-signing overhead, and measurable costs for data-pointer protection.
- Security evaluation: PARTS authenticates indirect-branch code pointers and data-pointer dereferences, providing practical pointer-integrity protection against existing attacks.The framework also evaluates protections for return addresses and demonstrates prevention of existing attacks.
- Security evaluation: PARTS prevents forged code pointers and typical ROP/JOP gadgets by authenticating forward-edge targets.Runtime type safety further restricts reuse to pointers with matching LLVM IR type identifiers while supporting callbacks with identical signatures.
- Security evaluation: PARTS data pointer signing prevents forged pointers to unintended memory regions, covering currently known DOP attacks.Runtime type safety limits substitution based on pointer type, but conversions can expand the reusable-pointer set under the threat model.
- Security evaluation: 45425 attempts are required for a 50%-likelihood PAC guess when b = 16, while complete multi-jump attacks lose success probability exponentially with each jump.Shared keys in pre-forked or multithreaded programs can enable sibling-targeted brute force, with 32768 average guesses for a 16-bit PAC.
- Performance evaluation: 6 to 8 cycles is the estimated cost of a single PAC creation or authentication, while return-address signing costs 12 to 16 cycles per non-leaf call.The FVP cannot estimate total PARTS runtime overhead because its simplified timing differs from actual hardware; the study instead estimates micro-level costs.
8 Related Work
The paper situates PARTS among software, cryptographic, hardware-assisted, and control-flow integrity defenses. Existing approaches trade protection scope, precision, deployment assumptions, or performance overhead.
- Pointer integrity: CPI protects code pointers in disjoint memory, while CPS narrows protection to code pointers for lower overhead.CPI’s protected storage requires isolation; reported CPS overhead is 1.8–2.2%.
- Cryptographic defenses: PointGuard and data randomization mask pointers or data but rely on secrets stored within the process address space.The paper states that neither remains effective under its threat model.
- Cryptographic defenses: CCFI uses MACs for control-flow data and function types for protection domains, but does not protect function pointers embedded in C structures.
- Hardware-assisted mechanisms: CHERI provides unforgeable memory capabilities, whereas HDFI offers tagged-memory isolation and pointer-integrity models with < 2% overhead but only two simultaneous protection domains.
- Hardware-assisted mechanisms: ARM memory tagging targets memory-error detection, while enabling it alongside PA reduces available PAC size by eight bits.
- Hardware-assisted mechanisms: Intel MPX detects spatial memory errors but can impose up to 4x slowdown in the worst case.
- Control-flow integrity: Stateless CFI restricts transfers to CFG edges but remains vulnerable to control-flow bending because it does not distinguish execution paths.
- Control-flow integrity: Stateful and context-sensitive CFI strengthen path enforcement through shadow stacks, branch history, or monitoring, but deployment remains constrained by protection and implementation costs.ARM-oriented approaches include shadow-stack-based CFI and heuristic or static-analysis restrictions; shadow-stack integrity remains important.
9 Comparison with other integrity policies
The comparison defines pointer integrity precisely, then characterizes PARTS as an approximation whose guarantees depend on pointer modifiers and process provenance. PARTS strengthens protection against pointer reuse, while stronger path-sensitive CFI carries substantial deployment costs.
- Fully precise pointer integrity: Fully-precise pointer integrity allows dereferences only when the pointer is based on its target object.
- Fully precise pointer integrity: CPI provides fully precise code-pointer integrity but requires dedicated, integrity-protected storage for sensitive pointers.
- Fully precise pointer integrity: PARTS approximates fully-precise pointer integrity because pointers sharing a PA modifier may substitute for one another.Unique modifiers would provide fully precise integrity, but practical program semantics and runtime choices prevent universally unique modifiers.
- Pointer reuse: PA-based solutions can block forged pointers originating outside the process, but pointer-reuse prevention retains limitations similar to stateless CFI.
- Pointer reuse: PARTS return-address signing requires a matching stack pointer and a reused address from a prior invocation of the same function in the process.This is stricter than static CFI, which permits returns to any call site of the function.
- Pointer reuse: PARTS forward-edge code-pointer integrity restricts forged pointers to the correct dynamic type and additionally requires process-local provenance.
- Path-sensitive CFI: Path-sensitive CFI can provide stronger policies, but current implementations require extensive runtime monitoring or a protected shadow stack.Software-isolated shadow stacks impose impractical overheads, and ARM lacks direct hardware support for them.
10 Conclusion and Future Work
PARTS has scope boundaries involving protection domains, shared libraries, legacy interoperability, and the distinction between pointer integrity and full memory safety. Future work includes extending protection and complementing PA with memory-tagging mechanisms.
- Scope boundaries: Authenticated pointers cannot be used directly by legacy code, so wrappers or annotations are needed for interoperability and selected shared or complex pointers.PARTS-instrumented code traps when pointers without PACs are used.
- Future work: Extending PARTS to OS kernels and hypervisors requires higher exception levels to manage PA key configuration and context switches.The authors identify this protection-domain extension as planned future work.
- Scope boundaries: PARTS currently assumes shared libraries are uninstrumented, requiring dynamic-linker changes to support instrumented libraries and PAC handling after linking.Future PA schemes with dynamic modifiers could let the dynamic linker harmonize modifiers among shared-object callees.
- Scope boundaries: Pointer integrity does not imply full memory safety because ARMv8-A PA does not provide bounds checking for authenticated pointer accesses.The general-purpose pacga instruction can instead support authenticated canaries and integrity checks for data such as counters.
- Future work: Complementing PA with emerging mechanisms such as ARMv8.5-A memory tagging remains an important direction for future work.
A nbench experimental setup
The nbench evaluation fixes per-test iteration counts to measure relative execution-time overhead, using repeated runs on the HiKey platform and separating instrumentation-related runtime events.
- Workload control: nbench dynamically adjusts workloads to exceed a predetermined minimum execution time, but the evaluation replaces this with a constant iteration count per test.Iteration counts were determined from unmodified nbench runs on the FVP.
- Measurement: The study measures relative execution time between uninstrumented and PA-analogue-instrumented nbench tests on HiKey using BusyBox time.
- Measurement: Each benchmark test runs 200 times, with instrumentation overhead reported relative to uninstrumented execution.Because nbench is single-threaded ANSI C, measurement considers user time and excludes context switches and system calls.
- Runtime events: The evaluation reports overhead separately for individual runtime events, including return-address signing triggered by function invocations.Instrumented event counts and uninstrumented user-mode runtimes are also reported.
B SPEC CPU2017 experimental setup
The SPEC CPU2017 setup verifies PARTS functionality on two small C benchmarks using long FVP simulations, while performance comparisons run on HiKey devices. The reported results vary by benchmark characteristics.
- Correctness verification: Correctness verification uses 505.mcf_r and 519.lbm_r, selected as the smallest C benchmarks from the SPECrate 2017 integer and floating-point suites.
- Correctness verification: The PARTS-instrumented binaries run on the FVP simulator, with tested benchmark simulations lasting 12 to 48 hours.
- Performance evaluation: Table 2 reports overhead ratios and standard deviations for return-address and forward-edge code-pointer signing on both SPEC benchmarks.
- Performance evaluation: 519.lbm_r shows no discernible overhead, attributed to lacking forward-edge code pointers and having few non-leaf calls relative to its arithmetic computation.
- Performance evaluation: Table 4 records uninstrumented user-mode runtime, iteration counts, and runtime events affected by instrumentation.
C ARMv8-A PA Instructions
Table 5 catalogs ARMv8-A pointer-authentication instructions, including their keys, address sources, modifiers, and backward compatibility.
- Instruction fields: Table 5 lists each PA instruction together with its PA key, address source, and modifier.Address and modifier sources may be specified through general-purpose registers.
- Instruction fields: The table indicates whether each instruction encoding occupies NOP space on pre-existing ARMv8-A processors.