Skip to content

Fix to_chars writing past the end of the range for negative zero - #317

Merged
mborland merged 1 commit into
boostorg:developfrom
mvanslobbe:fix-to-chars-negative-zero-range
Oct 20, 2026
Merged

mborland merged 1 commit into
boostorg:developfrom
mvanslobbe:fix-to-chars-negative-zero-range

Conversation

@mvanslobbe

Copy link
Copy Markdown
Contributor

Another write past the end of the range, in the same family as #314, #315 and #316, this time for floating point: the zero case in the dragonbox path.

For a negative zero, to_chars writes the '-' and then the "0" or "0e+00" without checking that the rest still fits. If the range is exactly one character too short, it writes one character past the end and reports success: ec == std::errc() and ptr == last + 1.

std::to_chars returns value_too_large and leaves ptr == last.

Problem

In to_chars_n_impl in dragonbox.hpp, the sign is written first and buffer moves past it. The zero branch then:

  • in general and fixed format, writes "0" with no check at all;
  • in scientific format, checks for 5 characters against buffer_size, the size of the whole range taken before the sign was written.
if (fmt != chars_format::scientific)
{
    std::memcpy(buffer, "0", 1);
    return {buffer + 1, std::errc()};
}

if (buffer_size >= 5)
{
    std::memcpy(buffer, "0e+00", 5);
    return {buffer + 5, std::errc()};
}

So "-0" is written into a range of one character and "-0e+00" into a range of five. Positive zero is not affected, because the empty range is rejected before this point. The same code is used for float and double.

As in #316, the sign is written and the rest is compared with a size that still counts the character the sign used. As in #315, a special case for zero returns before the usual room check.

Reproducer

char* p = new char[1];  // "-0" needs 2
auto r = boost::charconv::to_chars(p, p + 1, -0.0);
// r.ec == std::errc(), r.ptr == p + 2
// AddressSanitizer: heap-buffer-overflow, WRITE of size 1 in to_chars_n_impl

and in scientific format:

char* p = new char[5];  // "-0e+00" needs 6
auto r = boost::charconv::to_chars(p, p + 5, -0.0, boost::charconv::chars_format::scientific);
// AddressSanitizer: heap-buffer-overflow, WRITE of size 5

Fix

Check the room that is left after the sign: return value_too_large before writing "0" if the range is now empty, and compare last - buffer rather than buffer_size with the 5 characters of "0e+00".

Tests

test/to_chars_float.cpp: negative_zero_range_tests<T>() writes -0.0 into a range one character too short and into a range of exactly the right size, for float and double, in general ("-0") and scientific ("-0e+00") format. It checks for value_too_large, ptr == last and an untouched character after the range in the first case, and for the exact output and an untouched character after it in the second, as the tests in #314, #315 and #316 do. Without the source change, the new tests report 12 failures.

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.

Not in this PR

-0.0 in fixed format without a precision also returns value_too_large for any range of 7 characters or fewer, although "-0" needs 2. That comes from the shortest fixed format path rejecting ranges shorter than about max_digits10 characters for any value, which is a separate issue and not a memory safety bug, so I have left it for its own PR.

@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.21%. Comparing base (a690fe7) to head (1b94d90).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #317      +/-   ##
===========================================
+ Coverage    96.19%   96.21%   +0.01%     
===========================================
  Files           79       79              
  Lines         9284     9303      +19     
===========================================
+ Hits          8931     8951      +20     
+ Misses         353      352       -1     
Files with missing lines Coverage Δ
...lude/boost/charconv/detail/dragonbox/dragonbox.hpp 98.01% <100.00%> (+0.34%) ⬆️
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 a690fe7...1b94d90. Read the comment docs.

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

@mborland

Copy link
Copy Markdown
Member

@jk-jeon this may impact your upstream copy of Dragonbox

For a negative zero, the dragonbox path wrote the '-' and then printed
"0" without checking the room left, or checked the room for "0e+00"
against the size of the whole range. "-0" was written into a range of
one character and "-0e+00" into a range of five, one character past
the end, with ptr past last and no error.

Check the room that is left after the sign.
@mborland
mborland force-pushed the fix-to-chars-negative-zero-range branch from 14cf20f to 1b94d90 Compare October 20, 2026 12:44

@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.

Thanks! I rebased this on develop to fix a merge conflict with one of your other PRs, so will merge once it cycles green

@mborland
mborland merged commit ed350a0 into boostorg:develop Oct 20, 2026
51 checks passed
@jk-jeon

jk-jeon commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

@mborland Thanks for letting me know!

Fortunately(?) it doesn't affect the upstream because the upstream doesn't care about the buffer size (it assumes the size is enough and otherwise it's UB).

@mvanslobbe
mvanslobbe deleted the fix-to-chars-negative-zero-range 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.

3 participants