From 4d67c71951cba36315587d9aae5c62279c8eff73 Mon Sep 17 00:00:00 2001 From: kleonyuan Date: Mon, 5 Oct 2026 22:15:43 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9E=20Reject=20unescaped=20control=20c?= =?UTF-8?q?haracters=20in=20JSON=20strings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: kleonyuan --- src/utils/JSONParserLib.sol | 2 ++ test/JSONParserLib.t.sol | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/utils/JSONParserLib.sol b/src/utils/JSONParserLib.sol index 76c3e3ff2..349a49852 100644 --- a/src/utils/JSONParserLib.sol +++ b/src/utils/JSONParserLib.sol @@ -438,6 +438,7 @@ library JSONParserLib { if iszero(eq(c, 92)) { // Not '"'. if iszero(eq(c, 34)) { + if lt(c, 0x20) { fail() } mstore8(out, c) out := add(out, 1) continue @@ -649,6 +650,7 @@ library JSONParserLib { if eq(c_, 34) { break } // '"'. // Not '\'. if iszero(eq(c_, 92)) { + if lt(c_, 0x20) { fail() } _pOut := add(_pOut, 1) continue } diff --git a/test/JSONParserLib.t.sol b/test/JSONParserLib.t.sol index 81c02de77..93ddfea79 100644 --- a/test/JSONParserLib.t.sol +++ b/test/JSONParserLib.t.sol @@ -437,6 +437,8 @@ contract JSONParserLibTest is SoladyTest { _checkParseString('"\\n"'); _checkParseString('"\\r"'); _checkParseString('"\\t"'); + _checkParseString('"\\u0000"'); + _checkParseString('"\\u001f"'); _checkParseString('" \\u1234 \\"\\"\\\\ \\b\\f \\n\\r "'); _checkParseString('"\\u1234"'); _checkParseString('"\\uabcd"'); @@ -481,6 +483,13 @@ contract JSONParserLibTest is SoladyTest { _checkParseReverts('"\\u1234'); } + function testParseUnescapedControlCharactersRevert() public { + for (uint256 i; i < 0x20; ++i) { + string memory s = string(abi.encodePacked('"a', bytes1(uint8(i)), 'b"')); + _checkParseReverts(s); + } + } + function _checkItemIsSolo(JSONParserLib.Item memory item) internal { _checkItemHasNoParent(item); assertEq(item.size(), 0); @@ -723,6 +732,8 @@ contract JSONParserLibTest is SoladyTest { assertEq(this.decodeString('"\\r"'), "\r"); assertEq(this.decodeString('"\\t"'), "\t"); assertEq(this.decodeString('"\\u0020"'), " "); + assertEq(this.decodeString('"\\u0000"'), hex"00"); + assertEq(this.decodeString('"\\u001f"'), hex"1f"); bytes32 expectedHash; expectedHash = 0x40b2b6558413427ef2da03b1452640d701458e0ce57114db6b7423ae3b5fe857; assertEq(keccak256(bytes(this.decodeString('"\\u039e"'))), expectedHash); // Greek uppercase Xi. @@ -764,6 +775,13 @@ contract JSONParserLibTest is SoladyTest { _checkDecodeInvalidStringReverts('"\\uD83D"'); // Only half of a Smiley emoji. } + function testDecodeUnescapedControlCharactersRevert() public { + for (uint256 i; i < 0x20; ++i) { + string memory s = string(abi.encodePacked('"a', bytes1(uint8(i)), 'b"')); + _checkDecodeInvalidStringReverts(s); + } + } + function _checkDecodeInvalidStringReverts(string memory s) internal { vm.expectRevert(JSONParserLib.ParsingFailed.selector); this.decodeString(s);