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"