Reapply "compiler-rt: Introduce runtime functions for emulated PAC."
This reverts commit 0c0aa56cdcf1fe3970a5f3875db412530512fc07. 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 Reviewers: mstorsjo, pawosm-arm, atrosinenko Reviewed By: atrosinenko Pull Request: https://github.com/llvm/llvm-project/pull/148094
This commit is contained in:
parent
dadaa7941d
commit
de31584001
@ -162,7 +162,9 @@ endmacro()
|
||||
# OBJECT_LIBS <object libraries to use as sources>
|
||||
# PARENT_TARGET <convenience parent target>
|
||||
# ADDITIONAL_HEADERS <header files>
|
||||
# EXTENSIONS <boolean>)
|
||||
# EXTENSIONS <boolean>
|
||||
# C_STANDARD <version>
|
||||
# CXX_STANDARD <version>)
|
||||
function(add_compiler_rt_runtime name type)
|
||||
if(NOT type MATCHES "^(OBJECT|STATIC|SHARED|MODULE)$")
|
||||
message(FATAL_ERROR
|
||||
@ -171,7 +173,7 @@ function(add_compiler_rt_runtime name type)
|
||||
endif()
|
||||
cmake_parse_arguments(LIB
|
||||
""
|
||||
"PARENT_TARGET"
|
||||
"PARENT_TARGET;C_STANDARD;CXX_STANDARD"
|
||||
"OS;ARCHS;SOURCES;CFLAGS;LINK_FLAGS;DEFS;DEPS;LINK_LIBS;OBJECT_LIBS;ADDITIONAL_HEADERS;EXTENSIONS"
|
||||
${ARGN})
|
||||
set(libnames)
|
||||
@ -360,6 +362,12 @@ function(add_compiler_rt_runtime name type)
|
||||
set_target_link_flags(${libname} ${extra_link_flags_${libname}})
|
||||
set_property(TARGET ${libname} APPEND PROPERTY
|
||||
COMPILE_DEFINITIONS ${LIB_DEFS})
|
||||
if(LIB_C_STANDARD)
|
||||
set_property(TARGET ${libname} PROPERTY C_STANDARD ${LIB_C_STANDARD})
|
||||
endif()
|
||||
if(LIB_CXX_STANDARD)
|
||||
set_property(TARGET ${libname} PROPERTY CXX_STANDARD ${LIB_CXX_STANDARD})
|
||||
endif()
|
||||
set_target_output_directories(${libname} ${output_dir_${libname}})
|
||||
install(TARGETS ${libname}
|
||||
ARCHIVE DESTINATION ${install_dir_${libname}}
|
||||
|
@ -26,6 +26,7 @@ builtin_check_c_compiler_flag("-Xclang -mcode-object-version=none" COMPILER_RT_H
|
||||
builtin_check_c_compiler_flag(-Wbuiltin-declaration-mismatch COMPILER_RT_HAS_WBUILTIN_DECLARATION_MISMATCH_FLAG)
|
||||
builtin_check_c_compiler_flag(/Zl COMPILER_RT_HAS_ZL_FLAG)
|
||||
builtin_check_c_compiler_flag(-fcf-protection=full COMPILER_RT_HAS_FCF_PROTECTION_FLAG)
|
||||
builtin_check_c_compiler_flag(-nostdinc++ COMPILER_RT_HAS_NOSTDINCXX_FLAG)
|
||||
|
||||
builtin_check_c_compiler_source(COMPILER_RT_HAS_ATOMIC_KEYWORD
|
||||
"
|
||||
|
@ -6,7 +6,7 @@ if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
|
||||
project(CompilerRTBuiltins C ASM)
|
||||
project(CompilerRTBuiltins C CXX ASM)
|
||||
set(COMPILER_RT_STANDALONE_BUILD TRUE)
|
||||
set(COMPILER_RT_BUILTINS_STANDALONE_BUILD TRUE)
|
||||
|
||||
@ -64,6 +64,8 @@ include(CMakePushCheckState)
|
||||
option(COMPILER_RT_BUILTINS_HIDE_SYMBOLS
|
||||
"Do not export any symbols from the static library." ON)
|
||||
|
||||
include_directories(../../../third-party/siphash/include)
|
||||
|
||||
# TODO: Need to add a mechanism for logging errors when builtin source files are
|
||||
# added to a sub-directory and not this CMakeLists file.
|
||||
set(GENERIC_SOURCES
|
||||
@ -589,6 +591,7 @@ set(aarch64_SOURCES
|
||||
${GENERIC_TF_SOURCES}
|
||||
${GENERIC_SOURCES}
|
||||
cpu_model/aarch64.c
|
||||
aarch64/emupac.cpp
|
||||
aarch64/fp_mode.c
|
||||
)
|
||||
|
||||
@ -836,7 +839,7 @@ else ()
|
||||
append_list_if(COMPILER_RT_ENABLE_CET -fcf-protection=full BUILTIN_CFLAGS)
|
||||
endif()
|
||||
|
||||
append_list_if(COMPILER_RT_HAS_STD_C11_FLAG -std=c11 BUILTIN_CFLAGS)
|
||||
append_list_if(COMPILER_RT_HAS_NOSTDINCXX_FLAG -nostdinc++ BUILTIN_CFLAGS)
|
||||
append_list_if(COMPILER_RT_HAS_WBUILTIN_DECLARATION_MISMATCH_FLAG -Werror=builtin-declaration-mismatch BUILTIN_CFLAGS)
|
||||
|
||||
# Don't embed directives for picking any specific CRT
|
||||
@ -958,6 +961,8 @@ else ()
|
||||
SOURCES ${${arch}_SOURCES}
|
||||
DEFS ${BUILTIN_DEFS}
|
||||
CFLAGS ${BUILTIN_CFLAGS_${arch}}
|
||||
C_STANDARD 11
|
||||
CXX_STANDARD 17
|
||||
PARENT_TARGET builtins)
|
||||
cmake_pop_check_state()
|
||||
endif ()
|
||||
|
157
compiler-rt/lib/builtins/aarch64/emupac.cpp
Normal file
157
compiler-rt/lib/builtins/aarch64/emupac.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
//===--- emupac.cpp - Emulated PAC implementation -------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file implements Emulated PAC using SipHash_1_3 as the IMPDEF hashing
|
||||
// scheme.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "siphash/SipHash.h"
|
||||
|
||||
// EmuPAC implements runtime emulation of PAC instructions. If the current
|
||||
// CPU supports PAC, EmuPAC uses real PAC instructions. Otherwise, it uses the
|
||||
// emulation, which is effectively an implementation of PAC with an IMPDEF
|
||||
// hashing scheme based on SipHash_1_3.
|
||||
//
|
||||
// The purpose of the emulation is to allow programs to be built to be portable
|
||||
// to machines without PAC support, with some performance loss and increased
|
||||
// probability of false positives (due to not being able to portably determine
|
||||
// the VA size), while being functionally almost equivalent to running on a
|
||||
// machine with PAC support. One example of a use case is if PAC is used in
|
||||
// production as a security mitigation, but the testing environment is
|
||||
// heterogeneous (i.e. some machines lack PAC support). In this case we would
|
||||
// like the testing machines to be able to detect issues resulting
|
||||
// from the use of PAC instructions that would affect production by running
|
||||
// tests. This can be achieved by building test binaries with EmuPAC and
|
||||
// production binaries with real PAC.
|
||||
//
|
||||
// EmuPAC should not be used in production and is only intended for testing use
|
||||
// cases. This is not only because of the performance costs, which will exist
|
||||
// even on PAC-supporting machines because of the function call overhead for
|
||||
// each sign/auth operation, but because it provides weaker security compared to
|
||||
// real PAC: the key is constant and public, which means that we do not mix a
|
||||
// global secret.
|
||||
//
|
||||
// The emulation assumes that the VA size is at most 48 bits. The architecture
|
||||
// as of ARMv8.2, which was the last architecture version in which PAC was not
|
||||
// mandatory, permitted VA size up to 52 bits via ARMv8.2-LVA, but we are
|
||||
// unaware of an ARMv8.2 CPU that implemented ARMv8.2-LVA.
|
||||
|
||||
static const uint64_t max_va_size = 48;
|
||||
static const uint64_t pac_mask =
|
||||
((1ULL << 55) - 1) & ~((1ULL << max_va_size) - 1);
|
||||
static const uint64_t ttbr1_mask = 1ULL << 55;
|
||||
|
||||
// Determine whether PAC is supported without accessing memory. This utilizes
|
||||
// the XPACLRI instruction which will copy bit 55 of x30 into at least bit 54 if
|
||||
// PAC is supported and acts as a NOP if PAC is not supported.
|
||||
static bool pac_supported() {
|
||||
register uintptr_t x30 __asm__("x30") = 1ULL << 55;
|
||||
__asm__ __volatile__("xpaclri" : "+r"(x30));
|
||||
return x30 & (1ULL << 54);
|
||||
}
|
||||
|
||||
#ifdef __GCC_HAVE_DWARF2_CFI_ASM
|
||||
#define CFI_INST(inst) inst
|
||||
#else
|
||||
#define CFI_INST(inst)
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define ASM_SYMBOL(symbol) "_" #symbol
|
||||
#else
|
||||
#define ASM_SYMBOL(symbol) #symbol
|
||||
#endif
|
||||
|
||||
// This asm snippet is used to force the creation of a frame record when
|
||||
// calling the EmuPAC functions. This is important because the EmuPAC functions
|
||||
// may crash if an auth failure is detected and may be unwound past using a
|
||||
// frame pointer based unwinder.
|
||||
// clang-format off
|
||||
#define FRAME_POINTER_WRAP(sym) \
|
||||
CFI_INST(".cfi_startproc\n") \
|
||||
"stp x29, x30, [sp, #-16]!\n" \
|
||||
CFI_INST(".cfi_def_cfa_offset 16\n") \
|
||||
"mov x29, sp\n" \
|
||||
CFI_INST(".cfi_def_cfa w29, 16\n") \
|
||||
CFI_INST(".cfi_offset w30, -8\n") \
|
||||
CFI_INST(".cfi_offset w29, -16\n") \
|
||||
"bl " ASM_SYMBOL(sym) "\n" \
|
||||
CFI_INST(".cfi_def_cfa wsp, 16\n") \
|
||||
"ldp x29, x30, [sp], #16\n" \
|
||||
CFI_INST(".cfi_def_cfa_offset 0\n") \
|
||||
CFI_INST(".cfi_restore w30\n") \
|
||||
CFI_INST(".cfi_restore w29\n") \
|
||||
"ret\n" \
|
||||
CFI_INST(".cfi_endproc\n")
|
||||
// clang-format on
|
||||
|
||||
// Emulated DA key value.
|
||||
static const uint8_t emu_da_key[16] = {0xb5, 0xd4, 0xc9, 0xeb, 0x79, 0x10,
|
||||
0x4a, 0x79, 0x6f, 0xec, 0x8b, 0x1b,
|
||||
0x42, 0x87, 0x81, 0xd4};
|
||||
|
||||
extern "C" [[gnu::flatten]] uint64_t __emupac_pacda_impl(uint64_t ptr,
|
||||
uint64_t disc) {
|
||||
if (pac_supported()) {
|
||||
__asm__ __volatile__(".arch_extension pauth\npacda %0, %1"
|
||||
: "+r"(ptr)
|
||||
: "r"(disc));
|
||||
return ptr;
|
||||
}
|
||||
if (ptr & ttbr1_mask) {
|
||||
if ((ptr & pac_mask) != pac_mask) {
|
||||
return ptr | pac_mask;
|
||||
}
|
||||
} else {
|
||||
if (ptr & pac_mask) {
|
||||
return ptr & ~pac_mask;
|
||||
}
|
||||
}
|
||||
uint64_t hash;
|
||||
siphash<1, 3>(reinterpret_cast<uint8_t *>(&ptr), 8, emu_da_key,
|
||||
*reinterpret_cast<uint8_t (*)[8]>(&hash));
|
||||
return (ptr & ~pac_mask) | (hash & pac_mask);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
__asm__(
|
||||
".globl " ASM_SYMBOL(__emupac_pacda) "\n"
|
||||
ASM_SYMBOL(__emupac_pacda) ":\n"
|
||||
FRAME_POINTER_WRAP(__emupac_pacda_impl)
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
extern "C" [[gnu::flatten]] uint64_t __emupac_autda_impl(uint64_t ptr,
|
||||
uint64_t disc) {
|
||||
if (pac_supported()) {
|
||||
__asm__ __volatile__(".arch_extension pauth\nautda %0, %1"
|
||||
: "+r"(ptr)
|
||||
: "r"(disc));
|
||||
return ptr;
|
||||
}
|
||||
uint64_t ptr_without_pac =
|
||||
(ptr & ttbr1_mask) ? (ptr | pac_mask) : (ptr & ~pac_mask);
|
||||
uint64_t hash;
|
||||
siphash<1, 3>(reinterpret_cast<uint8_t *>(&ptr_without_pac), 8, emu_da_key,
|
||||
*reinterpret_cast<uint8_t (*)[8]>(&hash));
|
||||
if (((ptr & ~pac_mask) | (hash & pac_mask)) != ptr) {
|
||||
__builtin_trap();
|
||||
}
|
||||
return ptr_without_pac;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
__asm__(
|
||||
".globl " ASM_SYMBOL(__emupac_autda) "\n"
|
||||
ASM_SYMBOL(__emupac_autda) ":\n"
|
||||
FRAME_POINTER_WRAP(__emupac_autda_impl)
|
||||
);
|
||||
// clang-format on
|
@ -223,7 +223,7 @@ typedef union {
|
||||
#define CRT_HAS_TF_MODE
|
||||
#endif
|
||||
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
#if __STDC_VERSION__ >= 199901L && !defined(_MSC_VER)
|
||||
typedef float _Complex Fcomplex;
|
||||
typedef double _Complex Dcomplex;
|
||||
typedef long double _Complex Lcomplex;
|
||||
|
121
compiler-rt/test/builtins/Unit/aarch64/emupac.c
Normal file
121
compiler-rt/test/builtins/Unit/aarch64/emupac.c
Normal file
@ -0,0 +1,121 @@
|
||||
// REQUIRES: librt_has_emupac
|
||||
// RUN: %clang_builtins %s %librt -o %t
|
||||
// RUN: %run %t 1
|
||||
// RUN: %run %t 2
|
||||
// RUN: %expect_crash %run %t 3
|
||||
// RUN: %expect_crash %run %t 4
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
uint64_t __emupac_pacda(uint64_t ptr, uint64_t disc);
|
||||
uint64_t __emupac_autda(uint64_t ptr, uint64_t disc);
|
||||
|
||||
static bool pac_supported() {
|
||||
register uintptr_t x30 __asm__("x30") = 1ULL << 55;
|
||||
__asm__ __volatile__("xpaclri" : "+r"(x30));
|
||||
return x30 & (1ULL << 54);
|
||||
}
|
||||
|
||||
static bool fpac_supported(uint64_t ap) {
|
||||
// The meaning of values larger than 6 is reserved as of July 2025; in theory
|
||||
// larger values could mean that FEAT_FPAC is not implemented.
|
||||
return ap == 4 || ap == 5 || ap == 6;
|
||||
}
|
||||
|
||||
// The crash tests would fail to crash (causing the test to fail) if:
|
||||
// - The operating system did not enable the DA key, or
|
||||
// - The CPU supports FEAT_PAuth but not FEAT_FPAC.
|
||||
// Therefore, they call this function, which will crash the test process if one
|
||||
// of these cases is detected so that %expect_crash detects the crash and causes
|
||||
// the test to pass.
|
||||
//
|
||||
// We detect the former case by attempting to sign a pointer. If the signed
|
||||
// pointer is equal to the unsigned pointer, DA is likely disabled, so we crash.
|
||||
//
|
||||
// We detect the latter case by reading ID_AA64ISAR1_EL1 and ID_AA64ISAR2_EL1.
|
||||
// It is expected that the operating system will either trap and emulate reading
|
||||
// the system registers (as Linux does) or crash the process. In the
|
||||
// trap/emulate case we check the APA, API and APA3 fields for FEAT_FPAC support
|
||||
// and crash if it is not available. In the crash case we will crash when
|
||||
// reading the register leading to a passing test. This means that operating
|
||||
// systems with the crashing behavior do not support the crash tests.
|
||||
static void crash_if_crash_tests_unsupported() {
|
||||
if (!pac_supported())
|
||||
return;
|
||||
|
||||
uint64_t ptr = 0;
|
||||
__asm__ __volatile__(".arch_extension pauth\npacda %0, %1"
|
||||
: "+r"(ptr)
|
||||
: "r"(0ul));
|
||||
if (ptr == 0)
|
||||
__builtin_trap();
|
||||
|
||||
uint64_t aa64isar1;
|
||||
__asm__ __volatile__("mrs %0, id_aa64isar1_el1" : "=r"(aa64isar1));
|
||||
uint64_t apa = (aa64isar1 >> 4) & 0xf;
|
||||
uint64_t api = (aa64isar1 >> 8) & 0xf;
|
||||
if (fpac_supported(apa) || fpac_supported(api))
|
||||
return;
|
||||
|
||||
uint64_t aa64isar2;
|
||||
__asm__ __volatile__("mrs %0, id_aa64isar2_el1" : "=r"(aa64isar2));
|
||||
uint64_t apa3 = (aa64isar2 >> 12) & 0xf;
|
||||
if (fpac_supported(apa3))
|
||||
return;
|
||||
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
char stack_object1;
|
||||
uint64_t ptr1 = (uint64_t)&stack_object1;
|
||||
|
||||
char stack_object2;
|
||||
uint64_t ptr2 = (uint64_t)&stack_object2;
|
||||
|
||||
switch (atoi(argv[1])) {
|
||||
case 1: {
|
||||
// Normal case: test that a pointer authenticated with the same
|
||||
// discriminator is equal to the original pointer.
|
||||
uint64_t signed_ptr = __emupac_pacda(ptr1, ptr2);
|
||||
uint64_t authed_ptr = __emupac_autda(signed_ptr, ptr2);
|
||||
if (authed_ptr != ptr1) {
|
||||
printf("0x%lx != 0x%lx\n", authed_ptr, ptr1);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Test that negative addresses (addresses controlled by TTBR1,
|
||||
// conventionally kernel addresses) can be signed and authenticated.
|
||||
uint64_t unsigned_ptr = -1ULL;
|
||||
uint64_t signed_ptr = __emupac_pacda(unsigned_ptr, ptr2);
|
||||
uint64_t authed_ptr = __emupac_autda(signed_ptr, ptr2);
|
||||
if (authed_ptr != unsigned_ptr) {
|
||||
printf("0x%lx != 0x%lx\n", authed_ptr, unsigned_ptr);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
crash_if_crash_tests_unsupported();
|
||||
// Test that a corrupted signature crashes the program.
|
||||
uint64_t signed_ptr = __emupac_pacda(ptr1, ptr2);
|
||||
__emupac_autda(signed_ptr + (1ULL << 48), ptr2);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
crash_if_crash_tests_unsupported();
|
||||
// Test that signing a pointer with signature bits already set produces a pointer
|
||||
// that would fail auth.
|
||||
uint64_t signed_ptr = __emupac_pacda(ptr1 + (1ULL << 48), ptr2);
|
||||
__emupac_autda(signed_ptr, ptr2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -78,12 +78,14 @@ static_library("builtins") {
|
||||
cflags += [ "-fomit-frame-pointer" ]
|
||||
}
|
||||
cflags_c = [ "-std=c11" ]
|
||||
cflags_cc = [ "-nostdinc++" ]
|
||||
}
|
||||
|
||||
defines = builtins_defines
|
||||
sources = builtins_sources
|
||||
|
||||
deps = lse_targets
|
||||
include_dirs = [ "//third-party/siphash/include" ]
|
||||
}
|
||||
|
||||
# Currently unused but necessary to make sync_source_lists_from_cmake.py happy.
|
||||
|
@ -429,6 +429,7 @@ if (current_cpu == "arm") {
|
||||
if (current_cpu == "arm64") {
|
||||
builtins_sources -= [ "fp_mode.c" ]
|
||||
builtins_sources += [
|
||||
"aarch64/emupac.cpp",
|
||||
"aarch64/fp_mode.c",
|
||||
"cpu_model/aarch64.c",
|
||||
]
|
||||
|
@ -46,6 +46,7 @@ if (current_toolchain != host_toolchain) {
|
||||
"//compiler-rt/include($host_toolchain)",
|
||||
"//compiler-rt/lib/builtins",
|
||||
"//compiler-rt/test:lit_common_configured",
|
||||
"//llvm/utils/not($host_toolchain)",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user