Skip to content

Fix stack buffer overflow in to_chars general format with a precision - #318

Merged
mborland merged 1 commit into
boostorg:developfrom
mvanslobbe:fix-to-chars-general-precision-buffer
Oct 20, 2026
Merged

mborland merged 1 commit into
boostorg:developfrom
mvanslobbe:fix-to-chars-general-precision-buffer

Conversation

@mvanslobbe

Copy link
Copy Markdown
Contributor

Another case of a size that does not count the sign, like #314, #316 and #317, but this time the write goes past a buffer inside the library rather than past the caller's range.

General format with a precision prints into a local buffer and then copies the result to the caller's range if it fits. The buffer is sized for the longest output, but it is one character short, so for some negative values with a large precision floff writes one character past the end of a buffer on the stack. The output and the result are correct, and the caller cannot avoid it by passing a larger range.

Problem

In to_chars_float_impl, src/to_chars_float_impl.hpp:

constexpr int max_output_length = std::is_same<Real, double>::value ? 773 : 117;
constexpr int max_precision = std::is_same<Real, double>::value ? 767 : 112;
...
char temp_buffer[max_output_length];

The longest outputs are one character longer than that:

  • float: a value such as -2.1484e-4 with a precision of 112 prints in fixed form as "-0.000" followed by 112 digits, 118 characters.
  • double: a value such as -1.5e-300 with a precision of 767 prints in scientific form as "-d.", 766 more digits and "e-300", 774 characters.

A larger precision is clamped to max_precision, so any precision from 112 (float) or 767 (double) upwards reaches these cases.

Reproducer

char buf[200];
auto r = boost::charconv::to_chars(buf, buf + sizeof(buf), -2.1484e-4f,
                                   boost::charconv::chars_format::general, 112);
// r.ec == std::errc() and the output is correct, but under AddressSanitizer:
// stack-buffer-overflow, WRITE of size 2 in print_2_digits (floff.hpp)

Fix

Size the buffer from max_precision, with room for the sign, the decimal point, the leading zeros or the exponent, and a few characters to spare, since floff writes digits in blocks: max_precision + 16. This makes the buffer 11 characters larger for float and 10 for double.

Tests

test/to_chars_float.cpp: general_max_precision_tests() formats negative values of each worst-case shape (-0.000 before the digits, a two or three digit negative exponent, the smallest subnormal) for float and double, at the maximal precision, one above it and well above it, and compares the result with snprintf("%.*g").

The output was already correct, so these tests only fail under AddressSanitizer, which reports the stack-buffer-overflow without the source change. Without a sanitizer they pass before and after.

As a further check, about 6 million calls at and around the maximal precision, with random values and with values close to the worst cases, give the same output as snprintf and no AddressSanitizer or UndefinedBehaviorSanitizer reports.

Tested with GCC 15 and Clang 22 with -fsanitize=address,undefined: the full to_chars/from_chars test set passes in C++17, and to_chars_float also passes in C++11. test/to_chars_float.cpp builds without warnings with the flags from test/Jamfile.

General format with a precision prints into a local buffer sized for
the longest output, but the size missed one character: "-0.000" before
112 digits for float, or a three digit exponent after 767 digits for
double. A negative float such as -2.1484e-4 with a precision of 112 or
more wrote one byte past the buffer, as did a double such as -1.5e-300
with a precision of 767 or more. The output was correct, so only a
sanitizer shows the overflow.

Size the buffer from the maximal precision with room for the sign,
point, leading zeros and exponent, and some to spare as floff writes
digits in blocks.
@codecov

codecov Bot commented Oct 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.18%. Comparing base (7f797f3) to head (0082da3).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##           develop     #318   +/-   ##
========================================
  Coverage    96.17%   96.18%           
========================================
  Files           79       79           
  Lines         9254     9269   +15     
========================================
+ Hits          8900     8915   +15     
  Misses         354      354           
Files with missing lines Coverage Δ
src/to_chars_float_impl.hpp 83.81% <100.00%> (ø)
test/to_chars_float.cpp 99.69% <100.00%> (+<0.01%) ⬆️

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7f797f3...0082da3. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mborland mborland left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@mborland
mborland merged commit 865f0ec into boostorg:develop Oct 20, 2026
51 checks passed
@mvanslobbe
mvanslobbe deleted the fix-to-chars-general-precision-buffer branch October 22, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants