Skip to content

Commit

Permalink
Use default handler for symbolic system call arguments (#1785)
Browse files Browse the repository at this point in the history
* Use default handler for symbolic system call arguments

This will only be called when there is an implementation of the system
call in the concrete Linux class and no implementation in the symbolic
SLinux.

The action is simply to concretize any found symbolic system call
arguments.

This should hopefully ease the use of Manticore when it encounters
symbolic arguments to system calls that don't have specially implemented
symbolic handling. A debug message is printed when this happens.

* Make sure to return passed implementation

* Use rsplit instead of indexing to get owner class of method

Co-authored-by: Eric Hennenfent <eric.hennenfent@trailofbits.com>
  • Loading branch information
ekilmer and Eric Hennenfent committed Aug 6, 2020
1 parent 2347b21 commit f20a011
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 7 deletions.
12 changes: 10 additions & 2 deletions manticore/native/cpu/abstractcpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import struct
import types
from functools import wraps
from functools import wraps, partial
from itertools import islice

import unicorn
Expand Down Expand Up @@ -326,6 +326,9 @@ def get_argument_values(self, model: Callable, prefix_args: Tuple) -> Tuple:
:param prefix_args: Parameters to pass to model before actual ones
:return: Arguments to be passed to the model
"""
if type(model) is partial:
# mypy issue with partial types https://github.com/python/mypy/issues/1484
model = model.args[0] # type: ignore
sig = inspect.signature(model)
if _sig_is_varargs(sig):
model_name = getattr(model, "__qualname__", "<no name>")
Expand Down Expand Up @@ -424,8 +427,13 @@ def invoke(self, model, prefix_args=None):
# invoke() will call get_argument_values()
self._last_arguments = ()

self._cpu._publish("will_execute_syscall", model)
if type(model) is partial:
self._cpu._publish("will_execute_syscall", model.args[0])
else:
self._cpu._publish("will_execute_syscall", model)
ret = super().invoke(model, prefix_args)
if type(model) is partial:
model = model.args[0]
self._cpu._publish(
"did_execute_syscall",
model.__func__.__name__ if isinstance(model, types.MethodType) else model.__name__,
Expand Down
47 changes: 44 additions & 3 deletions manticore/platforms/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import tempfile

from abc import ABC, abstractmethod
from functools import partial

from dataclasses import dataclass
from itertools import chain

Expand All @@ -35,8 +37,7 @@
from ..native.state import State
from ..platforms.platform import Platform, SyscallNotImplemented, unimplemented

from typing import cast, Any, Deque, Dict, IO, Iterable, List, Optional, Set, Tuple, Union

from typing import cast, Any, Deque, Dict, IO, Iterable, List, Optional, Set, Tuple, Union, Callable

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -2577,13 +2578,22 @@ def syscall(self):
Syscall dispatcher.
"""

index = self._syscall_abi.syscall_number()
index: int = self._syscall_abi.syscall_number()
name: Optional[str] = None

try:
table = getattr(linux_syscalls, self.current.machine)
name = table.get(index, None)
if hasattr(self, name):
implementation = getattr(self, name)
# If this instance does not have an implementation for the system
# call, but the parent class does, use a partial function to do
# some processing of the unimplemented syscall before using the
# parent's implementation
# '.' is class separator
owner_class = implementation.__qualname__.rsplit(".", 1)[0]
if owner_class != self.__class__.__name__:
implementation = partial(self._handle_unimplemented_syscall, implementation)
else:
implementation = getattr(self.stubs, name)
except (AttributeError, KeyError):
Expand All @@ -2594,6 +2604,16 @@ def syscall(self):

return self._syscall_abi.invoke(implementation)

def _handle_unimplemented_syscall(self, impl: Callable, *args):
"""
Handle an unimplemented system call (for this class) in a generic way
before calling the implementation passed to this function.
:param impl: The real implementation
:param args: The arguments to the implementation
"""
return impl(*args)

def sys_clock_gettime(self, clock_id, timespec):
logger.warning("sys_clock_time not really implemented")
if clock_id == 1:
Expand Down Expand Up @@ -3257,6 +3277,27 @@ def _transform_write_data(self, data: MixedSymbolicBuffer) -> bytes:

# Dispatchers...

def _handle_unimplemented_syscall(self, impl: Callable, *args):
"""
Handle all unimplemented syscalls that could have symbolic arguments.
If a system call has symbolic argument values and there is no
specially implemented function to handle them, then just concretize
all symbolic arguments and call impl with args.
:param name: Name of the system call
:param args: Arguments for the system call
"""
for i, arg in enumerate(args):
if issymbolic(arg):
logger.debug(
f"Unimplemented symbolic argument to {impl.__name__}. Concretizing argument {i}"
)
raise ConcretizeArgument(self, i)

# Call the concrete Linux implementation
return impl(*args)

def sys_exit_group(self, error_code):
if issymbolic(error_code):
error_code = SelectedSolver.instance().get_value(self.constraints, error_code)
Expand Down
27 changes: 25 additions & 2 deletions tests/native/test_syscalls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import random
import struct
import socket
import tempfile
Expand All @@ -12,9 +11,10 @@
from glob import glob

from manticore.native import Manticore
from manticore.native.cpu.abstractcpu import ConcretizeRegister

from manticore.platforms import linux, linux_syscall_stubs
from manticore.platforms.linux import SymbolicSocket
from manticore.platforms.linux import SymbolicSocket, logger as linux_logger
from manticore.platforms.platform import SyscallNotImplemented, logger as platform_logger


Expand Down Expand Up @@ -403,6 +403,29 @@ def test_llseek_end_broken(self):
res = self.linux.sys_llseek(fd, 0, -2 * len(buf), resultp, os.SEEK_END)
self.assertTrue(res < 0)

def test_unimplemented_symbolic_syscall(self) -> None:
# Load a symbolic argument (address)
cpu = self.linux.current

# Store the address argument value in RDI
cpu.RDI = self.linux.constraints.new_bitvec(cpu.address_bit_size, "addr")
cpu.RAX = 12 # sys_brk

# Set logging level to debug so we can match against the message printed
# when executing our catch-all model for # functions with symbolic
# arguments
prev_log_level = linux_logger.getEffectiveLevel()
linux_logger.setLevel(logging.DEBUG)

with self.assertLogs(linux_logger, logging.DEBUG) as cm:
with self.assertRaises(ConcretizeRegister):
# Call the system call number in RAX
self.linux.syscall()
dmsg = "Unimplemented symbolic argument to sys_brk. Concretizing argument 0"
self.assertIn(dmsg, "\n".join(cm.output))

linux_logger.setLevel(prev_log_level)

def test_unimplemented_stubs(self) -> None:
stubs = linux_syscall_stubs.SyscallStubs(default_to_fail=False)

Expand Down

0 comments on commit f20a011

Please sign in to comment.