[compiler-rt] [llvm] [msan] Add interceptors for fortified memory functions (__memcpy_chk, etc.) (PR #206702)

Austin Schuh via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 17 03:05:14 PDT 2026


https://github.com/AustinSchuh updated https://github.com/llvm/llvm-project/pull/206702

>From c9c1615265b586d2b19adb34fe442d02b929312a Mon Sep 17 00:00:00 2001
From: Austin Schuh <austin.linux at gmail.com>
Date: Tue, 30 Jun 2026 03:23:09 -0700
Subject: [PATCH 1/5] [msan] Add interceptors for fortified memory functions
 (__memcpy_chk, etc.)

When compiled with glibc's source fortification enabled (e.g., -D_FORTIFY_SOURCE=2
along with compiler optimization), calls to standard memory functions like
memcpy, memmove, memset, and mempcpy are replaced with their fortified
variants: __memcpy_chk, __memmove_chk, __memset_chk, and __mempcpy_chk.

Clang's compile-time MemorySanitizer (MSan) pass does not lower these fortified
symbol calls to llvm.memcpy/memset intrinsics. Since compiler-rt lacked runtime
interceptors for these functions, shadow memory and origin state were not
propagated across fortified copy or fill operations. This led to false-positive
"use-of-uninitialized-value" warnings when memory copied via fortified functions
was subsequently accessed.

This patch adds MSan interceptors for:
- __memcpy_chk
- __memmove_chk
- __memset_chk
- __mempcpy_chk

Each interceptor updates the destination shadow and origin memory, and forwards
the execution to the underlying glibc implementation via REAL(...) to preserve
the original runtime buffer overflow checks. These interceptors are guarded
by `#if SANITIZER_GLIBC` as fortified _chk functions are a glibc extension.

We also add a new unit test suite (compiler-rt/test/msan/Linux/fortify.cpp)
to verify correct shadow propagation through these interceptors under both -O0
and -O2 compiler optimization levels.

Signed-off-by: Austin Schuh <austin.linux at gmail.com>
---
 compiler-rt/lib/msan/msan_interceptors.cpp | 88 ++++++++++++++++++++++
 compiler-rt/test/msan/Linux/fortify.cpp    | 68 +++++++++++++++++
 2 files changed, 156 insertions(+)
 create mode 100644 compiler-rt/test/msan/Linux/fortify.cpp

diff --git a/compiler-rt/lib/msan/msan_interceptors.cpp b/compiler-rt/lib/msan/msan_interceptors.cpp
index ce2eae02d4179..862ecd60adcf0 100644
--- a/compiler-rt/lib/msan/msan_interceptors.cpp
+++ b/compiler-rt/lib/msan/msan_interceptors.cpp
@@ -63,6 +63,17 @@ DECLARE_REAL(SIZE_T, strnlen, const char *s, SIZE_T maxlen)
 DECLARE_REAL(void *, memcpy, void *dest, const void *src, SIZE_T n)
 DECLARE_REAL(void *, memset, void *dest, int c, SIZE_T n)
 
+#if SANITIZER_GLIBC
+DECLARE_REAL(void *, __memcpy_chk, void *dest, const void *src, SIZE_T n,
+             SIZE_T dest_size)
+DECLARE_REAL(void *, __memmove_chk, void *dest, const void *src, SIZE_T n,
+             SIZE_T dest_size)
+DECLARE_REAL(void *, __memset_chk, void *dest, int c, SIZE_T n,
+             SIZE_T dest_size)
+DECLARE_REAL(void *, __mempcpy_chk, void *dest, const void *src, SIZE_T n,
+             SIZE_T dest_size)
+#endif
+
 // True if this is a nested interceptor.
 static THREADLOCAL int in_interceptor_scope;
 
@@ -158,6 +169,79 @@ INTERCEPTOR(void *, bcopy, const void *src, void *dest, SIZE_T n) {
   return __msan_memmove(dest, src, n);
 }
 
