[libc-commits] [libc] [libc][printf] Fix %s precision handling (PR #233098)

Nathan Cornaud via libc-commits libc-commits at lists.llvm.org
Sat Oct 17 08:02:19 PDT 2026


https://github.com/cornaud created https://github.com/llvm/llvm-project/pull/233098

This fixes an issue where the string converter for `%s` attempts to retrieve the string length of the argument by climbing to the null-terminator regardless of precision, even tho if a precision is specified the null-terminator isn't mandatory.

Now, the string length is calculated up to the null-terminator only if no precision is specified, otherwise the length calculation is capped up to the precision.

A test is added for %s conversion with a specified precision, checking if it correctly formats a non-null-terminated input string when the input contains at least the required number of characters.

However, it is important to note that this test does not guarantee that the out-of-bounds bug will not reoccur, as detecting it would require running the test under ASan, and the bug itself may not be reproducible deterministically.

Fixes #230110  .

>From 7a2474756da3929ef948d2d0d69095967aa0d7f1 Mon Sep 17 00:00:00 2001
From: Nathan-C <ncornaud at gmail.com>
Date: Sat, 17 Oct 2026 00:42:11 +0200
Subject: [PATCH 1/2] [libc][printf] Fix %s precision handling

This fixes an issue where the string converter for `%s` attempts to retrieve the string length of the argument by climbing to the null-terminator regardless of precision, even tho if a precision is specified the null-terminator isn't mandatory.

Now, the string length is calculated up to the null-terminator only if no precision is specified, otherwise the length calculation is capped up to the precision.

Fixes #230110.
---
 .../__support/printf_core/string_converter.h  | 12 ++++----
 .../__support/printf_core/converter_test.cpp  | 30 +++++++++++++++++++
 2 files changed, 37 insertions(+), 5 deletions(-)

diff --git a/libc/src/__support/printf_core/string_converter.h b/libc/src/__support/printf_core/string_converter.h
index 4a72ee03d43ada..1f82ff79893694 100644
--- a/libc/src/__support/printf_core/string_converter.h
+++ b/libc/src/__support/printf_core/string_converter.h
@@ -40,11 +40,13 @@ LIBC_INLINE int write_char_string_as_chars(Writer<mode, char> *writer,
   }
 #endif // LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
 
-  string_len = internal::string_length(str_ptr);
-
-  if (to_conv.precision >= 0 &&
-      static_cast<size_t>(to_conv.precision) < string_len)
-    string_len = to_conv.precision;
+  // If a precision is specified, it doesn't have to be null-terminated.
+  if (to_conv.precision >= 0) {
+    while (string_len < static_cast<size_t>(to_conv.precision) &&
+           str_ptr[string_len] != '\0')
+      string_len++;
+  } else
+    string_len = internal::string_length(str_ptr);
 
   size_t padding_spaces = to_conv.min_width > static_cast<int>(string_len)
                               ? to_conv.min_width - string_len
diff --git a/libc/test/src/__support/printf_core/converter_test.cpp b/libc/test/src/__support/printf_core/converter_test.cpp
index facbda405acb7d..17bd63c3817f60 100644
--- a/libc/test/src/__support/printf_core/converter_test.cpp
+++ b/libc/test/src/__support/printf_core/converter_test.cpp
@@ -279,6 +279,36 @@ TYPED_TEST(LlvmLibcPrintfConverterTest, StringConversionPrecisionLow,
   ASSERT_EQ(writer.get_chars_written(), size_t{2});
 }
 
+TYPED_TEST(LlvmLibcPrintfConverterTest, StringConversionNoNullTerminator,
+           TestCharTypes) {
+  // When a precision is specified for a %s conversion, the input string does
+  // not need to be null-terminated, provided it contains at least as many
+  // characters as the specified precision.
+
+  using CharT = ParamType;
+  CharT str[60];
+  Writer writer =
+      make_drop_overflow_writer(str, sizeof(str) / sizeof(CharT) - 1);
+  WriteBuffer<CharT> &wb = writer.get_write_buffer();
+
+  const CharT input[] = {ENCODED(CharT, 'j'), ENCODED(CharT, 'k'),
+                         ENCODED(CharT, 'l')};
+
+  FormatSection<CharT> no_null_terminator_conv;
+  no_null_terminator_conv.has_conv = true;
+  no_null_terminator_conv.raw_string = ENCODED(CharT, "%.3s");
+  no_null_terminator_conv.conv_name = ENCODED(CharT, 's');
+  no_null_terminator_conv.precision = 3;
+  no_null_terminator_conv.conv_val_ptr = const_cast<CharT *>(input);
+
+  LIBC_NAMESPACE::printf_core::convert(&writer, no_null_terminator_conv);
+
+  wb.buff[wb.buff_cur] = ENCODED(CharT, '\0');
+
+  ASSERT_STREQ(str, ENCODED(CharT, "jkl"));
+  ASSERT_EQ(writer.get_chars_written(), size_t{3});
+}
+
 TYPED_TEST(LlvmLibcPrintfConverterTest, StringConversionRightJustified,
            TestCharTypes) {
   using CharT = ParamType;

>From 807609c4d0388502615c58e48421c9631df6e930 Mon Sep 17 00:00:00 2001
From: cornaud <ncornaud at gmail.com>
Date: Sat, 17 Oct 2026 16:31:49 +0200
Subject: [PATCH 2/2] [libc] Fix wide string conversion test without null
 terminator

Set the l length modifier for the wchar_t case in StringConversionNoNullTerminator so the converter interprets the input as a wide string and correctly verifies the expected output.

This fixes 7a24747 (#230110).
---
 libc/test/src/__support/printf_core/converter_test.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/libc/test/src/__support/printf_core/converter_test.cpp b/libc/test/src/__support/printf_core/converter_test.cpp
index 17bd63c3817f60..3f2a96e82a535c 100644
--- a/libc/test/src/__support/printf_core/converter_test.cpp
+++ b/libc/test/src/__support/printf_core/converter_test.cpp
@@ -299,6 +299,10 @@ TYPED_TEST(LlvmLibcPrintfConverterTest, StringConversionNoNullTerminator,
   no_null_terminator_conv.raw_string = ENCODED(CharT, "%.3s");
   no_null_terminator_conv.conv_name = ENCODED(CharT, 's');
   no_null_terminator_conv.precision = 3;
+  if constexpr (LIBC_NAMESPACE::cpp::is_same_v<CharT, wchar_t>) {
+    no_null_terminator_conv.length_modifier =
+    LIBC_NAMESPACE::printf_core::LengthModifier::l;
+  }
   no_null_terminator_conv.conv_val_ptr = const_cast<CharT *>(input);
 
   LIBC_NAMESPACE::printf_core::convert(&writer, no_null_terminator_conv);



More information about the libc-commits mailing list