-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSimpleSymbolicFuzzer.py
104 lines (90 loc) · 3.84 KB
/
SimpleSymbolicFuzzer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import z3, inspect, ast
from fuzzingbook.Fuzzer import Fuzzer
from fuzzingbook.ControlFlow import PyCFG
from utils import used_vars, MAX_DEPTH, MAX_TRIES, MAX_ITER, to_src, checkpoint, define_symbolic_vars
# ******* *******
# ******* SAME AS FUZZING BOOK CODE *******
# ******* *******
class SimpleSymbolicFuzzer(Fuzzer):
def __init__(self, fn, **kwargs):
self.fn_name = fn.__name__
py_cfg = PyCFG()
py_cfg.gen_cfg(inspect.getsource(fn))
self.fnenter, self.fnexit = py_cfg.functions[self.fn_name]
self.used_variables = used_vars(fn)
self.fn_args = list(inspect.signature(fn).parameters)
self.z3 = z3.Solver()
self.paths = None
self.last_path = None
self.options(kwargs)
self.process()
def options(self, kwargs):
self.max_depth = kwargs.get('max_depth', MAX_DEPTH)
self.max_tries = kwargs.get('max_tries', MAX_TRIES)
self.max_iter = kwargs.get('max_iter', MAX_ITER)
self._options = kwargs
def get_all_paths(self, fenter, depth=0):
if depth > self.max_depth:
raise Exception('Maximum depth exceeded')
if not fenter.children:
return [[(0, fenter)]]
fnpaths = []
for idx, child in enumerate(fenter.children):
child_paths = self.get_all_paths(child, depth + 1)
for path in child_paths:
# In a conditional branch, idx is 0 for IF, and 1 for Else
fnpaths.append([(idx, fenter)] + path)
return fnpaths
def process(self):
self.paths = self.get_all_paths(self.fnenter)
self.last_path = len(self.paths)
def extract_constraints(self, path):
predicates = []
for (idx, elt) in path:
if isinstance(elt.ast_node, ast.AnnAssign):
if elt.ast_node.target.id in {'_if', '_while'}:
s = to_src(elt.ast_node.annotation)
predicates.append(("%s" if idx == 0 else "z3.Not%s") % s)
elif isinstance(elt.ast_node.annotation, ast.Call):
assert elt.ast_node.annotation.func.id == self.fn_name
else:
node = elt.ast_node
t = ast.Compare(node.target, [ast.Eq()], [node.value])
predicates.append(to_src(t))
elif isinstance(elt.ast_node, ast.Assign):
node = elt.ast_node
t = ast.Compare(node.targets[0], [ast.Eq()], [node.value])
predicates.append(to_src(t))
else:
pass
return predicates
def solve_path_constraint(self, path):
# re-initializing does not seem problematic.
# a = z3.Int('a').get_id() remains the same.
constraints = self.extract_constraints(path)
decl = define_symbolic_vars(self.used_variables, '')
exec(decl)
solutions = {}
with checkpoint(self.z3):
st = 'self.z3.add(%s)' % ', '.join(constraints)
eval(st)
if self.z3.check() != z3.sat:
return {}
m = self.z3.model()
solutions = {d.name(): m[d] for d in m.decls()}
my_args = {k: solutions.get(k, None) for k in self.fn_args}
predicate = 'z3.And(%s)' % ','.join(
["%s == %s" % (k, v) for k, v in my_args.items()])
eval('self.z3.add(z3.Not(%s))' % predicate)
return my_args
def get_next_path(self):
self.last_path -= 1
if self.last_path == -1:
self.last_path = len(self.paths) - 1
return self.paths[self.last_path]
def fuzz(self):
for i in range(self.max_tries):
res = self.solve_path_constraint(self.get_next_path())
if res:
return res
return {}