Skip to content

Commit d43769c

Browse files
committed
Add fuzzer for json_encode module
1 parent 71ede86 commit d43769c

3 files changed

Lines changed: 92 additions & 2 deletions

File tree

Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
all : fuzzer-html fuzzer-email fuzzer-httpclient fuzzer-json fuzzer-difflib fuzzer-csv fuzzer-decode fuzzer-ast fuzzer-tarfile fuzzer-tarfile-hypothesis fuzzer-zipfile fuzzer-zipfile-hypothesis fuzzer-re fuzzer-configparser fuzzer-tomllib fuzzer-plistlib fuzzer-xml fuzzer-zoneinfo
1+
all : fuzzer-html fuzzer-email fuzzer-httpclient fuzzer-json fuzzer-difflib fuzzer-csv fuzzer-decode fuzzer-ast fuzzer-tarfile fuzzer-tarfile-hypothesis fuzzer-zipfile fuzzer-zipfile-hypothesis fuzzer-re fuzzer-configparser fuzzer-tomllib fuzzer-plistlib fuzzer-xml fuzzer-zoneinfo fuzzer-json-encode
22

33
PYTHON_CONFIG_PATH=$(CPYTHON_INSTALL_PATH)/bin/python3-config
44
CXXFLAGS += $(shell $(PYTHON_CONFIG_PATH) --cflags)
5-
LDFLAGS += -rdynamic $(shell $(PYTHON_CONFIG_PATH) --ldflags --embed)
5+
LDFLAGS += -rdynamic $(shell $(PYTHON_CONFIG_PATH) --ldflags --embed) $(CPYTHON_MODLIBS) -Wl,--allow-multiple-definition
66

77
fuzzer-html:
88
clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"html.py\"" -ldl $(LDFLAGS) -o fuzzer-html
@@ -40,3 +40,6 @@ fuzzer-xml:
4040
clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"xml.py\"" -ldl $(LDFLAGS) -o fuzzer-xml
4141
fuzzer-zoneinfo:
4242
clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"zoneinfo.py\"" -ldl $(LDFLAGS) -o fuzzer-zoneinfo
43+
44+
fuzzer-json-encode:
45+
clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"json_encode.py\"" -ldl $(LDFLAGS) -o fuzzer-json-encode

fuzz_targets.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ email email.py
77
html html.py
88
httpclient httpclient.py
99
json json.py
10+
json-encode json_encode.py
1011
plistlib plist.py
1112
re re.py
1213
tarfile tarfile.py

json_encode.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from fuzzeddataprovider import FuzzedDataProvider
2+
import json
3+
4+
# Container type constants for build_container
5+
CONTAINER_INT_LIST = 0
6+
CONTAINER_STRING = 1
7+
CONTAINER_DICT = 2
8+
CONTAINER_TUPLE = 3
9+
CONTAINER_FLOAT = 4
10+
CONTAINER_INT = 5
11+
12+
# Encode operation constants for FuzzerRunOne
13+
ENCODE_DEFAULT = 0
14+
ENCODE_ASCII = 1
15+
ENCODE_NON_ASCII = 2
16+
ENCODE_SORTED = 3
17+
ENCODE_INDENTED = 4
18+
ENCODE_CUSTOM = 5
19+
20+
21+
def build_container(fdp):
22+
ctype = fdp.ConsumeIntInRange(CONTAINER_INT_LIST, CONTAINER_INT)
23+
if ctype == CONTAINER_INT_LIST:
24+
n = fdp.ConsumeIntInRange(0, min(fdp.remaining_bytes(), 200))
25+
return fdp.ConsumeIntList(n, 1)
26+
elif ctype == CONTAINER_STRING:
27+
n = (
28+
fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 1000))
29+
if fdp.remaining_bytes() > 0
30+
else 0
31+
)
32+
return fdp.ConsumeBytes(n).decode("latin-1") if n > 0 else ""
33+
elif ctype == CONTAINER_DICT:
34+
n = fdp.ConsumeIntInRange(0, min(fdp.remaining_bytes(), 50))
35+
d = {}
36+
for _ in range(n):
37+
if fdp.remaining_bytes() == 0:
38+
break
39+
kn = fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 20))
40+
key = fdp.ConsumeBytes(kn).decode("latin-1")
41+
val = fdp.ConsumeRandomValue()
42+
d[key] = val
43+
return d
44+
elif ctype == CONTAINER_TUPLE:
45+
n = fdp.ConsumeIntInRange(0, min(fdp.remaining_bytes(), 200))
46+
return tuple(fdp.ConsumeIntList(n, 1))
47+
elif ctype == CONTAINER_FLOAT:
48+
return fdp.ConsumeFloat()
49+
else:
50+
return fdp.ConsumeInt(4)
51+
52+
53+
# Fuzzes the _json C module's encoding paths (Modules/_json.c).
54+
# Builds Python containers (int lists, string dicts, tuples, floats)
55+
# from fuzzed data and encodes them with json.dumps() using varied
56+
# options (ensure_ascii, sort_keys, indent) and custom JSONEncoder
57+
# settings (separators, allow_nan, default handler).
58+
def FuzzerRunOne(FuzzerInput):
59+
if len(FuzzerInput) < 1 or len(FuzzerInput) > 0x100000:
60+
return
61+
fdp = FuzzedDataProvider(FuzzerInput)
62+
target = fdp.ConsumeIntInRange(ENCODE_DEFAULT, ENCODE_CUSTOM)
63+
try:
64+
obj = build_container(fdp)
65+
if target == ENCODE_DEFAULT:
66+
json.dumps(obj)
67+
elif target == ENCODE_ASCII:
68+
json.dumps(obj, ensure_ascii=True)
69+
elif target == ENCODE_NON_ASCII:
70+
json.dumps(obj, ensure_ascii=False)
71+
elif target == ENCODE_SORTED:
72+
json.dumps(obj, sort_keys=True)
73+
elif target == ENCODE_INDENTED:
74+
indent = fdp.ConsumeIntInRange(0, 8)
75+
json.dumps(obj, indent=indent)
76+
else:
77+
enc = json.JSONEncoder(
78+
ensure_ascii=fdp.ConsumeBool(),
79+
sort_keys=fdp.ConsumeBool(),
80+
indent=fdp.ConsumeIntInRange(0, 4) if fdp.ConsumeBool() else None,
81+
)
82+
enc.encode(obj)
83+
except (ValueError, TypeError, RecursionError, OverflowError):
84+
pass
85+
except Exception:
86+
pass

0 commit comments

Comments
 (0)