Skip to content

Reject a literal 0x1A after the value in strict mode (CVE-2026-113602) - #305

Open
knutwannheden wants to merge 1 commit into
netplex:masterfrom
knutwannheden:json-smart-reject-a-literal-0x1a-cve-2026-113602
Open

knutwannheden wants to merge 1 commit into
netplex:masterfrom
knutwannheden:json-smart-reject-a-literal-0x1a-cve-2026-113602

Conversation

@knutwannheden

Copy link
Copy Markdown

JSONValue.isValidJsonStrict returns true for {"a":1} followed by a 0x1A byte and arbitrary trailing data, and parseStrict returns {"a":1} while silently dropping the rest (#302, CVE-2026-113602). A strict validator in front of another JSON parser therefore passes documents that the other parser reads differently. Every backend is affected: String, byte[], Reader and InputStream.

The parser marks the end of input by setting the current character to EOI = 0x1A. The trailing-data check c != EOI that MODE_RFC4627 and MODE_STRICTEST run after the value cannot tell that marker apart from a 0x1A read from the input. Before this change, both modes returned these results on all four backends:

{"a":1}<0x1A>garbage  ->  {"a":1}
1<0x1A>xx             ->  1
true<0x1A>false       ->  true

Ask the backend whether the input ended

The trailing-data check now calls a new isEndOfInput() instead of comparing c with the marker:

  • JSONParserMemory (String, byte[]) returns pos >= len.
  • JSONParserReader (Reader, InputStream) returns a flag that each read sets from whether in.read() returned -1.

The other c == EOI checks are left alone. The parser never reads past the marker, so a 0x1A that an inner check took for the end is still the current character when the trailing-data check runs. In strict mode those inner checks already reject it, as an unexpected EOF or, inside a string, as a control character.

Consulting the flag at every check was rejected because it would turn a literal 0x1A into an ordinary control character. skipSpace skips every character up to ' ', so {"a":1}<0x1A> would then pass strict mode as trailing whitespace, and permissive results would change ([1<0x1A>,2] would parse as [1,2]).

Not changed here

Modes that accept trailing data (MODE_PERMISSIVE, MODE_JSON_SIMPLE, and MultipleJsonParser.hasNext()) still treat a literal 0x1A as the end of input. They already return the first value and ignore whatever follows it, so the 0x1A gets nothing past a check. Custom modes that omit ACCEPT_TAILLING_DATA or ACCEPT_TAILLING_SPACE run the trailing-data check and now reject it too.

Tests

TestCVE2026113602 parses with MODE_RFC4627 on all four backends. strictParserRejectsSubAfterValue fails on every backend without the fix. strictParserRejectsSubInString passes either way. It keeps a 0x1A inside a string rejected, and fails on Reader and InputStream when readString2 is mutated to append the 0x1A as data.

Fixes #302.

The parser marks the end of input with the character 0x1A, so a literal
0x1A in the input looked like the end of the document. The trailing-data
check of MODE_RFC4627 and MODE_STRICTEST (JSONValue.parseStrict and
isValidJsonStrict) therefore accepted {"a":1} followed by 0x1A and
arbitrary garbage as {"a":1}, on the String, byte[], Reader and
InputStream backends.

The trailing-data check now asks the backend whether the input is
exhausted. The String and byte[] parsers compare the position with the
length, and the Reader parser (which also serves InputStream) records
whether its last read returned -1. Modes that accept trailing data keep
treating a literal 0x1A as the end of input.

Fixes netplex#302 (CVE-2026-113602).
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.

In-Band EOI Sentinel (0x1A) Truncates Input and Bypasses Strict JSON Validation

1 participant