xref: /freebsd/contrib/llvm-project/compiler-rt/lib/builtins/aarch64/emupac.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
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.
pac_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 
__emupac_pacda_impl(uint64_t ptr,uint64_t disc)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 
__emupac_autda_impl(uint64_t ptr,uint64_t disc)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