Skip to content

Fix heap-buffer-overflow in parse_string on truncated escapes - #74

Open
afonsojanu wants to merge 1 commit into
Bwar:masterfrom
afonsojanu:fix/parse-string-heap-overflow
Open

afonsojanu wants to merge 1 commit into
Bwar:masterfrom
afonsojanu:fix/parse-string-heap-overflow

Conversation

@afonsojanu

Copy link
Copy Markdown

Fixes the heap-buffer-overflow from #73.

I reproduced the exact crash bytes from that report against a standalone build of cJSON.c under ASan/UBSan and got the same overflow at parse_string line 261 (write past a 6-byte allocation).

The cause: parse_string first walks the string once to estimate how many bytes it will need, then walks it again to actually decode into a buffer of that size. The \u handling in the second pass does ptr += 4 (or += 6 for a surrogate pair) unconditionally, without checking whether the string actually has that many characters left. In the crash input there's an embedded NUL right after \u, so the first (counting) pass stops there, but the second pass's fixed skip jumps past that point and keeps decoding trailing bytes as if they were still part of the string, writing past the buffer the first pass sized.

While tracking this down I found a second variant of the same problem: a string ending in a bare trailing backslash with no closing quote lets the length-counting loop step one character past the terminator and read out of bounds on the next iteration.

Both are now treated as malformed input, bailing out and freeing the partial buffer rather than reading/writing past it. Valid escapes, including surrogate pairs like 😀, still decode the same as before.

Added test/parse_string_overflow_test.c, which reproduces the exact crash bytes from #73 plus a few related truncation cases, and confirms normal string/unicode parsing is unaffected. I checked that it crashes under ASan against the current cJSON.c and passes cleanly with this fix. Build/run it directly, e.g.:

clang -g -O0 -fsanitize=address,undefined -o parse_string_overflow_test test/parse_string_overflow_test.c cJSON.c
./parse_string_overflow_test

parse_string() estimates the output buffer size in a first pass over
the input, then decodes into a buffer of that size in a second pass.
The \u escape handling in the second pass advanced its cursor by a
fixed 4 (or 6, for a surrogate pair) characters without checking that
the string actually had that many characters left. When the input
contained an embedded NUL byte or simply ran out mid-escape, the
cursor could jump past where the first pass had stopped counting, so
the second pass kept copying bytes the allocation was never sized
for and wrote past the end of it.

A bare trailing backslash right at the end of an unterminated string
had the same class of problem in the length-counting pass itself,
skipping one character past the terminator and reading out of bounds
on the next iteration.

Both cases are now treated as malformed input: parse_string bails out
and frees the partial buffer instead of reading or writing past it.
Valid \u escapes, including surrogate pairs, are unaffected.

Reported in Bwar#73 with an ASan repro from libFuzzer; this adds a
regression test that reproduces the exact crash bytes plus a few
related truncation cases, and checks that normal unicode escapes
still round-trip correctly.
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.

1 participant