-
Notifications
You must be signed in to change notification settings - Fork 14.5k
Reapply "compiler-rt: Introduce runtime functions for emulated PAC." #148094
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
//===--- emupac.cpp - Emulated PAC implementation -------------------------===// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This file implements Emulated PAC using SipHash_1_3 as the IMPDEF hashing | ||
// scheme. | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#include <stdint.h> | ||
|
||
#include "siphash/SipHash.h" | ||
|
||
// EmuPAC implements runtime emulation of PAC instructions. If the current | ||
// CPU supports PAC, EmuPAC uses real PAC instructions. Otherwise, it uses the | ||
// emulation, which is effectively an implementation of PAC with an IMPDEF | ||
// hashing scheme based on SipHash_1_3. | ||
// | ||
// The purpose of the emulation is to allow programs to be built to be portable | ||
// to machines without PAC support, with some performance loss and increased | ||
// probability of false positives (due to not being able to portably determine | ||
// the VA size), while being functionally almost equivalent to running on a | ||
// machine with PAC support. One example of a use case is if PAC is used in | ||
// production as a security mitigation, but the testing environment is | ||
// heterogeneous (i.e. some machines lack PAC support). In this case we would | ||
// like the testing machines to be able to detect issues resulting | ||
// from the use of PAC instructions that would affect production by running | ||
// tests. This can be achieved by building test binaries with EmuPAC and | ||
// production binaries with real PAC. | ||
// | ||
// EmuPAC should not be used in production and is only intended for testing use | ||
// cases. This is not only because of the performance costs, which will exist | ||
// even on PAC-supporting machines because of the function call overhead for | ||
// each sign/auth operation, but because it provides weaker security compared to | ||
// real PAC: the key is constant and public, which means that we do not mix a | ||
// global secret. | ||
// | ||
// The emulation assumes that the VA size is at most 48 bits. The architecture | ||
// as of ARMv8.2, which was the last architecture version in which PAC was not | ||
// mandatory, permitted VA size up to 52 bits via ARMv8.2-LVA, but we are | ||
// unaware of an ARMv8.2 CPU that implemented ARMv8.2-LVA. | ||
|
||
const uint64_t max_va_size = 48; | ||
const uint64_t pac_mask = ((1ULL << 55) - 1) & ~((1ULL << max_va_size) - 1); | ||
const uint64_t ttbr1_mask = 1ULL << 55; | ||
Comment on lines
+47
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be |
||
|
||
// Determine whether PAC is supported without accessing memory. This utilizes | ||
// the XPACLRI instruction which will copy bit 55 of x30 into at least bit 54 if | ||
// PAC is supported and acts as a NOP if PAC is not supported. | ||
static bool pac_supported() { | ||
register uintptr_t x30 __asm__("x30") = 1ULL << 55; | ||
__asm__ __volatile__("xpaclri" : "+r"(x30)); | ||
return x30 & (1ULL << 54); | ||
} | ||
|
||
// This asm snippet is used to force the creation of a frame record when | ||
// calling the EmuPAC functions. This is important because the EmuPAC functions | ||
// may crash if an auth failure is detected and may be unwound past using a | ||
// frame pointer based unwinder. | ||
#ifdef __GCC_HAVE_DWARF2_CFI_ASM | ||
#define CFI_INST(inst) inst | ||
#else | ||
#define CFI_INST(inst) | ||
#endif | ||
atrosinenko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#ifdef __APPLE__ | ||
#define ASM_SYMBOL(symbol) "_" #symbol | ||
#else | ||
#define ASM_SYMBOL(symbol) #symbol | ||
#endif | ||
Comment on lines
+70
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the sake of completeness, it seems there is another exception, Arm64EC ABI:
This seems to be something Clang already knows about: for a trivial
Furthermore, some aliases are mentioned in AArch64MCInstLower.cpp. Please feel free to ignore / postpone this as long as builders are not affected. |
||
|
||
// clang-format off | ||
#define FRAME_POINTER_WRAP(sym) \ | ||
CFI_INST(".cfi_startproc\n") \ | ||
"stp x29, x30, [sp, #-16]!\n" \ | ||
CFI_INST(".cfi_def_cfa_offset 16\n") \ | ||
"mov x29, sp\n" \ | ||
CFI_INST(".cfi_def_cfa w29, 16\n") \ | ||
CFI_INST(".cfi_offset w30, -8\n") \ | ||
CFI_INST(".cfi_offset w29, -16\n") \ | ||
"bl " ASM_SYMBOL(sym) "\n" \ | ||
CFI_INST(".cfi_def_cfa wsp, 16\n") \ | ||
"ldp x29, x30, [sp], #16\n" \ | ||
CFI_INST(".cfi_def_cfa_offset 0\n") \ | ||
CFI_INST(".cfi_restore w30\n") \ | ||
CFI_INST(".cfi_restore w29\n") \ | ||
"ret\n" \ | ||
CFI_INST(".cfi_endproc\n") | ||
// clang-format on | ||
|
||
// Emulated DA key value. | ||
static const uint8_t emu_da_key[16] = {0xb5, 0xd4, 0xc9, 0xeb, 0x79, 0x10, | ||
0x4a, 0x79, 0x6f, 0xec, 0x8b, 0x1b, | ||
0x42, 0x87, 0x81, 0xd4}; | ||
|
||
extern "C" [[gnu::flatten]] uint64_t __emupac_pacda_impl(uint64_t ptr, | ||
uint64_t disc) { | ||
if (pac_supported()) { | ||
__asm__ __volatile__(".arch_extension pauth\npacda %0, %1" | ||
: "+r"(ptr) | ||
: "r"(disc)); | ||
return ptr; | ||
} | ||
if (ptr & ttbr1_mask) { | ||
if ((ptr & pac_mask) != pac_mask) { | ||
return ptr | pac_mask; | ||
} | ||
} else { | ||
if (ptr & pac_mask) { | ||
return ptr & ~pac_mask; | ||
} | ||
} | ||
uint64_t hash; | ||
siphash<1, 3>(reinterpret_cast<uint8_t *>(&ptr), 8, emu_da_key, | ||
*reinterpret_cast<uint8_t (*)[8]>(&hash)); | ||
return (ptr & ~pac_mask) | (hash & pac_mask); | ||
} | ||
|
||
// clang-format off | ||
__asm__( | ||
".globl " ASM_SYMBOL(__emupac_pacda) "\n" | ||
ASM_SYMBOL(__emupac_pacda) ":\n" | ||
FRAME_POINTER_WRAP(__emupac_pacda_impl) | ||
); | ||
// clang-format on | ||
|
||
extern "C" [[gnu::flatten]] uint64_t __emupac_autda_impl(uint64_t ptr, | ||
uint64_t disc) { | ||
if (pac_supported()) { | ||
__asm__ __volatile__(".arch_extension pauth\nautda %0, %1" | ||
: "+r"(ptr) | ||
: "r"(disc)); | ||
return ptr; | ||
} | ||
uint64_t ptr_without_pac = | ||
(ptr & ttbr1_mask) ? (ptr | pac_mask) : (ptr & ~pac_mask); | ||
uint64_t hash; | ||
siphash<1, 3>(reinterpret_cast<uint8_t *>(&ptr_without_pac), 8, emu_da_key, | ||
*reinterpret_cast<uint8_t (*)[8]>(&hash)); | ||
if (((ptr & ~pac_mask) | (hash & pac_mask)) != ptr) { | ||
__builtin_trap(); | ||
} | ||
return ptr_without_pac; | ||
} | ||
|
||
// clang-format off | ||
__asm__( | ||
".globl " ASM_SYMBOL(__emupac_autda) "\n" | ||
ASM_SYMBOL(__emupac_autda) ":\n" | ||
FRAME_POINTER_WRAP(__emupac_autda_impl) | ||
); | ||
// clang-format on |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// REQUIRES: librt_has_emupac | ||
// RUN: %clang_builtins %s %librt -o %t | ||
// RUN: %run %t 1 | ||
// RUN: %run %t 2 | ||
// RUN: %expect_crash %run %t 3 | ||
// RUN: %expect_crash %run %t 4 | ||
|
||
#include <stdbool.h> | ||
#include <stdint.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
|
||
uint64_t __emupac_pacda(uint64_t ptr, uint64_t disc); | ||
uint64_t __emupac_autda(uint64_t ptr, uint64_t disc); | ||
|
||
static bool pac_supported() { | ||
register uintptr_t x30 __asm__("x30") = 1ULL << 55; | ||
__asm__ __volatile__("xpaclri" : "+r"(x30)); | ||
return x30 & (1ULL << 54); | ||
} | ||
|
||
static bool fpac_supported(uint64_t ap) { | ||
// The meaning of values larger than 6 is reserved as of July 2025; in theory | ||
// larger values could mean that FEAT_FPAC is not implemented. | ||
return ap == 4 || ap == 5 || ap == 6; | ||
} | ||
|
||
// The crash tests would fail to crash (causing the test to fail) if: | ||
// - The operating system did not enable the DA key, or | ||
// - The CPU supports FEAT_PAuth but not FEAT_FPAC. | ||
// Therefore, they call this function, which will crash the test process if one | ||
// of these cases is detected so that %expect_crash detects the crash and causes | ||
// the test to pass. | ||
// | ||
// We detect the former case by attempting to sign a pointer. If the signed | ||
// pointer is equal to the unsigned pointer, DA is likely disabled, so we crash. | ||
// | ||
// We detect the latter case by reading ID_AA64ISAR1_EL1 and ID_AA64ISAR2_EL1. | ||
// It is expected that the operating system will either trap and emulate reading | ||
// the system registers (as Linux does) or crash the process. In the | ||
// trap/emulate case we check the APA, API and APA3 fields for FEAT_FPAC support | ||
// and crash if it is not available. In the crash case we will crash when | ||
// reading the register leading to a passing test. This means that operating | ||
// systems with the crashing behavior do not support the crash tests. | ||
static void crash_if_crash_tests_unsupported() { | ||
if (!pac_supported()) | ||
return; | ||
|
||
uint64_t ptr = 0; | ||
__asm__ __volatile__(".arch_extension pauth\npacda %0, %1" | ||
: "+r"(ptr) | ||
: "r"(0ul)); | ||
if (ptr == 0) | ||
__builtin_trap(); | ||
|
||
uint64_t aa64isar1; | ||
__asm__ __volatile__("mrs %0, id_aa64isar1_el1" : "=r"(aa64isar1)); | ||
uint64_t apa = (aa64isar1 >> 4) & 0xf; | ||
uint64_t api = (aa64isar1 >> 8) & 0xf; | ||
if (fpac_supported(apa) || fpac_supported(api)) | ||
return; | ||
|
||
uint64_t aa64isar2; | ||
__asm__ __volatile__("mrs %0, id_aa64isar2_el1" : "=r"(aa64isar2)); | ||
uint64_t apa3 = (aa64isar2 >> 12) & 0xf; | ||
if (fpac_supported(apa3)) | ||
return; | ||
|
||
__builtin_trap(); | ||
} | ||
|
||
int main(int argc, char **argv) { | ||
char stack_object1; | ||
uint64_t ptr1 = (uint64_t)&stack_object1; | ||
|
||
char stack_object2; | ||
uint64_t ptr2 = (uint64_t)&stack_object2; | ||
|
||
switch (atoi(argv[1])) { | ||
case 1: { | ||
// Normal case: test that a pointer authenticated with the same | ||
// discriminator is equal to the original pointer. | ||
uint64_t signed_ptr = __emupac_pacda(ptr1, ptr2); | ||
uint64_t authed_ptr = __emupac_autda(signed_ptr, ptr2); | ||
if (authed_ptr != ptr1) { | ||
printf("0x%lx != 0x%lx\n", authed_ptr, ptr1); | ||
return 1; | ||
} | ||
break; | ||
} | ||
case 2: { | ||
// Test that negative addresses (addresses controlled by TTBR1, | ||
// conventionally kernel addresses) can be signed and authenticated. | ||
uint64_t unsigned_ptr = -1ULL; | ||
uint64_t signed_ptr = __emupac_pacda(unsigned_ptr, ptr2); | ||
uint64_t authed_ptr = __emupac_autda(signed_ptr, ptr2); | ||
if (authed_ptr != unsigned_ptr) { | ||
printf("0x%lx != 0x%lx\n", authed_ptr, unsigned_ptr); | ||
return 1; | ||
} | ||
break; | ||
} | ||
case 3: { | ||
crash_if_crash_tests_unsupported(); | ||
// Test that a corrupted signature crashes the program. | ||
uint64_t signed_ptr = __emupac_pacda(ptr1, ptr2); | ||
__emupac_autda(signed_ptr + (1ULL << 48), ptr2); | ||
break; | ||
} | ||
case 4: { | ||
crash_if_crash_tests_unsupported(); | ||
// Test that signing a pointer with signature bits already set produces a pointer | ||
// that would fail auth. | ||
uint64_t signed_ptr = __emupac_pacda(ptr1 + (1ULL << 48), ptr2); | ||
__emupac_autda(signed_ptr, ptr2); | ||
break; | ||
} | ||
} | ||
|
||
return 0; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.