From 5b5392e54dd3a653adfdae8c09198ceaf73042cf Mon Sep 17 00:00:00 2001 From: Mark Mossberg Date: Wed, 31 May 2017 15:48:19 -0400 Subject: [PATCH] Implement user variadic model interface (#276) * Add variadic decorator * Add check for _variadic func attribute * Expose @variadic at top level * Use variable for the actual name of attr * Update naming * Use helper * Maybe more pythonic? * Add variadic to public API * Add variadic decorator tests * Clean * Remove variadic parameter * Add docstrings * Document parameter * Clean * Better use of autodoc * Update docstring * Add todo comment --- docs/api.rst | 3 ++- manticore/__init__.py | 1 + manticore/core/cpu/abstractcpu.py | 19 +++++++++++-------- manticore/core/state.py | 7 ++++--- manticore/models.py | 20 ++++++++++++++++++++ manticore/platforms/platform.py | 4 ++-- tests/test_abi.py | 7 +++++-- tests/test_models.py | 14 +++++++++++++- 8 files changed, 58 insertions(+), 17 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e406876..72a8ae4 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -7,7 +7,8 @@ This API is under active development, and should be considered unstable. Helpers ------- -.. autofunction:: manticore.issymbolic +.. automodule:: manticore + :members: issymbolic, variadic Manticore --------- diff --git a/manticore/__init__.py b/manticore/__init__.py index b372319..35a0400 100644 --- a/manticore/__init__.py +++ b/manticore/__init__.py @@ -1,2 +1,3 @@ from .manticore import Manticore +from .models import variadic from .utils.helpers import issymbolic diff --git a/manticore/core/cpu/abstractcpu.py b/manticore/core/cpu/abstractcpu.py index 66fbff6..0bdafeb 100644 --- a/manticore/core/cpu/abstractcpu.py +++ b/manticore/core/cpu/abstractcpu.py @@ -220,16 +220,16 @@ class Abi(object): yield base base += word_bytes - def invoke(self, model, prefix_args=None, varargs=False): + def invoke(self, model, prefix_args=None): ''' - Invoke a callable `model` as if it was a native function. If `varargs` - is true, model receives a single argument that is a generator for - function arguments. Pass a tuple of arguments for `prefix_args` you'd - like to precede the actual arguments. + Invoke a callable `model` as if it was a native function. If + :func:`~manticore.models.isvariadic` returns true for `model`, `model` receives a single + argument that is a generator for function arguments. Pass a tuple of + arguments for `prefix_args` you'd like to precede the actual + arguments. :param callable model: Python model of the function :param tuple prefix_args: Parameters to pass to model before actual ones - :param bool varargs: Whether the function expects a variable number of arguments :return: The result of calling `model` ''' prefix_args = prefix_args or () @@ -255,8 +255,11 @@ class Abi(object): descriptors = self.get_arguments() argument_iter = imap(resolve_argument, descriptors) + # TODO(mark) this is here as a hack to avoid circular import issues + from ...models import isvariadic + try: - if varargs: + if isvariadic(model): result = model(*(prefix_args + (argument_iter,))) else: argument_tuple = prefix_args + tuple(islice(argument_iter, nargs)) @@ -265,7 +268,7 @@ class Abi(object): assert e.argnum >= len(prefix_args), "Can't concretize a constant arg" idx = e.argnum - len(prefix_args) - # Arguments were lazily computed in case of varargs, so recompute here + # Arguments were lazily computed in case of variadic, so recompute here descriptors = self.get_arguments() src = next(islice(descriptors, idx, idx+1)) diff --git a/manticore/core/state.py b/manticore/core/state.py index 168f186..c49a05f 100644 --- a/manticore/core/state.py +++ b/manticore/core/state.py @@ -243,10 +243,11 @@ class State(object): def invoke_model(self, model): ''' Invoke a `model`. A `model` is a callable whose first argument is a - :class:`~manticore.core.state.State`, and whose following arguments correspond to - the C function being modeled. + :class:`~manticore.core.state.State`. If the `model` models a normal (non-variadic) + function, the following arguments correspond to the arguments of the C function + being modeled. If the `model` models a variadic function, the following argument + is a generator object, which can be used to access function arguments dynamically. :param callable model: Model to invoke ''' - # TODO(mark): this can't support varargs core models! self.platform.invoke_model(model, prefix_args=(self,)) diff --git a/manticore/models.py b/manticore/models.py index 9175f60..a3f4d7e 100644 --- a/manticore/models.py +++ b/manticore/models.py @@ -3,6 +3,26 @@ from .utils.helpers import issymbolic from .core.smtlib.solver import solver from .core.smtlib.operators import ITEBV, ZEXTEND +VARIADIC_FUNC_ATTR = '_variadic' + +def isvariadic(model): + """ + :param callable model: Function model + :return: Whether `model` models a variadic function + :rtype: bool + """ + return getattr(model, VARIADIC_FUNC_ATTR, False) + +def variadic(func): + """ + A decorator used to mark a function model as variadic. This function should + take two parameters: a :class:`~manticore.core.state.State` object, and + a generator object for the arguments. + + :param callable func: Function model + """ + setattr(func, VARIADIC_FUNC_ATTR, True) + return func def _find_zero(cpu, constrs, ptr): """ diff --git a/manticore/platforms/platform.py b/manticore/platforms/platform.py index 38e55a8..9374f18 100644 --- a/manticore/platforms/platform.py +++ b/manticore/platforms/platform.py @@ -9,5 +9,5 @@ class Platform(object): def __init__(self, path): self._path = path - def invoke_model(self, model, prefix_args=None, varargs=False): - self._function_abi.invoke(model, prefix_args, varargs) + def invoke_model(self, model, prefix_args=None): + self._function_abi.invoke(model, prefix_args) diff --git a/tests/test_abi.py b/tests/test_abi.py index d9430c6..a2f80c8 100644 --- a/tests/test_abi.py +++ b/tests/test_abi.py @@ -12,6 +12,7 @@ import collections import time from manticore import Manticore, issymbolic +from manticore import variadic from manticore.core.smtlib import BitVecVariable from manticore.core.cpu.abstractcpu import ConcretizeArgument, ConcretizeRegister, ConcretizeMemory from manticore.core.cpu.arm import Armv7Cpu, Armv7LinuxSyscallAbi, Armv7CdeclAbi @@ -250,11 +251,12 @@ class ABITests(unittest.TestCase): # save return cpu.push(0x1234, cpu.address_bit_size) + @variadic def test(params): for val, idx in zip(params, range(1, 4)): self.assertEqual(val, idx) - cpu.func_abi.invoke(test, varargs=True) + cpu.func_abi.invoke(test) self.assertEquals(cpu.EIP, 0x1234) @@ -333,11 +335,12 @@ class ABITests(unittest.TestCase): # save return cpu.push(0x1234, cpu.address_bit_size) + @variadic def test(params): for val, idx in zip(params, range(3)): self.assertEqual(val, idx) - cpu.func_abi.invoke(test, varargs=True) + cpu.func_abi.invoke(test) self.assertEquals(cpu.RIP, 0x1234) diff --git a/tests/test_models.py b/tests/test_models.py index d695a4a..7c5ce3e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -4,7 +4,19 @@ from manticore.core.smtlib import ConstraintSet, solver from manticore.core.state import State from manticore.platforms import linux -from manticore.models import strcmp, strlen +from manticore.models import variadic, isvariadic, strcmp, strlen + +class ModelMiscTest(unittest.TestCase): + def test_variadic_dec(self): + @variadic + def f(): + pass + self.assertTrue(isvariadic(f)) + + def test_no_variadic_dec(self): + def f(): + pass + self.assertFalse(isvariadic(f)) class ModelTest(unittest.TestCase):