Skip to content

Commit c3e5e8a

Browse files
committed
Reapply "compiler-rt: Introduce runtime functions for emulated PAC."
This reverts commit 0c0aa56. This time with the following fixes for buildbot failures: - Add underscore prefixes to symbol names on Apple platforms. - Modify the test so that it skips the crash tests on platforms where they are not expected to pass: - Platforms that implement FEAT_PAuth but not FEAT_FPAC (e.g. Apple M1, Cortex-A78C) - Platforms where DA key is disabled (e.g. older Linux kernels, Linux kernels with PAC disabled, likely Windows) Original commit message follows: 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 Pull Request: llvm#148094
1 parent 6fc3b40 commit c3e5e8a

File tree

9 files changed

+301
-5
lines changed

9 files changed

+301
-5
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: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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

compiler-rt/lib/builtins/int_types.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ typedef union {
223223
#define CRT_HAS_TF_MODE
224224
#endif
225225

226-
#if __STDC_VERSION__ >= 199901L
226+
#if __STDC_VERSION__ >= 199901L && !defined(_MSC_VER)
227227
typedef float _Complex Fcomplex;
228228
typedef double _Complex Dcomplex;
229229
typedef long double _Complex Lcomplex;
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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 <stdbool.h>
9+
#include <stdint.h>
10+
#include <stdio.h>
11+
#include <stdlib.h>
12+
13+
uint64_t __emupac_pacda(uint64_t ptr, uint64_t disc);
14+
uint64_t __emupac_autda(uint64_t ptr, uint64_t disc);
15+
16+
static bool pac_supported() {
17+
register uintptr_t x30 __asm__("x30") = 1ULL << 55;
18+
__asm__ __volatile__("xpaclri" : "+r"(x30));
19+
return x30 & (1ULL << 54);
20+
}
21+
22+
static bool fpac_supported(uint64_t ap) {
23+
// The meaning of values larger than 6 is reserved as of July 2025; in theory
24+
// larger values could mean that FEAT_FPAC is not implemented.
25+
return ap == 4 || ap == 5 || ap == 6;
26+
}
27+
28+
// The crash tests would fail to crash (causing the test to fail) if:
29+
// - The operating system did not enable the DA key, or
30+
// - The CPU supports FEAT_PAuth but not FEAT_FPAC.
31+
// Therefore, they call this function, which will crash the test process if one
32+
// of these cases is detected so that %expect_crash detects the crash and causes
33+
// the test to pass.
34+
//
35+
// We detect the former case by attempting to sign a pointer. If the signed
36+
// pointer is equal to the unsigned pointer, DA is likely disabled, so we crash.
37+
//
38+
// We detect the latter case by reading ID_AA64ISAR1_EL1 and ID_AA64ISAR2_EL1.
39+
// It is expected that the operating system will either trap and emulate reading
40+
// the system registers (as Linux does) or crash the process. In the
41+
// trap/emulate case we check the APA, API and APA3 fields for FEAT_FPAC support
42+
// and crash if it is not available. In the crash case we will crash when
43+
// reading the register leading to a passing test. This means that operating
44+
// systems with the crashing behavior do not support the crash tests.
45+
static void crash_if_crash_tests_unsupported() {
46+
if (!pac_supported())
47+
return;
48+
49+
uint64_t ptr = 0;
50+
__asm__ __volatile__(".arch_extension pauth\npacda %0, %1"
51+
: "+r"(ptr)
52+
: "r"(0ul));
53+
if (ptr == 0)
54+
__builtin_trap();
55+
56+
uint64_t aa64isar1;
57+
__asm__ __volatile__("mrs %0, id_aa64isar1_el1" : "=r"(aa64isar1));
58+
uint64_t apa = (aa64isar1 >> 4) & 0xf;
59+
uint64_t api = (aa64isar1 >> 8) & 0xf;
60+
if (fpac_supported(apa) || fpac_supported(api))
61+
return;
62+
63+
uint64_t aa64isar2;
64+
__asm__ __volatile__("mrs %0, id_aa64isar2_el1" : "=r"(aa64isar2));
65+
uint64_t apa3 = (aa64isar2 >> 12) & 0xf;
66+
if (fpac_supported(apa3))
67+
return;
68+
69+
__builtin_trap();
70+
}
71+
72+
int main(int argc, char **argv) {
73+
char stack_object1;
74+
uint64_t ptr1 = (uint64_t)&stack_object1;
75+
76+
char stack_object2;
77+
uint64_t ptr2 = (uint64_t)&stack_object2;
78+
79+
switch (atoi(argv[1])) {
80+
case 1: {
81+
// Normal case: test that a pointer authenticated with the same
82+
// discriminator is equal to the original pointer.
83+
uint64_t signed_ptr = __emupac_pacda(ptr1, ptr2);
84+
uint64_t authed_ptr = __emupac_autda(signed_ptr, ptr2);
85+
if (authed_ptr != ptr1) {
86+
printf("0x%lx != 0x%lx\n", authed_ptr, ptr1);
87+
return 1;
88+
}
89+
break;
90+
}
91+
case 2: {
92+
// Test that negative addresses (addresses controlled by TTBR1,
93+
// conventionally kernel addresses) can be signed and authenticated.
94+
uint64_t unsigned_ptr = -1ULL;
95+
uint64_t signed_ptr = __emupac_pacda(unsigned_ptr, ptr2);
96+
uint64_t authed_ptr = __emupac_autda(signed_ptr, ptr2);
97+
if (authed_ptr != unsigned_ptr) {
98+
printf("0x%lx != 0x%lx\n", authed_ptr, unsigned_ptr);
99+
return 1;
100+
}
101+
break;
102+
}
103+
case 3: {
104+
crash_if_crash_tests_unsupported();
105+
// Test that a corrupted signature crashes the program.
106+
uint64_t signed_ptr = __emupac_pacda(ptr1, ptr2);
107+
__emupac_autda(signed_ptr + (1ULL << 48), ptr2);
108+
break;
109+
}
110+
case 4: {
111+
crash_if_crash_tests_unsupported();
112+
// Test that signing a pointer with signature bits already set produces a pointer
113+
// that would fail auth.
114+
uint64_t signed_ptr = __emupac_pacda(ptr1 + (1ULL << 48), ptr2);
115+
__emupac_autda(signed_ptr, ptr2);
116+
break;
117+
}
118+
}
119+
120+
return 0;
121+
}

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
]

0 commit comments

Comments
 (0)