Skip to content

Commit 5b1db59

Browse files
authored
compiler-rt: Introduce runtime functions for emulated PAC.
The emulated PAC runtime functions emulate the ARMv8.3a pointer authentication instructions and are intended for use in heterogeneous testing environments. For more information, see the associated RFC: https://discourse.llvm.org/t/rfc-emulated-pac/85557 Reviewers: llvm-beanz, petrhosek Pull Request: #133530
1 parent 20a68c6 commit 5b1db59

File tree

8 files changed

+224
-4
lines changed

8 files changed

+224
-4
lines changed

compiler-rt/cmake/Modules/AddCompilerRT.cmake

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ endmacro()
162162
# OBJECT_LIBS <object libraries to use as sources>
163163
# PARENT_TARGET <convenience parent target>
164164
# ADDITIONAL_HEADERS <header files>
165-
# EXTENSIONS <boolean>)
165+
# EXTENSIONS <boolean>
166+
# C_STANDARD <version>
167+
# CXX_STANDARD <version>)
166168
function(add_compiler_rt_runtime name type)
167169
if(NOT type MATCHES "^(OBJECT|STATIC|SHARED|MODULE)$")
168170
message(FATAL_ERROR
@@ -171,7 +173,7 @@ function(add_compiler_rt_runtime name type)
171173
endif()
172174
cmake_parse_arguments(LIB
173175
""
174-
"PARENT_TARGET"
176+
"PARENT_TARGET;C_STANDARD;CXX_STANDARD"
175177
"OS;ARCHS;SOURCES;CFLAGS;LINK_FLAGS;DEFS;DEPS;LINK_LIBS;OBJECT_LIBS;ADDITIONAL_HEADERS;EXTENSIONS"
176178
${ARGN})
177179
set(libnames)
@@ -360,6 +362,12 @@ function(add_compiler_rt_runtime name type)
360362
set_target_link_flags(${libname} ${extra_link_flags_${libname}})
361363
set_property(TARGET ${libname} APPEND PROPERTY
362364
COMPILE_DEFINITIONS ${LIB_DEFS})
365+
if(LIB_C_STANDARD)
366+
set_property(TARGET ${libname} PROPERTY C_STANDARD ${LIB_C_STANDARD})
367+
endif()
368+
if(LIB_CXX_STANDARD)
369+
set_property(TARGET ${libname} PROPERTY CXX_STANDARD ${LIB_CXX_STANDARD})
370+
endif()
363371
set_target_output_directories(${libname} ${output_dir_${libname}})
364372
install(TARGETS ${libname}
365373
ARCHIVE DESTINATION ${install_dir_${libname}}

compiler-rt/cmake/builtin-config-ix.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ builtin_check_c_compiler_flag("-Xclang -mcode-object-version=none" COMPILER_RT_H
2626
builtin_check_c_compiler_flag(-Wbuiltin-declaration-mismatch COMPILER_RT_HAS_WBUILTIN_DECLARATION_MISMATCH_FLAG)
2727
builtin_check_c_compiler_flag(/Zl COMPILER_RT_HAS_ZL_FLAG)
2828
builtin_check_c_compiler_flag(-fcf-protection=full COMPILER_RT_HAS_FCF_PROTECTION_FLAG)
29+
builtin_check_c_compiler_flag(-nostdinc++ COMPILER_RT_HAS_NOSTDINCXX_FLAG)
2930

3031
builtin_check_c_compiler_source(COMPILER_RT_HAS_ATOMIC_KEYWORD
3132
"

compiler-rt/lib/builtins/CMakeLists.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
66
cmake_minimum_required(VERSION 3.20.0)
77

88
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
9-
project(CompilerRTBuiltins C ASM)
9+
project(CompilerRTBuiltins C CXX ASM)
1010
set(COMPILER_RT_STANDALONE_BUILD TRUE)
1111
set(COMPILER_RT_BUILTINS_STANDALONE_BUILD TRUE)
1212

@@ -64,6 +64,8 @@ include(CMakePushCheckState)
6464
option(COMPILER_RT_BUILTINS_HIDE_SYMBOLS
6565
"Do not export any symbols from the static library." ON)
6666

67+
include_directories(../../../third-party/siphash/include)
68+
6769
# TODO: Need to add a mechanism for logging errors when builtin source files are
6870
# added to a sub-directory and not this CMakeLists file.
6971
set(GENERIC_SOURCES
@@ -589,6 +591,7 @@ set(aarch64_SOURCES
589591
${GENERIC_TF_SOURCES}
590592
${GENERIC_SOURCES}
591593
cpu_model/aarch64.c
594+
aarch64/emupac.cpp
592595
aarch64/fp_mode.c
593596
)
594597

@@ -836,7 +839,7 @@ else ()
836839
append_list_if(COMPILER_RT_ENABLE_CET -fcf-protection=full BUILTIN_CFLAGS)
837840
endif()
838841

839-
append_list_if(COMPILER_RT_HAS_STD_C11_FLAG -std=c11 BUILTIN_CFLAGS)
842+
append_list_if(COMPILER_RT_HAS_NOSTDINCXX_FLAG -nostdinc++ BUILTIN_CFLAGS)
840843
append_list_if(COMPILER_RT_HAS_WBUILTIN_DECLARATION_MISMATCH_FLAG -Werror=builtin-declaration-mismatch BUILTIN_CFLAGS)
841844

842845
# Don't embed directives for picking any specific CRT
@@ -958,6 +961,8 @@ else ()
958961
SOURCES ${${arch}_SOURCES}
959962
DEFS ${BUILTIN_DEFS}
960963
CFLAGS ${BUILTIN_CFLAGS_${arch}}
964+
C_STANDARD 11
965+
CXX_STANDARD 17
961966
PARENT_TARGET builtins)
962967
cmake_pop_check_state()
963968
endif ()
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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+
const uint64_t max_va_size = 48;
48+
const uint64_t pac_mask = ((1ULL << 55) - 1) & ~((1ULL << max_va_size) - 1);
49+
const uint64_t ttbr1_mask = 1ULL << 55;
50+
51+
// Determine whether PAC is supported without accessing memory. This utilizes
52+
// the XPACLRI instruction which will copy bit 55 of x30 into at least bit 54 if
53+
// PAC is supported and acts as a NOP if PAC is not supported.
54+
static bool pac_supported() {
55+
register uintptr_t x30 __asm__("x30") = 1ULL << 55;
56+
__asm__ __volatile__("xpaclri" : "+r"(x30));
57+
return x30 & (1ULL << 54);
58+
}
59+
60+
// This asm snippet is used to force the creation of a frame record when
61+
// calling the EmuPAC functions. This is important because the EmuPAC functions
62+
// may crash if an auth failure is detected and may be unwound past using a
63+
// frame pointer based unwinder.
64+
#ifdef __GCC_HAVE_DWARF2_CFI_ASM
65+
#define CFI_INST(inst) inst
66+
#else
67+
#define CFI_INST(inst)
68+
#endif
69+
70+
// clang-format off
71+
#define FRAME_POINTER_WRAP(sym) \
72+
CFI_INST(".cfi_startproc\n") \
73+
"stp x29, x30, [sp, #-16]!\n" \
74+
CFI_INST(".cfi_def_cfa_offset 16\n") \
75+
"mov x29, sp\n" \
76+
CFI_INST(".cfi_def_cfa w29, 16\n") \
77+
CFI_INST(".cfi_offset w30, -8\n") \
78+
CFI_INST(".cfi_offset w29, -16\n") \
79+
"bl " #sym "\n" \
80+
CFI_INST(".cfi_def_cfa wsp, 16\n") \
81+
"ldp x29, x30, [sp], #16\n" \
82+
CFI_INST(".cfi_def_cfa_offset 0\n") \
83+
CFI_INST(".cfi_restore w30\n") \
84+
CFI_INST(".cfi_restore w29\n") \
85+
"ret\n" \
86+
CFI_INST(".cfi_endproc\n")
87+
// clang-format on
88+
89+
// Emulated DA key value.
90+
static const uint8_t emu_da_key[16] = {0xb5, 0xd4, 0xc9, 0xeb, 0x79, 0x10,
91+
0x4a, 0x79, 0x6f, 0xec, 0x8b, 0x1b,
92+
0x42, 0x87, 0x81, 0xd4};
93+
94+
extern "C" [[gnu::flatten]] uint64_t
95+
__emupac_pacda_impl(uint64_t ptr, uint64_t disc) {
96+
if (pac_supported()) {
97+
__asm__ __volatile__(".arch_extension pauth\npacda %0, %1"
98+
: "+r"(ptr)
99+
: "r"(disc));
100+
return ptr;
101+
}
102+
if (ptr & ttbr1_mask) {
103+
if ((ptr & pac_mask) != pac_mask) {
104+
return ptr | pac_mask;
105+
}
106+
} else {
107+
if (ptr & pac_mask) {
108+
return ptr & ~pac_mask;
109+
}
110+
}
111+
uint64_t hash;
112+
siphash<1, 3>(reinterpret_cast<uint8_t *>(&ptr), 8, emu_da_key,
113+
*reinterpret_cast<uint8_t(*)[8]>(&hash));
114+
return (ptr & ~pac_mask) | (hash & pac_mask);
115+
}
116+
117+
__asm__(".globl __emupac_pacda\n"
118+
"__emupac_pacda:\n" FRAME_POINTER_WRAP(__emupac_pacda_impl));
119+
120+
extern "C" [[gnu::flatten]] uint64_t
121+
__emupac_autda_impl(uint64_t ptr, uint64_t disc) {
122+
if (pac_supported()) {
123+
__asm__ __volatile__(".arch_extension pauth\nautda %0, %1"
124+
: "+r"(ptr)
125+
: "r"(disc));
126+
return ptr;
127+
}
128+
uint64_t ptr_without_pac =
129+
(ptr & ttbr1_mask) ? (ptr | pac_mask) : (ptr & ~pac_mask);
130+
uint64_t hash;
131+
siphash<1, 3>(reinterpret_cast<uint8_t *>(&ptr_without_pac), 8, emu_da_key,
132+
*reinterpret_cast<uint8_t(*)[8]>(&hash));
133+
if (((ptr & ~pac_mask) | (hash & pac_mask)) != ptr) {
134+
__builtin_trap();
135+
}
136+
return ptr_without_pac;
137+
}
138+
139+
__asm__(".globl __emupac_autda\n"
140+
"__emupac_autda:\n" FRAME_POINTER_WRAP(__emupac_autda_impl));
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// REQUIRES: librt_has_emupac
2+
// RUN: %clang_builtins %s %librt -o %t
3+
// RUN: %run %t 1
4+
// RUN: %run %t 2
5+
// RUN: %expect_crash %run %t 3
6+
// RUN: %expect_crash %run %t 4
7+
8+
#include <stdint.h>
9+
#include <stdio.h>
10+
#include <stdlib.h>
11+
12+
uint64_t __emupac_pacda(uint64_t ptr, uint64_t disc);
13+
uint64_t __emupac_autda(uint64_t ptr, uint64_t disc);
14+
15+
int main(int argc, char **argv) {
16+
char stack_object1;
17+
uint64_t ptr1 = (uint64_t)&stack_object1;
18+
19+
char stack_object2;
20+
uint64_t ptr2 = (uint64_t)&stack_object2;
21+
22+
switch (atoi(argv[1])) {
23+
case 1: {
24+
// Normal case: test that a pointer authenticated with the same
25+
// discriminator is equal to the original pointer.
26+
uint64_t signed_ptr = __emupac_pacda(ptr1, ptr2);
27+
uint64_t authed_ptr = __emupac_autda(signed_ptr, ptr2);
28+
if (authed_ptr != ptr1) {
29+
printf("0x%lx != 0x%lx\n", authed_ptr, ptr1);
30+
return 1;
31+
}
32+
break;
33+
}
34+
case 2: {
35+
// Test that negative addresses (addresses controlled by TTBR1,
36+
// conventionally kernel addresses) can be signed and authenticated.
37+
uint64_t unsigned_ptr = -1ULL;
38+
uint64_t signed_ptr = __emupac_pacda(unsigned_ptr, ptr2);
39+
uint64_t authed_ptr = __emupac_autda(signed_ptr, ptr2);
40+
if (authed_ptr != unsigned_ptr) {
41+
printf("0x%lx != 0x%lx\n", authed_ptr, unsigned_ptr);
42+
return 1;
43+
}
44+
break;
45+
}
46+
case 3: {
47+
// Test that a corrupted signature crashes the program.
48+
uint64_t signed_ptr = __emupac_pacda(ptr1, ptr2);
49+
__emupac_autda(signed_ptr + (1ULL << 48), ptr2);
50+
break;
51+
}
52+
case 4: {
53+
// Test that signing a pointer with signature bits already set produces a pointer
54+
// that would fail auth.
55+
uint64_t signed_ptr = __emupac_pacda(ptr1 + (1ULL << 48), ptr2);
56+
__emupac_autda(signed_ptr, ptr2);
57+
break;
58+
}
59+
}
60+
61+
return 0;
62+
}

llvm/utils/gn/secondary/compiler-rt/lib/builtins/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,14 @@ static_library("builtins") {
7878
cflags += [ "-fomit-frame-pointer" ]
7979
}
8080
cflags_c = [ "-std=c11" ]
81+
cflags_cc = [ "-nostdinc++" ]
8182
}
8283

8384
defines = builtins_defines
8485
sources = builtins_sources
8586

8687
deps = lse_targets
88+
include_dirs = [ "//third-party/siphash/include" ]
8789
}
8890

8991
# Currently unused but necessary to make sync_source_lists_from_cmake.py happy.

llvm/utils/gn/secondary/compiler-rt/lib/builtins/sources.gni

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ if (current_cpu == "arm") {
429429
if (current_cpu == "arm64") {
430430
builtins_sources -= [ "fp_mode.c" ]
431431
builtins_sources += [
432+
"aarch64/emupac.cpp",
432433
"aarch64/fp_mode.c",
433434
"cpu_model/aarch64.c",
434435
]

llvm/utils/gn/secondary/compiler-rt/test/builtins/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ if (current_toolchain != host_toolchain) {
4646
"//compiler-rt/include($host_toolchain)",
4747
"//compiler-rt/lib/builtins",
4848
"//compiler-rt/test:lit_common_configured",
49+
"//llvm/utils/not($host_toolchain)",
4950
]
5051
}
5152
}

0 commit comments

Comments
 (0)