Skip to content

Commit 67ea34f

Browse files
committed
Add fuzzer for operator module
1 parent 71ede86 commit 67ea34f

File tree

3 files changed

+168
-2
lines changed

3 files changed

+168
-2
lines changed

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

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+
operator operator.py
1011
plistlib plist.py
1112
re re.py
1213
tarfile tarfile.py

operator.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
from fuzzeddataprovider import FuzzedDataProvider
2+
import operator
3+
4+
MAX_LIST_SIZE = 50 # cap on generated list/sequence sizes to avoid OOM
5+
6+
# Top-level fuzzer operation targets
7+
OP_COMPARISONS = 0
8+
OP_ARITHMETIC = 1
9+
OP_UNARY = 2
10+
OP_SEQUENCE = 3
11+
OP_ITEMGETTER = 4
12+
OP_ATTRGETTER = 5
13+
OP_METHODCALLER = 6
14+
15+
# Sequence operation targets
16+
SEQ_CONTAINS = 0
17+
SEQ_COUNT_OF = 1
18+
SEQ_INDEX_OF = 2
19+
SEQ_GETITEM = 3
20+
SEQ_CONCAT = 4
21+
SEQ_SETITEM = 5
22+
SEQ_DELITEM = 6
23+
SEQ_LENGTH_HINT = 7
24+
25+
26+
def op_comparisons(fdp):
27+
a = fdp.ConsumeRandomValue()
28+
b = fdp.ConsumeRandomValue()
29+
ops = [operator.lt, operator.le, operator.gt, operator.ge, operator.eq, operator.ne]
30+
op = fdp.PickValueInList(ops)
31+
op(a, b)
32+
33+
34+
def op_arithmetic(fdp):
35+
a = fdp.ConsumeInt(4)
36+
b = fdp.ConsumeInt(4)
37+
ops = [
38+
operator.add,
39+
operator.sub,
40+
operator.mul,
41+
operator.mod,
42+
operator.floordiv,
43+
operator.truediv,
44+
operator.pow,
45+
operator.lshift,
46+
operator.rshift,
47+
operator.and_,
48+
operator.or_,
49+
operator.xor,
50+
]
51+
op = fdp.PickValueInList(ops)
52+
if op == operator.pow and isinstance(b, (int, float)):
53+
b = b % 20 if isinstance(b, int) else b
54+
if op in (operator.lshift, operator.rshift) and isinstance(b, int):
55+
b = abs(b) % 64
56+
op(a, b)
57+
58+
59+
def op_unary(fdp):
60+
a = fdp.ConsumeRandomValue()
61+
ops = [operator.neg, operator.pos, operator.abs, operator.invert, operator.index]
62+
op = fdp.PickValueInList(ops)
63+
op(a)
64+
65+
66+
def op_sequence(fdp):
67+
n = fdp.ConsumeIntInRange(1, min(fdp.remaining_bytes(), 100))
68+
lst = fdp.ConsumeIntList(n, 1)
69+
target = fdp.ConsumeIntInRange(SEQ_CONTAINS, SEQ_LENGTH_HINT)
70+
if target == SEQ_CONTAINS:
71+
operator.contains(lst, fdp.ConsumeInt(1))
72+
elif target == SEQ_COUNT_OF:
73+
operator.countOf(lst, fdp.ConsumeInt(1))
74+
elif target == SEQ_INDEX_OF:
75+
try:
76+
operator.indexOf(lst, fdp.ConsumeInt(1))
77+
except ValueError:
78+
pass
79+
elif target == SEQ_GETITEM:
80+
idx = fdp.ConsumeIntInRange(0, max(len(lst) - 1, 0))
81+
operator.getitem(lst, idx)
82+
elif target == SEQ_CONCAT:
83+
operator.concat(
84+
lst, fdp.ConsumeIntList(fdp.ConsumeIntInRange(0, MAX_LIST_SIZE), 1)
85+
)
86+
elif target == SEQ_SETITEM:
87+
idx = fdp.ConsumeIntInRange(0, max(len(lst) - 1, 0))
88+
operator.setitem(lst, idx, fdp.ConsumeInt(1))
89+
elif target == SEQ_DELITEM:
90+
idx = fdp.ConsumeIntInRange(0, max(len(lst) - 1, 0))
91+
operator.delitem(lst, idx)
92+
elif target == SEQ_LENGTH_HINT:
93+
operator.length_hint(lst)
94+
95+
96+
def op_itemgetter(fdp):
97+
n = fdp.ConsumeIntInRange(1, MAX_LIST_SIZE)
98+
lst = fdp.ConsumeIntList(n, 1)
99+
if not lst:
100+
return
101+
num_keys = fdp.ConsumeIntInRange(1, len(lst))
102+
keys = [fdp.ConsumeIntInRange(0, len(lst) - 1) for _ in range(num_keys)]
103+
getter = (
104+
operator.itemgetter(*keys) if len(keys) > 1 else operator.itemgetter(keys[0])
105+
)
106+
getter(lst)
107+
108+
109+
def op_attrgetter(fdp):
110+
class Obj:
111+
pass
112+
113+
obj = Obj()
114+
attrs = ["x", "y", "z", "w"]
115+
for a in attrs:
116+
setattr(obj, a, fdp.ConsumeInt(1))
117+
num_attrs = fdp.ConsumeIntInRange(1, len(attrs))
118+
chosen = [fdp.PickValueInList(attrs) for _ in range(num_attrs)]
119+
getter = (
120+
operator.attrgetter(*chosen)
121+
if len(chosen) > 1
122+
else operator.attrgetter(chosen[0])
123+
)
124+
getter(obj)
125+
126+
127+
def op_methodcaller(fdp):
128+
s = fdp.ConsumeUnicode(fdp.ConsumeIntInRange(1, 100))
129+
methods = ["upper", "lower", "strip", "title", "swapcase"]
130+
method = fdp.PickValueInList(methods)
131+
caller = operator.methodcaller(method)
132+
caller(s)
133+
134+
135+
# Fuzzes the _operator C module (Modules/_operator.c). Exercises
136+
# comparison operators (lt/le/gt/ge/eq/ne), arithmetic operators
137+
# (add/sub/mul/mod/div/pow/shifts/bitwise), unary operators
138+
# (neg/pos/abs/invert/index), sequence operations (contains/countOf/
139+
# indexOf/getitem/concat/setitem/delitem/length_hint), and the
140+
# itemgetter, attrgetter, and methodcaller helpers.
141+
def FuzzerRunOne(FuzzerInput):
142+
if len(FuzzerInput) < 1 or len(FuzzerInput) > 0x10000:
143+
return
144+
fdp = FuzzedDataProvider(FuzzerInput)
145+
op = fdp.ConsumeIntInRange(OP_COMPARISONS, OP_METHODCALLER)
146+
try:
147+
if op == OP_COMPARISONS:
148+
op_comparisons(fdp)
149+
elif op == OP_ARITHMETIC:
150+
op_arithmetic(fdp)
151+
elif op == OP_UNARY:
152+
op_unary(fdp)
153+
elif op == OP_SEQUENCE:
154+
op_sequence(fdp)
155+
elif op == OP_ITEMGETTER:
156+
op_itemgetter(fdp)
157+
elif op == OP_ATTRGETTER:
158+
op_attrgetter(fdp)
159+
elif op == OP_METHODCALLER:
160+
op_methodcaller(fdp)
161+
except Exception:
162+
pass

0 commit comments

Comments
 (0)