Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unsound symbolication - A general approach to handle symbolic imprecisions #1526

Merged
merged 45 commits into from
Oct 31, 2019
Merged
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
4046047
Unsound symbolication and asorted fixes
feliam Sep 16, 2019
b667b98
Add test for ULE
feliam Sep 16, 2019
9665aa1
More operators tests
feliam Sep 16, 2019
d021257
remove TT
feliam Sep 16, 2019
87df083
remove TT
feliam Sep 16, 2019
ee2dea1
remove TT
feliam Sep 16, 2019
fd55973
Add test for simple sha3 concretization
feliam Sep 16, 2019
c8653b4
Add test for simple sha3 concretization
feliam Sep 16, 2019
482ed11
Add coverage to integration tests
feliam Sep 17, 2019
e2ecac1
Add coverage to integration tests
feliam Sep 17, 2019
c8f3f25
Added timeout to sha3 matching
feliam Sep 23, 2019
7422bc0
2 files reformatted
feliam Sep 23, 2019
401e06c
Review fixes
feliam Sep 24, 2019
31aa8eb
black
feliam Sep 24, 2019
a958d71
Fix KeepOnlyIfStorageChanges test
feliam Sep 24, 2019
d0099cd
remove dead code
feliam Sep 24, 2019
0b0c193
cleanups
feliam Sep 26, 2019
f7d94f3
remove unused code
feliam Sep 27, 2019
4098ee5
Blacken
Oct 1, 2019
270102c
Better concretize and calldataload
feliam Oct 1, 2019
406692d
Merge branch 'dev-unsound-symb' of github.com:trailofbits/manticore i…
feliam Oct 1, 2019
0471086
black
feliam Oct 1, 2019
1de2fbd
blkn
feliam Oct 1, 2019
31f79c4
mulmod fix
feliam Oct 2, 2019
b11bd92
Blacken
Oct 2, 2019
fa47381
Merge branch 'dev-unsound-symb' of github.com:trailofbits/manticore i…
feliam Oct 3, 2019
5ec8afc
fix calldata rel tests
feliam Oct 3, 2019
632221c
blk
feliam Oct 3, 2019
7b32c59
Merge branch 'master' into dev-unsound-symb
feliam Oct 3, 2019
f482842
Better doc- review
feliam Oct 10, 2019
870fa05
Better doc. Cleanups. sha3timeout changed
feliam Oct 11, 2019
b66dff1
Merge branch 'dev-unsound-symb' of github.com:trailofbits/manticore i…
feliam Oct 11, 2019
c3ff249
Merge branch 'master' into dev-unsound-symb
feliam Oct 12, 2019
d3a5e8a
sha3 - 42mins
feliam Oct 15, 2019
ac14ec3
Merge branch 'dev-unsound-symb' of github.com:trailofbits/manticore i…
feliam Oct 15, 2019
7893d93
blkn
feliam Oct 15, 2019
2d2a3ee
Fix spelling issues, duplicate code, and add type hints
Oct 23, 2019
4370551
Merge branch 'master' into dev-unsound-symb
Oct 23, 2019
df15457
Rm commented code
Oct 23, 2019
07edfd5
review
feliam Oct 30, 2019
47105d5
blkn
feliam Oct 30, 2019
f3db81d
Remove hardcoded conf
feliam Oct 30, 2019
b88ed27
Fix expression bool test
feliam Oct 30, 2019
4c79130
Fix sha3concrete test
feliam Oct 30, 2019
d52ccbd
Merge branch 'master' into dev-unsound-symb
feliam Oct 30, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Better concretize and calldataload
feliam committed Oct 1, 2019
commit 270102c98d120843c07198e5932761d48ce845d8
2 changes: 1 addition & 1 deletion manticore/core/smtlib/visitors.py
Original file line number Diff line number Diff line change
@@ -786,7 +786,7 @@ def to_constant(expression):

@lru_cache(maxsize=128, typed=True)
def simplify(expression):
# expression = constant_folder(expression)
expression = constant_folder(expression)
expression = arithmetic_simplify(expression)
return expression

