| 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 | |