[libc-commits] [libc] [libc] Don't read memory in vector find_first_character if n is 0 (PR #235289)
Fabian Meumertzheim via libc-commits
libc-commits at lists.llvm.org
Sat Oct 24 05:26:05 PDT 2026
https://github.com/fmeum created https://github.com/llvm/llvm-project/pull/235289
The vectorized `find_first_character` implementations (`clang_vector` and the x86_64 `arch_vector`) loaded the aligned block containing the start pointer before checking the length. With a length of zero, the pointer doesn't have to be dereferenceable, so `memchr(p, c, 0)` and `strnlen(p, 0)` crashed if `p` was the start of an inaccessible page.
The new test covers all implementations, since no configuration selects the vectorized ones by default. On aarch64 Linux, it segfaults without the fix and passes with it. The x86_64 changes were only compiled, not run.
Assisted-by: Claude Code (Claude Opus 5.5)
>From b4b51780c6d219f2e87504fa3e3ed285b1a7c855 Mon Sep 17 00:00:00 2001
From: Fabian Meumertzheim <fabian at meumertzhe.im>
Date: Sat, 24 Oct 2026 14:25:09 +0200
Subject: [PATCH] [libc] Don't read memory in vector find_first_character if n
is 0
The vectorized find_first_character implementations (clang_vector and
the x86_64 arch_vector) loaded the aligned block containing the start
pointer before checking the length. With a length of zero, the pointer
doesn't have to be dereferenceable, so memchr(p, c, 0) and
strnlen(p, 0) crashed if p was the start of an inaccessible page.
The new test covers all implementations, since no configuration selects
the vectorized ones by default.
Assisted-by: Claude Code (Claude Opus 5.5)
---
.../memory_utils/generic/inline_strlen.h | 4 ++++
.../memory_utils/x86_64/inline_strlen.h | 4 ++++
.../src/strings/wide_read_memory_test.cpp | 21 +++++++++++++++++++
3 files changed, 29 insertions(+)
diff --git a/libc/src/string/memory_utils/generic/inline_strlen.h b/libc/src/string/memory_utils/generic/inline_strlen.h
index 833ff9fbcbf80a..2758f97c58cc55 100644
--- a/libc/src/string/memory_utils/generic/inline_strlen.h
+++ b/libc/src/string/memory_utils/generic/inline_strlen.h
@@ -57,6 +57,10 @@ LIBC_INLINE static void *calculate_find_first_character_return(
LIBC_NO_SANITIZE_OOB_ACCESS LIBC_INLINE static void *
find_first_character(const unsigned char *s, unsigned char c, size_t n) {
+ // s doesn't have to be dereferenceable if there is nothing to search.
+ if (n == 0)
+ return nullptr;
+
using Vector = cpp::simd<char>;
using Mask = cpp::simd_mask<char>;
Vector c_byte = c;
diff --git a/libc/src/string/memory_utils/x86_64/inline_strlen.h b/libc/src/string/memory_utils/x86_64/inline_strlen.h
index 0343e22f3e58e3..a3d3eade0d4522 100644
--- a/libc/src/string/memory_utils/x86_64/inline_strlen.h
+++ b/libc/src/string/memory_utils/x86_64/inline_strlen.h
@@ -59,6 +59,10 @@ template <typename Vector, typename Mask,
Mask (*CompareAndMask)(const Vector *, char)>
LIBC_NO_SANITIZE_OOB_ACCESS LIBC_INLINE static void *
find_first_character_vector(const unsigned char *s, unsigned char c, size_t n) {
+ // s doesn't have to be dereferenceable if there is nothing to search.
+ if (n == 0)
+ return nullptr;
+
uintptr_t misalign_bytes = reinterpret_cast<uintptr_t>(s) % sizeof(Vector);
const Vector *block_ptr =
diff --git a/libc/test/src/strings/wide_read_memory_test.cpp b/libc/test/src/strings/wide_read_memory_test.cpp
index 7b173d479dc9db..c146901fb3732f 100644
--- a/libc/test/src/strings/wide_read_memory_test.cpp
+++ b/libc/test/src/strings/wide_read_memory_test.cpp
@@ -40,6 +40,7 @@ using TwoKilobyteBuffer = cpp::array<char, 2048>;
static constexpr unsigned long kLargestTestVectorSize = 512;
class LlvmLibcWideAccessMemoryTest : public testing::Test {
+protected:
char *page0_;
char *page1_;
char *page2_;
@@ -99,6 +100,26 @@ TEST_F(LlvmLibcWideAccessMemoryTest, StringLength) {
});
}
+TEST_F(LlvmLibcWideAccessMemoryTest, FindFirstCharZeroLength) {
+ // All implementations, since the configured one is usually not vectorized.
+ static constexpr void *(*kImpls[])(const unsigned char *, unsigned char,
+ size_t) = {
+ internal::element::find_first_character,
+ internal::word::find_first_character,
+#if LIBC_HAS_VECTOR_TYPE
+ clang_vector::find_first_character,
+#endif
+#ifdef LIBC_TARGET_ARCH_IS_X86
+ internal::arch_vector::find_first_character,
+#endif
+ };
+ // A zero-length search must not read the start of an inaccessible page.
+ for (auto find_first_character : kImpls)
+ ASSERT_EQ(reinterpret_cast<const void *>(find_first_character(
+ reinterpret_cast<const unsigned char *>(page2_), 'a', 0)),
+ nullptr);
+}
+
TEST_F(LlvmLibcWideAccessMemoryTest, FindFirstChar) {
// 1.5 k long vector of a's.
TwoKilobyteBuffer buf;
More information about the libc-commits
mailing list