1 change: 1 addition & 0 deletions manticore/ethereum/abi.py
Original file line number Diff line number Diff line change
@@ -253,6 +253,7 @@ def _deserialize(ty, buf, offset=0):
elif ty[0] in ("array"):
result = []
dyn_offset = ABI._deserialize_int(buf[offset : offset + 32])
dyn_offset = to_constant(dyn_offset)
rep = ty[1]
ty_size = ABI._type_size(ty[2])
if rep is None:
1 change: 0 additions & 1 deletion manticore/ethereum/detectors.py
Original file line number Diff line number Diff line change
@@ -75,7 +75,6 @@ def add_finding(self, state, address, pc, finding, at_init, constraint=True):
self.get_findings(state).append((address, pc, finding, at_init, constraint))
with self.locked_global_findings() as gf:
gf.append((address, pc, finding, at_init))

# Fixme for ever broken logger
logger.warning(finding)

3 changes: 3 additions & 0 deletions manticore/ethereum/manticore.py
Original file line number Diff line number Diff line change
@@ -1502,6 +1502,9 @@ def generate_testcase(self, state, message="", only_if=None, name="user"):
if len(local_findings):
with testcase.open_stream("findings") as findings:
for address, pc, finding, at_init, constraint in local_findings:
if not state.can_be_true(constraint):
continue
state.constrain(constraint)
findings.write("- %s -\n" % finding)
write_findings(findings, " ", address, pc, at_init)
md = self.get_metadata(address)
66 changes: 38 additions & 28 deletions manticore/platforms/evm.py
Original file line number Diff line number Diff line change
@@ -78,6 +78,11 @@ def globalsha3(data):
default=1024 * 1024,
description="Max calldata offset to explore with. Iff offset or size in a calldata related instruction are symbolic it will be constrained to this constant",
)
consts.add(
"calldata_max_size",
default = 64,
description="Max calldata size to explore in each CALLDATACOPY. Iff size in a calldata related instruction are symbolic it will be constrained to be less than this constant. -1 means free(only use when gas is being tracked)",
)

# Auxiliary constants and functions
TT256 = 2 ** 256
@@ -142,14 +147,17 @@ def __init__(
self.gas = gas
self.set_result(result, return_data)

def concretize(self, state):
conc_caller = state.solve_one(self.caller)
conc_address = state.solve_one(self.address)
conc_value = state.solve_one(self.value)
conc_gas = state.solve_one(self.gas)
conc_data = state.solve_one(self.data)
conc_return_data = state.solve_one(self.return_data)

def concretize(self, state, constrain=False):
"""
:param state: a manticore state
:param bool constrain: If True, constrain expr to concretized value
"""
conc_caller = state.solve_one(self.caller, constrain=constrain)
conc_address = state.solve_one(self.address, constrain=constrain)
conc_value = state.solve_one(self.value, constrain=constrain)
conc_gas = state.solve_one(self.gas, constrain=constrain)
conc_data = state.solve_one(self.data, constrain=constrain)
conc_return_data = state.solve_one(self.return_data, constrain=constrain)
return Transaction(
self.sort,
conc_address,
@@ -160,7 +168,7 @@ def concretize(self, state):
conc_gas,
depth=self.depth,
result=self.result,
return_data=bytearray(conc_return_data),
return_data=conc_return_data,
)

def to_dict(self, mevm):
@@ -556,8 +564,8 @@ def wrapper(*args, **kwargs):
"the value might not be tracked properly (This may affect detectors)"
)
logger.info(
f"Concretizing instruction {args[0].world.current_vm.instruction} argument {arg} by {policy}"
)
f"Concretizing instruction {args[0].world.current_vm.instruction} argument {arg} by {policy}")

raise ConcretizeArgument(index, policy=policy)
return func(*args, **kwargs)

@@ -834,7 +842,7 @@ def _get_memfee(self, address, size=1):
return Operators.ITEBV(512, size == 0, 0, Operators.ITEBV(512, flag, memfee, 0))

