[libc-commits] [libc] [libc][wchar] Fix OOB read and integer underflow in wcslcat and wcsncat (PR #203704)

Chaitanya Jain via libc-commits libc-commits at lists.llvm.org
Sat Jun 13 07:51:34 PDT 2026


https://github.com/crj0x created https://github.com/llvm/llvm-project/pull/203704

* Bound `dstlen` calculation to `dstsize` to prevent unbounded reads. Add early return if `dstlen == dstsize` to prevent `size_t` underflow in copy limit.
* do `i < n` check before `s2[i]` to prevent OOB read on unterminated strings.
* add corresponding tests.

Fixes #203649

>From 9eec3933f3f605468d6dfe3e021d62e6f253a912 Mon Sep 17 00:00:00 2001
From: crj0x <243283810+crj0x at users.noreply.github.com>
Date: Sat, 13 Jun 2026 20:17:02 +0530
Subject: [PATCH] [libc][wchar] Fix OOB read and integer underflow in wcslcat
 and wcsncat

---
 libc/src/wchar/wcslcat.cpp           | 24 +++++++++++++-----------
 libc/src/wchar/wcsncat.cpp           |  2 +-
 libc/test/src/wchar/wcslcat_test.cpp | 12 ++++++++++++
 libc/test/src/wchar/wcsncat_test.cpp | 27 +++++++++++++++++++++++++++
 4 files changed, 53 insertions(+), 12 deletions(-)

diff --git a/libc/src/wchar/wcslcat.cpp b/libc/src/wchar/wcslcat.cpp
index eb318e066f7a0..23337b7c37395 100644
--- a/libc/src/wchar/wcslcat.cpp
+++ b/libc/src/wchar/wcslcat.cpp
@@ -19,21 +19,23 @@ namespace LIBC_NAMESPACE_DECL {
 LLVM_LIBC_FUNCTION(size_t, wcslcat,
                    (wchar_t *__restrict dst, const wchar_t *__restrict src,
                     size_t dstsize)) {
-  const size_t dstlen = internal::string_length(dst);
+  size_t dstlen = 0;
+  while (dstlen < dstsize && dst[dstlen] != L'\0') {
+    dstlen++;
+  }
   const size_t srclen = internal::string_length(src);
-  int limit = static_cast<int>(dstsize - dstlen - 1);
-  size_t returnval = (dstsize < dstlen ? dstsize : dstlen) + srclen;
-  if (limit < 0)
-    return returnval;
-  int i = 0;
-  for (; i < limit && src[i] != L'\0'; ++i) {
+  if (dstlen == dstsize) {
+    return dstlen + srclen;
+  }
+  size_t copy_limit = dstsize - dstlen - 1;
+  size_t i = 0;
+  for (; i < copy_limit && src[i] != L'\0'; ++i) {
     dst[dstlen + i] = src[i];
   }
 
-  // appending null terminator if there is room
-  if (dstlen + i < dstlen + dstsize)
-    dst[dstlen + i] = L'\0';
-  return returnval;
+  // appending null terminator to the end of the result
+  dst[dstlen + i] = L'\0';
+  return dstlen + srclen;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcsncat.cpp b/libc/src/wchar/wcsncat.cpp
index 62595b4b5418c..985fb5f1cbace 100644
--- a/libc/src/wchar/wcsncat.cpp
+++ b/libc/src/wchar/wcsncat.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(wchar_t *, wcsncat,
                     size_t n)) {
   size_t size = internal::string_length(s1);
   size_t i = 0;
-  for (; s2[i] && i < n; ++i)
+  for (; i < n && s2[i]; ++i)
     s1[size + i] = s2[i];
   // Appending null character to the end of the result.
   s1[size + i] = L'\0';
diff --git a/libc/test/src/wchar/wcslcat_test.cpp b/libc/test/src/wchar/wcslcat_test.cpp
index bad37496384e2..55236c28f94f7 100644
--- a/libc/test/src/wchar/wcslcat_test.cpp
+++ b/libc/test/src/wchar/wcslcat_test.cpp
@@ -55,3 +55,15 @@ TEST(LlvmLibcWCSLCatTest, SmallerNoOverwriteAfter0) {
   ASSERT_TRUE(dst[7] == L'\0');
   ASSERT_EQ(res, size_t(4));
 }
+
+TEST(LlvmLibcWCSLCatTest, DestUnterminatedWithinBuffer) {
+  const wchar_t *src = L"de";
+  wchar_t dst[3]{L'a', L'b', L'c'};
+
+  // the function must not access memory after the 3rd character
+  size_t res = LIBC_NAMESPACE::wcslcat(dst, src, 3);
+  ASSERT_TRUE(dst[0] == L'a');
+  ASSERT_TRUE(dst[1] == L'b');
+  ASSERT_TRUE(dst[2] == L'c');
+  ASSERT_EQ(res, size_t(5));
+}
\ No newline at end of file
diff --git a/libc/test/src/wchar/wcsncat_test.cpp b/libc/test/src/wchar/wcsncat_test.cpp
index 47359f88cec9e..b61bfd1c5afbb 100644
--- a/libc/test/src/wchar/wcsncat_test.cpp
+++ b/libc/test/src/wchar/wcsncat_test.cpp
@@ -80,3 +80,30 @@ TEST(LlvmLibcWCSNCatTest, NonEmptyDest) {
   ASSERT_TRUE(dest[3] == L'\0');
   ASSERT_TRUE(dest[4] == L'Z');
 }
+
+TEST(LlvmLibcWCSNCatTest, UnterminatedSrcArray) {
+  wchar_t dest[7] = {L'x', L'y', L'z', L'\0'};
+  wchar_t src[3] = {L'a', L'b', L'c'};
+
+  // Adding only part of the src string
+  LIBC_NAMESPACE::wcsncat(dest, src, 2);
+  ASSERT_TRUE(dest[0] == L'x');
+  ASSERT_TRUE(dest[1] == L'y');
+  ASSERT_TRUE(dest[2] == L'z');
+  ASSERT_TRUE(dest[3] == L'a');
+  ASSERT_TRUE(dest[4] == L'b');
+  ASSERT_TRUE(dest[5] == L'\0');
+
+  // resetting for last test
+  dest[3] = L'\0';
+
+  // Adding full src string
+  LIBC_NAMESPACE::wcsncat(dest, src, 3);
+  ASSERT_TRUE(dest[0] == L'x');
+  ASSERT_TRUE(dest[1] == L'y');
+  ASSERT_TRUE(dest[2] == L'z');
+  ASSERT_TRUE(dest[3] == L'a');
+  ASSERT_TRUE(dest[4] == L'b');
+  ASSERT_TRUE(dest[5] == L'c');
+  ASSERT_TRUE(dest[6] == L'\0');
+}
\ No newline at end of file



More information about the libc-commits mailing list