From 725376b45c8504946fbe5ed26dd13584d019ef9a Mon Sep 17 00:00:00 2001 From: Qinkun Bao Date: Fri, 4 Apr 2025 18:00:15 +0000 Subject: [PATCH 1/5] Implement a simple e2e test for PFP. --- .../cmake/Modules/AllSupportedArchDefs.cmake | 4 ++ compiler-rt/cmake/config-ix.cmake | 4 ++ compiler-rt/test/CMakeLists.txt | 3 + compiler-rt/test/pfp/CMakeLists.txt | 55 +++++++++++++++++++ compiler-rt/test/pfp/lit.cfg.py | 37 +++++++++++++ compiler-rt/test/pfp/lit.site.cfg.py.in | 16 ++++++ compiler-rt/test/pfp/use-after-free-fixed.cpp | 43 +++++++++++++++ compiler-rt/test/pfp/use-after-free.cpp | 45 +++++++++++++++ 8 files changed, 207 insertions(+) create mode 100644 compiler-rt/test/pfp/CMakeLists.txt create mode 100644 compiler-rt/test/pfp/lit.cfg.py create mode 100644 compiler-rt/test/pfp/lit.site.cfg.py.in create mode 100644 compiler-rt/test/pfp/use-after-free-fixed.cpp create mode 100644 compiler-rt/test/pfp/use-after-free.cpp diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake index 2683259e93e37..1ad927f4677e2 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 (UNIX) + 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..9dd90aef7ebd9 100644 --- a/compiler-rt/test/CMakeLists.txt +++ b/compiler-rt/test/CMakeLists.txt @@ -106,6 +106,9 @@ 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) + # PointerFieldProtection does not yet provide a runtime with compiler-rt, the tests + # include their own minimal runtime + 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..11e9985ddd3c4 --- /dev/null +++ b/compiler-rt/test/pfp/CMakeLists.txt @@ -0,0 +1,55 @@ +set(PFP_TESTSUITES) +set(PFP_TEST_DEPS + ${SANITIZER_COMMON_LIT_TEST_DEPS}) + + +macro(add_pfp_testsuite arch lld thinlto compilerrt libunwind) + 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(CONFIG_NAME "thinlto-${CONFIG_NAME}") + list(APPEND PFP_TEST_DEPS LTO) + endif() + if (${libunwind}) + set(CONFIG_NAME "libunwind-${CONFIG_NAME}") + list(APPEND PFP_TEST_DEPS unwind) + endif() + if (${lld}) + set(CONFIG_NAME "lld-${CONFIG_NAME}") + if (TARGET lld) + list(APPEND PFP_TEST_DEPS lld) + endif() + endif() + if (${compilerrt}) + set(CONFIG_NAME "compiler-rt-${CONFIG_NAME}") + list(APPEND PFP_TEST_DEPS compiler-rt) + endif() + set(PFP_TEST_USE_THINLTO ${thinlto}) + set(PFP_TEST_USE_LLD ${lld}) + + 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}) + if (arch STREQUAL ${X86_64}) + add_pfp_testsuite(${arch} False False False False) + endif() + if (arch STREQUAL ${ARM64}) + add_pfp_testsuite(${arch} True True True True) + endif() +endforeach() + +message(STATUS "PFP tests. " "Arch: ${PFP_TEST_ARCH} Deps: ${PFP_TEST_DEPS}") +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..39a6252236509 --- /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"] + +if config.host_os not in ["Linux"]: + config.unsupported = True + +# 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 +if config.target_arch == 'aarch64': + clang_pfp_cxxflags = clang_pfp_cxxflags + ["-fuse-ld=lld --rtlib=compiler-rt --unwindlib=libunwind -static-libgcc"] + + +def build_invocation(compile_flags): + return " " + " ".join([config.clang] + compile_flags) + " " + + +config.substitutions.append(("%clangxx ", build_invocation(clang_cxxflags))) +config.substitutions.append(("%clangxx_pfp ", build_invocation(clang_pfp_cxxflags))) 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..ec39759982a1d --- /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 = @PFP_TEST_USE_LLD@ +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; +} From 858863e1e603dfc1443d0ccca75463fc64c48a4c Mon Sep 17 00:00:00 2001 From: Qinkun Bao Date: Sat, 5 Apr 2025 00:12:44 +0000 Subject: [PATCH 2/5] address comments --- .../cmake/Modules/AllSupportedArchDefs.cmake | 2 +- compiler-rt/test/CMakeLists.txt | 2 -- compiler-rt/test/pfp/CMakeLists.txt | 33 ++++--------------- compiler-rt/test/pfp/lit.cfg.py | 19 ++++++----- 4 files changed, 18 insertions(+), 38 deletions(-) diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake index 1ad927f4677e2..e75115fbbee9a 100644 --- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake +++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake @@ -120,6 +120,6 @@ if (WIN32) set(ALL_ORC_SUPPORTED_ARCH ${X86_64}) endif() -if (UNIX) +if (OS_NAME MATCHES "Linux") set(ALL_PFP_SUPPORTED_ARCH ${X86_64} ${ARM64}) endif() diff --git a/compiler-rt/test/CMakeLists.txt b/compiler-rt/test/CMakeLists.txt index 9dd90aef7ebd9..c4d2d181fd5df 100644 --- a/compiler-rt/test/CMakeLists.txt +++ b/compiler-rt/test/CMakeLists.txt @@ -106,8 +106,6 @@ 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) - # PointerFieldProtection does not yet provide a runtime with compiler-rt, the tests - # include their own minimal runtime add_subdirectory(pfp) endif() diff --git a/compiler-rt/test/pfp/CMakeLists.txt b/compiler-rt/test/pfp/CMakeLists.txt index 11e9985ddd3c4..631b73cc2165f 100644 --- a/compiler-rt/test/pfp/CMakeLists.txt +++ b/compiler-rt/test/pfp/CMakeLists.txt @@ -1,34 +1,19 @@ set(PFP_TESTSUITES) -set(PFP_TEST_DEPS - ${SANITIZER_COMMON_LIT_TEST_DEPS}) +set(PFP_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS} unwind lld compiler-rt) +set(PFP_TEST_USE_LLD True) - -macro(add_pfp_testsuite arch lld thinlto compilerrt libunwind) +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() - if (${libunwind}) - set(CONFIG_NAME "libunwind-${CONFIG_NAME}") - list(APPEND PFP_TEST_DEPS unwind) - endif() - if (${lld}) - set(CONFIG_NAME "lld-${CONFIG_NAME}") - if (TARGET lld) - list(APPEND PFP_TEST_DEPS lld) - endif() - endif() - if (${compilerrt}) - set(CONFIG_NAME "compiler-rt-${CONFIG_NAME}") - list(APPEND PFP_TEST_DEPS compiler-rt) endif() set(PFP_TEST_USE_THINLTO ${thinlto}) - set(PFP_TEST_USE_LLD ${lld}) configure_lit_site_cfg( ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in @@ -38,17 +23,11 @@ endmacro() set(PFP_TEST_ARCH ${PFP_SUPPORTED_ARCH}) - foreach(arch ${PFP_TEST_ARCH}) - if (arch STREQUAL ${X86_64}) - add_pfp_testsuite(${arch} False False False False) - endif() - if (arch STREQUAL ${ARM64}) - add_pfp_testsuite(${arch} True True True True) - endif() + add_pfp_testsuite(${arch} False) + add_pfp_testsuite(${arch} True) endforeach() -message(STATUS "PFP tests. " "Arch: ${PFP_TEST_ARCH} Deps: ${PFP_TEST_DEPS}") 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 index 39a6252236509..6c2773ea07e53 100644 --- a/compiler-rt/test/pfp/lit.cfg.py +++ b/compiler-rt/test/pfp/lit.cfg.py @@ -11,9 +11,6 @@ # Default test suffixes. config.suffixes = [".c", ".cpp"] -if config.host_os not in ["Linux"]: - config.unsupported = True - # Setup source root. config.test_source_root = os.path.dirname(__file__) # Setup default compiler flags used with -fsanitize=memory option. @@ -25,13 +22,19 @@ clang_pfp_cxxflags = config.cxx_mode_flags + clang_pfp_tagged_common_cflags -if config.target_arch == 'aarch64': - clang_pfp_cxxflags = clang_pfp_cxxflags + ["-fuse-ld=lld --rtlib=compiler-rt --unwindlib=libunwind -static-libgcc"] +clang_pfp_cxxflags = clang_pfp_cxxflags + ["-fuse-ld=lld --rtlib=compiler-rt --unwindlib=libunwind -static-libgcc"] -def build_invocation(compile_flags): - return " " + " ".join([config.clang] + compile_flags) + " " +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))) +if config.target_arch == 'aarch64': + config.substitutions.append(("%clangxx_pfp ", build_invocation(clang_pfp_cxxflags, True))) +else: + config.substitutions.append(("%clangxx_pfp ", build_invocation(clang_pfp_cxxflags))) From c2a6405bf4e87c93e39713d28a40cc20e19895a9 Mon Sep 17 00:00:00 2001 From: Qinkun Bao Date: Sat, 5 Apr 2025 00:49:15 +0000 Subject: [PATCH 3/5] Remove PFP_TEST_USE_LLD --- compiler-rt/test/pfp/CMakeLists.txt | 1 - compiler-rt/test/pfp/lit.site.cfg.py.in | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler-rt/test/pfp/CMakeLists.txt b/compiler-rt/test/pfp/CMakeLists.txt index 631b73cc2165f..737955998e3de 100644 --- a/compiler-rt/test/pfp/CMakeLists.txt +++ b/compiler-rt/test/pfp/CMakeLists.txt @@ -1,6 +1,5 @@ set(PFP_TESTSUITES) set(PFP_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS} unwind lld compiler-rt) -set(PFP_TEST_USE_LLD True) macro(add_pfp_testsuite arch thinlto) set(PFP_TEST_TARGET_ARCH ${arch}) diff --git a/compiler-rt/test/pfp/lit.site.cfg.py.in b/compiler-rt/test/pfp/lit.site.cfg.py.in index ec39759982a1d..7bc6331a0a376 100644 --- a/compiler-rt/test/pfp/lit.site.cfg.py.in +++ b/compiler-rt/test/pfp/lit.site.cfg.py.in @@ -4,7 +4,7 @@ config.name_suffix = "@PFP_TEST_CONFIG_SUFFIX@" config.target_cflags = "@PFP_TEST_TARGET_CFLAGS@" config.target_arch = "@PFP_TEST_TARGET_ARCH@" -config.use_lld = @PFP_TEST_USE_LLD@ +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@" From 98295661ce32b539055fb47d87ce3fe33cf6a22f Mon Sep 17 00:00:00 2001 From: Qinkun Bao Date: Sat, 5 Apr 2025 01:08:10 +0000 Subject: [PATCH 4/5] pass config.use_thinlto to build_invocation --- compiler-rt/test/pfp/lit.cfg.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler-rt/test/pfp/lit.cfg.py b/compiler-rt/test/pfp/lit.cfg.py index 6c2773ea07e53..2d58972804039 100644 --- a/compiler-rt/test/pfp/lit.cfg.py +++ b/compiler-rt/test/pfp/lit.cfg.py @@ -34,7 +34,4 @@ def build_invocation(compile_flags, with_lto=False): config.substitutions.append(("%clangxx ", build_invocation(clang_cxxflags))) -if config.target_arch == 'aarch64': - config.substitutions.append(("%clangxx_pfp ", build_invocation(clang_pfp_cxxflags, True))) -else: - config.substitutions.append(("%clangxx_pfp ", build_invocation(clang_pfp_cxxflags))) +config.substitutions.append(("%clangxx_pfp ", build_invocation(clang_pfp_cxxflags, config.use_thinlto))) From 162b0d07e0ebfcff9f3d6e69232b7d9c530ca299 Mon Sep 17 00:00:00 2001 From: Qinkun Bao Date: Wed, 9 Apr 2025 21:20:04 +0000 Subject: [PATCH 5/5] make lld as optional deps --- compiler-rt/test/pfp/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler-rt/test/pfp/CMakeLists.txt b/compiler-rt/test/pfp/CMakeLists.txt index 737955998e3de..1ee8eea8dbaac 100644 --- a/compiler-rt/test/pfp/CMakeLists.txt +++ b/compiler-rt/test/pfp/CMakeLists.txt @@ -1,5 +1,8 @@ set(PFP_TESTSUITES) -set(PFP_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS} unwind lld compiler-rt) +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})