Skip to content

Commit f1e1751

Browse files
committed
Add fuzzer for compression module
1 parent 71ede86 commit f1e1751

3 files changed

Lines changed: 127 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-compression
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-compression:
45+
clang++ $(CXXFLAGS) $(LIB_FUZZING_ENGINE) -std=c++17 fuzzer.cpp -DPYTHON_HARNESS_PATH="\"compression.py\"" -ldl $(LDFLAGS) -o fuzzer-compression

compression.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from fuzzeddataprovider import FuzzedDataProvider
2+
import zlib
3+
import bz2
4+
import lzma
5+
6+
WBITS_CHOICES = [-15, 0, 15, 31, 47]
7+
MAX_DECOMPRESS_LEN = 1024 * 1024 # 1 MiB cap to prevent OOM from small inputs
8+
9+
OP_ZLIB_DECOMPRESS = 0
10+
OP_ZLIB_COMPRESS = 1
11+
OP_ZLIB_CHECKSUM = 2
12+
OP_BZ2_COMPRESS_DECOMPRESS = 3
13+
OP_LZMA_DECOMPRESS = 4
14+
OP_LZMA_COMPRESS = 5
15+
NUM_OPS = 6
16+
17+
def op_zlib_decompress(fdp):
18+
wbits = fdp.PickValueInList(WBITS_CHOICES)
19+
use_zdict = fdp.ConsumeBool()
20+
do_flush = fdp.ConsumeBool()
21+
do_copy = fdp.ConsumeBool()
22+
zdict = b''
23+
if use_zdict:
24+
zdict_size = fdp.ConsumeIntInRange(1, 32768)
25+
zdict = fdp.ConsumeBytes(zdict_size)
26+
data = fdp.ConsumeBytes(fdp.remaining_bytes())
27+
kwargs = {}
28+
if zdict:
29+
kwargs['zdict'] = zdict
30+
dobj = zlib.decompressobj(wbits, **kwargs)
31+
dobj.decompress(data, MAX_DECOMPRESS_LEN)
32+
if do_flush:
33+
dobj.flush()
34+
if do_copy:
35+
copy_obj = dobj.copy()
36+
copy_obj.decompress(data, MAX_DECOMPRESS_LEN)
37+
38+
def op_zlib_compress(fdp):
39+
level = fdp.ConsumeIntInRange(0, 9)
40+
use_obj = fdp.ConsumeBool()
41+
do_copy = fdp.ConsumeBool()
42+
n = fdp.ConsumeIntInRange(1, 10000)
43+
data = fdp.ConsumeBytes(n)
44+
if not data:
45+
return
46+
if use_obj:
47+
cobj = zlib.compressobj(level)
48+
cobj.compress(data)
49+
if do_copy:
50+
copy_obj = cobj.copy()
51+
copy_obj.flush()
52+
cobj.flush()
53+
else:
54+
zlib.compress(data, level)
55+
56+
def op_zlib_checksum(fdp):
57+
use_crc = fdp.ConsumeBool()
58+
data = fdp.ConsumeBytes(fdp.remaining_bytes())
59+
if use_crc:
60+
zlib.crc32(data)
61+
else:
62+
zlib.adler32(data)
63+
64+
def op_bz2(fdp):
65+
do_compress = fdp.ConsumeBool()
66+
n = fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 10000)) if fdp.remaining_bytes() > 0 else 0
67+
if n == 0:
68+
return
69+
data = fdp.ConsumeBytes(n)
70+
if do_compress:
71+
bz2.compress(data)
72+
else:
73+
dobj = bz2.BZ2Decompressor()
74+
dobj.decompress(data, MAX_DECOMPRESS_LEN)
75+
76+
def op_lzma_decompress(fdp):
77+
formats = [lzma.FORMAT_AUTO, lzma.FORMAT_XZ, lzma.FORMAT_ALONE, lzma.FORMAT_RAW]
78+
fmt = fdp.PickValueInList(formats)
79+
n = fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 10000)) if fdp.remaining_bytes() > 0 else 0
80+
if n == 0:
81+
return
82+
data = fdp.ConsumeBytes(n)
83+
kwargs = {'format': fmt, 'memlimit': 16 * 1024 * 1024}
84+
if fmt == lzma.FORMAT_RAW:
85+
kwargs['filters'] = [{'id': lzma.FILTER_LZMA2}]
86+
del kwargs['memlimit']
87+
dobj = lzma.LZMADecompressor(**kwargs)
88+
dobj.decompress(data, MAX_DECOMPRESS_LEN)
89+
90+
def op_lzma_compress(fdp):
91+
n = fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 10000)) if fdp.remaining_bytes() > 0 else 0
92+
if n == 0:
93+
return
94+
data = fdp.ConsumeBytes(n)
95+
lzma.compress(data)
96+
97+
# Fuzzes zlib, bz2, and lzma C modules (Modules/zlibmodule.c,
98+
# Modules/_bz2module.c, Modules/_lzmamodule.c). Exercises decompression
99+
# with various wbits/format settings and optional zlib dictionaries,
100+
# compression at different levels with compressobj/compress, CRC32/Adler32
101+
# checksums, and BZ2/LZMA decompressor objects with memory limits.
102+
def FuzzerRunOne(FuzzerInput):
103+
if len(FuzzerInput) < 1 or len(FuzzerInput) > 0x100000:
104+
return
105+
fdp = FuzzedDataProvider(FuzzerInput)
106+
op = fdp.ConsumeIntInRange(0, NUM_OPS - 1)
107+
try:
108+
if op == OP_ZLIB_DECOMPRESS:
109+
op_zlib_decompress(fdp)
110+
elif op == OP_ZLIB_COMPRESS:
111+
op_zlib_compress(fdp)
112+
elif op == OP_ZLIB_CHECKSUM:
113+
op_zlib_checksum(fdp)
114+
elif op == OP_BZ2_COMPRESS_DECOMPRESS:
115+
op_bz2(fdp)
116+
elif op == OP_LZMA_DECOMPRESS:
117+
op_lzma_decompress(fdp)
118+
else:
119+
op_lzma_compress(fdp)
120+
except Exception:
121+
pass

fuzz_targets.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
ast ast.py
2+
compression compression.py
23
configparser configparser.py
34
csv csv.py
45
decode decode.py

0 commit comments

Comments
 (0)