diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake index 2683259e93e37..e75115fbbee9a 100644 --- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake +++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake @@ -119,3 +119,7 @@ endif() if (WIN32) set(ALL_ORC_SUPPORTED_ARCH ${X86_64}) endif() + +if (OS_NAME MATCHES "Linux") + set(ALL_PFP_SUPPORTED_ARCH ${X86_64} ${ARM64}) +endif() diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake index cf729c3adb1f5..8b45685132ab3 100644 --- a/compiler-rt/cmake/config-ix.cmake +++ b/compiler-rt/cmake/config-ix.cmake @@ -686,6 +686,9 @@ if(APPLE) list_intersect(ORC_SUPPORTED_ARCH ALL_ORC_SUPPORTED_ARCH SANITIZER_COMMON_SUPPORTED_ARCH) + list_intersect(PFP_SUPPORTED_ARCH + ALL_PFP_SUPPORTED_ARCH + SANITIZER_COMMON_SUPPORTED_ARCH) else() # Architectures supported by compiler-rt libraries. @@ -721,6 +724,7 @@ else() filter_available_targets(GWP_ASAN_SUPPORTED_ARCH ${ALL_GWP_ASAN_SUPPORTED_ARCH}) filter_available_targets(NSAN_SUPPORTED_ARCH ${ALL_NSAN_SUPPORTED_ARCH}) filter_available_targets(ORC_SUPPORTED_ARCH ${ALL_ORC_SUPPORTED_ARCH}) + filter_available_targets(PFP_SUPPORTED_ARCH ${ALL_PFP_SUPPORTED_ARCH}) endif() if (MSVC) diff --git a/compiler-rt/test/CMakeLists.txt b/compiler-rt/test/CMakeLists.txt index fad5b7e03925e..c4d2d181fd5df 100644 --- a/compiler-rt/test/CMakeLists.txt +++ b/compiler-rt/test/CMakeLists.txt @@ -106,6 +106,7 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS) # ShadowCallStack does not yet provide a runtime with compiler-rt, the tests # include their own minimal runtime add_subdirectory(shadowcallstack) + add_subdirectory(pfp) endif() # Now that we've traversed all the directories and know all the lit testsuites, diff --git a/compiler-rt/test/pfp/CMakeLists.txt b/compiler-rt/test/pfp/CMakeLists.txt new file mode 100644 index 0000000000000..1ee8eea8dbaac --- /dev/null +++ b/compiler-rt/test/pfp/CMakeLists.txt @@ -0,0 +1,36 @@ +set(PFP_TESTSUITES) +set(PFP_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS} unwind compiler-rt) +if(COMPILER_RT_HAS_LLD AND TARGET lld) + list(APPEND PFP_TEST_DEPS lld) +endif() + +macro(add_pfp_testsuite arch thinlto) + set(PFP_TEST_TARGET_ARCH ${arch}) + get_test_cc_for_arch(${arch} PFP_TEST_TARGET_CC PFP_TEST_TARGET_CFLAGS) + + string(TOUPPER ${arch} CONFIG_NAME) + + if (${thinlto}) + set(PFP_TEST_USE_THINLTO ${thinlto}) + set(CONFIG_NAME "thinlto-${CONFIG_NAME}") + list(APPEND PFP_TEST_DEPS LTO) + endif() + set(PFP_TEST_USE_THINLTO ${thinlto}) + + configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py) + list(APPEND PFP_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}) +endmacro() + +set(PFP_TEST_ARCH ${PFP_SUPPORTED_ARCH}) + +foreach(arch ${PFP_TEST_ARCH}) + add_pfp_testsuite(${arch} False) + add_pfp_testsuite(${arch} True) +endforeach() + +add_lit_testsuite(check-pfp "Running the PointerFieldProtection tests" + ${PFP_TESTSUITES} + DEPENDS ${PFP_TEST_DEPS}) + diff --git a/compiler-rt/test/pfp/lit.cfg.py b/compiler-rt/test/pfp/lit.cfg.py new file mode 100644 index 0000000000000..2d58972804039 --- /dev/null +++ b/compiler-rt/test/pfp/lit.cfg.py @@ -0,0 +1,37 @@ +# -*- Python -*- + +import os + +from lit.llvm import llvm_config +from lit.llvm.subst import ToolSubst, FindTool + +# Setup config name. +config.name = "pfp" + config.name_suffix + +# Default test suffixes. +config.suffixes = [".c", ".cpp"] + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) +# Setup default compiler flags used with -fsanitize=memory option. +clang_cflags = [config.target_cflags] + config.debug_info_flags +clang_cxxflags = config.cxx_mode_flags + clang_cflags +clang_pfp_tagged_common_cflags = clang_cflags + [ + "-fexperimental-pointer-field-protection=tagged" +] + + +clang_pfp_cxxflags = config.cxx_mode_flags + clang_pfp_tagged_common_cflags +clang_pfp_cxxflags = clang_pfp_cxxflags + ["-fuse-ld=lld --rtlib=compiler-rt --unwindlib=libunwind -static-libgcc"] + + +def build_invocation(compile_flags, with_lto=False): + lto_flags = [] + if with_lto and config.lto_supported: + lto_flags += config.lto_flags + + return " " + " ".join([config.clang] + lto_flags + compile_flags) + " " + + +config.substitutions.append(("%clangxx ", build_invocation(clang_cxxflags))) +config.substitutions.append(("%clangxx_pfp ", build_invocation(clang_pfp_cxxflags, config.use_thinlto))) diff --git a/compiler-rt/test/pfp/lit.site.cfg.py.in b/compiler-rt/test/pfp/lit.site.cfg.py.in new file mode 100644 index 0000000000000..7bc6331a0a376 --- /dev/null +++ b/compiler-rt/test/pfp/lit.site.cfg.py.in @@ -0,0 +1,16 @@ +@LIT_SITE_CFG_IN_HEADER@ + +# Tool-specific config options. +config.name_suffix = "@PFP_TEST_CONFIG_SUFFIX@" +config.target_cflags = "@PFP_TEST_TARGET_CFLAGS@" +config.target_arch = "@PFP_TEST_TARGET_ARCH@" +config.use_lld = True +config.use_thinlto = @PFP_TEST_USE_THINLTO@ +config.libunwind_shared = "@LIBUNWIND_ENABLE_SHARED@" +config.libunwind_install_dir = "@LLVM_BINARY_DIR@/@LIBUNWIND_INSTALL_LIBRARY_DIR@" + +# Load common config for all compiler-rt lit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") + +# Load tool-specific config that would do the real work. +lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg.py") diff --git a/compiler-rt/test/pfp/use-after-free-fixed.cpp b/compiler-rt/test/pfp/use-after-free-fixed.cpp new file mode 100644 index 0000000000000..2fe21111cdd49 --- /dev/null +++ b/compiler-rt/test/pfp/use-after-free-fixed.cpp @@ -0,0 +1,43 @@ +// RUN: %clangxx_pfp %s -o %t1 +// RUN: %run %t1 2>&1 +// RUN: %clangxx %s -o %t2 +// RUN: %run %t2 2>&1 + +#include + +// Struct1.ptr and Struct2.ptr have different locks. +struct Struct1 { + int *ptr; + Struct1() : num(1), ptr(&num) {} + +private: + int num; +}; + +struct Struct2 { + int *ptr; + Struct2() : num(2), ptr(&num) {} + +private: + int num; +}; + +Struct1 *new_object1() { + Struct1 *ptr = new Struct1; + return ptr; +} + +Struct2 *new_object2() { + Struct2 *ptr = new Struct2; + return ptr; +} + +int main() { + Struct1 *obj1 = new_object1(); + Struct2 *obj2 = new_object2(); + std::cout << "Struct2: " << *(obj2->ptr) << "\n"; + std::cout << "Struct1: " << *(obj1->ptr) << "\n"; + delete obj1; + delete obj2; + return 0; +} diff --git a/compiler-rt/test/pfp/use-after-free.cpp b/compiler-rt/test/pfp/use-after-free.cpp new file mode 100644 index 0000000000000..17b7661b2a904 --- /dev/null +++ b/compiler-rt/test/pfp/use-after-free.cpp @@ -0,0 +1,45 @@ +// RUN: %clangxx_pfp %s -o %t1 +// RUN: %expect_crash %run %t1 2>&1 +// RUN: %clangxx %s -o %t2 +// RUN: %run %t2 2>&1 + +#include + +// Struct1.ptr and Struct2.ptr have different locks. +struct Struct1 { + int *ptr; + Struct1() : num(1), ptr(&num) {} + +private: + int num; +}; + +struct Struct2 { + int *ptr; + Struct2() : num(2), ptr(&num) {} + +private: + int num; +}; + +Struct1 *new_object1() { + Struct1 *ptr = new Struct1; + return ptr; +} + +Struct2 *new_object2() { + Struct2 *ptr = new Struct2; + return ptr; +} + +int main() { + Struct1 *obj1 = new_object1(); + delete obj1; + // obj1's memory will be reused. + Struct2 *obj2 = new_object2(); + std::cout << "Struct2: " << *(obj2->ptr) << "\n"; + // Uses a wrong lock. The Program should crash when "-fexperimental-pointer-field-protection=tagged". + std::cout << "Struct1: " << *(obj1->ptr) << "\n"; + delete obj2; + return 0; +}