[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 22:06:52 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/9] [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/9] 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/9] 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/9] 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/9] 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 &&  \

>From 272cf8ba2e6f01644db0da1fa5302734e22026ea Mon Sep 17 00:00:00 2001
From: Austin Schuh <austin.linux at gmail.com>
Date: Fri, 17 Jul 2026 03:17:35 -0700
Subject: [PATCH 6/9] Clang format

---
 compiler-rt/lib/msan/msan_interceptors.cpp    | 10 +++---
 ...izer_common_interceptors_memintrinsics.inc | 33 +++++++++----------
 2 files changed, 21 insertions(+), 22 deletions(-)

diff --git a/compiler-rt/lib/msan/msan_interceptors.cpp b/compiler-rt/lib/msan/msan_interceptors.cpp
index 09a8af62d7a76..0e9be2d303edf 100644
--- a/compiler-rt/lib/msan/msan_interceptors.cpp
+++ b/compiler-rt/lib/msan/msan_interceptors.cpp
@@ -1515,7 +1515,7 @@ int OnExit() {
     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;                                                        \
   }
@@ -1528,7 +1528,7 @@ int OnExit() {
       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;                                                           \
   }
@@ -1541,7 +1541,7 @@ int OnExit() {
       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;                                                          \
   }
@@ -1550,13 +1550,13 @@ int OnExit() {
     (void)ctx;                                                            \
     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;                                                           \
   }
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 de31dea974613..caa48ded1934f 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc
@@ -243,50 +243,49 @@ INTERCEPTOR(void *, bzero, void *block, usize size) {
 
 #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;
+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);
+#  define INIT___MEMSET_CHK COMMON_INTERCEPT_FUNCTION(__memset_chk);
 #else
-#define INIT___MEMSET_CHK
+#  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,
+INTERCEPTOR(void*, __memmove_chk, void* dst, const void* src, usize size,
             usize dst_size) {
-  void *ctx;
+  void* ctx;
   COMMON_INTERCEPTOR_MEMMOVE_CHK_IMPL(ctx, dst, src, size, dst_size);
 }
-#define INIT___MEMMOVE_CHK COMMON_INTERCEPT_FUNCTION(__memmove_chk);
+#  define INIT___MEMMOVE_CHK COMMON_INTERCEPT_FUNCTION(__memmove_chk);
 #else
-#define INIT___MEMMOVE_CHK
+#  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,
+INTERCEPTOR(void*, __memcpy_chk, void* dst, const void* src, usize size,
             usize dst_size) {
-  void *ctx;
+  void* ctx;
   COMMON_INTERCEPTOR_MEMCPY_CHK_IMPL(ctx, dst, src, size, dst_size);
 }
-#define INIT___MEMCPY_CHK COMMON_INTERCEPT_FUNCTION(__memcpy_chk);
+#  define INIT___MEMCPY_CHK COMMON_INTERCEPT_FUNCTION(__memcpy_chk);
 #else
-#define INIT___MEMCPY_CHK
+#  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,
+INTERCEPTOR(void*, __mempcpy_chk, void* dst, const void* src, usize size,
             usize dst_size) {
-  void *ctx;
+  void* ctx;
   COMMON_INTERCEPTOR_MEMPCPY_CHK_IMPL(ctx, dst, src, size, dst_size);
 }
-#define INIT___MEMPCPY_CHK COMMON_INTERCEPT_FUNCTION(__mempcpy_chk);
+#  define INIT___MEMPCPY_CHK COMMON_INTERCEPT_FUNCTION(__mempcpy_chk);
 #else
