Reject invalid UTF-16 surrogate pairs and lone low surrogates for clarity
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (push) Successful in 54s
CI / pre-commit (push) Successful in 53s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (push) Successful in 50s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (push) Successful in 1m32s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (push) Successful in 1m24s

The string unescape logic treated any code unit in the surrogate range

(0xD800-0xDFFF) as the start of a surrogate pair. The concrete example

from issue #39 (\uDC00\uDC00) was already rejected by the existing

0x10FFFF bounds check, but the check was misleading and would accept a

lone low surrogate as a regular BMP code point if the second surrogate

happened to be in a narrower range. Clean this up so the intent is

obvious.

Changes in src/parser3.h:

- Fast path (n_string2): only treat high surrogates (0xD800-0xDBFF) as

the start of a surrogate pair, and explicitly reject lone low surrogates

(0xDC00-0xDFFF).

- Slow path (t_hex2): same high-surrogate check, with explicit rejection

of lone low surrogates.

The t_hex3 path already validates that the second code unit is a low

surrogate (0xDC00-0xDFFF), so no change is needed there.

Also commit the current fuzzer corpus.

Closes #39
This commit is contained in:
2026-06-29 13:37:58 -04:00
parent 4ce2002663
commit 08b864d31b
27 changed files with 931 additions and 2 deletions
@@ -0,0 +1,71 @@
[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ 8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{},[[[{},8,[[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[8,
[[-41.8,[-41.8,[8,[8,8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,␍9777E-1,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[83,6663,66666666,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[[[[[[-41.8,[83,666377777777777, 777728273720, 1, "1", 77777777, 1, "1", 77, 677777777777777777777777777777777777, 1, "1", 7738888888888, 1, "1", 67731, "1-", 7777777777777, 1,7777777777777777777, 1, "1", 7777777, "1", 77, 677777777777777777777777777777777777, 1, 77777777777, 1, "1", 7738888888888, 1, "1",7777777, 7777777777, 777728273720, 1, "1", 777777777777777, ,66666666,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[[
[[-418,[8,[[[
[[-41,[[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
["[-Ò418,