Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c1f9892

Browse files
committed
Add fuzzer for collections module
1 parent 71ede86 commit c1f9892

3 files changed

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

collections.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
from fuzzeddataprovider import FuzzedDataProvider
2+
import collections
3+
4+
# Top-level fuzzer dispatch operations
5+
OP_FUZZER_COUNT_ELEMENTS = 0
6+
OP_FUZZER_DEQUE = 1
7+
OP_FUZZER_DEFAULTDICT = 2
8+
OP_FUZZER_ORDERED_DICT = 3
9+
10+
# Deque operations
11+
OP_DEQUE_APPEND = 0
12+
OP_DEQUE_APPENDLEFT = 1
13+
OP_DEQUE_POP = 2
14+
OP_DEQUE_POPLEFT = 3
15+
OP_DEQUE_EXTEND = 4
16+
OP_DEQUE_EXTENDLEFT = 5
17+
OP_DEQUE_ROTATE = 6
18+
OP_DEQUE_REVERSE = 7
19+
OP_DEQUE_COUNT = 8
20+
OP_DEQUE_INDEX = 9
21+
OP_DEQUE_REMOVE = 10
22+
OP_DEQUE_CLEAR = 11
23+
OP_DEQUE_COPY = 12
24+
OP_DEQUE_COMPARE = 13
25+
OP_DEQUE_ITERATE = 14
26+
27+
# Defaultdict operations
28+
OP_DDICT_INCREMENT = 0
29+
OP_DDICT_ACCESS = 1
30+
OP_DDICT_CONTAINS = 2
31+
OP_DDICT_POP = 3
32+
33+
# OrderedDict operations
34+
OP_ODICT_SET = 0
35+
OP_ODICT_POP = 1
36+
OP_ODICT_MOVE_TO_END = 2
37+
OP_ODICT_LIST_KEYS = 3
38+
OP_ODICT_REVERSED = 4
39+
OP_ODICT_POPITEM = 5
40+
41+
# Exercises collections._count_elements(), an internal C helper that counts
42+
# occurrences of each character in a string into a dict. Targets the
43+
# _count_elements C function which has fast-path logic for exact-dict types
44+
# vs dict subclasses.
45+
def op_count_elements(fdp):
46+
n = fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 10000)) if fdp.remaining_bytes() > 0 else 0
47+
if n == 0:
48+
return
49+
s = fdp.ConsumeBytes(n).decode('latin-1')
50+
d = {}
51+
collections._count_elements(d, s)
52+
53+
# Exercises collections.deque with an optional maxlen constraint. Runs a
54+
# sequence of fuzzed operations that exercise the deque's C implementation:
55+
# append/pop from both ends, extend/extendleft with lists, rotate, reverse,
56+
# search (count/index/remove with random-typed values for error path
57+
# coverage), clear, copy, rich comparison against a second deque, and
58+
# iteration via list()/len()/bool().
59+
def op_deque(fdp):
60+
maxlen = fdp.ConsumeIntInRange(0, 100) if fdp.ConsumeBool() else None
61+
init_n = fdp.ConsumeIntInRange(0, min(fdp.remaining_bytes(), 50))
62+
init_data = fdp.ConsumeIntList(init_n, 1)
63+
dq = collections.deque(init_data, maxlen=maxlen)
64+
num_ops = fdp.ConsumeIntInRange(1, 30)
65+
for _ in range(num_ops):
66+
if fdp.remaining_bytes() == 0:
67+
break
68+
op = fdp.ConsumeIntInRange(OP_DEQUE_APPEND, OP_DEQUE_ITERATE)
69+
if op == OP_DEQUE_APPEND:
70+
dq.append(fdp.ConsumeRandomValue())
71+
elif op == OP_DEQUE_APPENDLEFT:
72+
dq.appendleft(fdp.ConsumeRandomValue())
73+
elif op == OP_DEQUE_POP and len(dq) > 0:
74+
dq.pop()
75+
elif op == OP_DEQUE_POPLEFT and len(dq) > 0:
76+
dq.popleft()
77+
elif op == OP_DEQUE_EXTEND:
78+
n = fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 50))
79+
dq.extend(fdp.ConsumeIntList(n, 1))
80+
elif op == OP_DEQUE_EXTENDLEFT:
81+
n = fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 50))
82+
dq.extendleft(fdp.ConsumeIntList(n, 1))
83+
elif op == OP_DEQUE_ROTATE:
84+
dq.rotate(fdp.ConsumeIntInRange(-10, 10))
85+
elif op == OP_DEQUE_REVERSE:
86+
dq.reverse()
87+
elif op == OP_DEQUE_COUNT:
88+
dq.count(fdp.ConsumeRandomValue())
89+
elif op == OP_DEQUE_INDEX and len(dq) > 0:
90+
try:
91+
dq.index(fdp.ConsumeRandomValue())
92+
except ValueError:
93+
pass
94+
elif op == OP_DEQUE_REMOVE and len(dq) > 0:
95+
try:
96+
dq.remove(fdp.ConsumeRandomValue())
97+
except ValueError:
98+
pass
99+
elif op == OP_DEQUE_CLEAR:
100+
dq.clear()
101+
elif op == OP_DEQUE_COPY:
102+
dq.copy()
103+
elif op == OP_DEQUE_COMPARE:
104+
dq2 = collections.deque(fdp.ConsumeIntList(
105+
fdp.ConsumeIntInRange(0, min(fdp.remaining_bytes(), 20)), 1))
106+
_ = dq == dq2
107+
_ = dq < dq2
108+
elif op == OP_DEQUE_ITERATE:
109+
_ = list(dq)
110+
_ = len(dq)
111+
_ = bool(dq)
112+
113+
# Exercises collections.defaultdict with int as the default factory.
114+
# Runs fuzzed sequences of key increment (triggers __missing__ on new keys),
115+
# key access, containment checks, and pop operations. Keys are fuzzed
116+
# latin-1 strings so the same key may be accessed multiple times, exercising
117+
# both the hit and miss paths in the underlying dict C implementation.
118+
def op_defaultdict(fdp):
119+
dd = collections.defaultdict(int)
120+
num_ops = fdp.ConsumeIntInRange(1, 20)
121+
for _ in range(num_ops):
122+
if fdp.remaining_bytes() == 0:
123+
break
124+
op = fdp.ConsumeIntInRange(OP_DDICT_INCREMENT, OP_DDICT_POP)
125+
key = fdp.ConsumeBytes(fdp.ConsumeIntInRange(1, 10)).decode('latin-1')
126+
if op == OP_DDICT_INCREMENT:
127+
dd[key] += fdp.ConsumeInt(1)
128+
elif op == OP_DDICT_ACCESS:
129+
_ = dd[key]
130+
elif op == OP_DDICT_CONTAINS:
131+
_ = key in dd
132+
elif op == OP_DDICT_POP:
133+
dd.pop(key, None)
134+
135+
# Exercises collections.OrderedDict's C implementation (odictobject.c).
136+
# Runs fuzzed sequences of set (with random-typed values), pop,
137+
# move_to_end (with fuzzed last= direction), key listing, reversed
138+
# iteration, and popitem (with fuzzed last= direction). The key reuse
139+
# from short fuzzed strings exercises the internal linked-list
140+
# reordering logic.
141+
def op_ordered_dict(fdp):
142+
od = collections.OrderedDict()
143+
num_ops = fdp.ConsumeIntInRange(1, 20)
144+
for _ in range(num_ops):
145+
if fdp.remaining_bytes() == 0:
146+
break
147+
op = fdp.ConsumeIntInRange(OP_ODICT_SET, OP_ODICT_POPITEM)
148+
key = fdp.ConsumeBytes(fdp.ConsumeIntInRange(1, 10)).decode('latin-1')
149+
if op == OP_ODICT_SET:
150+
od[key] = fdp.ConsumeRandomValue()
151+
elif op == OP_ODICT_POP:
152+
od.pop(key, None)
153+
elif op == OP_ODICT_MOVE_TO_END:
154+
od.move_to_end(key, last=fdp.ConsumeBool()) if key in od else None
155+
elif op == OP_ODICT_LIST_KEYS:
156+
_ = list(od.keys())
157+
elif op == OP_ODICT_REVERSED:
158+
_ = list(reversed(od))
159+
elif op == OP_ODICT_POPITEM and len(od) > 0:
160+
od.popitem(last=fdp.ConsumeBool())
161+
162+
# Fuzzes the _collections C module (Modules/_collectionsmodule.c).
163+
# Exercises _count_elements() with fuzzed iterables, deque operations
164+
# (append, pop, extend, rotate, reverse, count, index, remove, copy),
165+
# defaultdict key access patterns, and OrderedDict manipulation
166+
# (set, pop, move_to_end, popitem, reversed iteration).
167+
def FuzzerRunOne(FuzzerInput):
168+
if len(FuzzerInput) < 1 or len(FuzzerInput) > 0x10000:
169+
return
170+
fdp = FuzzedDataProvider(FuzzerInput)
171+
op = fdp.ConsumeIntInRange(OP_FUZZER_COUNT_ELEMENTS, OP_FUZZER_ORDERED_DICT)
172+
try:
173+
if op == OP_FUZZER_COUNT_ELEMENTS:
174+
op_count_elements(fdp)
175+
elif op == OP_FUZZER_DEQUE:
176+
op_deque(fdp)
177+
elif op == OP_FUZZER_DEFAULTDICT:
178+
op_defaultdict(fdp)
179+
else:
180+
op_ordered_dict(fdp)
181+
except Exception:
182+
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+
collections collections.py
23
configparser configparser.py
34
csv csv.py
45
decode decode.py

0 commit comments

Comments
 (0)