diff --git a/manticore/core/cpu/abstractcpu.py b/manticore/core/cpu/abstractcpu.py index cf728e0..bfc89e2 100644 --- a/manticore/core/cpu/abstractcpu.py +++ b/manticore/core/cpu/abstractcpu.py @@ -756,10 +756,21 @@ class Cpu(Eventful): for l in self.render_registers(): register_logger.debug(l) - implementation(*insn.operands) - self._icount += 1 + #FIXME(yan): In the case the instruction implementation invokes a system call, we would not be able to + # publish the did_execute_instruction event from here, so we capture and attach it to the syscall + # exception for the platform to emit it for us once the syscall has successfully been executed. + def did_exec(): + self._icount += 1 + self._publish('did_execute_instruction', self._last_pc, self.PC, insn) + + try: + implementation(*insn.operands) + except (Interruption, Syscall) as e: + e.on_handled = did_exec + raise e + else: + did_exec() - self._publish('did_execute_instruction', self._last_pc, self.PC, insn) def emulate(self, insn): ''' diff --git a/manticore/core/state.py b/manticore/core/state.py index 0bcde5f..64ad78b 100644 --- a/manticore/core/state.py +++ b/manticore/core/state.py @@ -192,7 +192,7 @@ class State(Eventful): introduce it into the program state. :param int nbytes: Length of the new buffer - :param str name: (keyword arg only) The name to assign to the buffer + :param str label: (keyword arg only) The label to assign to the buffer :param bool cstring: (keyword arg only) Whether or not to enforce that the buffer is a cstring (i.e. no \0 bytes, except for the last byte). (bool) :param taint: Taint identifier of the new buffer @@ -200,9 +200,9 @@ class State(Eventful): :return: :class:`~manticore.core.smtlib.expression.Expression` representing the buffer. ''' - name = options.get('label', 'buffer') + label = options.get('label', 'buffer') taint = options.get('taint', frozenset()) - expr = self._constraints.new_array(name=name, index_max=nbytes, taint=taint) + expr = self._constraints.new_array(name=label, index_max=nbytes, taint=taint) self._input_symbols.append(expr) if options.get('cstring', False): diff --git a/manticore/platforms/linux.py b/manticore/platforms/linux.py index b1a16cb..32884be 100644 --- a/manticore/platforms/linux.py +++ b/manticore/platforms/linux.py @@ -18,7 +18,7 @@ from ..core.memory import SMemory32, SMemory64, Memory32, Memory64 from ..core.smtlib import Operators, ConstraintSet, SolverException, solver from ..core.cpu.arm import * from ..core.executor import TerminateState -from ..platforms.platform import Platform +from ..platforms.platform import Platform, SyscallNotImplemented from ..utils.helpers import issymbolic, is_binja_disassembler from . import linux_syscalls @@ -39,6 +39,9 @@ def perms_from_elf(elf_flags): def perms_from_protflags(prot_flags): return [' ', 'r ', ' w ', 'rw ', ' x', 'r x', ' wx', 'rwx'][prot_flags&7] +def mode_from_flags(file_flags): + return {os.O_RDWR: 'r+', os.O_RDONLY: 'r', os.O_WRONLY: 'w'}[file_flags&7] + class File(object): def __init__(self, *args, **kwargs): @@ -747,7 +750,7 @@ class Linux(Platform): elf = self.elf arch = self.arch addressbitsize = {'x86':32, 'x64':64, 'ARM': 32}[elf.get_machine_arch()] - logger.debug("Loading %s as a %s elf"%(filename, arch)) + logger.debug("Loading %s as a %s elf",filename, arch) assert elf.header.e_type in ['ET_DYN', 'ET_EXEC', 'ET_CORE'] @@ -839,8 +842,8 @@ class Linux(Platform): #cpu.write_bytes(elf_bss, '\x00'*((elf_bss | (align-1))-elf_bss)) logger.debug("Zeroing main elf fractional pages. From %x to %x.", elf_bss, elf_brk) - logger.debug("Main elf bss:%x"%elf_bss) - logger.debug("Main elf brk %x:"%elf_brk) + logger.debug("Main elf bss:%x",elf_bss) + logger.debug("Main elf brk %x:",elf_brk) #FIXME Need a way to inspect maps and perms so #we can rollback all to the initial state after zeroing @@ -932,7 +935,7 @@ class Linux(Platform): try: cpu.memory[elf_bss:elf_brk] = '\x00'*(elf_brk-elf_bss) except Exception, e: - logger.debug("Exception zeroing Interpreter fractional pages: %s"%str(e)) + logger.debug("Exception zeroing Interpreter fractional pages: %s",str(e)) #TODO #FIXME mprotect as it was before zeroing? @@ -1025,6 +1028,17 @@ class Linux(Platform): else: return self.files[fd] + def _transform_write_data(self, data): + ''' + Implement in subclass to transform data written by write(2)/writev(2) + + Nop by default. + :param list data: Anything being written to a file descriptor + :rtype list + :return: Transformed data + ''' + return data + def sys_umask(self, mask): ''' umask - Set file creation mode mask @@ -1166,6 +1180,7 @@ class Linux(Platform): raise RestartSyscall() data = cpu.read_bytes(buf, count) + data = self._transform_write_data(data) write_fd.write(data) for line in ''.join([str(x) for x in data]).split('\n'): @@ -1279,8 +1294,8 @@ class Linux(Platform): else: return -errno.EINVAL - def _sys_open_get_file(self, filename, flags, mode): - f = File(filename, mode) # TODO (theo) modes, flags + def _sys_open_get_file(self, filename, flags): + f = File(filename, mode_from_flags(flags)) return f def sys_open(self, buf, flags, mode): @@ -1296,15 +1311,16 @@ class Linux(Platform): filename = os.path.abspath(self.program) else: logger.info("FIXME!") - mode = {os.O_RDWR: 'r+', os.O_RDONLY: 'r', os.O_WRONLY: 'w'}[flags&7] - f = self._sys_open_get_file(filename, flags, mode) - logger.debug("Opening file %s for %s real fd %d", - filename, mode, f.fileno()) - # FIXME(theo) generic exception - except Exception as e: - logger.info("Could not open file %s. Reason %s" % (filename, str(e))) - return -1 + f = self._sys_open_get_file(filename, flags) + logger.debug("Opening file %s for real fd %d", + filename, f.fileno()) + except IOError as e: + logger.info("Could not open file %s. Reason: %s", filename, str(e)) + if e.errno is not None: + return -e.errno + else: + return -errno.EINVAL return self._open(f) @@ -1650,6 +1666,12 @@ class Linux(Platform): ptrsize = cpu.address_bit_size sizeof_iovec = 2 * (ptrsize // 8) total = 0 + try: + write_fd = self._get_fd(fd) + except BadFd: + logger.error("writev: Not a valid file descriptor ({})".format(fd)) + return -errno.EBADF + for i in xrange(0, count): buf = cpu.read_int(iov + i * sizeof_iovec, ptrsize) size = cpu.read_int(iov + i * sizeof_iovec + (sizeof_iovec // 2), ptrsize) @@ -1657,8 +1679,9 @@ class Linux(Platform): data = "" for j in xrange(0,size): data += Operators.CHR(cpu.read_int(buf + j, 8)) - logger.debug("WRITEV(%r, %r, %r) -> <%r> (size:%r)"%(fd, buf, size, data, len(data))) - self.files[fd].write(data) + logger.debug("WRITEV(%r, %r, %r) -> <%r> (size:%r)",fd, buf, size, data, len(data)) + data = self._transform_write_data(data) + write_fd.write(data) self.syscall_trace.append(("_write", fd, data)) total+=size return total @@ -1874,7 +1897,10 @@ class Linux(Platform): name = table.get(index, None) implementation = getattr(self, name) except (AttributeError, KeyError): - raise Exception("SyscallNotImplemented %d %d"%(self.current.address_bit_size, index)) + if name is not None: + raise SyscallNotImplemented(index, name) + else: + raise Exception("Bad syscall index, {}".format(index)) return self._syscall_abi.invoke(implementation) @@ -2029,9 +2055,11 @@ class Linux(Platform): if self.clocks % 10000 == 0: self.check_timers() self.sched() - except (Interruption, Syscall): + except (Interruption, Syscall) as e: try: self.syscall() + if hasattr(e, 'on_handled'): + e.on_handled() except RestartSyscall: pass @@ -2316,17 +2344,30 @@ class SLinux(Linux): self.symbolic_files = state['symbolic_files'] super(SLinux, self).__setstate__(state) - def _sys_open_get_file(self, filename, flags, mode): + def _sys_open_get_file(self, filename, flags): if filename in self.symbolic_files: logger.debug("%s file is considered symbolic", filename) - assert flags & 7 == os.O_RDWR or flags & 7 == os.O_RDONLY, ( - "Symbolic files should be readable?") - f = SymbolicFile(self.constraints, filename, mode) + f = SymbolicFile(self.constraints, filename, mode_from_flags(flags)) else: - f = super(SLinux, self)._sys_open_get_file(filename, flags, mode) + f = super(SLinux, self)._sys_open_get_file(filename, flags) return f + + def _transform_write_data(self, data): + bytes_concretized = 0; + concrete_data = [] + for c in data: + if issymbolic(c): + bytes_concretized += 1 + c = chr(solver.get_value(self.constraints, c)) + concrete_data.append(c) + + if bytes_concretized > 0: + logger.debug("Concretized {} written bytes.".format(bytes_concretized)) + + return super(SLinux, self)._transform_write_data(concrete_data) + #Dispatchers... def sys_read(self, fd, buf, count): diff --git a/manticore/platforms/platform.py b/manticore/platforms/platform.py index db5ba93..72bef92 100644 --- a/manticore/platforms/platform.py +++ b/manticore/platforms/platform.py @@ -11,7 +11,9 @@ class SyscallNotImplemented(OSException): ''' Exception raised when you try to call a not implemented system call. Go to linux.py and add it! ''' - pass + def __init__(self, idx, name): + msg = 'Syscall index "{}" ({}) not implemented.'.format(idx, name) + super(SyscallNotImplemented, self).__init__(msg) class ConcretizeSyscallArgument(OSException): def __init__(self, reg_num, message='Concretizing syscall argument', policy='SAMPLED'): @@ -20,7 +22,6 @@ class ConcretizeSyscallArgument(OSException): self.policy = policy super(ConcretizeSyscallArgument, self).__init__(message) - class Platform(Eventful): ''' Base class for all operating system platforms. diff --git a/tests/test_linux.py b/tests/test_linux.py index 5f486bc..95465fa 100644 --- a/tests/test_linux.py +++ b/tests/test_linux.py @@ -90,3 +90,41 @@ class LinuxTest(unittest.TestCase): self.assertIn('stderr', files) self.assertIn('net', files) + def test_syscall_events(self): + nr_fstat64 = 197 + + class Receiver(object): + def __init__(self): + self.nevents = 0 + def will_exec(self, pc, i): + self.nevents += 1 + def did_exec(self, last_pc, pc, i): + self.nevents += 1 + + # Create a minimal state + model = self.symbolic_linux + model.current.memory.mmap(0x1000, 0x1000, 'rw ') + model.current.SP = 0x2000-4 + model.current.memory.mmap(0x2000, 0x2000, 'rwx') + model.current.PC = 0x2000 + model.current.write_int(model.current.PC, 0x050f) + + r = Receiver() + model.current.subscribe('will_execute_instruction', r.will_exec) + model.current.subscribe('did_execute_instruction', r.did_exec) + + filename = model.current.push_bytes('/bin/true\x00') + fd = model.sys_open(filename, os.O_RDONLY, 0600) + + stat = model.current.SP - 0x100 + model.current.R0 = fd + model.current.R1 = stat + model.current.R7 = nr_fstat64 + self.assertEquals(linux_syscalls.armv7[nr_fstat64], 'sys_fstat64') + + pre_icount = model.current.icount + model.execute() + post_icount = model.current.icount + + self.assertEquals(pre_icount+1, post_icount) + self.assertEquals(r.nevents, 2)