Use-after-return sanitizer binary metadata
Currently per-function metadata consists of:
(start-pc, size, features)
This adds a new UAR feature and if it's set an additional element:
(start-pc, size, features, stack-args-size)
Reviewed By: melver
Differential Revision: https://reviews.llvm.org/D136078
GitOrigin-RevId: dbe8c2c316c40b25a0a37b91f1a1a02a55182378
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 4fc51e0..04ff819 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -105,6 +105,8 @@
# ShadowCallStack does not yet provide a runtime with compiler-rt, the tests
# include their own minimal runtime
add_subdirectory(shadowcallstack)
+ # These tests are self-contained and don't need an additional runtime.
+ add_subdirectory(metadata)
endif()
if(COMPILER_RT_STANDALONE_BUILD)
diff --git a/test/metadata/CMakeLists.txt b/test/metadata/CMakeLists.txt
new file mode 100644
index 0000000..b3765d7
--- /dev/null
+++ b/test/metadata/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(TEST_ARCH ${X86_64})
+
+set(METADATA_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(METADATA_LIT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
+
+set(SANITIZER_COMMON_TEST_TARGET_ARCH ${X86_64})
+get_test_cc_for_arch(${X86_64} METADATA_TEST_TARGET_CC METADATA_TEST_TARGET_CFLAGS)
+configure_lit_site_cfg(
+ ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
+ ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py)
+
+add_lit_testsuite(check-sanmd "Running the SanitizerBinaryMetadata tests"
+ ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS ${SANITIZER_COMMON_LIT_TEST_DEPS})
+set_target_properties(check-sanmd PROPERTIES FOLDER "Compiler-RT Misc")
diff --git a/test/metadata/common.h b/test/metadata/common.h
new file mode 100644
index 0000000..b121e87
--- /dev/null
+++ b/test/metadata/common.h
@@ -0,0 +1,75 @@
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+
+int main() { printf("main\n"); }
+
+typedef unsigned long uptr;
+
+#define FN(X) \
+ if (pc == reinterpret_cast<uptr>(X)) \
+ return #X
+
+const char *symbolize(uptr pc) {
+ FUNCTIONS;
+ return nullptr;
+}
+
+template <typename T> T consume(const char *&pos, const char *end) {
+ T v = *reinterpret_cast<const T *>(pos);
+ pos += sizeof(T);
+ assert(pos <= end);
+ return v;
+}
+
+uint32_t meta_version;
+const char *meta_start;
+const char *meta_end;
+
+extern "C" {
+void __sanitizer_metadata_covered_add(uint32_t version, const char *start,
+ const char *end) {
+ printf("metadata add version %u\n", version);
+ for (const char *pos = start; pos < end;) {
+ const uptr base = reinterpret_cast<uptr>(pos);
+ const long offset = (version & (1 << 16)) ? consume<long>(pos, end)
+ : consume<int>(pos, end);
+ const uint32_t size = consume<uint32_t>(pos, end);
+ const uint32_t features = consume<uint32_t>(pos, end);
+ uint32_t stack_args = 0;
+ if (features & (1 << 1))
+ stack_args = consume<uint32_t>(pos, end);
+ if (const char *name = symbolize(base + offset))
+ printf("%s: features=%x stack_args=%u\n", name, features, stack_args);
+ }
+ meta_version = version;
+ meta_start = start;
+ meta_end = end;
+}
+
+void __sanitizer_metadata_covered_del(uint32_t version, const char *start,
+ const char *end) {
+ assert(version == meta_version);
+ assert(start == meta_start);
+ assert(end == meta_end);
+}
+
+const char *atomics_start;
+const char *atomics_end;
+
+void __sanitizer_metadata_atomics_add(uint32_t version, const char *start,
+ const char *end) {
+ assert(version == meta_version);
+ assert(start);
+ assert(end >= end);
+ atomics_start = start;
+ atomics_end = end;
+}
+
+void __sanitizer_metadata_atomics_del(uint32_t version, const char *start,
+ const char *end) {
+ assert(version == meta_version);
+ assert(atomics_start == start);
+ assert(atomics_end == end);
+}
+}
diff --git a/test/metadata/covered.cpp b/test/metadata/covered.cpp
new file mode 100644
index 0000000..d00f970
--- /dev/null
+++ b/test/metadata/covered.cpp
@@ -0,0 +1,75 @@
+// RUN: %clangxx %s -o %t && %t | FileCheck %s
+// RUN: %clangxx %s -o %t -fexperimental-sanitize-metadata=covered && %t | FileCheck -check-prefix=CHECK-C %s
+// RUN: %clangxx %s -o %t -fexperimental-sanitize-metadata=atomics && %t | FileCheck -check-prefix=CHECK-A %s
+// RUN: %clangxx %s -o %t -fexperimental-sanitize-metadata=uar && %t | FileCheck -check-prefix=CHECK-U %s
+// RUN: %clangxx %s -o %t -fexperimental-sanitize-metadata=covered,atomics && %t | FileCheck -check-prefix=CHECK-CA %s
+// RUN: %clangxx %s -o %t -fexperimental-sanitize-metadata=covered,uar && %t | FileCheck -check-prefix=CHECK-CU %s
+// RUN: %clangxx %s -o %t -fexperimental-sanitize-metadata=atomics,uar && %t | FileCheck -check-prefix=CHECK-AU %s
+// RUN: %clangxx %s -o %t -fexperimental-sanitize-metadata=covered,atomics,uar && %t | FileCheck -check-prefix=CHECK-CAU %s
+
+// CHECK-NOT: metadata add
+// CHECK: main
+// CHECK-NOT: metadata del
+
+// CHECK-C: empty: features=0
+// CHECK-A-NOT: empty:
+// CHECK-U-NOT: empty:
+// CHECK-CA: empty: features=1
+// CHECK-CU: empty: features=0
+// CHECK-AU-NOT: empty:
+// CHECK-CAU: empty: features=1
+void empty() {}
+
+// CHECK-C: normal: features=0
+// CHECK-A: normal: features=1
+// CHECK-U: normal: features=2
+// CHECK-CA: normal: features=1
+// CHECK-CU: normal: features=2
+// CHECK-AU: normal: features=3
+// CHECK-CAU:normal: features=3
+void normal() {
+ volatile int x;
+ x = 0;
+}
+
+// CHECK-C: with_atomic: features=0
+// CHECK-A: with_atomic: features=1
+// CHECK-U: with_atomic: features=2
+// CHECK-CA: with_atomic: features=1
+// CHECK-CU: with_atomic: features=2
+// CHECK-AU: with_atomic: features=3
+// CHECK-CAU: with_atomic: features=3
+int with_atomic(int *p) { return __atomic_load_n(p, __ATOMIC_RELAXED); }
+
+// CHECK-C: ellipsis: features=0
+// CHECK-A: ellipsis: features=1
+// CHECK-U-NOT: ellipsis:
+// CHECK-CA: ellipsis: features=1
+// CHECK-CU: ellipsis: features=0
+// CHECK-AU: ellipsis: features=1
+// CHECK-CAU: ellipsis: features=1
+void ellipsis(int *p, ...) {
+ volatile int x;
+ x = 0;
+}
+
+// CHECK-C: ellipsis_with_atomic: features=0
+// CHECK-A: ellipsis_with_atomic: features=1
+// CHECK-U-NOT: ellipsis_with_atomic:
+// CHECK-CA: ellipsis_with_atomic: features=1
+// CHECK-CU: ellipsis_with_atomic: features=0
+// CHECK-AU: ellipsis_with_atomic: features=1
+// CHECK-CAU: ellipsis_with_atomic: features=1
+int ellipsis_with_atomic(int *p, ...) {
+ return __atomic_load_n(p, __ATOMIC_RELAXED);
+}
+
+#define FUNCTIONS \
+ FN(empty); \
+ FN(normal); \
+ FN(with_atomic); \
+ FN(ellipsis); \
+ FN(ellipsis_with_atomic); \
+ /**/
+
+#include "common.h"
diff --git a/test/metadata/lit.cfg.py b/test/metadata/lit.cfg.py
new file mode 100644
index 0000000..aefc97f
--- /dev/null
+++ b/test/metadata/lit.cfg.py
@@ -0,0 +1,9 @@
+import os
+
+config.name = 'SanitizerBinaryMetadata'
+config.test_source_root = os.path.dirname(__file__)
+config.suffixes = ['.cpp']
+# Binary metadata is currently emited only for ELF binaries
+# and sizes of stack arguments depend on the arch.
+if config.host_os not in ['Linux'] or config.target_arch not in ['x86_64']:
+ config.unsupported = True
diff --git a/test/metadata/lit.site.cfg.py.in b/test/metadata/lit.site.cfg.py.in
new file mode 100644
index 0000000..7b95b4b
--- /dev/null
+++ b/test/metadata/lit.site.cfg.py.in
@@ -0,0 +1,14 @@
+@LIT_SITE_CFG_IN_HEADER@
+
+# Tool-specific config options.
+config.clang = "@METADATA_TEST_TARGET_CC@"
+config.target_cflags = "@METADATA_TEST_TARGET_CFLAGS@"
+config.target_arch = "x86_64"
+
+# 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, "@METADATA_LIT_SOURCE_DIR@/lit.cfg.py")
+
+config.substitutions.append(("%clangxx ", " " + config.clang + " " + config.target_cflags + " "))
diff --git a/test/metadata/uar.cpp b/test/metadata/uar.cpp
new file mode 100644
index 0000000..dc6abdf
--- /dev/null
+++ b/test/metadata/uar.cpp
@@ -0,0 +1,59 @@
+// RUN: %clangxx %s -o %t -fexperimental-sanitize-metadata=covered,uar && %t | FileCheck %s
+
+// CHECK: metadata add version 1
+
+// CHECK: empty: features=0 stack_args=0
+void empty() {}
+
+// CHECK: ellipsis: features=0 stack_args=0
+void ellipsis(const char *fmt, ...) {
+ volatile int x;
+ x = 1;
+}
+
+// CHECK: non_empty_function: features=2 stack_args=0
+void non_empty_function() {
+ // Completely empty functions don't get uar metadata.
+ volatile int x;
+ x = 1;
+}
+
+// CHECK: no_stack_args: features=2 stack_args=0
+void no_stack_args(long a0, long a1, long a2, long a3, long a4, long a5) {
+ volatile int x;
+ x = 1;
+}
+
+// CHECK: stack_args: features=2 stack_args=16
+void stack_args(long a0, long a1, long a2, long a3, long a4, long a5, long a6) {
+ volatile int x;
+ x = 1;
+}
+
+// CHECK: more_stack_args: features=2 stack_args=32
+void more_stack_args(long a0, long a1, long a2, long a3, long a4, long a5,
+ long a6, long a7, long a8) {
+ volatile int x;
+ x = 1;
+}
+
+// CHECK: struct_stack_args: features=2 stack_args=144
+struct large {
+ char x[131];
+};
+void struct_stack_args(large a) {
+ volatile int x;
+ x = 1;
+}
+
+#define FUNCTIONS \
+ FN(empty); \
+ FN(ellipsis); \
+ FN(non_empty_function); \
+ FN(no_stack_args); \
+ FN(stack_args); \
+ FN(more_stack_args); \
+ FN(struct_stack_args); \
+ /**/
+
+#include "common.h"