def testReplace(self): old_env = env.Env(b"") new_env = old_env.clean_copy() state = State(new_env) state.storage_written[old_env.caller] = old_env.value state.replace(functools.partial(env.replace, old_env, state.env)) self.assertIs(state.storage_written[new_env.caller], new_env.value)
def test_sha3_key(self): """Exercise solidity-like mappings, with the key being a sha3.""" state = State(self.env) state_write = state.copy() # Arbitrary write input[1], at SHA3(input[0]) state_write.storage_written = { Sha3(self.env.calldata.read(4, 32)): self.env.calldata.read(36, 32) } # Needs that: storage[SHA3(input[0])] == 43, made possible by the previous call state_selfdestruct = state.copy() state_selfdestruct.selfdestruct_to = self.env.calldata.read(36, 32) storage_input = claripy.BVS("storage[SHA3(input)]", 256) state_selfdestruct.storage_read = { Sha3(self.env.calldata.read(4, 32)): storage_input } state_selfdestruct.solver.add(storage_input == 0xDEADBEEF101010) storage = { 55186156870478567193644641351382124067713781048612400765092754877653207859685: 0 } self.assertTrue( self.check_states([state_write, state_selfdestruct], mock_storage=storage) ) self.assertFalse(self.check_states([state_selfdestruct], mock_storage=storage)) self.assertFalse(self.check_states([state_write]))
def test_write_write_and_selfdestruct(self): state = State(self.env) # Anybody can set owner state_write1 = state.copy() state_write1.storage_written = {utils.bvv(0): self.env.calldata.read(4, 32)} # Onlyowner: set a magic constant allowing the selfdestruct bug, at an # user-controlled storage key. state_write2 = state.copy() read_0 = claripy.BVS("storage[0]", 256) state_write2.storage_read = {utils.bvv(0): read_0} state_write2.storage_written = { self.env.calldata.read(36, 32): self.env.calldata.read(4, 32) } state_write2.solver.add(read_0 == self.env.caller) # Suicide, when owner and magic constant set state_selfdestruct = state.copy() read_0 = claripy.BVS("storage[0]", 256) read_40 = claripy.BVS("storage[4]", 256) state_selfdestruct.storage_read = {utils.bvv(0): read_0, utils.bvv(40): read_40} state_selfdestruct.solver.add(self.env.caller == read_0) state_selfdestruct.solver.add(read_40 == 1337) state_selfdestruct.selfdestruct_to = self.env.caller states = [state_write1, state_write2, state_selfdestruct] random.shuffle(states) storage = {0: 123456789, 40: 387642} for s in itertools.combinations(states, 2): self.assertFalse(self.check_states(s, mock_storage=storage)) self.assertTrue(self.check_states(states, mock_storage=storage))
def test_sha3_value2(self): """Same as above, but we need to pass the computed SHA3.""" state = State(self.env) state_write = state.copy() state_write.storage_written = { utils.bvv(0): Sha3(self.env.calldata.read(4, 32)) } state_selfdestruct = state.copy() state_selfdestruct.selfdestruct_to = self.env.calldata.read(36, 32) storage_input = claripy.BVS("storage[0]", 256) state_selfdestruct.storage_read = {utils.bvv(0): storage_input} state_selfdestruct.solver.add( storage_input == self.env.calldata.read(4, 32)) state_selfdestruct.solver.add(storage_input != 0) storage = {0: 0} self.assertTrue( self.check_states([state_write, state_selfdestruct], mock_storage=storage)) self.assertFalse( self.check_states([state_selfdestruct], mock_storage=storage)) self.assertFalse(self.check_states([state_write], mock_storage=storage))
def test_symbolic_storage(self): """Specific test for using a storage key that cannot be symbolized.""" state = State(self.env) storage = {10: 1} # We write to an arbitrary address state_write = state.copy() state_write.storage_written[state_write.env.calldata.read( 4, 32)] = state_write.env.calldata.read(36, 32) # We send twice what we receive, but only if we have 1 at two arbitrary # keys. state_send = state.copy() storage_a = claripy.BVS("storage[a]", 256) storage_b = claripy.BVS("storage[b]", 256) k_a = state_send.env.calldata.read(4, 32) k_b = state_send.env.calldata.read(36, 32) state_send.storage_read[k_a] = storage_a state_send.storage_read[k_b] = storage_b state_send.solver.add(storage_a == 1) state_send.solver.add(storage_b == 1) state_send.calls.append(self.get_call((state.env.value * 128) / 64)) # If k_a == 10 and k_b == 10, it works! self.assertTrue(self.check_states([state_send], mock_storage=storage)) state_send.solver.add(k_a != k_b) self.assertFalse(self.check_states([state_send], mock_storage=storage)) self.assertFalse(self.check_states([state_write], mock_storage=storage)) # Now we have to first write, then send. bug = self.check_states([state_send, state_write], mock_storage=storage) self.assertTrue(bug) self.assertEqual(len(bug[1]), 2) # If we force k_a to be != 10, we can use k_b == 10 instead. state_send.solver.add(k_a != 10) bug = self.check_states([state_send, state_write], mock_storage=storage) self.assertTrue(bug) self.assertEqual(len(bug[1]), 2) # If we force both, it's impossible and we have to do two writes. state_send.solver.add(k_b != 10) bug = self.check_states([state_send, state_write], mock_storage=storage) self.assertTrue(bug) self.assertEqual(len(bug[1]), 3)
def testSameHashIfDifferentOrder(self): a = State() b = State() self.assertEqual(hash(a), hash(b)) e = env.Env(b"") a.solver.add(e.value == 1) a.solver.add(e.block_timestamp == 2) # Same thing, different order b.solver.add(e.block_timestamp == 2) b.solver.add(e.value == 1) self.assertEqual(hash(a), hash(b))
def test_write_and_selfdestruct(self): state = State(self.env) state_write = state.copy() state_write.storage_written = {utils.bvv(0): self.env.calldata.read(4, 32)} state_selfdestruct = state.copy() state_selfdestruct.selfdestruct_to = self.env.calldata.read(4, 32) storage_0 = claripy.BVS("storage[0]", 256) state_selfdestruct.storage_read = {utils.bvv(0): storage_0} state_selfdestruct.solver.add(storage_0 == 0xDEADBEEF0101) storage = {0: 0xBAD1DEA} self.assertTrue( self.check_states([state_write, state_selfdestruct], mock_storage=storage) ) self.assertFalse(self.check_states([state_selfdestruct], mock_storage=storage)) self.assertFalse(self.check_states([state_write]))
def setUp(self): self.env = Env(b"", caller=utils.DEFAULT_CALLER, address=utils.DEFAULT_ADDRESS) self.state = State(self.env) self.analyzer = Analyzer( address=self.env.address, caller=self.env.caller, max_wei_to_send=Web3.toWei(10, "ether"), min_wei_to_receive=Web3.toWei(1, "milliether"), )
def check_states(self, states, timeout, max_depth): states = [state for state in states if state.is_interesting()] if not states: return # Each state must have its own independent environment assert not self.reference_states # For each state, a list of equivalent state, but each in a different # env so that they can be stacked together. self.reference_states = [] for state in states: self.reference_states.append( [with_new_env(state) for _ in range(max_depth)]) # Add it to the paths to explore self.path_queue.append((State(), [self.reference_states[-1][0]])) # Recursive exploration last_path_len = 0 time_start = time.process_time() while self.path_queue: initial_composite_state, path = self.path_queue.popleft() if len(path) > last_path_len: logger.log( utils.INFO_INTERACTIVE, "Now scanning paths of length %i.", len(path), ) last_path_len = len(path) if len(path) > max_depth: logger.debug("Over the max allowed depth, stopping.") return if DEBUG_MARK_PATH and all( is_function(s, f) for s, f in zip(path, DEBUG_MARK_PATH)): logger.warning("DEBUG_MARK_PATH len %i", len(path)) logger.warning("path: %s", path) breakpoint() new_composite_states = self._append_state(initial_composite_state, path[-1]) for composite_state in new_composite_states: solver = self._search_path(composite_state, path) if solver is not None: return composite_state, path, solver if timeout and time.process_time() - time_start > timeout: logger.debug("Timeout at depth %i, stopping.", len(path)) return
def test_env_replace_merge_with_recursive_hash(self): old_env = env.Env(b"") new_env = old_env.clean_copy() old_state = State(old_env) old_state.solver.add(Sha3(Sha3(old_env.caller)) == Sha3(old_env.value)) self.assertTrue(old_state.solver.satisfiable()) self.assertFalse( old_state.solver.satisfiable( extra_constraints=[old_env.value == 5])) new_state = old_state.copy() new_state.replace(functools.partial(env.replace, old_env, new_env)) new_state.replace(new_state.solver.regenerate_hash_symbols()) self.assertTrue(new_state.solver.satisfiable()) self.assertFalse( new_state.solver.satisfiable( extra_constraints=[new_env.value == 5])) self.assertTrue( new_state.solver.satisfiable( extra_constraints=[old_env.value == 5])) new_state.solver.add(old_env.value == new_env.value) self.assertTrue(new_state.solver.satisfiable()) self.assertFalse( new_state.solver.satisfiable( extra_constraints=[new_env.value == 5])) self.assertFalse( new_state.solver.satisfiable( extra_constraints=[old_env.value == 5])) old_state.solver = old_state.solver.combine([new_state.solver]) self.assertTrue(new_state.solver.satisfiable()) self.assertEqual(len(old_state.solver.constraints), 3) self.assertEqual(len(old_state.solver.hashes), len(new_state.solver.hashes) * 2)
def test_sha3_value1(self): """Exercise comparison of two SHA3 (as values).""" state = State(self.env) state_write = state.copy() state_write.storage_written = { utils.bvv(0): Sha3(self.env.calldata.read(4, 32)) } state_selfdestruct = state.copy() state_selfdestruct.selfdestruct_to = self.env.calldata.read(36, 32) storage_input = claripy.BVS("storage[0]", 256) state_selfdestruct.storage_read = {utils.bvv(0): storage_input} state_selfdestruct.solver.add( storage_input == Sha3(self.env.calldata.read(4, 32)) ) storage = {0: 0} self.assertTrue( self.check_states([state_write, state_selfdestruct], mock_storage=storage) ) self.assertFalse(self.check_states([state_selfdestruct], mock_storage=storage)) self.assertFalse(self.check_states([state_write], mock_storage=storage))
def __init__(self, env): self.code = env.code logger.debug("Initializing symbolic machine with source code: %s", self.code) # For use by heapq only. Contains couples (score, state). self.branch_queue = [] self.states_seen = set() self.coverage = [0] * len(self.code) # List of all normal/good terminations of the contract self.outcomes = [] # List of all the place where we didn't know how to continue execution self.partial_outcomes = [] self.fuzz = True self.code_errors = collections.Counter() self.interpreter_errors = collections.Counter() self.add_branch(State(env))
def test_with_new_env(self): env = Env(b"") state = State(env) storage_0 = claripy.BVS("storage[0]", 256) storage_1 = claripy.BVS("storage[0]", 256) storage_2 = claripy.BVS("storage[0]", 256) state.storage_read[utils.bvv(0)] = storage_0 state.storage_read[utils.bvv(1)] = storage_1 state.storage_read[utils.bvv(2)] = storage_2 state.storage_written[utils.bvv(0)] = utils.bvv(0) state.storage_written[utils.bvv(1)] = utils.bvv(0) state.storage_written[utils.bvv(2)] = utils.bvv(0) state.calls.append([ utils.bvv(1), storage_0 + storage_1 + storage_2, utils.bvv(2), 5 * (storage_0 + storage_1 + storage_2), ]) state.solver.add(storage_0 == 42) state.solver.add(storage_1 == 0) state.solver.add(storage_2 == 0) self.assertEqual(state.solver.eval(state.calls[0][1], 2), (42, )) for i in range(3): new_state = with_new_env(state) self.assertIsNot(state.env.value, new_state.env.value) self.assertIsNot(state.storage_read[utils.bvv(0)], new_state.storage_read[utils.bvv(0)]) self.assertIsNot(state.storage_read[utils.bvv(1)], new_state.storage_read[utils.bvv(1)]) self.assertIsNot(state.storage_read[utils.bvv(2)], new_state.storage_read[utils.bvv(2)]) self.assertNotEqual(new_state.solver.eval(state.calls[0][1], 2), (42, )) self.assertEqual(new_state.solver.eval(new_state.calls[0][1], 2), (42, ))
def __init__(self, env, fuzz=True): self.code = env.code logger.debug("Initializing symbolic machine with source code: %s", self.code) # For use by heapq only. Contains couples (score, state). self.branch_queue = [] self.states_seen = set() self.coverage = [0] * len(self.code) # List of all normal/good terminations of the contract self.outcomes = [] # List of all the place where we didn't know how to continue execution self.partial_outcomes = [] # Do we want to enable fuzzing? (see add_for_fuzzing below) self.fuzz = fuzz # Did fuzzing got used? self.fuzzed = False # Errors that happened during execution. These are normal. self.code_errors = collections.Counter() # Errors of the interpreter / symbolic execution engine. Not cool :( self.interpreter_errors = collections.Counter() self.add_branch(State(env))
def testHashWorks(self): state = State(env.Env(b"")) state.pc = 5 state.memory.write(0, 1, claripy.BVV(42, 8)) state.memory.write(10, 1, claripy.BVV(43, 8)) state.memory.write(20, 1, claripy.BVV(44, 8)) state_copy = state.copy() self.assertEqual(hash(state), hash(state_copy)) state.pc = 6 self.assertNotEqual(hash(state), hash(state_copy)) state_copy.pc = 6 self.assertEqual(hash(state), hash(state_copy)) state.memory.write(10, 1, claripy.BVV(45, 8)) self.assertNotEqual(hash(state), hash(state_copy)) state_copy.memory.write(10, 1, claripy.BVV(45, 8)) self.assertEqual(hash(state), hash(state_copy)) state.stack_push(state.env.calldata.read(0, 1)) self.assertNotEqual(hash(state), hash(state_copy)) state_copy.stack_push(state_copy.env.calldata.read(0, 1)) self.assertEqual(hash(state), hash(state_copy))
def test_call_simple(self): state = State(self.env) state.calls.append(self.get_call(self.env.balance)) self.assertTrue(self.check_states([state]))
def test_selfdestruct_simple(self): state = State(self.env) state.selfdestruct_to = self.env.caller self.assertTrue(self.check_states([state]))
def test_simple(self): state = State(self.env) self.assertFalse(self.check_states([state]))
def test_send_after_write(self): state = State(self.env) # We send storage[0] state_send = state.copy() storage_0 = claripy.BVS("storage[0]", 256) state_send.storage_read = {utils.bvv(0): storage_0} state_send.calls.append(self.get_call(storage_0)) # storage[0] is 0.5 ETH storage = {0: Web3.toWei(0.5, "ether")} self.assertTrue(self.check_states([state_send], mock_storage=storage)) # storage[0] is 0 ETH storage = {0: 0} self.assertFalse(self.check_states([state_send], mock_storage=storage)) # storage[0] is still 0 ETH initially, but we have an arbitrary write now state_write = state.copy() state_write.storage_written = {utils.bvv(0): self.env.calldata.read(4, 32)} state_write.solver.add(self.env.calldata.read(0, 4) == 0x1337) state_write.solver.add(self.env.calldata.read(4, 32) < Web3.toWei(1, "ether")) self.assertFalse(self.check_states([state_write], mock_storage=storage)) self.assertTrue( self.check_states([state_send, state_write], mock_storage=storage) ) self.assertTrue( self.check_states([state_write, state_send], mock_storage=storage) ) # ...arbitrary write of 1 wei only, which is too little state_write_0 = state_write.copy() state_write_0.solver.add(self.env.calldata.read(4, 32) == 1) self.assertFalse( self.check_states([state_write_0, state_send], mock_storage=storage) ) # ...arbitrary write only if the block timestamp is <10, which is impossible. state_write_ts = state_write.copy() state_write_ts.solver.add(self.env.block_timestamp < 10) self.assertFalse( self.check_states([state_write_ts, state_send], mock_storage=storage) ) self.assertFalse( self.check_states( [state_write_0, state_send, state_write_ts], mock_storage=storage ) ) # now we put all these state_write* together, so there is a solution. self.assertTrue( self.check_states( [state_write_0, state_send, state_write, state_write_ts], mock_storage=storage, ) ) self.assertTrue( self.check_states( [state_write_0, state_write, state_write_ts, state_send], mock_storage=storage, ) )