Fix indentation overflow and correctness bug#5186
Conversation
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
LD-RW
left a comment
There was a problem hiding this comment.
Took a look at the indentation overflow fix — the guarded resize plus honoring indent_char both look right. Left two notes inline: a suggestion to extract the now-triplicated resize-and-assert guard into a small reserve_indent helper so the three branches can't drift, and a question about whether the unsigned current_indent + indent_step accumulation can still wrap on deeply nested input even after this fix. Neither is a blocker — the second is more "is this in scope?" than a defect claim.
| @@ -128,7 +128,8 @@ class serializer | |||
| const auto new_indent = current_indent + indent_step; | |||
There was a problem hiding this comment.
question: this fixes the buffer-growth side (the * 2 could undershoot a large indent_step), but is the accumulation of new_indent itself still vulnerable to wraparound? new_indent = current_indent + indent_step is unsigned, and the public dump(const int indent, ...) lets a caller pass a very large indent. For a deeply nested document the running current_indent + indent_step can overflow unsigned int and wrap to a small value; the indent_string.size() < new_indent guard would then pass on the wrapped-small new_indent, and write_characters(indent_string.c_str(), new_indent) would emit silently-truncated indentation rather than over-reading. Is that case considered out of scope here, or worth a saturating check / assert on new_indent as well?
| if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) | ||
| { | ||
| indent_string.resize(indent_string.size() * 2, ' '); | ||
| indent_string.resize((std::max)(indent_string.size() * 2, static_cast<std::size_t>(new_indent)), indent_char); |
There was a problem hiding this comment.
suggestion: this resize-and-assert guard is now duplicated verbatim at all three sites (object branch L131, array branch L205, single-element branch L267). The original bug existed in three places precisely because the logic was copy-pasted, and the fix had to be pasted three times too — so the next change to the growth policy has three spots to keep in sync. Extracting a small private helper would collapse them into one and make future divergence impossible:
void reserve_indent(std::size_t new_indent)
{
if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))
{
indent_string.resize((std::max)(indent_string.size() * 2, new_indent), indent_char);
}
JSON_ASSERT(indent_string.size() >= new_indent);
}then each site becomes a single reserve_indent(new_indent); call before write_characters.
There are two issues in the current
dumpfunction:indent_stepvalues,indent_stringgrowth is insufficient and can lead to buffer over-read / memory safety problems depending on write_characters() behavior. A GitHub Security Advisory will be published referencing this PR.indent_string, a space character instead of the passedindent_charwas used.