+#if SANITIZER_GLIBC
+INTERCEPTOR(void *, __memcpy_chk, void *dest, const void *src, SIZE_T n,
+            SIZE_T dest_size) {
+  if (!msan_inited)
+    return internal_memcpy(dest, src, n);
+  if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
+    return REAL(__memcpy_chk)(dest, src, n, dest_size);
+  ENSURE_MSAN_INITED();
+  GET_STORE_STACK_TRACE;
+  void *res = REAL(__memcpy_chk)(dest, src, n, dest_size);
+  CopyShadowAndOrigin(dest, src, n, &stack);
+  return res;
+}
+#define MSAN_MAYBE_INTERCEPT___MEMCPY_CHK INTERCEPT_FUNCTION(__memcpy_chk)
+#else
+#define MSAN_MAYBE_INTERCEPT___MEMCPY_CHK
+#endif
+
+#if SANITIZER_GLIBC
+INTERCEPTOR(void *, __memmove_chk, void *dest, const void *src, SIZE_T n,
+            SIZE_T dest_size) {
+  if (!msan_inited)
+    return internal_memmove(dest, src, n);
+  if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
+    return REAL(__memmove_chk)(dest, src, n, dest_size);
+  ENSURE_MSAN_INITED();
+  GET_STORE_STACK_TRACE;
+  void *res = REAL(__memmove_chk)(dest, src, n, dest_size);
+  MoveShadowAndOrigin(dest, src, n, &stack);
+  return res;
+}
+#define MSAN_MAYBE_INTERCEPT___MEMMOVE_CHK INTERCEPT_FUNCTION(__memmove_chk)
+#else
+#define MSAN_MAYBE_INTERCEPT___MEMMOVE_CHK
+#endif
+
+#if SANITIZER_GLIBC
+INTERCEPTOR(void *, __memset_chk, void *dest, int c, SIZE_T n,
+            SIZE_T dest_size) {
+  if (!msan_inited)
+    return internal_memset(dest, c, n);
+  if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
+    return REAL(__memset_chk)(dest, c, n, dest_size);
+  ENSURE_MSAN_INITED();
+  void *res = REAL(__memset_chk)(dest, c, n, dest_size);
+  __msan_unpoison(dest, n);
+  return res;
+}
+#define MSAN_MAYBE_INTERCEPT___MEMSET_CHK INTERCEPT_FUNCTION(__memset_chk)
+#else
+#define MSAN_MAYBE_INTERCEPT___MEMSET_CHK
+#endif
+
+#if SANITIZER_GLIBC
+INTERCEPTOR(void *, __mempcpy_chk, void *dest, const void *src, SIZE_T n,
+            SIZE_T dest_size) {
+  if (!msan_inited) {
+    internal_memcpy(dest, src, n);
+    return (char *)dest + n;
+  }
+  if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
+    return REAL(__mempcpy_chk)(dest, src, n, dest_size);
+  ENSURE_MSAN_INITED();
+  GET_STORE_STACK_TRACE;
+  void *res = REAL(__mempcpy_chk)(dest, src, n, dest_size);
+  CopyShadowAndOrigin(dest, src, n, &stack);
+  return res;
+}
+#define MSAN_MAYBE_INTERCEPT___MEMPCPY_CHK INTERCEPT_FUNCTION(__mempcpy_chk)
+#else
+#define MSAN_MAYBE_INTERCEPT___MEMPCPY_CHK
+#endif
+
 INTERCEPTOR(int, posix_memalign, void **memptr, SIZE_T alignment, SIZE_T size) {
   GET_MALLOC_STACK_TRACE;
   CHECK_NE(memptr, 0);
@@ -1822,6 +1906,10 @@ void InitializeInterceptors() {
   MSAN_MAYBE_INTERCEPT_FREAD_UNLOCKED;
   INTERCEPT_FUNCTION(memccpy);
   MSAN_MAYBE_INTERCEPT_MEMPCPY;
+  MSAN_MAYBE_INTERCEPT___MEMCPY_CHK;
+  MSAN_MAYBE_INTERCEPT___MEMMOVE_CHK;
+  MSAN_MAYBE_INTERCEPT___MEMSET_CHK;
+  MSAN_MAYBE_INTERCEPT___MEMPCPY_CHK;
   INTERCEPT_FUNCTION(bcopy);
   INTERCEPT_FUNCTION(wmemset);
   INTERCEPT_FUNCTION(wmemcpy);
diff --git a/compiler-rt/test/msan/Linux/fortify.cpp b/compiler-rt/test/msan/Linux/fortify.cpp
new file mode 100644
index 0000000000000..4568aa1827a94
--- /dev/null
+++ b/compiler-rt/test/msan/Linux/fortify.cpp
@@ -0,0 +1,68 @@
+// RUN: %clangxx_msan -O0 %s -o %t && %run %t
+// RUN: %clangxx_msan -O2 %s -o %t && %run %t
+
+// REQUIRES: glibc
+
+#include <assert.h>
+#include <string.h>
+#include <sanitizer/msan_interface.h>
+
+extern "C" void *__memcpy_chk(void *dest, const void *src, size_t len, size_t destlen);
+extern "C" void *__memmove_chk(void *dest, const void *src, size_t len, size_t destlen);
+extern "C" void *__memset_chk(void *dest, int c, size_t len, size_t destlen);
+extern "C" void *__mempcpy_chk(void *dest, const void *src, size_t len, size_t destlen);
+
+int main(int argc, char *argv[]) {
+  // Test __memcpy_chk shadow propagation
+  {
+    char src[10];
+    char dest[10];
+    __msan_unpoison(dest, sizeof(dest));
+    __msan_poison(src, sizeof(src));
+    src[1] = 1;
+    src[2] = 2;
+    __memcpy_chk(dest, src, 5, sizeof(dest));
+    assert(__msan_test_shadow(dest, 5) == 0); // dest[0] is uninitialized
+    assert(__msan_test_shadow(dest + 1, 4) == 2); // dest[1], dest[2] are initialized, dest[3..4] are uninitialized
+    assert(__msan_test_shadow(dest + 5, 5) == -1); // dest[5..9] remain initialized
+  }
+
+  // Test __memmove_chk shadow propagation
+  {
+    char src[10];
+    char dest[10];
+    __msan_unpoison(dest, sizeof(dest));
+    __msan_poison(src, sizeof(src));
+    src[1] = 1;
+    src[2] = 2;
+    __memmove_chk(dest, src, 5, sizeof(dest));
+    assert(__msan_test_shadow(dest, 5) == 0);
+    assert(__msan_test_shadow(dest + 1, 4) == 2);
+    assert(__msan_test_shadow(dest + 5, 5) == -1);
+  }
+
+  // Test __memset_chk shadow propagation
+  {
+    char dest[10];
+    __msan_poison(dest, sizeof(dest));
+    __memset_chk(dest, 42, 5, sizeof(dest));
+    assert(__msan_test_shadow(dest, 10) == 5); // first 5 bytes are initialized, remaining 5 are uninitialized
+  }
+
+  // Test __mempcpy_chk shadow propagation
+  {
+    char src[10];
+    char dest[10];
+    __msan_unpoison(dest, sizeof(dest));
+    __msan_poison(src, sizeof(src));
+    src[1] = 1;
+    src[2] = 2;
+    char *res = (char *)__mempcpy_chk(dest, src, 5, sizeof(dest));
+    assert(res == dest + 5);
+    assert(__msan_test_shadow(dest, 5) == 0);
+    assert(__msan_test_shadow(dest + 1, 4) == 2);
+    assert(__msan_test_shadow(dest + 5, 5) == -1);
+  }
+
+  return 0;
+}

>From 87907e7caa5efd8ec4988d6d70bd60f7f66607e8 Mon Sep 17 00:00:00 2001
From: Austin Schuh <austin.linux at gmail.com>
Date: Tue, 30 Jun 2026 12:33:18 -0700
Subject: [PATCH 2/5] Clang format

---
 compiler-rt/lib/msan/msan_interceptors.cpp | 43 +++++++++++-----------
 compiler-rt/test/msan/Linux/fortify.cpp    | 23 ++++++++----
 2 files changed, 36 insertions(+), 30 deletions(-)

diff --git a/compiler-rt/lib/msan/msan_interceptors.cpp b/compiler-rt/lib/msan/msan_interceptors.cpp
index 862ecd60adcf0..6a5565564bca7 100644
--- a/compiler-rt/lib/msan/msan_interceptors.cpp
+++ b/compiler-rt/lib/msan/msan_interceptors.cpp
@@ -64,13 +64,12 @@ DECLARE_REAL(void *, memcpy, void *dest, const void *src, SIZE_T n)
 DECLARE_REAL(void *, memset, void *dest, int c, SIZE_T n)
 
 #if SANITIZER_GLIBC
-DECLARE_REAL(void *, __memcpy_chk, void *dest, const void *src, SIZE_T n,
+DECLARE_REAL(void*, __memcpy_chk, void* dest, const void* src, SIZE_T n,
              SIZE_T dest_size)
-DECLARE_REAL(void *, __memmove_chk, void *dest, const void *src, SIZE_T n,
+DECLARE_REAL(void*, __memmove_chk, void* dest, const void* src, SIZE_T n,
              SIZE_T dest_size)
-DECLARE_REAL(void *, __memset_chk, void *dest, int c, SIZE_T n,
-             SIZE_T dest_size)
-DECLARE_REAL(void *, __mempcpy_chk, void *dest, const void *src, SIZE_T n,
+DECLARE_REAL(void*, __memset_chk, void* dest, int c, SIZE_T n, SIZE_T dest_size)
+DECLARE_REAL(void*, __mempcpy_chk, void* dest, const void* src, SIZE_T n,
              SIZE_T dest_size)
 #endif
 
@@ -170,7 +169,7 @@ INTERCEPTOR(void *, bcopy, const void *src, void *dest, SIZE_T n) {
 }
 
 #if SANITIZER_GLIBC
-INTERCEPTOR(void *, __memcpy_chk, void *dest, const void *src, SIZE_T n,
+INTERCEPTOR(void*, __memcpy_chk, void* dest, const void* src, SIZE_T n,
             SIZE_T dest_size) {
   if (!msan_inited)
     return internal_memcpy(dest, src, n);
@@ -178,17 +177,17 @@ INTERCEPTOR(void *, __memcpy_chk, void *dest, const void *src, SIZE_T n,
     return REAL(__memcpy_chk)(dest, src, n, dest_size);
   ENSURE_MSAN_INITED();
   GET_STORE_STACK_TRACE;
-  void *res = REAL(__memcpy_chk)(dest, src, n, dest_size);
+  void* res = REAL(__memcpy_chk)(dest, src, n, dest_size);
   CopyShadowAndOrigin(dest, src, n, &stack);
   return res;
 }
-#define MSAN_MAYBE_INTERCEPT___MEMCPY_CHK INTERCEPT_FUNCTION(__memcpy_chk)
+#  define MSAN_MAYBE_INTERCEPT___MEMCPY_CHK INTERCEPT_FUNCTION(__memcpy_chk)
 #else
-#define MSAN_MAYBE_INTERCEPT___MEMCPY_CHK
+#  define MSAN_MAYBE_INTERCEPT___MEMCPY_CHK
 #endif
 
 #if SANITIZER_GLIBC
-INTERCEPTOR(void *, __memmove_chk, void *dest, const void *src, SIZE_T n,
+INTERCEPTOR(void*, __memmove_chk, void* dest, const void* src, SIZE_T n,
             SIZE_T dest_size) {
   if (!msan_inited)
     return internal_memmove(dest, src, n);
@@ -196,50 +195,50 @@ INTERCEPTOR(void *, __memmove_chk, void *dest, const void *src, SIZE_T n,
     return REAL(__memmove_chk)(dest, src, n, dest_size);
   ENSURE_MSAN_INITED();
   GET_STORE_STACK_TRACE;
-  void *res = REAL(__memmove_chk)(dest, src, n, dest_size);
+  void* res = REAL(__memmove_chk)(dest, src, n, dest_size);
   MoveShadowAndOrigin(dest, src, n, &stack);
   return res;
 }
-#define MSAN_MAYBE_INTERCEPT___MEMMOVE_CHK INTERCEPT_FUNCTION(__memmove_chk)
+#  define MSAN_MAYBE_INTERCEPT___MEMMOVE_CHK INTERCEPT_FUNCTION(__memmove_chk)
 #else
-#define MSAN_MAYBE_INTERCEPT___MEMMOVE_CHK
+#  define MSAN_MAYBE_INTERCEPT___MEMMOVE_CHK
 #endif
 
 #if SANITIZER_GLIBC
-INTERCEPTOR(void *, __memset_chk, void *dest, int c, SIZE_T n,
+INTERCEPTOR(void*, __memset_chk, void* dest, int c, SIZE_T n,
             SIZE_T dest_size) {
   if (!msan_inited)
     return internal_memset(dest, c, n);
   if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
     return REAL(__memset_chk)(dest, c, n, dest_size);
   ENSURE_MSAN_INITED();
-  void *res = REAL(__memset_chk)(dest, c, n, dest_size);
+  void* res = REAL(__memset_chk)(dest, c, n, dest_size);
   __msan_unpoison(dest, n);
   return res;
 }
-#define MSAN_MAYBE_INTERCEPT___MEMSET_CHK INTERCEPT_FUNCTION(__memset_chk)
+#  define MSAN_MAYBE_INTERCEPT___MEMSET_CHK INTERCEPT_FUNCTION(__memset_chk)
 #else
-#define MSAN_MAYBE_INTERCEPT___MEMSET_CHK
+#  define MSAN_MAYBE_INTERCEPT___MEMSET_CHK
 #endif
 
 #if SANITIZER_GLIBC
-INTERCEPTOR(void *, __mempcpy_chk, void *dest, const void *src, SIZE_T n,
+INTERCEPTOR(void*, __mempcpy_chk, void* dest, const void* src, SIZE_T n,
             SIZE_T dest_size) {
   if (!msan_inited) {
     internal_memcpy(dest, src, n);
-    return (char *)dest + n;
+    return (char*)dest + n;
   }
   if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
     return REAL(__mempcpy_chk)(dest, src, n, dest_size);
   ENSURE_MSAN_INITED();
   GET_STORE_STACK_TRACE;
-  void *res = REAL(__mempcpy_chk)(dest, src, n, dest_size);
+  void* res = REAL(__mempcpy_chk)(dest, src, n, dest_size);
   CopyShadowAndOrigin(dest, src, n, &stack);
   return res;
 }
-#define MSAN_MAYBE_INTERCEPT___MEMPCPY_CHK INTERCEPT_FUNCTION(__mempcpy_chk)
+#  define MSAN_MAYBE_INTERCEPT___MEMPCPY_CHK INTERCEPT_FUNCTION(__mempcpy_chk)
 #else
-#define MSAN_MAYBE_INTERCEPT___MEMPCPY_CHK
+#  define MSAN_MAYBE_INTERCEPT___MEMPCPY_CHK
 #endif
 
 INTERCEPTOR(int, posix_memalign, void **memptr, SIZE_T alignment, SIZE_T size) {
diff --git a/compiler-rt/test/msan/Linux/fortify.cpp b/compiler-rt/test/msan/Linux/fortify.cpp
index 4568aa1827a94..041428655d847 100644
--- a/compiler-rt/test/msan/Linux/fortify.cpp
+++ b/compiler-rt/test/msan/Linux/fortify.cpp
@@ -4,13 +4,16 @@
 // REQUIRES: glibc
 
 #include <assert.h>
-#include <string.h>
 #include <sanitizer/msan_interface.h>
+#include <string.h>
 
-extern "C" void *__memcpy_chk(void *dest, const void *src, size_t len, size_t destlen);
-extern "C" void *__memmove_chk(void *dest, const void *src, size_t len, size_t destlen);
+extern "C" void *__memcpy_chk(void *dest, const void *src, size_t len,
+                              size_t destlen);
+extern "C" void *__memmove_chk(void *dest, const void *src, size_t len,
+                               size_t destlen);
 extern "C" void *__memset_chk(void *dest, int c, size_t len, size_t destlen);
-extern "C" void *__mempcpy_chk(void *dest, const void *src, size_t len, size_t destlen);
+extern "C" void *__mempcpy_chk(void *dest, const void *src, size_t len,
+                               size_t destlen);
 
 int main(int argc, char *argv[]) {
   // Test __memcpy_chk shadow propagation
@@ -22,9 +25,12 @@ int main(int argc, char *argv[]) {
     src[1] = 1;
     src[2] = 2;
     __memcpy_chk(dest, src, 5, sizeof(dest));
-    assert(__msan_test_shadow(dest, 5) == 0); // dest[0] is uninitialized
-    assert(__msan_test_shadow(dest + 1, 4) == 2); // dest[1], dest[2] are initialized, dest[3..4] are uninitialized
-    assert(__msan_test_shadow(dest + 5, 5) == -1); // dest[5..9] remain initialized
+    // dest[0] is uninitialized
+    assert(__msan_test_shadow(dest, 5) == 0);
+    // dest[1], dest[2] are initialized, dest[3..4] are uninitialized
+    assert(__msan_test_shadow(dest + 1, 4) == 2);
+    // dest[5..9] remain initialized
+    assert(__msan_test_shadow(dest + 5, 5) == -1);
   }
 
   // Test __memmove_chk shadow propagation
@@ -46,7 +52,8 @@ int main(int argc, char *argv[]) {
     char dest[10];
     __msan_poison(dest, sizeof(dest));
     __memset_chk(dest, 42, 5, sizeof(dest));
-    assert(__msan_test_shadow(dest, 10) == 5); // first 5 bytes are initialized, remaining 5 are uninitialized
+    // first 5 bytes are initialized, remaining 5 are uninitialized
+    assert(__msan_test_shadow(dest, 10) == 5);
   }
 
   // Test __mempcpy_chk shadow propagation

>From 23a328703506f3b81d724989478d48e2bc83e5b2 Mon Sep 17 00:00:00 2001
From: Austin Schuh <austin.linux at gmail.com>
Date: Wed, 1 Jul 2026 15:46:55 -0700
Subject: [PATCH 3/5] Addressed comments

---
 .../TestCases}/Linux/fortify.cpp              | 44 ++++++++++++++++---
 llvm/runtimes/CMakeLists.txt                  |  5 ++-
 2 files changed, 41 insertions(+), 8 deletions(-)
 rename compiler-rt/test/{msan => sanitizer_common/TestCases}/Linux/fortify.cpp (67%)

diff --git a/compiler-rt/test/msan/Linux/fortify.cpp b/compiler-rt/test/sanitizer_common/TestCases/Linux/fortify.cpp
similarity index 67%
rename from compiler-rt/test/msan/Linux/fortify.cpp
rename to compiler-rt/test/sanitizer_common/TestCases/Linux/fortify.cpp
index 041428655d847..381697f74cf98 100644
--- a/compiler-rt/test/msan/Linux/fortify.cpp
+++ b/compiler-rt/test/sanitizer_common/TestCases/Linux/fortify.cpp
@@ -1,12 +1,13 @@
-// RUN: %clangxx_msan -O0 %s -o %t && %run %t
-// RUN: %clangxx_msan -O2 %s -o %t && %run %t
+// RUN: %clangxx -O0 %s -o %t && %run %t
+// RUN: %clangxx -O2 %s -o %t && %run %t
 
 // REQUIRES: glibc
 
 #include <assert.h>
-#include <sanitizer/msan_interface.h>
 #include <string.h>
 
+#include "sanitizer_common/sanitizer_specific.h"
+
 extern "C" void *__memcpy_chk(void *dest, const void *src, size_t len,
                               size_t destlen);
 extern "C" void *__memmove_chk(void *dest, const void *src, size_t len,
@@ -16,59 +17,88 @@ extern "C" void *__mempcpy_chk(void *dest, const void *src, size_t len,
                                size_t destlen);
 
 int main(int argc, char *argv[]) {
-  // Test __memcpy_chk shadow propagation
+  // Test __memcpy_chk basic behavior & shadow propagation
   {
     char src[10];
     char dest[10];
+#if __has_feature(memory_sanitizer)
     __msan_unpoison(dest, sizeof(dest));
     __msan_poison(src, sizeof(src));
+#endif
     src[1] = 1;
     src[2] = 2;
     __memcpy_chk(dest, src, 5, sizeof(dest));
+#if __has_feature(memory_sanitizer)
     // dest[0] is uninitialized
     assert(__msan_test_shadow(dest, 5) == 0);
     // dest[1], dest[2] are initialized, dest[3..4] are uninitialized
     assert(__msan_test_shadow(dest + 1, 4) == 2);
     // dest[5..9] remain initialized
     assert(__msan_test_shadow(dest + 5, 5) == -1);
+#else
+    assert(dest[1] == 1);
+    assert(dest[2] == 2);
+#endif
   }
 
-  // Test __memmove_chk shadow propagation
+  // Test __memmove_chk basic behavior & shadow propagation
   {
     char src[10];
     char dest[10];
+#if __has_feature(memory_sanitizer)
     __msan_unpoison(dest, sizeof(dest));
     __msan_poison(src, sizeof(src));
+#endif
     src[1] = 1;
     src[2] = 2;
     __memmove_chk(dest, src, 5, sizeof(dest));
+#if __has_feature(memory_sanitizer)
     assert(__msan_test_shadow(dest, 5) == 0);
     assert(__msan_test_shadow(dest + 1, 4) == 2);
     assert(__msan_test_shadow(dest + 5, 5) == -1);
+#else
+    assert(dest[1] == 1);
+    assert(dest[2] == 2);
+#endif
   }
 
-  // Test __memset_chk shadow propagation
+  // Test __memset_chk basic behavior & shadow propagation
   {
     char dest[10];
+#if __has_feature(memory_sanitizer)
     __msan_poison(dest, sizeof(dest));
+#endif
     __memset_chk(dest, 42, 5, sizeof(dest));
+#if __has_feature(memory_sanitizer)
     // first 5 bytes are initialized, remaining 5 are uninitialized
     assert(__msan_test_shadow(dest, 10) == 5);
+#else
+    for (int i = 0; i < 5; ++i) {
+      assert(dest[i] == 42);
+    }
+#endif
   }
 
-  // Test __mempcpy_chk shadow propagation
+  // Test __mempcpy_chk basic behavior & shadow propagation
   {
     char src[10];
     char dest[10];
+#if __has_feature(memory_sanitizer)
     __msan_unpoison(dest, sizeof(dest));
     __msan_poison(src, sizeof(src));
+#endif
     src[1] = 1;
     src[2] = 2;
     char *res = (char *)__mempcpy_chk(dest, src, 5, sizeof(dest));
     assert(res == dest + 5);
+#if __has_feature(memory_sanitizer)
     assert(__msan_test_shadow(dest, 5) == 0);
     assert(__msan_test_shadow(dest + 1, 4) == 2);
     assert(__msan_test_shadow(dest + 5, 5) == -1);
+#else
+    assert(dest[1] == 1);
+    assert(dest[2] == 2);
+#endif
   }
 
   return 0;
diff --git a/llvm/runtimes/CMakeLists.txt b/llvm/runtimes/CMakeLists.txt
index 501ea55a327c7..529253542785a 100644
--- a/llvm/runtimes/CMakeLists.txt
+++ b/llvm/runtimes/CMakeLists.txt
@@ -327,7 +327,10 @@ function(runtime_default_target)
 
   if(LLVM_INCLUDE_TESTS)
     set_property(GLOBAL APPEND PROPERTY LLVM_ALL_LIT_TESTSUITES "@${LLVM_BINARY_DIR}/runtimes/runtimes-bins/lit.tests")
-    list(APPEND test_targets runtimes-test-depends check-runtimes check-builtins)
+    list(APPEND test_targets runtimes-test-depends check-runtimes)
+    if(NOT "compiler-rt" IN_LIST LLVM_ENABLE_PROJECTS)
+      list(APPEND test_targets check-builtins)
+    endif()
     list(APPEND ARG_CMAKE_ARGS "-DCOMPILER_RT_TEST_EXTERNAL_BUILTINS=ON")
   endif()
 

>From a1e0d6f30a73a4d3ec265436becdd02ae5b56185 Mon Sep 17 00:00:00 2001
From: Austin Schuh <austin.linux at gmail.com>
Date: Tue, 14 Jul 2026 18:38:59 -0700
Subject: [PATCH 4/5] Revert CMakeLists change

---
 llvm/runtimes/CMakeLists.txt | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/llvm/runtimes/CMakeLists.txt b/llvm/runtimes/CMakeLists.txt
index 529253542785a..501ea55a327c7 100644
--- a/llvm/runtimes/CMakeLists.txt
+++ b/llvm/runtimes/CMakeLists.txt
@@ -327,10 +327,7 @@ function(runtime_default_target)
 
   if(LLVM_INCLUDE_TESTS)
     set_property(GLOBAL APPEND PROPERTY LLVM_ALL_LIT_TESTSUITES "@${LLVM_BINARY_DIR}/runtimes/runtimes-bins/lit.tests")
-    list(APPEND test_targets runtimes-test-depends check-runtimes)
-    if(NOT "compiler-rt" IN_LIST LLVM_ENABLE_PROJECTS)
-      list(APPEND test_targets check-builtins)
-    endif()
+    list(APPEND test_targets runtimes-test-depends check-runtimes check-builtins)
     list(APPEND ARG_CMAKE_ARGS "-DCOMPILER_RT_TEST_EXTERNAL_BUILTINS=ON")
   endif()
 

>From 9039c25bca4d79f1cbe72cbfee979cc2aac94097 Mon Sep 17 00:00:00 2001
From: Austin Schuh <austin.linux at gmail.com>
Date: Fri, 17 Jul 2026 03:01:34 -0700
Subject: [PATCH 5/5] Switch to COMMON_INTERCEPTOR_MEM*

---
 compiler-rt/lib/msan/msan_interceptors.cpp    | 145 +++++++-----------
 ...izer_common_interceptors_memintrinsics.inc |  60 ++++++++
 .../sanitizer_platform_interceptors.h         |   4 +
 3 files changed, 122 insertions(+), 87 deletions(-)

diff --git a/compiler-rt/lib/msan/msan_interceptors.cpp b/compiler-rt/lib/msan/msan_interceptors.cpp
index 6a5565564bca7..09a8af62d7a76 100644
--- a/compiler-rt/lib/msan/msan_interceptors.cpp
+++ b/compiler-rt/lib/msan/msan_interceptors.cpp
@@ -63,16 +63,6 @@ DECLARE_REAL(SIZE_T, strnlen, const char *s, SIZE_T maxlen)
 DECLARE_REAL(void *, memcpy, void *dest, const void *src, SIZE_T n)
 DECLARE_REAL(void *, memset, void *dest, int c, SIZE_T n)
 
-#if SANITIZER_GLIBC
-DECLARE_REAL(void*, __memcpy_chk, void* dest, const void* src, SIZE_T n,
-             SIZE_T dest_size)
-DECLARE_REAL(void*, __memmove_chk, void* dest, const void* src, SIZE_T n,
-             SIZE_T dest_size)
-DECLARE_REAL(void*, __memset_chk, void* dest, int c, SIZE_T n, SIZE_T dest_size)
-DECLARE_REAL(void*, __mempcpy_chk, void* dest, const void* src, SIZE_T n,
-             SIZE_T dest_size)
-#endif
-
 // True if this is a nested interceptor.
 static THREADLOCAL int in_interceptor_scope;
 
@@ -168,79 +158,6 @@ INTERCEPTOR(void *, bcopy, const void *src, void *dest, SIZE_T n) {
   return __msan_memmove(dest, src, n);
 }
 
-#if SANITIZER_GLIBC
-INTERCEPTOR(void*, __memcpy_chk, void* dest, const void* src, SIZE_T n,
-            SIZE_T dest_size) {
-  if (!msan_inited)
-    return internal_memcpy(dest, src, n);
-  if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
-    return REAL(__memcpy_chk)(dest, src, n, dest_size);
-  ENSURE_MSAN_INITED();
-  GET_STORE_STACK_TRACE;
-  void* res = REAL(__memcpy_chk)(dest, src, n, dest_size);
-  CopyShadowAndOrigin(dest, src, n, &stack);
-  return res;
-}
-#  define MSAN_MAYBE_INTERCEPT___MEMCPY_CHK INTERCEPT_FUNCTION(__memcpy_chk)
-#else
-#  define MSAN_MAYBE_INTERCEPT___MEMCPY_CHK
-#endif
-
-#if SANITIZER_GLIBC
-INTERCEPTOR(void*, __memmove_chk, void* dest, const void* src, SIZE_T n,
-            SIZE_T dest_size) {
-  if (!msan_inited)
-    return internal_memmove(dest, src, n);
-  if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
-    return REAL(__memmove_chk)(dest, src, n, dest_size);
-  ENSURE_MSAN_INITED();
-  GET_STORE_STACK_TRACE;
-  void* res = REAL(__memmove_chk)(dest, src, n, dest_size);
-  MoveShadowAndOrigin(dest, src, n, &stack);
-  return res;
-}
-#  define MSAN_MAYBE_INTERCEPT___MEMMOVE_CHK INTERCEPT_FUNCTION(__memmove_chk)
-#else
-#  define MSAN_MAYBE_INTERCEPT___MEMMOVE_CHK
-#endif
-
-#if SANITIZER_GLIBC
-INTERCEPTOR(void*, __memset_chk, void* dest, int c, SIZE_T n,
-            SIZE_T dest_size) {
-  if (!msan_inited)
-    return internal_memset(dest, c, n);
-  if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
-    return REAL(__memset_chk)(dest, c, n, dest_size);
-  ENSURE_MSAN_INITED();
-  void* res = REAL(__memset_chk)(dest, c, n, dest_size);
-  __msan_unpoison(dest, n);
-  return res;
-}
-#  define MSAN_MAYBE_INTERCEPT___MEMSET_CHK INTERCEPT_FUNCTION(__memset_chk)
-#else
-#  define MSAN_MAYBE_INTERCEPT___MEMSET_CHK
-#endif
-
-#if SANITIZER_GLIBC
-INTERCEPTOR(void*, __mempcpy_chk, void* dest, const void* src, SIZE_T n,
-            SIZE_T dest_size) {
-  if (!msan_inited) {
-    internal_memcpy(dest, src, n);
-    return (char*)dest + n;
-  }
-  if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
-    return REAL(__mempcpy_chk)(dest, src, n, dest_size);
-  ENSURE_MSAN_INITED();
-  GET_STORE_STACK_TRACE;
-  void* res = REAL(__mempcpy_chk)(dest, src, n, dest_size);
-  CopyShadowAndOrigin(dest, src, n, &stack);
-  return res;
-}
-#  define MSAN_MAYBE_INTERCEPT___MEMPCPY_CHK INTERCEPT_FUNCTION(__mempcpy_chk)
-#else
-#  define MSAN_MAYBE_INTERCEPT___MEMPCPY_CHK
-#endif
-
 INTERCEPTOR(int, posix_memalign, void **memptr, SIZE_T alignment, SIZE_T size) {
   GET_MALLOC_STACK_TRACE;
   CHECK_NE(memptr, 0);
@@ -1586,6 +1503,64 @@ int OnExit() {
     return __msan_memcpy(to, from, size);                   \
   }
 
+// The "_chk" variants delegate to the corresponding REAL(__*_chk) function so
+// that glibc's _FORTIFY_SOURCE overflow checks are preserved, while still
+// propagating shadow like the plain memory intrinsics above. These are only
+// expanded by the glibc-gated interceptors in the memintrinsics .inc file.
+#define COMMON_INTERCEPTOR_MEMSET_CHK_IMPL(ctx, dest, c, n, dest_size) \
+  {                                                                    \
+    (void)ctx;                                                         \
+    if (!msan_inited)                                                  \
+      return internal_memset(dest, c, n);                              \
+    if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())     \
+      return REAL(__memset_chk)(dest, c, n, dest_size);                \
+    ENSURE_MSAN_INITED();                                              \
+    void *res = REAL(__memset_chk)(dest, c, n, dest_size);             \
+    __msan_unpoison(dest, n);                                          \
+    return res;                                                        \
+  }
+#define COMMON_INTERCEPTOR_MEMMOVE_CHK_IMPL(ctx, dest, src, n, dest_size) \
+  {                                                                       \
+    (void)ctx;                                                            \
+    if (!msan_inited)                                                     \
+      return internal_memmove(dest, src, n);                              \
+    if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())        \
+      return REAL(__memmove_chk)(dest, src, n, dest_size);                \
+    ENSURE_MSAN_INITED();                                                 \
+    GET_STORE_STACK_TRACE;                                                \
+    void *res = REAL(__memmove_chk)(dest, src, n, dest_size);             \
+    MoveShadowAndOrigin(dest, src, n, &stack);                            \
+    return res;                                                           \
+  }
+#define COMMON_INTERCEPTOR_MEMCPY_CHK_IMPL(ctx, dest, src, n, dest_size) \
+  {                                                                      \
+    (void)ctx;                                                           \
+    if (!msan_inited)                                                    \
+      return internal_memcpy(dest, src, n);                              \
+    if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())       \
+      return REAL(__memcpy_chk)(dest, src, n, dest_size);                \
+    ENSURE_MSAN_INITED();                                                \
+    GET_STORE_STACK_TRACE;                                               \
+    void *res = REAL(__memcpy_chk)(dest, src, n, dest_size);             \
+    CopyShadowAndOrigin(dest, src, n, &stack);                           \
+    return res;                                                          \
+  }
+#define COMMON_INTERCEPTOR_MEMPCPY_CHK_IMPL(ctx, dest, src, n, dest_size) \
+  {                                                                       \
+    (void)ctx;                                                            \
+    if (!msan_inited) {                                                   \
+      internal_memcpy(dest, src, n);                                      \
+      return (char *)dest + n;                                            \
+    }                                                                     \
+    if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())        \
+      return REAL(__mempcpy_chk)(dest, src, n, dest_size);                \
+    ENSURE_MSAN_INITED();                                                 \
+    GET_STORE_STACK_TRACE;                                                \
+    void *res = REAL(__mempcpy_chk)(dest, src, n, dest_size);             \
+    CopyShadowAndOrigin(dest, src, n, &stack);                            \
+    return res;                                                           \
+  }
+
 #define COMMON_INTERCEPTOR_COPY_STRING(ctx, to, from, size) \
   do {                                                      \
     GET_STORE_STACK_TRACE;                                  \
@@ -1905,10 +1880,6 @@ void InitializeInterceptors() {
   MSAN_MAYBE_INTERCEPT_FREAD_UNLOCKED;
   INTERCEPT_FUNCTION(memccpy);
   MSAN_MAYBE_INTERCEPT_MEMPCPY;
-  MSAN_MAYBE_INTERCEPT___MEMCPY_CHK;
-  MSAN_MAYBE_INTERCEPT___MEMMOVE_CHK;
-  MSAN_MAYBE_INTERCEPT___MEMSET_CHK;
-  MSAN_MAYBE_INTERCEPT___MEMPCPY_CHK;
   INTERCEPT_FUNCTION(bcopy);
   INTERCEPT_FUNCTION(wmemset);
   INTERCEPT_FUNCTION(wmemcpy);
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc
index 0b6731c89950b..de31dea974613 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc
@@ -22,6 +22,14 @@
 //   COMMON_INTERCEPTOR_MEMMOVE_IMPL
 //   COMMON_INTERCEPTOR_MEMCPY_IMPL
 //   COMMON_INTERCEPTOR_NOTHING_IS_INITIALIZED
+//
+// A tool may additionally define any of the following macros to intercept the
+// fortified _chk variants; the corresponding interceptor is only emitted when
+// the matching macro is defined:
+//   COMMON_INTERCEPTOR_MEMSET_CHK_IMPL
+//   COMMON_INTERCEPTOR_MEMMOVE_CHK_IMPL
+//   COMMON_INTERCEPTOR_MEMCPY_CHK_IMPL
+//   COMMON_INTERCEPTOR_MEMPCPY_CHK_IMPL
 //===----------------------------------------------------------------------===//
 
 #ifdef SANITIZER_REDEFINE_BUILTINS_H
@@ -233,6 +241,54 @@ INTERCEPTOR(void *, bzero, void *block, usize size) {
 #define INIT_BZERO
 #endif  // SANITIZER_INTERCEPT_BZERO
 
+#if SANITIZER_INTERCEPT___MEMSET_CHK && \
+    defined(COMMON_INTERCEPTOR_MEMSET_CHK_IMPL)
+INTERCEPTOR(void *, __memset_chk, void *dst, int v, usize size,
+            usize dst_size) {
+  void *ctx;
+  COMMON_INTERCEPTOR_MEMSET_CHK_IMPL(ctx, dst, v, size, dst_size);
+}
+#define INIT___MEMSET_CHK COMMON_INTERCEPT_FUNCTION(__memset_chk);
+#else
+#define INIT___MEMSET_CHK
+#endif  // SANITIZER_INTERCEPT___MEMSET_CHK
+
+#if SANITIZER_INTERCEPT___MEMMOVE_CHK && \
+    defined(COMMON_INTERCEPTOR_MEMMOVE_CHK_IMPL)
+INTERCEPTOR(void *, __memmove_chk, void *dst, const void *src, usize size,
+            usize dst_size) {
+  void *ctx;
+  COMMON_INTERCEPTOR_MEMMOVE_CHK_IMPL(ctx, dst, src, size, dst_size);
+}
+#define INIT___MEMMOVE_CHK COMMON_INTERCEPT_FUNCTION(__memmove_chk);
+#else
+#define INIT___MEMMOVE_CHK
+#endif  // SANITIZER_INTERCEPT___MEMMOVE_CHK
+
+#if SANITIZER_INTERCEPT___MEMCPY_CHK && \
+    defined(COMMON_INTERCEPTOR_MEMCPY_CHK_IMPL)
+INTERCEPTOR(void *, __memcpy_chk, void *dst, const void *src, usize size,
+            usize dst_size) {
+  void *ctx;
+  COMMON_INTERCEPTOR_MEMCPY_CHK_IMPL(ctx, dst, src, size, dst_size);
+}
+#define INIT___MEMCPY_CHK COMMON_INTERCEPT_FUNCTION(__memcpy_chk);
+#else
+#define INIT___MEMCPY_CHK
+#endif  // SANITIZER_INTERCEPT___MEMCPY_CHK
+
+#if SANITIZER_INTERCEPT___MEMPCPY_CHK && \
+    defined(COMMON_INTERCEPTOR_MEMPCPY_CHK_IMPL)
+INTERCEPTOR(void *, __mempcpy_chk, void *dst, const void *src, usize size,
+            usize dst_size) {
+  void *ctx;
+  COMMON_INTERCEPTOR_MEMPCPY_CHK_IMPL(ctx, dst, src, size, dst_size);
+}
+#define INIT___MEMPCPY_CHK COMMON_INTERCEPT_FUNCTION(__mempcpy_chk);
+#else
+#define INIT___MEMPCPY_CHK
+#endif  // SANITIZER_INTERCEPT___MEMPCPY_CHK
+
 namespace __sanitizer {
 // This does not need to be called if InitializeCommonInterceptors() is called.
 void InitializeMemintrinsicInterceptors() {
@@ -242,5 +298,9 @@ void InitializeMemintrinsicInterceptors() {
   INIT_AEABI_MEM;
   INIT___BZERO;
   INIT_BZERO;
+  INIT___MEMSET_CHK;
+  INIT___MEMMOVE_CHK;
+  INIT___MEMCPY_CHK;
+  INIT___MEMPCPY_CHK;
 }
 }  // namespace __sanitizer
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
index 9f6fc4e9b9bfa..c72a7e4990be9 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
@@ -181,6 +181,10 @@ SANITIZER_WEAK_IMPORT void *aligned_alloc(__sanitizer::usize __alignment,
 #define SANITIZER_INTERCEPT_MEMSET 1
 #define SANITIZER_INTERCEPT_MEMMOVE 1
 #define SANITIZER_INTERCEPT_MEMCPY 1
+#define SANITIZER_INTERCEPT___MEMCPY_CHK SI_GLIBC
+#define SANITIZER_INTERCEPT___MEMMOVE_CHK SI_GLIBC
+#define SANITIZER_INTERCEPT___MEMSET_CHK SI_GLIBC
+#define SANITIZER_INTERCEPT___MEMPCPY_CHK SI_GLIBC
 #define SANITIZER_INTERCEPT_MEMCMP SI_NOT_FUCHSIA
 #define SANITIZER_INTERCEPT_BCMP \
   SANITIZER_INTERCEPT_MEMCMP &&  \



More information about the llvm-commits mailing list