[compiler-rt] 73a7638 - [compiler-rt][sanitizer_common] Size readlink/getsockopt post-hook unpoison by bytes written (#209209)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 22 21:49:59 PDT 2026
Author: Patrik Dokoupil
Date: 2026-07-22T21:49:55-07:00
New Revision: 73a7638c0d832182dd7df912115d80be87567cb4
URL: https://github.com/llvm/llvm-project/commit/73a7638c0d832182dd7df912115d80be87567cb4
DIFF: https://github.com/llvm/llvm-project/commit/73a7638c0d832182dd7df912115d80be87567cb4.diff
LOG: [compiler-rt][sanitizer_common] Size readlink/getsockopt post-hook unpoison by bytes written (#209209)
# About
`readlink`/`readlinkat` return the number of bytes placed in the buffer
in res and do not NUL-terminate it, and getsockopt writes *optlen bytes
of binary option data. The post-hooks instead sized their POST_WRITE
(MSan unpoison) with internal_strlen(buf) + 1, which reads past what the
kernel wrote -- over- unpoisoning the uninitialized tail (masking real
bugs) and, on a buffer with no NUL, reading out of bounds inside the
runtime. For binary option data an early zero byte instead
under-unpoisons.
Size the unpoison by the actual written length, matching the
corresponding libc interceptors (readlink unpoisons res bytes, and
getsockopt unpoisons *optlen bytes).
Seems to be present since the file's 2013 import.
Added:
compiler-rt/test/msan/Linux/syscalls_post_output_size.cpp
Modified:
compiler-rt/lib/sanitizer_common/sanitizer_common_syscalls.inc
Removed:
################################################################################
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common_syscalls.inc b/compiler-rt/lib/sanitizer_common/sanitizer_common_syscalls.inc
index 5f8f840accbf2..6616dc35ec6d1 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common_syscalls.inc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common_syscalls.inc
@@ -1510,7 +1510,7 @@ PRE_SYSCALL(readlink)(const void *path, void *buf, long bufsiz) {
POST_SYSCALL(readlink)(long res, const void *path, void *buf, long bufsiz) {
if (res >= 0) {
if (buf)
- POST_WRITE(buf, __sanitizer::internal_strlen((const char *)buf) + 1);
+ POST_WRITE(buf, res);
}
}
@@ -1905,11 +1905,13 @@ PRE_SYSCALL(getsockopt)
POST_SYSCALL(getsockopt)
(long res, long fd, long level, long optname, void *optval, void *optlen) {
if (res >= 0) {
- if (optval)
- POST_WRITE(optval,
- __sanitizer::internal_strlen((const char *)optval) + 1);
- if (optlen)
+ // getsockopt cannot succeed with a null optlen, but guard it before
+ // dereferencing since the hook is called with raw, untrusted arguments.
+ if (optlen) {
POST_WRITE(optlen, sizeof(int));
+ if (optval)
+ POST_WRITE(optval, *(unsigned int*)optlen);
+ }
}
}
@@ -2900,7 +2902,7 @@ POST_SYSCALL(readlinkat)
(long res, long dfd, const void *path, void *buf, long bufsiz) {
if (res >= 0) {
if (buf)
- POST_WRITE(buf, __sanitizer::internal_strlen((const char *)buf) + 1);
+ POST_WRITE(buf, res);
}
}
diff --git a/compiler-rt/test/msan/Linux/syscalls_post_output_size.cpp b/compiler-rt/test/msan/Linux/syscalls_post_output_size.cpp
new file mode 100644
index 0000000000000..6319ff83f02c5
--- /dev/null
+++ b/compiler-rt/test/msan/Linux/syscalls_post_output_size.cpp
@@ -0,0 +1,57 @@
+// RUN: %clangxx_msan -O0 %s -o %t && %run %t
+
+// readlink/readlinkat return the byte count in res and do not NUL-terminate;
+// getsockopt writes *optlen bytes of binary option data. The POST hooks used
+// internal_strlen() to size the unpoison, which reads past what the kernel
+// wrote. After the fix the unpoisoned span matches the real written length.
+
+#include <assert.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include <sanitizer/linux_syscall_hooks.h>
+#include <sanitizer/msan_interface.h>
+
+int main() {
+ {
+ char buf[64];
+ memset(buf, 'x', 64);
+ buf[63] = 0; // in-bounds NUL so a stray strlen would run to 63, not OOB
+ __msan_poison(buf, 64);
+ __sanitizer_syscall_post_readlink(3, "/x", buf, 64);
+ assert(__msan_test_shadow(buf, 64) == 3); // exactly res bytes
+ }
+ {
+ char buf[64];
+ memset(buf, 'x', 64);
+ buf[63] = 0;
+ __msan_poison(buf, 64);
+ __sanitizer_syscall_post_readlinkat(3, AT_FDCWD, "/x", buf, 64);
+ assert(__msan_test_shadow(buf, 64) == 3);
+ }
+ {
+ unsigned char ov[16];
+ __msan_poison(ov, 16);
+ socklen_t ol = 4;
+ __sanitizer_syscall_post_getsockopt(0, 3, 1, 2, ov, &ol);
+ assert(__msan_test_shadow(ov, 16) == 4); // exactly *optlen bytes
+ }
+ {
+ // optlen == NULL: the hook must not dereference it and must leave optval
+ // untouched (getsockopt cannot succeed with a NULL optlen, but the hook is
+ // called with raw arguments and must stay safe regardless).
+ unsigned char ov[16];
+ __msan_poison(ov, 16);
+ __sanitizer_syscall_post_getsockopt(0, 3, 1, 2, ov, nullptr);
+ assert(__msan_test_shadow(ov, 16) == 0); // nothing unpoisoned
+ }
+ {
+ // optval == NULL: only optlen is unpoisoned, optval is never dereferenced.
+ socklen_t ol = 4;
+ __msan_poison(&ol, sizeof(ol));
+ __sanitizer_syscall_post_getsockopt(0, 3, 1, 2, nullptr, &ol);
+ assert(__msan_test_shadow(&ol, sizeof(ol)) == -1);
+ }
+ return 0;
+}
More information about the llvm-commits
mailing list