[compiler-rt] [msan] Add interceptors for fortified memory functions (__memcpy_chk, etc.) (PR #206702)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 30 03:39:46 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-compiler-rt-sanitizer
Author: Austin Schuh (AustinSchuh)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/206702.diff
2 Files Affected:
- (modified) compiler-rt/lib/msan/msan_interceptors.cpp (+88)
- (added) compiler-rt/test/msan/Linux/fortify.cpp (+68)
``````````diff
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;
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/206702
More information about the llvm-commits
mailing list