evm: Fix MUL overflow false positive (#767)

* initial

* Add test for mul checker

* clean

* Rename and clean

* Rm testing

* Better comment, and clean

* Split tests, add more tests

* Clean up stuff, remove unnecessary masking

* fmt

* Add back constrain to make it match the originall buggy situation

* Clean up surrounding code, make it unit testable

* Correct name

* Use individual arguments for helpers
This commit is contained in:
Mark Mossberg 2018-02-23 12:42:15 -08:00 committed by GitHub
parent 3f1c51fa91
commit b9aa483745
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 5 deletions

View File

@ -1,7 +1,7 @@
import string import string
from . import Manticore from . import Manticore
from .core.smtlib import ConstraintSet, Operators, solver, issymbolic, Array, Expression, Constant from .core.smtlib import ConstraintSet, Operators, solver, issymbolic, Array, Expression, Constant, operators
from .core.smtlib.visitors import arithmetic_simplifier from .core.smtlib.visitors import arithmetic_simplifier
from .platforms import evm from .platforms import evm
from .core.state import State from .core.state import State
@ -55,13 +55,30 @@ class IntegerOverflow(Detector):
''' '''
Detects potential overflow and underflow conditions on ADD and SUB instructions. Detects potential overflow and underflow conditions on ADD and SUB instructions.
''' '''
@staticmethod
def _can_add_overflow(state, result, a, b):
# TODO FIXME (mark) this is using a signed LT. need to check if this is correct
return state.can_be_true(result < a) or state.can_be_true(result < b)
@staticmethod
def _can_mul_overflow(state, result, a, b):
return state.can_be_true(operators.ULT(result, a) & operators.ULT(result, b))
@staticmethod
def _can_sub_underflow(state, a, b):
return state.can_be_true(b > a)
def did_evm_execute_instruction_callback(self, state, instruction, arguments, result): def did_evm_execute_instruction_callback(self, state, instruction, arguments, result):
mnemonic = instruction.semantics mnemonic = instruction.semantics
if mnemonic in ('ADD', 'MUL'):
if state.can_be_true(result < arguments[0]) or state.can_be_true(result < arguments[1]): if mnemonic == 'ADD':
if self._can_add_overflow(state, result, *arguments):
self.add_finding(state, "Integer overflow at {} instruction".format(mnemonic))
elif mnemonic == 'MUL':
if self._can_mul_overflow(state, result, *arguments):
self.add_finding(state, "Integer overflow at {} instruction".format(mnemonic)) self.add_finding(state, "Integer overflow at {} instruction".format(mnemonic))
elif mnemonic == 'SUB': elif mnemonic == 'SUB':
if state.can_be_true(arguments[1] > arguments[0]): if self._can_sub_underflow(state, *arguments):
self.add_finding(state, "Integer underflow at {} instruction".format(mnemonic)) self.add_finding(state, "Integer underflow at {} instruction".format(mnemonic))
class UninitializedMemory(Detector): class UninitializedMemory(Detector):

View File

@ -1,15 +1,20 @@
import unittest import unittest
import os import os
from manticore.core.smtlib import ConstraintSet, operators
from manticore.core.state import State
from manticore.ethereum import ManticoreEVM, IntegerOverflow, Detector from manticore.ethereum import ManticoreEVM, IntegerOverflow, Detector
from manticore.platforms.evm import EVMWorld
THIS_DIR = os.path.dirname(os.path.abspath(__file__)) THIS_DIR = os.path.dirname(os.path.abspath(__file__))
# FIXME(mark): Remove these two lines when logging works for ManticoreEVM # FIXME(mark): Remove these two lines when logging works for ManticoreEVM
from manticore.utils.log import init_logging from manticore.utils.log import init_logging
init_logging() init_logging()
class EthDetectors(unittest.TestCase):
class EthDetectorsIntegrationTest(unittest.TestCase):
def test_int_ovf(self): def test_int_ovf(self):
mevm = ManticoreEVM() mevm = ManticoreEVM()
mevm.register_detector(IntegerOverflow()) mevm.register_detector(IntegerOverflow())
@ -21,6 +26,50 @@ class EthDetectors(unittest.TestCase):
self.assertIn('overflow at ADD', all_findings) self.assertIn('overflow at ADD', all_findings)
self.assertIn('overflow at MUL', all_findings) self.assertIn('overflow at MUL', all_findings)
class EthDetectors(unittest.TestCase):
def setUp(self):
self.io = IntegerOverflow()
self.state = self.make_mock_evm_state()
@staticmethod
def make_mock_evm_state():
cs = ConstraintSet()
fakestate = State(cs, EVMWorld(cs))
return fakestate
def test_mul_no_overflow(self):
"""
Regression test added for issue 714, where we were using the ADD ovf check for MUL
"""
arguments = [1 << (8 * 31), self.state.new_symbolic_value(256)]
self.state.constrain(operators.ULT(arguments[1], 256))
# TODO(mark) We should actually call into the EVM cpu here, and below, rather than
# effectively copy pasting what the MUL does
result = arguments[0] * arguments[1]
check = self.io._can_mul_overflow(self.state, result, *arguments)
self.assertFalse(check)
def test_mul_overflow0(self):
arguments = [2 << (8 * 31), self.state.new_symbolic_value(256)]
self.state.constrain(operators.ULT(arguments[1], 256))
result = arguments[0] * arguments[1]
check = self.io._can_mul_overflow(self.state, result, *arguments)
self.assertTrue(check)
def test_mul_overflow1(self):
arguments = [1 << 255, self.state.new_symbolic_value(256)]
result = arguments[0] * arguments[1]
check = self.io._can_mul_overflow(self.state, result, *arguments)
self.assertTrue(check)
class EthTests(unittest.TestCase): class EthTests(unittest.TestCase):
def test_emit_did_execute_end_instructions(self): def test_emit_did_execute_end_instructions(self):
class TestDetector(Detector): class TestDetector(Detector):