1 //===--- emupac.cpp - Emulated PAC implementation -------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file implements Emulated PAC using SipHash_1_3 as the IMPDEF hashing 10 // scheme. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include <stdint.h> 15 16 #include "siphash/SipHash.h" 17 18 // EmuPAC implements runtime emulation of PAC instructions. If the current 19 // CPU supports PAC, EmuPAC uses real PAC instructions. Otherwise, it uses the 20 // emulation, which is effectively an implementation of PAC with an IMPDEF 21 // hashing scheme based on SipHash_1_3. 22 // 23 // The purpose of the emulation is to allow programs to be built to be portable 24 // to machines without PAC support, with some performance loss and increased 25 // probability of false positives (due to not being able to portably determine 26 // the VA size), while being functionally almost equivalent to running on a 27 // machine with PAC support. One example of a use case is if PAC is used in 28 // production as a security mitigation, but the testing environment is 29 // heterogeneous (i.e. some machines lack PAC support). In this case we would 30 // like the testing machines to be able to detect issues resulting 31 // from the use of PAC instructions that would affect production by running 32 // tests. This can be achieved by building test binaries with EmuPAC and 33 // production binaries with real PAC. 34 // 35 // EmuPAC should not be used in production and is only intended for testing use 36 // cases. This is not only because of the performance costs, which will exist 37 // even on PAC-supporting machines because of the function call overhead for 38 // each sign/auth operation, but because it provides weaker security compared to 39 // real PAC: the key is constant and public, which means that we do not mix a 40 // global secret. 41 // 42 // The emulation assumes that the VA size is at most 48 bits. The architecture 43 // as of ARMv8.2, which was the last architecture version in which PAC was not 44 // mandatory, permitted VA size up to 52 bits via ARMv8.2-LVA, but we are 45 // unaware of an ARMv8.2 CPU that implemented ARMv8.2-LVA. 46 47 static const uint64_t max_va_size = 48; 48 static const uint64_t pac_mask = 49 ((1ULL << 55) - 1) & ~((1ULL << max_va_size) - 1); 50 static const uint64_t ttbr1_mask = 1ULL << 55; 51 52 // Determine whether PAC is supported without accessing memory. This utilizes 53 // the XPACLRI instruction which will copy bit 55 of x30 into at least bit 54 if 54 // PAC is supported and acts as a NOP if PAC is not supported. 55 static bool pac_supported() { 56 register uintptr_t x30 __asm__("x30") = 1ULL << 55; 57 __asm__ __volatile__("xpaclri" : "+r"(x30)); 58 return x30 & (1ULL << 54); 59 } 60 61 #ifdef __GCC_HAVE_DWARF2_CFI_ASM 62 #define CFI_INST(inst) inst 63 #else 64 #define CFI_INST(inst) 65 #endif 66 67 #ifdef __APPLE__ 68 #define ASM_SYMBOL(symbol) "_" #symbol 69 #else 70 #define ASM_SYMBOL(symbol) #symbol 71 #endif 72 73 // This asm snippet is used to force the creation of a frame record when 74 // calling the EmuPAC functions. This is important because the EmuPAC functions 75 // may crash if an auth failure is detected and may be unwound past using a 76 // frame pointer based unwinder. 77 // clang-format off 78 #define FRAME_POINTER_WRAP(sym) \ 79 CFI_INST(".cfi_startproc\n") \ 80 "stp x29, x30, [sp, #-16]!\n" \ 81 CFI_INST(".cfi_def_cfa_offset 16\n") \ 82 "mov x29, sp\n" \ 83 CFI_INST(".cfi_def_cfa w29, 16\n") \ 84 CFI_INST(".cfi_offset w30, -8\n") \ 85 CFI_INST(".cfi_offset w29, -16\n") \ 86 "bl " ASM_SYMBOL(sym) "\n" \ 87 CFI_INST(".cfi_def_cfa wsp, 16\n") \ 88 "ldp x29, x30, [sp], #16\n" \ 89 CFI_INST(".cfi_def_cfa_offset 0\n") \ 90 CFI_INST(".cfi_restore w30\n") \ 91 CFI_INST(".cfi_restore w29\n") \ 92 "ret\n" \ 93 CFI_INST(".cfi_endproc\n") 94 // clang-format on 95 96 // Emulated DA key value. 97 static const uint8_t emu_da_key[16] = {0xb5, 0xd4, 0xc9, 0xeb, 0x79, 0x10, 98 0x4a, 0x79, 0x6f, 0xec, 0x8b, 0x1b, 99 0x42, 0x87, 0x81, 0xd4}; 100 101 extern "C" [[gnu::flatten]] uint64_t __emupac_pacda_impl(uint64_t ptr, 102 uint64_t disc) { 103 if (pac_supported()) { 104 __asm__ __volatile__(".arch_extension pauth\npacda %0, %1" 105 : "+r"(ptr) 106 : "r"(disc)); 107 return ptr; 108 } 109 if (ptr & ttbr1_mask) { 110 if ((ptr & pac_mask) != pac_mask) { 111 return ptr | pac_mask; 112 } 113 } else { 114 if (ptr & pac_mask) { 115 return ptr & ~pac_mask; 116 } 117 } 118 uint64_t hash; 119 siphash<1, 3>(reinterpret_cast<uint8_t *>(&ptr), 8, emu_da_key, 120 *reinterpret_cast<uint8_t (*)[8]>(&hash)); 121 return (ptr & ~pac_mask) | (hash & pac_mask); 122 } 123 124 // clang-format off 125 __asm__( 126 ".globl " ASM_SYMBOL(__emupac_pacda) "\n" 127 ASM_SYMBOL(__emupac_pacda) ":\n" 128 FRAME_POINTER_WRAP(__emupac_pacda_impl) 129 ); 130 // clang-format on 131 132 extern "C" [[gnu::flatten]] uint64_t __emupac_autda_impl(uint64_t ptr, 133 uint64_t disc) { 134 if (pac_supported()) { 135 __asm__ __volatile__(".arch_extension pauth\nautda %0, %1" 136 : "+r"(ptr) 137 : "r"(disc)); 138 return ptr; 139 } 140 uint64_t ptr_without_pac = 141 (ptr & ttbr1_mask) ? (ptr | pac_mask) : (ptr & ~pac_mask); 142 uint64_t hash; 143 siphash<1, 3>(reinterpret_cast<uint8_t *>(&ptr_without_pac), 8, emu_da_key, 144 *reinterpret_cast<uint8_t (*)[8]>(&hash)); 145 if (((ptr & ~pac_mask) | (hash & pac_mask)) != ptr) { 146 __builtin_trap(); 147 } 148 return ptr_without_pac; 149 } 150 151 // clang-format off 152 __asm__( 153 ".globl " ASM_SYMBOL(__emupac_autda) "\n" 154 ASM_SYMBOL(__emupac_autda) ":\n" 155 FRAME_POINTER_WRAP(__emupac_autda_impl) 156 ); 157 // clang-format on 158