-#define INIT___MEMPCPY_CHK
+#  define INIT___MEMPCPY_CHK
 #endif  // SANITIZER_INTERCEPT___MEMPCPY_CHK
 
 namespace __sanitizer {

>From 6ac364fb5ace99df2549b720b40cbb8e13c0ce04 Mon Sep 17 00:00:00 2001
From: Austin Schuh <austin.linux at gmail.com>
Date: Fri, 17 Jul 2026 13:33:18 -0700
Subject: [PATCH 7/9] Split out __msan_memset_chk

---
 compiler-rt/lib/msan/msan_interceptors.cpp | 104 +++++++++++++--------
 1 file changed, 63 insertions(+), 41 deletions(-)

diff --git a/compiler-rt/lib/msan/msan_interceptors.cpp b/compiler-rt/lib/msan/msan_interceptors.cpp
index 0e9be2d303edf..45db510933880 100644
--- a/compiler-rt/lib/msan/msan_interceptors.cpp
+++ b/compiler-rt/lib/msan/msan_interceptors.cpp
@@ -1503,62 +1503,29 @@ 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.
+// The "_chk" variants forward to dedicated __msan_*_chk helpers (defined below)
+// that mirror the plain __msan_mem* handlers while calling the fortified
+// REAL(__*_chk). 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;                                                        \
+    return __msan_memset_chk(dest, c, n, dest_size);                   \
   }
 #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;                                                           \
+    return __msan_memmove_chk(dest, src, n, dest_size);                   \
   }
 #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;                                                          \
+    return __msan_memcpy_chk(dest, src, n, dest_size);                   \
   }
 #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;                                                           \
+    return (char*)__msan_memcpy_chk(dest, src, n, dest_size) + n;         \
   }
 
 #define COMMON_INTERCEPTOR_COPY_STRING(ctx, to, from, size) \
@@ -1574,6 +1541,18 @@ int OnExit() {
     return mmap_interceptor(REAL(mmap), addr, sz, prot, flags, fd, off);       \
   } while (false)
 
+#if SANITIZER_GLIBC
+// Forward declarations for the fortified "_chk" helpers used by the
+// memintrinsic interceptors below. They are defined after the interceptors that
+// provide the REAL(__*_chk) pointers, and are intentionally not part of the
+// public MSan interface (hence not declared in the interface header).
+static void* __msan_memset_chk(void* dest, int c, SIZE_T n, SIZE_T dest_size);
+static void* __msan_memmove_chk(void* dest, const void* src, SIZE_T n,
+                                SIZE_T dest_size);
+static void* __msan_memcpy_chk(void* dest, const void* src, SIZE_T n,
+                               SIZE_T dest_size);
+#endif  // SANITIZER_GLIBC
+
 #include "sanitizer_common/sanitizer_platform_interceptors.h"
 #include "sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc"
 #include "sanitizer_common/sanitizer_common_interceptors.inc"
@@ -1840,6 +1819,49 @@ void *__msan_memmove(void *dest, const void *src, SIZE_T n) {
   return res;
 }
 
