[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