def _allocate(self, address, size=1):
address_c = Operators.UDIV(Operators.ZEXTEND(address, 512) + size + 31, 32) * 32
address_c = Operators.UDIV(self.safe_add(address, size, 31), 32) * 32
self._allocated = Operators.ITEBV(
512, Operators.UGT(address_c, self._allocated), address_c, self.allocated
)
@@ -1213,7 +1221,11 @@ def setstate(state, value):
raise Concretize(
"Concretize PC", expression=expression, setstate=setstate, policy="ALL"
)
# print(self)
#if self._checkpoint_data is None:
# print(self)
#else:
# print ("back from a fork")
#import pdb; pdb.set_trace()
try:
# import time
# limbo = 0.0
@@ -1334,10 +1346,12 @@ def _store(self, offset, value, size=1):
"did_evm_write_memory", offset + i, Operators.EXTRACT(value, (size - i - 1) * 8, 8)
)

def safe_add(self, a, b):
def safe_add(self, a, b, *args):
a = Operators.ZEXTEND(a, 512)
b = Operators.ZEXTEND(b, 512)
result = a + b
if len(args) > 0:
result = self.safe_add(result, *args)
return result

def safe_mul(self, a, b):
@@ -1597,22 +1611,18 @@ def CALLVALUE(self):
def CALLDATALOAD(self, offset):
"""Get input data of current environment"""
# calldata_overflow = const.calldata_overflow
# calldata_underflow = const.calldata_underflow
calldata_overflow = None # 32
calldata_underflow = None # 32
# if const.calldata_overlap:
if calldata_overflow is not None:
self.constraints.add(offset + 32 <= len(self.data) + calldata_overflow)
self.constraints.add(self.safe_add(offset, 32) <= len(self.data) + calldata_overflow)
if calldata_underflow is not None:
self.constraints.add(offset >= -calldata_underflow)

self._use_calldata(offset, 32)

data_length = len(self.data)
bytes = []
for i in range(32):
try:
c = simplify(Operators.ITEBV(8, offset + i < data_length, self.data[offset + i], 0))
c = simplify(Operators.ITEBV(8, Operators.ULT(self.safe_add(offset, i), data_length), self.data[offset + i], 0))
except IndexError:
# offset + i is concrete and outside data
c = 0
@@ -1636,17 +1646,14 @@ def CALLDATACOPY_gas(self, mem_offset, data_offset, size):
memfee = self._get_memfee(mem_offset, size)
return self.safe_add(copyfee, memfee)

@concretized_args(size="SAMPLED")
#@concretized_args(size="SAMPLED")
def CALLDATACOPY(self, mem_offset, data_offset, size):
"""Copy input data in current environment to memory"""
# calldata_overflow = const.calldata_overflow
# calldata_underflow = const.calldata_underflow
calldata_overflow = None # 32
calldata_underflow = None # 32
# if const.calldata_overlap:
if calldata_underflow is not None:
self.constraints.add(data_offset + size <= len(self.data) + calldata_overflow)
self.constraints.add(data_offset >= -calldata_underflow)
if calldata_overflow is not None:
self.constraints.add(Operators.ULT( self.safe_add(data_offset, size), len(self.data) + calldata_overflow))

self._use_calldata(data_offset, size)
self._allocate(mem_offset, size)
@@ -1658,6 +1665,9 @@ def CALLDATACOPY(self, mem_offset, data_offset, size):
raise NotEnoughGas()
self.constraints.add(cond)

if consts.calldata_max_size >= 0:
self.constraints.add(Operators.ULE(size, consts.calldata_max_size))

max_size = size
if issymbolic(max_size):
max_size = Z3Solver.instance().max(self.constraints, size)
@@ -1677,7 +1687,7 @@ def CALLDATACOPY(self, mem_offset, data_offset, size):
try:
c1 = Operators.ITEBV(
8,
data_offset + i < len(self.data),
Operators.ULT(self.safe_add(data_offset, i), len(self.data)),
Operators.ORD(self.data[data_offset + i]),
0,
)
@@ -2202,7 +2212,7 @@ def __str__(self):
gas = self.gas
if issymbolic(gas):
gas = simplify(gas)
result.append(f"Gas: {translate_to_smtlib(gas)} {gas.taint}")
result.append(f"Gas: {translate_to_smtlib(gas)[:20]} {gas.taint}")
else:
result.append(f"Gas: {gas}")