+#if SANITIZER_GLIBC
+// Fortified "_chk" helpers. These mirror the plain __msan_mem* handlers above
+// but call the corresponding REAL(__*_chk) so that glibc's _FORTIFY_SOURCE
+// overflow checks are preserved. They have internal linkage and are not part of
+// the MSan interface.
+static void* __msan_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)
+    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;
+}
+
+static void* __msan_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)
+    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;
+}
+
+static void* __msan_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;
+}
+#endif  // SANITIZER_GLIBC
+
 void __msan_unpoison_string(const char* s) {
   if (!MEM_IS_APP(s)) return;
   __msan_unpoison(s, internal_strlen(s) + 1);

>From baa330414b78fbf4392258592ad8c8fa661efae1 Mon Sep 17 00:00:00 2001
From: Austin Schuh <austin.linux at gmail.com>
Date: Fri, 17 Jul 2026 22:06:17 -0700
Subject: [PATCH 8/9] Add fallback definitions

---
 compiler-rt/lib/msan/msan_interceptors.cpp    |  6 +--
 ...izer_common_interceptors_memintrinsics.inc | 44 +++++++++++++++----
 2 files changed, 38 insertions(+), 12 deletions(-)

diff --git a/compiler-rt/lib/msan/msan_interceptors.cpp b/compiler-rt/lib/msan/msan_interceptors.cpp
index 45db510933880..8d4db30cd06d6 100644
--- a/compiler-rt/lib/msan/msan_interceptors.cpp
+++ b/compiler-rt/lib/msan/msan_interceptors.cpp
@@ -1826,7 +1826,7 @@ void *__msan_memmove(void *dest, const void *src, SIZE_T n) {
 // the MSan interface.
 static void* __msan_memset_chk(void* dest, int c, SIZE_T n, SIZE_T dest_size) {
   if (!msan_inited)
-    return internal_memset(dest, c, n);
+    return internal_memset(dest, c, Min(n, dest_size));
   if (msan_init_is_running)
     return REAL(__memset_chk)(dest, c, n, dest_size);
   ENSURE_MSAN_INITED();
@@ -1838,7 +1838,7 @@ static void* __msan_memset_chk(void* dest, int c, SIZE_T n, SIZE_T dest_size) {
 static void* __msan_memmove_chk(void* dest, const void* src, SIZE_T n,
                                 SIZE_T dest_size) {
   if (!msan_inited)
-    return internal_memmove(dest, src, n);
+    return internal_memmove(dest, src, Min(n, dest_size));
   if (msan_init_is_running)
     return REAL(__memmove_chk)(dest, src, n, dest_size);
   ENSURE_MSAN_INITED();
@@ -1851,7 +1851,7 @@ static void* __msan_memmove_chk(void* dest, const void* src, SIZE_T n,
 static void* __msan_memcpy_chk(void* dest, const void* src, SIZE_T n,
                                SIZE_T dest_size) {
   if (!msan_inited)
-    return internal_memcpy(dest, src, n);
+    return internal_memcpy(dest, src, Min(n, dest_size));
   if (msan_init_is_running || __msan::IsInSymbolizerOrUnwider())
     return REAL(__memcpy_chk)(dest, src, n, dest_size);
   ENSURE_MSAN_INITED();
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 caa48ded1934f..45cbab3c8ce57 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc
@@ -23,12 +23,13 @@
 //   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:
+// The fortified _chk variants below reuse the macros above by default (a tool
+// may override the following to customize their behavior):
 //   COMMON_INTERCEPTOR_MEMSET_CHK_IMPL
 //   COMMON_INTERCEPTOR_MEMMOVE_CHK_IMPL
 //   COMMON_INTERCEPTOR_MEMCPY_CHK_IMPL
+// There is no common plain "mempcpy" interceptor to fall back on, so the
+// __mempcpy_chk interceptor is only emitted when a tool defines:
 //   COMMON_INTERCEPTOR_MEMPCPY_CHK_IMPL
 //===----------------------------------------------------------------------===//
 
@@ -91,6 +92,34 @@
   }
 #endif
 
+// By default the fortified _chk variants behave like their plain counterparts;
+// the fortification bound (dst_size) is unused because the tool's own bounds
+// checking already covers the access. A tool may override these to preserve the
+// libc _FORTIFY_SOURCE semantics (e.g. by calling REAL(__memset_chk)).
+#ifndef COMMON_INTERCEPTOR_MEMSET_CHK_IMPL
+#define COMMON_INTERCEPTOR_MEMSET_CHK_IMPL(ctx, dst, v, size, dst_size) \
+  {                                                                     \
+    (void)dst_size;                                                     \
+    COMMON_INTERCEPTOR_MEMSET_IMPL(ctx, dst, v, size);                  \
+  }
+#endif
+
+#ifndef COMMON_INTERCEPTOR_MEMMOVE_CHK_IMPL
+#define COMMON_INTERCEPTOR_MEMMOVE_CHK_IMPL(ctx, dst, src, size, dst_size) \
+  {                                                                        \
+    (void)dst_size;                                                        \
+    COMMON_INTERCEPTOR_MEMMOVE_IMPL(ctx, dst, src, size);                  \
+  }
+#endif
+
+#ifndef COMMON_INTERCEPTOR_MEMCPY_CHK_IMPL
+#define COMMON_INTERCEPTOR_MEMCPY_CHK_IMPL(ctx, dst, src, size, dst_size) \
+  {                                                                       \
+    (void)dst_size;                                                       \
+    COMMON_INTERCEPTOR_MEMCPY_IMPL(ctx, dst, src, size);                  \
+  }
+#endif
+
 #if SANITIZER_INTERCEPT_MEMSET
 INTERCEPTOR(void *, memset, void *dst, int v, usize size) {
   void *ctx;
@@ -241,8 +270,7 @@ 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)
+#if SANITIZER_INTERCEPT___MEMSET_CHK
 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);
@@ -252,8 +280,7 @@ INTERCEPTOR(void*, __memset_chk, void* dst, int v, usize size, usize dst_size) {
 #  define INIT___MEMSET_CHK
 #endif  // SANITIZER_INTERCEPT___MEMSET_CHK
 
-#if SANITIZER_INTERCEPT___MEMMOVE_CHK && \
-    defined(COMMON_INTERCEPTOR_MEMMOVE_CHK_IMPL)
+#if SANITIZER_INTERCEPT___MEMMOVE_CHK
 INTERCEPTOR(void*, __memmove_chk, void* dst, const void* src, usize size,
             usize dst_size) {
   void* ctx;
@@ -264,8 +291,7 @@ INTERCEPTOR(void*, __memmove_chk, void* dst, const void* src, usize size,
 #  define INIT___MEMMOVE_CHK
 #endif  // SANITIZER_INTERCEPT___MEMMOVE_CHK
 
-#if SANITIZER_INTERCEPT___MEMCPY_CHK && \
-    defined(COMMON_INTERCEPTOR_MEMCPY_CHK_IMPL)
+#if SANITIZER_INTERCEPT___MEMCPY_CHK
 INTERCEPTOR(void*, __memcpy_chk, void* dst, const void* src, usize size,
             usize dst_size) {
   void* ctx;

>From c774e19624f4394b165d296e8a44024f1feb5c9c Mon Sep 17 00:00:00 2001
From: Austin Schuh <austin.linux at gmail.com>
Date: Fri, 17 Jul 2026 22:06:34 -0700
Subject: [PATCH 9/9] Extend _chk variants to more platforms

---
 .../sanitizer_platform_interceptors.h            | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
index c72a7e4990be9..f5229899d3f28 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_platform_interceptors.h
@@ -181,10 +181,18 @@ 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
+// The fortified _chk variants are provided by:
+//   - glibc
+//   - Bionic (Android)
+//   - Apple libc (macOS/iOS)
+//   - FreeBSD libc
+//   - MinGW/Windows (via GCC's libssp, linked with -lssp)
+#define SANITIZER_FORTIFY_CHK \
+  (SI_GLIBC || SI_ANDROID || SI_MAC || SI_FREEBSD || SI_WINDOWS)
+#define SANITIZER_INTERCEPT___MEMCPY_CHK SANITIZER_FORTIFY_CHK
+#define SANITIZER_INTERCEPT___MEMMOVE_CHK SANITIZER_FORTIFY_CHK
+#define SANITIZER_INTERCEPT___MEMSET_CHK SANITIZER_FORTIFY_CHK
+#define SANITIZER_INTERCEPT___MEMPCPY_CHK SANITIZER_FORTIFY_CHK
 #define SANITIZER_INTERCEPT_MEMCMP SI_NOT_FUCHSIA
 #define SANITIZER_INTERCEPT_BCMP \
   SANITIZER_INTERCEPT_MEMCMP &&  \



More information about the llvm-commits mailing list