Repository navigation
Fix stack buffer overflow in to_chars general format with a precision - #318
Merged
mborland merged 1 commit intoOct 20, 2026
Merged
Conversation
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 Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #318 +/- ##
========================================
Coverage 96.17% 96.18%
========================================
Files 79 79
Lines 9254 9269 +15
========================================
+ Hits 8900 8915 +15
Misses 354 354
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:The longest outputs are one character longer than that:
float: a value such as-2.1484e-4with a precision of 112 prints in fixed form as"-0.000"followed by 112 digits, 118 characters.double: a value such as-1.5e-300with 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
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 forfloatand 10 fordouble.Tests
test/to_chars_float.cpp:general_max_precision_tests()formats negative values of each worst-case shape (-0.000before the digits, a two or three digit negative exponent, the smallest subnormal) forfloatanddouble, at the maximal precision, one above it and well above it, and compares the result withsnprintf("%.*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
snprintfand no AddressSanitizer or UndefinedBehaviorSanitizer reports.Tested with GCC 15 and Clang 22 with
-fsanitize=address,undefined: the fullto_chars/from_charstest set passes in C++17, andto_chars_floatalso passes in C++11.test/to_chars_float.cppbuilds without warnings with the flags fromtest/Jamfile.