[gcc r15-11609] libstdc++: Fix std::from_chars to not read past end of input [PR127666]

Jonathan Wakely redi@gcc.gnu.org
Sat Oct 3 14:26:26 GMT 2026


https://gcc.gnu.org/g:816c1a39ca9b656ba2d001c7b6fa421ebe44b3a0

commit r15-11609-g816c1a39ca9b656ba2d001c7b6fa421ebe44b3a0
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Thu Oct 1 17:39:33 2026 +0100

    libstdc++: Fix std::from_chars to not read past end of input [PR127666]
    
    The bug report points out one out-of-bounds read and the second was
    found by an LLM. The testcase was generated by an LLM. The code fixes
    are artisanal handwritten code.
    
    Assisted-by: LLM
    
    libstdc++-v3/ChangeLog:
    
            PR libstdc++/127666
            * src/c++17/floating_from_chars.cc (pattern): Avoid reading past
            the end of the input. Remove unused variable.
            * testsuite/20_util/from_chars/127666.cc: New test.
    
    Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
    (cherry picked from commit 524847fec983eaedca147840ce46fcbb0cd38952)

Diff:
---
 libstdc++-v3/src/c++17/floating_from_chars.cc      |  9 ++-
 .../testsuite/20_util/from_chars/127666.cc         | 70 ++++++++++++++++++++++
 2 files changed, 76 insertions(+), 3 deletions(-)

diff --git a/libstdc++-v3/src/c++17/floating_from_chars.cc b/libstdc++-v3/src/c++17/floating_from_chars.cc
index 9bad12c7f69e..9846784c57f1 100644
--- a/libstdc++-v3/src/c++17/floating_from_chars.cc
+++ b/libstdc++-v3/src/c++17/floating_from_chars.cc
@@ -452,13 +452,16 @@ namespace
     // fmt has the value of one of the enumerators of chars_format.
     __glibcxx_assert(valid_fmt(fmt));
 
-    string_view res;
-
+    // Fail if the input is empty or starts with '+'
     if (first == last || *first == '+') [[unlikely]]
 	return nullptr;
 
     const int neg = (*first == '-');
 
+    // Fail if the input consists of a lone '-'
+    if (first + neg == last)
+      return nullptr;
+
     if (std::memchr("iInN", (unsigned char)first[neg], 4))
       {
 	ptrdiff_t len = last - first;
@@ -490,7 +493,7 @@ namespace
 	else // Only need 4 chars for "-NAN"
 	  len = neg + 3;
 
-	buf.assign(first, 0, len);
+	buf.assign(first, len);
 	// prevent make_result correcting for "0x"
 	fmt = chars_format::general;
 	return buf.c_str();
diff --git a/libstdc++-v3/testsuite/20_util/from_chars/127666.cc b/libstdc++-v3/testsuite/20_util/from_chars/127666.cc
new file mode 100644
index 000000000000..25989f2cfee8
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/from_chars/127666.cc
@@ -0,0 +1,70 @@
+// { dg-do run { target c++17 } }
+// { dg-require-effective-target mmap }
+// { dg-require-effective-target sysconf }
+// { dg-add-options ieee }
+// { dg-additional-options "-DSKIP_LONG_DOUBLE" { target aarch64-*-rtems* aarch64-*-vxworks* x86_64-*-vxworks* } }
+
+// Bug 127666
+// std::from_chars(first, last, long double&) reads past 'last' for "inf"/"nan"
+
+#include <charconv>
+#include <string_view>
+#include <cstring>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <testsuite_hooks.h>
+
+using namespace std::string_view_literals;
+
+// One past the last writable byte; the guard page starts here, so reading
+// at or beyond this address faults.
+static char* buf_end;
+
+template<typename T>
+void
+check(std::string_view input, std::errc ec, std::size_t len)
+{
+  // Position the input against the guard page so an over-read faults.
+  char* const first = buf_end - input.size();
+  std::memcpy(first, input.data(), input.size());
+  T value = 1;
+  auto res = std::from_chars(first, buf_end, value);
+  VERIFY( res.ec == ec );
+  VERIFY( res.ptr == first + len );
+}
+
+template<typename T>
+void
+test_type()
+{
+  // The inf/nan forms used to read past the end via strlen.
+  check<T>("inf"sv, std::errc{}, 3);
+  check<T>("infinity"sv, std::errc{}, 8);
+  check<T>("nan"sv, std::errc{}, 3);
+  check<T>("nan(1)"sv, std::errc{}, 6);
+  check<T>("-nan(ab)"sv, std::errc{}, 8);
+
+  // A lone sign used to read one byte past the end.
+  check<T>("-"sv, std::errc::invalid_argument, 0);
+  check<T>("+"sv, std::errc::invalid_argument, 0);
+}
+
+int
+main()
+{
+  const long pg = ::sysconf(_SC_PAGESIZE);
+  VERIFY( pg > 0 );
+  char* const base = (char*) ::mmap(nullptr, 2 * pg, PROT_READ | PROT_WRITE,
+				    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  VERIFY( base != MAP_FAILED );
+  VERIFY( ::mprotect(base + pg, pg, PROT_NONE) == 0 );
+  buf_end = base + pg;
+
+  test_type<float>();
+  test_type<double>();
+#ifndef SKIP_LONG_DOUBLE
+  test_type<long double>();
+#endif
+
+  ::munmap(base, 2 * pg);
+}


More information about the Libstdc++-cvs mailing list