1061 lines
40 KiB
Python
1061 lines
40 KiB
Python
import cgcrandom
|
|
import weakref
|
|
import sys, os, struct
|
|
# TODO use cpu factory
|
|
from core.cpu.x86 import I386Cpu
|
|
from core.cpu.abstractcpu import Interruption, Syscall, ConcretizeRegister
|
|
from core.memory import SMemory32
|
|
from core.smtlib import *
|
|
from binary import CGCElf
|
|
from binary import CGCGrr
|
|
from contextlib import closing
|
|
import StringIO
|
|
import logging
|
|
import random
|
|
from core.executor import SyscallNotImplemented, ProcessExit, Deadlock, RestartSyscall
|
|
logger = logging.getLogger("DECREE")
|
|
|
|
|
|
class SymbolicSyscallArgument(ConcretizeRegister):
|
|
def __init__(self, number, message='Concretizing syscall argument', policy='SAMPLED'):
|
|
reg_name = ['EBX', 'ECX', 'EDX', 'ESI', 'EDI', 'EBP' ][number]
|
|
super(SymbolicSyscallArgument, self).__init__(reg_name, message, policy)
|
|
|
|
class Socket(object):
|
|
@staticmethod
|
|
def pair():
|
|
a = Socket()
|
|
b = Socket()
|
|
a.connect(b)
|
|
return a,b
|
|
|
|
def __init__(self):
|
|
self.buffer = [] #queue os bytes
|
|
self.peer = None
|
|
|
|
def __repr__(self):
|
|
return "SOCKET(%x, %r, %x)"%(hash(self), self.buffer, hash(self.peer))
|
|
|
|
def is_connected(self):
|
|
return self.peer is not None
|
|
|
|
def is_empty(self):
|
|
return len(self.buffer)==0
|
|
|
|
def is_full(self):
|
|
return len(self.buffer)>2*1024
|
|
|
|
def connect(self, peer):
|
|
assert not self.is_connected()
|
|
assert not peer.is_connected()
|
|
self.peer = peer
|
|
if peer.peer is None:
|
|
peer.peer = self
|
|
|
|
def receive(self, size):
|
|
rx_bytes = min(size, len(self.buffer))
|
|
ret = []
|
|
for i in xrange(rx_bytes):
|
|
ret.append(self.buffer.pop())
|
|
return ret
|
|
|
|
def transmit(self, buf):
|
|
assert self.is_connected()
|
|
return self.peer._transmit(buf)
|
|
|
|
def _transmit(self, buf):
|
|
for c in buf:
|
|
self.buffer.insert(0,c)
|
|
return len(buf)
|
|
|
|
|
|
class Decree(object):
|
|
'''
|
|
A simple Decree Operating System Model.
|
|
This class emulates the most common Decree system calls
|
|
'''
|
|
CGC_EBADF=1
|
|
CGC_EFAULT=2
|
|
CGC_EINVAL=3
|
|
CGC_ENOMEM=4
|
|
CGC_ENOSYS=5
|
|
CGC_EPIPE=6
|
|
CGC_SSIZE_MAX=2147483647
|
|
CGC_SIZE_MAX=4294967295
|
|
CGC_FD_SETSIZE=32
|
|
|
|
def __init__(self, programs):
|
|
'''
|
|
Builds a Decree OS model
|
|
@param cpus: CPU for this model.
|
|
@param mem: memory for this model.
|
|
@todo: generalize for more CPUs.
|
|
@todo: fix deps?
|
|
'''
|
|
programs = programs.split(",")
|
|
self.clocks = 0
|
|
self.files = []
|
|
self.syscall_trace = []
|
|
|
|
self.files = []
|
|
# open standard files stdin, stdout, stderr
|
|
logger.info("Opening file descriptors (0,1,2)")
|
|
self.input = Socket()
|
|
self.output = Socket()
|
|
|
|
stdin = Socket()
|
|
stdout = Socket()
|
|
stderr = Socket()
|
|
#A transmit to stdin,stdout or stderr will be directed to out
|
|
stdin.peer = self.output
|
|
stdout.peer = self.output
|
|
stderr.peer = self.output
|
|
#A receive from stdin will get data from inp
|
|
self.input.peer = stdin
|
|
#A receive on stdout or stderr will return no data (rx_bytes: 0)
|
|
|
|
assert self._open(stdin) == 0
|
|
assert self._open(stdout) == 1
|
|
assert self._open(stderr) == 2
|
|
|
|
#Load process and setup socketpairs
|
|
self.procs = []
|
|
for program in programs:
|
|
self.procs += self.load(program)
|
|
socka,sockb = Socket.pair()
|
|
self._open(socka)
|
|
self._open(sockb)
|
|
|
|
nprocs = len(self.procs)
|
|
nfiles = len(self.files)
|
|
assert nprocs > 0
|
|
self.running = range(nprocs)
|
|
self._current = 0
|
|
|
|
#Each process can wait for one timeout
|
|
self.timers = [ None ] * nprocs
|
|
#each fd has a waitlist
|
|
self.rwait = [set() for _ in xrange(nfiles)]
|
|
self.twait = [set() for _ in xrange(nfiles)]
|
|
|
|
|
|
def _mk_proc(self):
|
|
return I386Cpu(Memory32())
|
|
|
|
@property
|
|
def current(self):
|
|
return self.procs[self._current]
|
|
|
|
def __getstate__(self):
|
|
state = {}
|
|
state['clocks'] = self.clocks
|
|
state['input'] = self.input.buffer
|
|
state['output'] = self.output.buffer
|
|
state['files'] = [x.buffer for x in self.files]
|
|
state['procs'] = self.procs
|
|
state['current'] = self._current
|
|
state['running'] = self.running
|
|
state['rwait'] = self.rwait
|
|
state['twait'] = self.twait
|
|
state['timers'] = self.timers
|
|
|
|
state['syscall_trace'] = self.syscall_trace
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
"""
|
|
@todo: some asserts
|
|
@todo: fix deps? (last line)
|
|
"""
|
|
self.input = Socket()
|
|
self.input.buffer = state['input']
|
|
self.output = Socket()
|
|
self.output.buffer = state['output']
|
|
|
|
self.files = []
|
|
for buf in state['files']:
|
|
f = Socket()
|
|
f.buffer = buf
|
|
self.files.append(f)
|
|
for fd in range(len(self.files)):
|
|
if self.connections(fd) is not None:
|
|
self.files[fd].peer = self.files[self.connections(fd)]
|
|
|
|
self.files[0].peer = self.output
|
|
self.files[1].peer = self.output
|
|
self.files[2].peer = self.output
|
|
self.input.peer = self.files[0]
|
|
|
|
self.procs = state['procs']
|
|
self._current = state['current']
|
|
self.running = state['running']
|
|
self.rwait = state['rwait']
|
|
self.twait = state['twait']
|
|
self.timers = state['timers']
|
|
self.clocks = state['clocks']
|
|
|
|
self.syscall_trace = state['syscall_trace']
|
|
|
|
def _read_string(self, cpu, buf):
|
|
"""
|
|
Reads a null terminated concrete buffer form memory
|
|
@todo: FIX. move to cpu or memory
|
|
"""
|
|
filename = ""
|
|
for i in xrange(0,1024):
|
|
c = Operators.CHR(cpu.read_int(buf+i,8))
|
|
if c == '\x00':
|
|
break
|
|
filename += c
|
|
return filename
|
|
|
|
|
|
def load(self, filename):
|
|
magic = file(filename).read(4)
|
|
if magic == '\x7fCGC':
|
|
return self._load_cgc(filename)
|
|
else:
|
|
assert magic == 'GRRS'
|
|
return self._load_grr(filename)
|
|
|
|
def _load_grr(self, filename):
|
|
'''
|
|
Loads a GRR CGC snapshot in memory and restores the CPU state.
|
|
@param filename: pathname of the file to be executed.
|
|
'''
|
|
grr = CGCGrr(filename)
|
|
logger.info("Loading %s grr snapshot", filename)
|
|
|
|
#make cpu and memory (Only 1 thread in Grr)
|
|
cpu = self._mk_proc()
|
|
for (vaddr, memsz, perms, name, offset, filesz) in grr.maps():
|
|
addr = cpu.memory.mmapFile(vaddr, memsz, perms, name, offset)
|
|
assert addr == vaddr, "Overlapping maps!?"
|
|
#Only one thread in Decree
|
|
status, thread = next(grr.threads())
|
|
assert status == 'Running'
|
|
|
|
logger.info("Restoring cpu state from snapshot")
|
|
#set initial CPU state
|
|
for reg in thread:
|
|
cpu.write_register(reg, thread[reg])
|
|
return [cpu]
|
|
|
|
|
|
def _load_cgc(self, filename):
|
|
'''
|
|
Loads a CGC-ELF program in memory and prepares the initial CPU state
|
|
and the stack.
|
|
@param filename: pathname of the file to be executed.
|
|
'''
|
|
CGC_MIN_PAGE_SIZE = 4096
|
|
CGC_MIN_ALIGN = CGC_MIN_PAGE_SIZE
|
|
TASK_SIZE=0x80000000
|
|
|
|
def CGC_PAGESTART(_v):
|
|
return ((_v) & ~ (CGC_MIN_ALIGN-1))
|
|
def CGC_PAGEOFFSET(_v):
|
|
return ((_v) & (CGC_MIN_ALIGN-1))
|
|
def CGC_PAGEALIGN(_v):
|
|
return (((_v) + CGC_MIN_ALIGN - 1) & ~(CGC_MIN_ALIGN - 1))
|
|
def BAD_ADDR(x):
|
|
return ((x) >= TASK_SIZE)
|
|
|
|
#load elf See https://github.com/CyberdyneNYC/linux-source-3.13.2-cgc/blob/master/fs/binfmt_cgc.c
|
|
#read the ELF object file
|
|
cgc = CGCElf(filename)
|
|
logger.info("Loading %s as a %s elf"%(filename, cgc.arch))
|
|
#make cpu and memory (Only 1 thread in Decree)
|
|
cpu = self._mk_proc()
|
|
|
|
bss = brk = 0
|
|
start_code = 0xffffffff
|
|
end_code = start_data = end_data = 0
|
|
|
|
for (vaddr, memsz, perms, name, offset, filesz) in cgc.maps():
|
|
if vaddr < start_code:
|
|
start_code = vaddr
|
|
if start_data < vaddr:
|
|
start_data = vaddr
|
|
|
|
if vaddr > TASK_SIZE or filesz > memsz or \
|
|
memsz > TASK_SIZE or TASK_SIZE - memsz < vaddr :
|
|
raise Exception("Set_brk can never work. avoid overflows")
|
|
|
|
#CGCMAP--
|
|
addr = None
|
|
if filesz > 0:
|
|
hint = CGC_PAGESTART(vaddr)
|
|
size = CGC_PAGEALIGN(filesz + CGC_PAGEOFFSET(vaddr))
|
|
offset = CGC_PAGESTART(offset)
|
|
addr = cpu.memory.mmapFile(hint, size, perms, name, offset)
|
|
assert not BAD_ADDR(addr)
|
|
|
|
lo = CGC_PAGEALIGN(vaddr + filesz)
|
|
hi = CGC_PAGEALIGN(vaddr + memsz)
|
|
else:
|
|
# for 0 filesz, we have to include the first page as bss.
|
|
lo = CGC_PAGESTART(vaddr + filesz)
|
|
hi = CGC_PAGEALIGN(vaddr + memsz)
|
|
|
|
|
|
#map anon pages for the rest (no prefault)
|
|
if hi - lo > 0:
|
|
zaddr = cpu.memory.mmap(lo, hi-lo,perms)
|
|
assert not BAD_ADDR(zaddr)
|
|
|
|
lo = vaddr + filesz
|
|
hi = CGC_PAGEALIGN(vaddr + memsz)
|
|
if hi - lo > 0 :
|
|
old_perms = cpu.memory.perms(lo)
|
|
cpu.memory.mprotect(lo, hi-lo, 'rw')
|
|
try:
|
|
cpu.memory[lo:hi] = '\x00' *(hi-lo)
|
|
except Exception, e:
|
|
logger.debug("Exception zeroing main elf fractional pages: %s"%str(e))
|
|
cpu.memory.mprotect(lo, hi, old_perms)
|
|
|
|
if addr is None:
|
|
addr = zaddr
|
|
assert addr is not None
|
|
|
|
|
|
k = vaddr + filesz
|
|
if k > bss:
|
|
bss = k
|
|
if 'x' in perms and end_code < k:
|
|
end_code = k
|
|
if end_data < k:
|
|
end_data = k
|
|
|
|
k = vaddr + memsz
|
|
if k > brk:
|
|
brk = k
|
|
|
|
|
|
bss = brk
|
|
stack_base = 0xbaaaaffc
|
|
stack_size = 0x800000
|
|
stack = cpu.memory.mmap(0xbaaab000-stack_size, stack_size,'rwx')+stack_size-4
|
|
assert (stack_base) in cpu.memory and (stack_base-stack_size+4) in cpu.memory
|
|
|
|
#Only one thread in Decree
|
|
status, thread = next(cgc.threads())
|
|
assert status == 'Running'
|
|
|
|
logger.info("Setting initial cpu state")
|
|
#set initial CPU state
|
|
cpu.write_register('EAX', 0x0)
|
|
cpu.write_register('ECX', 0x0)
|
|
cpu.write_register('EDX', 0x0)
|
|
cpu.write_register('EBX', 0x0)
|
|
cpu.write_register('ESP', stack)
|
|
cpu.write_register('EBP', 0x0)
|
|
cpu.write_register('ESI', 0x0)
|
|
cpu.write_register('EDI', 0x0)
|
|
cpu.write_register('EIP', thread['EIP'])
|
|
cpu.write_register('RFLAGS', 0x202)
|
|
cpu.write_register('CS', 0x0)
|
|
cpu.write_register('SS', 0x0)
|
|
cpu.write_register('DS', 0x0)
|
|
cpu.write_register('ES', 0x0)
|
|
cpu.write_register('FS', 0x0)
|
|
cpu.write_register('GS', 0x0)
|
|
|
|
cpu.memory.mmap(0x4347c000,0x1000, 'r')
|
|
#cpu.memory[0x4347c000:0x4347d000] = 'A' 0x1000
|
|
|
|
logger.info("Entry point: %016x", cpu.EIP)
|
|
logger.info("Stack start: %016x", cpu.ESP)
|
|
logger.info("Brk: %016x", brk)
|
|
logger.info("Mappings:")
|
|
for m in str(cpu.memory).split('\n'):
|
|
logger.info(" %s", m)
|
|
return [cpu]
|
|
|
|
def _open(self, f):
|
|
'''
|
|
It opens a file on the given a file descriptor
|
|
@rtype: int
|
|
@param filename: pathname of the file to open.
|
|
@param mode: file permissions mode.
|
|
@return: a file description of the opened file.
|
|
'''
|
|
if None in self.files:
|
|
fd = self.files.index(None)
|
|
self.files[fd]=f
|
|
else:
|
|
fd = len(self.files)
|
|
self.files.append(f)
|
|
return fd
|
|
|
|
def _close(self, fd):
|
|
'''
|
|
Closes a file descriptor
|
|
@rtype: int
|
|
@param fd: the file descriptor to close.
|
|
@return: C{0} on success.
|
|
'''
|
|
self.files[fd] = None
|
|
|
|
def _dup(self, fd):
|
|
'''
|
|
Duplicates a file descriptor
|
|
@rtype: int
|
|
@param fd: the file descriptor to close.
|
|
@return: C{0} on success.
|
|
'''
|
|
return self._open(self.files[fd])
|
|
|
|
def _is_open(self, fd):
|
|
return fd >= 0 and fd < len(self.files) and self.files[fd] is not None
|
|
|
|
def sys_allocate(self, cpu, length, isX, addr):
|
|
''' allocate - allocate virtual memory
|
|
|
|
The allocate system call creates a new allocation in the virtual address
|
|
space of the calling process. The length argument specifies the length of
|
|
the allocation in bytes which will be rounded up to the hardware page size.
|
|
|
|
The kernel chooses the address at which to create the allocation; the
|
|
address of the new allocation is returned in *addr as the result of the call.
|
|
|
|
All newly allocated memory is readable and writeable. In addition, the
|
|
is_X argument is a boolean that allows newly allocated memory to be marked
|
|
as executable (non-zero) or non-executable (zero).
|
|
|
|
The allocate function is invoked through system call number 5.
|
|
|
|
@param cpu current CPU
|
|
@parm length the length of the allocation in bytes
|
|
@parm isX boolean that allows newly allocated memory to be marked
|
|
as executable
|
|
@parm addr the address of the new allocation is returned in *addr
|
|
|
|
@return On success, allocate returns zero and a pointer to the allocated area
|
|
is returned in *addr. Otherwise, an error code is returned
|
|
and *addr is undefined.
|
|
EINVAL length is zero.
|
|
EINVAL length is too large.
|
|
EFAULT addr points to an invalid address.
|
|
ENOMEM No memory is available or the process' maximum number of allocations
|
|
would have been exceeded.
|
|
'''
|
|
#TODO: check 4 bytes from addr
|
|
if not addr in cpu.memory:
|
|
logger.info("ALLOCATE: addr points to invalid address. Returning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
|
|
perms = [ 'rw ', 'rwx'][bool(isX)]
|
|
try:
|
|
result = cpu.memory.mmap(None, length, perms)
|
|
except Exception, e:
|
|
logger.info("ALLOCATE exception %s. Returning ENOMEM %r", str(e), length)
|
|
return Decree.CGC_ENOMEM
|
|
cpu.write_int(addr, result, 32)
|
|
logger.info("ALLOCATE(%d, %s, 0x%08x) -> 0x%08x"%(length, perms, addr, result))
|
|
return 0
|
|
|
|
def sys_random(self, cpu, buf, count, rnd_bytes):
|
|
''' random - fill a buffer with random data
|
|
|
|
The random system call populates the buffer referenced by buf with up to
|
|
count bytes of random data. If count is zero, random returns 0 and optionallyi
|
|
sets *rx_bytes to zero. If count is greater than SSIZE_MAX, the result is unspecified.
|
|
|
|
@param cpu current CPU
|
|
@param buf a memory buffer
|
|
@param count max number of bytes to receive
|
|
@param rnd_bytes if valid, points to the actual number of random bytes
|
|
|
|
@return 0 On success
|
|
EINVAL count is invalid.
|
|
EFAULT buf or rnd_bytes points to an invalid address.
|
|
'''
|
|
|
|
ret = 0
|
|
if count != 0:
|
|
if count > Decree.CGC_SSIZE_MAX or count < 0:
|
|
ret = Decree.CGC_EINVAL
|
|
else:
|
|
# TODO check count bytes from buf
|
|
if buf not in cpu.memory or (buf+count) not in cpu.memory:
|
|
logger.info("RANDOM: buf points to invalid address. Returning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
|
|
data = file("/dev/urandom","r").read(count)
|
|
cpu.write_bytes(buf, data)
|
|
|
|
# TODO check 4 bytes from rx_bytes
|
|
if rx_bytes:
|
|
if rx_bytes not in cpu.memory :
|
|
logger.info("RANDOM: Not valid rx_bytes. Returning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
cpu.write_int(rx_bytes, len(data), 32)
|
|
|
|
logger.info("RANDOM(0x%08x, %d, 0x%08x) -> <%s>)"%(buf, count, rnd_bytes, repr(data[:10])))
|
|
return ret
|
|
|
|
def sys_receive(self, cpu, fd, buf, count, rx_bytes):
|
|
''' receive - receive bytes from a file descriptor
|
|
|
|
The receive system call reads up to count bytes from file descriptor fd to the
|
|
buffer pointed to by buf. If count is zero, receive returns 0 and optionally
|
|
dets *rx_bytes to zero.
|
|
|
|
@param cpu: current CPU.
|
|
@param fd a valid file descripor
|
|
@param buf a memory buffer
|
|
@param count max number of bytes to receive
|
|
@param rx_bytes if valid, points to the actual number of bytes received
|
|
@result 0 Success
|
|
EBADF fd is not a valid file descriptor or is not open
|
|
EFAULT buf or rx_bytes points to an invalid address.
|
|
'''
|
|
data = ''
|
|
if count != 0:
|
|
if not self._is_open(fd):
|
|
logger.info("RECEIVE: Not valid file descriptor on receive. Returning EBADF")
|
|
return Decree.CGC_EBADF
|
|
|
|
# TODO check count bytes from buf
|
|
if buf not in cpu.memory: # or not buf+count in cpu.memory:
|
|
logger.info("RECEIVE: buf points to invalid address. Returning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
|
|
#import random
|
|
#count = random.randint(1,count)
|
|
if fd > 2 and self.files[fd].is_empty():
|
|
cpu.PC -= cpu.instruction.size
|
|
self.wait([fd], [], None)
|
|
raise RestartSyscall()
|
|
|
|
#get some potential delay
|
|
#if random.randint(5) == 0 and count > 1:
|
|
# count = count/2
|
|
|
|
# Read the data and put in tin memory
|
|
data = self.files[fd].receive(count)
|
|
self.syscall_trace.append(("_receive", fd, data))
|
|
cpu.write_bytes(buf, data)
|
|
|
|
self.signal_receive(fd)
|
|
|
|
|
|
# TODO check 4 bytes from rx_bytes
|
|
if rx_bytes:
|
|
if rx_bytes not in cpu.memory:
|
|
logger.info("RECEIVE: Not valid file descriptor on receive. Returning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
cpu.write_int(rx_bytes, len(data), 32)
|
|
|
|
logger.info("RECEIVE(%d, 0x%08x, %d, 0x%08x) -> <%s> (size:%d)"%(fd, buf, count, rx_bytes, repr(data)[:min(count,10)],len(data)))
|
|
return 0
|
|
|
|
def sys_transmit(self, cpu, fd, buf, count, tx_bytes):
|
|
''' transmit - send bytes through a file descriptor
|
|
The transmit system call writes up to count bytes from the buffer pointed
|
|
to by buf to the file descriptor fd. If count is zero, transmit returns 0
|
|
and optionally sets *tx_bytes to zero.
|
|
|
|
@param cpu current CPU
|
|
@param fd a valid file descripor
|
|
@param buf a memory buffer
|
|
@param count number of bytes to send
|
|
@param tx_bytes if valid, points to the actual number of bytes transmitted
|
|
@result 0 Success
|
|
EBADF fd is not a valid file descriptor or is not open.
|
|
EFAULT buf or tx_bytes points to an invalid address.
|
|
'''
|
|
data = []
|
|
if count != 0:
|
|
|
|
if not self._is_open(fd):
|
|
logger.error("TRANSMIT: Not valid file descriptor. Returning EBADFD %d", fd)
|
|
return Decree.CGC_EBADF
|
|
|
|
# TODO check count bytes from buf
|
|
if buf not in cpu.memory or (buf+count) not in cpu.memory:
|
|
logger.debug("TRANSMIT: buf points to invalid address. Rerurning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
|
|
if fd > 2 and self.files[fd].is_full():
|
|
cpu.PC -= cpu.instruction.size
|
|
self.wait([],[fd],None)
|
|
raise RestartSyscall()
|
|
|
|
for i in xrange(0, count):
|
|
value = Operators.CHR(cpu.read_int(buf+i,8))
|
|
if not isinstance(value, str):
|
|
logger.debug("TRANSMIT: Writing symbolic values to file %d", fd)
|
|
#value = str(value)
|
|
data.append(value)
|
|
self.files[fd].transmit(data)
|
|
|
|
logger.info("TRANSMIT(%d, 0x%08x, %d, 0x%08x) -> <%.24r>"%(fd, buf, count, tx_bytes, ''.join([str(x) for x in data])))
|
|
self.syscall_trace.append(("_transmit", fd, data))
|
|
self.signal_transmit(fd)
|
|
|
|
# TODO check 4 bytes from tx_bytes
|
|
if tx_bytes:
|
|
if tx_bytes not in cpu.memory :
|
|
logger.debug("TRANSMIT: Not valid tx_bytes pointer on transmit. Returning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
cpu.write_int(tx_bytes, len(data), 32)
|
|
|
|
return 0
|
|
|
|
def sys_terminate(self, cpu, error_code):
|
|
'''
|
|
Exits all threads in a process
|
|
@param cpu: current CPU.
|
|
@raise Exception: 'Finished'
|
|
'''
|
|
procid = self.procs.index(cpu)
|
|
self.sched()
|
|
self.running.remove(procid)
|
|
#self.procs[procid] = None #let it there so we can report?
|
|
if isinstance(error_code, Expression):
|
|
logger.info("TERMINATE PROC_%02d with symbolic exit code [%d,%d]", procid, solver.minmax(constraints, error_code))
|
|
else:
|
|
logger.info("TERMINATE PROC_%02d %x", procid, error_code)
|
|
if len(self.running) == 0 :
|
|
raise ProcessExit(error_code)
|
|
return error_code
|
|
|
|
def sys_deallocate(self, cpu, addr, size):
|
|
''' deallocate - remove allocations
|
|
The deallocate system call deletes the allocations for the specified
|
|
address range, and causes further references to the addresses within the
|
|
range to generate invalid memory accesses. The region is also
|
|
automatically deallocated when the process is terminated.
|
|
|
|
The address addr must be a multiple of the page size. The length parameter
|
|
specifies the size of the region to be deallocated in bytes. All pages
|
|
containing a part of the indicated range are deallocated, and subsequent
|
|
references will terminate the process. It is not an error if the indicated
|
|
range does not contain any allocated pages.
|
|
|
|
The deallocate function is invoked through system call number 6.
|
|
|
|
@param cpu: current CPU
|
|
@param addr: the starting address to unmap.
|
|
@param size: the size of the portion to unmap.
|
|
@return 0 On success
|
|
EINVAL addr is not page aligned.
|
|
EINVAL length is zero.
|
|
EINVAL any part of the region being deallocated is outside the valid
|
|
address range of the process.
|
|
|
|
@param cpu: current CPU.
|
|
@return: C{0} on success.
|
|
'''
|
|
logger.info("DEALLOCATE(0x%08x, %d)"%(addr, size))
|
|
|
|
if addr & 0xfff != 0:
|
|
logger.info("DEALLOCATE: addr is not page aligned")
|
|
return Decree.CGC_EINVAL
|
|
if size == 0 :
|
|
logger.info("DEALLOCATE:length is zero")
|
|
return Decree.CGC_EINVAL
|
|
#unlikely AND WRONG!!!
|
|
#if addr > Decree.CGC_SSIZE_MAX or addr+size > Decree.CGC_SSIZE_MAX:
|
|
# logger.info("DEALLOCATE: part of the region being deallocated is outside the valid address range of the process")
|
|
# return Decree.CGC_EINVAL
|
|
|
|
cpu.memory.munmap(addr, size)
|
|
return 0
|
|
|
|
def sys_fdwait(self, cpu, nfds, readfds, writefds, timeout, readyfds):
|
|
''' fdwait - wait for file descriptors to become ready
|
|
'''
|
|
logger.debug("FDWAIT(%d, 0x%08x, 0x%08x, 0x%08x, 0x%08x)"%(nfds, readfds, writefds, timeout, readyfds))
|
|
|
|
if timeout:
|
|
if timeout not in cpu.memory : #todo: size
|
|
logger.info("FDWAIT: timeput is pointing to invalid memory. Returning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
|
|
if readyfds:
|
|
if readyfds not in cpu.memory :
|
|
logger.info("FDWAIT: readyfds pointing to invalid memory. Returning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
|
|
writefds_wait = set()
|
|
writefds_ready = set()
|
|
|
|
fds_bitsize = (nfds+7)&~7
|
|
if writefds:
|
|
if writefds not in cpu.memory :
|
|
logger.info("FDWAIT: writefds pointing to invalid memory. Returning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
bits = cpu.read_int(writefds, fds_bitsize)
|
|
|
|
for fd in range(nfds):
|
|
if (bits & 1<<fd):
|
|
if self.files[fd].is_full():
|
|
writefds_wait.add(fd)
|
|
else:
|
|
writefds_ready.add(fd)
|
|
|
|
readfds_wait = set()
|
|
readfds_ready = set()
|
|
if readfds:
|
|
if readfds not in cpu.memory :
|
|
logger.info("FDWAIT: readfds pointing to invalid memory. Returning EFAULT")
|
|
return Decree.CGC_EFAULT
|
|
bits = cpu.read_int(readfds, fds_bitsize)
|
|
for fd in range(nfds):
|
|
if (bits & 1<<fd):
|
|
if self.files[fd].is_empty():
|
|
readfds_wait.add(fd)
|
|
else:
|
|
readfds_ready.add(fd)
|
|
n = len(readfds_ready) + len(writefds_ready)
|
|
if n == 0:
|
|
#TODO FIX timout symbolic
|
|
if timeout != 0:
|
|
seconds = cpu.read_int(timeout, 32)
|
|
microseconds = cpu.read_int(timeout+4, 32)
|
|
logger.info("FDWAIT: waiting for read on fds: {%s} and write to: {%s} timeout: %d", repr(list(readfds_wait)), repr(list(writefds_wait)), microseconds+1000*seconds)
|
|
to = microseconds+1000*seconds
|
|
#no ready file, wait
|
|
else:
|
|
to = None
|
|
logger.info("FDWAIT: waiting for read on fds: {%s} and write to: {%s} timeout: INDIFENITELY", repr(list(readfds_wait)), repr(list(writefds_wait)))
|
|
|
|
cpu.PC -= cpu.instruction.size
|
|
self.wait(readfds_wait, writefds_wait, to)
|
|
raise RestartSyscall() #When comming back from a timeout remember
|
|
#not to backtrack instruction and set EAX to 0! :( uglyness alert!
|
|
|
|
if readfds:
|
|
bits = 0
|
|
for fd in readfds_ready:
|
|
bits |= 1<<fd
|
|
for byte in range(0, nfds,8):
|
|
cpu.write_int(readfds, (bits>>byte)&0xff, 8)
|
|
if writefds:
|
|
bits = 0
|
|
for fd in writefds_ready:
|
|
bits |= 1<<fd
|
|
for byte in range(0, nfds,8):
|
|
cpu.write_int(writefds, (bits>>byte)&0xff, 8)
|
|
|
|
logger.info("FDWAIT: continuing. Some file is ready Readyfds: %08x", readyfds)
|
|
if readyfds:
|
|
cpu.write_int(readyfds, n, 32)
|
|
return 0
|
|
|
|
def int80(self, cpu):
|
|
'''
|
|
32 bit dispatcher.
|
|
@param cpu: current CPU.
|
|
_terminate, transmit, receive, fdwait, allocate, deallocate and random
|
|
'''
|
|
syscalls = { 0x00000001: self.sys_terminate,
|
|
0x00000002: self.sys_transmit,
|
|
0x00000003: self.sys_receive,
|
|
0x00000004: self.sys_fdwait,
|
|
0x00000005: self.sys_allocate,
|
|
0x00000006: self.sys_deallocate,
|
|
0x00000007: self.sys_random,
|
|
}
|
|
if cpu.EAX not in syscalls.keys():
|
|
raise SyscallNotImplemented("32 bit DECREE system call number %s Not Implemented" % cpu.EAX)
|
|
func = syscalls[cpu.EAX]
|
|
logger.debug("SYSCALL32: %s (nargs: %d)", func.func_name, func.func_code.co_argcount)
|
|
nargs = func.func_code.co_argcount
|
|
args = [ cpu, cpu.EBX, cpu.ECX, cpu.EDX, cpu.ESI, cpu.EDI, cpu.EBP ]
|
|
cpu.EAX = func(*args[:nargs-1])
|
|
|
|
|
|
def sched(self):
|
|
''' Yield CPU.
|
|
This will choose another process from the RUNNNIG list and change
|
|
current running process. May give the same cpu if only one running
|
|
proccess.
|
|
'''
|
|
if len(self.procs)>1:
|
|
logger.info("SCHED:")
|
|
logger.info("\tProcess: %r", self.procs)
|
|
logger.info("\tRunning: %r", self.running)
|
|
logger.info("\tRWait: %r", self.rwait)
|
|
logger.info("\tTWait: %r", self.twait)
|
|
logger.info("\tTimers: %r", self.timers)
|
|
logger.info("\tCurrent clock: %d", self.clocks)
|
|
logger.info("\tCurrent cpu: %d", self._current)
|
|
|
|
if len(self.running) == 0:
|
|
logger.info("None running checking if there is some process waiting for a timeout")
|
|
if all([x is None for x in self.timers]):
|
|
raise Deadlock()
|
|
self.clocks = min(filter(lambda x: x is not None, self.timers))+1
|
|
self.check_timers()
|
|
assert len(self.running) != 0, "DEADLOCK!"
|
|
self._current = self.running[0]
|
|
return
|
|
next_index = (self.running.index(self._current) + 1) % len(self.running)
|
|
next = self.running[ next_index ]
|
|
if len(self.procs)>1:
|
|
logger.info("\tTransfer control from process %d to %d", self._current, next)
|
|
self._current = next
|
|
|
|
def wait(self, readfds, writefds, timeout):
|
|
''' Wait for filedescriptors or timout.
|
|
Adds the current proceess in the correspondant wainting list and
|
|
yield the cpu to another running process.
|
|
'''
|
|
logger.info("WAIT:")
|
|
logger.info("\tProcess %d is going to wait for [ %r %r %r ]", self._current, readfds, writefds, timeout)
|
|
logger.info("\tProcess: %r", self.procs)
|
|
logger.info("\tRunning: %r", self.running)
|
|
logger.info("\tRWait: %r", self.rwait)
|
|
logger.info("\tTWait: %r", self.twait)
|
|
logger.info("\tTimers: %r", self.timers)
|
|
|
|
|
|
for fd in readfds:
|
|
self.rwait[fd].add(self._current)
|
|
for fd in writefds:
|
|
self.twait[fd].add(self._current)
|
|
if timeout is not None:
|
|
self.timers[self._current] = self.clocks + timeout
|
|
else:
|
|
self.timers[self._current] = None
|
|
procid = self._current
|
|
#self.sched()
|
|
next_index = (self.running.index(procid) + 1) % len(self.running)
|
|
self._current = self.running[ next_index ]
|
|
logger.info("\tTransfer control from process %d to %d", procid, self._current)
|
|
logger.info( "\tREMOVING %r from %r. Current: %r",procid, self.running, self._current)
|
|
self.running.remove(procid)
|
|
if self._current not in self.running:
|
|
logger.info( "\tCurrent not running. Checking for timers...")
|
|
self._current=None
|
|
if all([x is None for x in self.timers]):
|
|
raise Deadlock()
|
|
self.check_timers()
|
|
|
|
def awake(self, procid):
|
|
''' Remove procid from waitlists and restablish it in the running list '''
|
|
logger.info("Remove procid:%d from waitlists and restablish it in the running list", procid)
|
|
for wait_list in self.rwait:
|
|
if procid in wait_list: wait_list.remove(procid)
|
|
for wait_list in self.twait:
|
|
if procid in wait_list: wait_list.remove(procid)
|
|
self.timers[procid]=None
|
|
self.running.append(procid)
|
|
if self._current is None:
|
|
self._current = procid
|
|
|
|
def connections(self, fd):
|
|
if fd in [0,1,2]:
|
|
return None
|
|
if fd%2:
|
|
return fd + 1
|
|
else:
|
|
return fd - 1
|
|
|
|
def signal_receive(self, fd):
|
|
''' Awake one process waiting to receive data on fd '''
|
|
connections = self.connections
|
|
if connections(fd) and self.twait[connections(fd)]:
|
|
procid = random.sample(self.twait[connections(fd)], 1)[0]
|
|
self.awake(procid)
|
|
|
|
def signal_transmit(self, fd):
|
|
''' Awake one process waiting to transmit data on fd '''
|
|
connections = self.connections
|
|
if connections(fd) and self.rwait[connections(fd)]:
|
|
procid = random.sample(self.rwait[connections(fd)], 1)[0]
|
|
self.awake(procid)
|
|
|
|
def check_timers(self):
|
|
''' Awake proccess if timer has expired '''
|
|
if self._current is None:
|
|
#Advance the clocks. Go to future!!
|
|
advance = min(filter( lambda x: x is not None, self.timers)) +1
|
|
logger.info("Advancing the clock from %d to %d", self.clocks, advance)
|
|
self.clocks = advance
|
|
for procid in range(len(self.timers)):
|
|
if self.timers[procid] is not None:
|
|
if self.clocks > self.timers[procid]:
|
|
self.procs[procid].PC += self.procs[procid].instruction.size
|
|
self.awake(procid)
|
|
|
|
def execute(self):
|
|
"""
|
|
Execute one cpu instruction in the current thread (only one suported).
|
|
@rtype: bool
|
|
@return: C{True}
|
|
|
|
@todo: This is where we could implement a simple schedule.
|
|
"""
|
|
try:
|
|
self.current.execute()
|
|
self.clocks += 1
|
|
if self.clocks % 10000 == 0:
|
|
self.check_timers()
|
|
self.sched()
|
|
except Interruption, e:
|
|
if e.N != 0x80:
|
|
raise
|
|
try:
|
|
self.int80(self.current)
|
|
except RestartSyscall:
|
|
pass
|
|
|
|
return True
|
|
|
|
############################################################################
|
|
# Symbolic versions follows
|
|
|
|
class SDecree(Decree):
|
|
'''
|
|
A symbolic extension of a Decree Operating System Model.
|
|
'''
|
|
def __init__(self, constraints, programs, symbolic_random=None):
|
|
'''
|
|
Builds a symbolic extension of a Decree OS
|
|
@param constraints: a constraint set
|
|
@param cpus: CPU for this model
|
|
@param mem: memory for this model
|
|
'''
|
|
self._constraints = constraints
|
|
self.random = 0
|
|
super(SDecree, self).__init__(programs)
|
|
|
|
def _mk_proc(self):
|
|
return I386Cpu(SMemory32(self.constraints))
|
|
|
|
@property
|
|
def constraints(self):
|
|
return self._constraints
|
|
|
|
#marshaling/pickle
|
|
def __getstate__(self):
|
|
state = super(SDecree, self).__getstate__()
|
|
state['constraints'] = self.constraints
|
|
state['random'] = self.random
|
|
return state
|
|
|
|
def __setstate__(self, state):
|
|
self._constraints = state['constraints']
|
|
self.random = state['random']
|
|
super(SDecree, self).__setstate__(state)
|
|
|
|
def sys_receive(self, cpu, fd, buf, count, rx_bytes):
|
|
''' Symbolic version of Decree.sys_receive
|
|
'''
|
|
if isinstance(fd, Expression):
|
|
logger.info("Ask to read from a symbolic file descriptor!!")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(0)
|
|
|
|
if isinstance(buf, Expression):
|
|
logger.info("Ask to read to a symbolic buffer")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(1)
|
|
|
|
if isinstance(count, Expression):
|
|
logger.info("Ask to read a symbolic number of bytes ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(2)
|
|
|
|
if isinstance(rx_bytes, Expression):
|
|
logger.info("Ask to return size to a symbolic address ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(3)
|
|
|
|
return super(SDecree, self).sys_receive(cpu, fd, buf, count, rx_bytes)
|
|
|
|
|
|
def sys_transmit(self, cpu, fd, buf, count, tx_bytes):
|
|
''' Symbolic version of Decree.sys_receive
|
|
'''
|
|
if isinstance(fd, Expression):
|
|
logger.info("Ask to write to a symbolic file descriptor!!")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(0)
|
|
|
|
if isinstance(buf, Expression):
|
|
logger.info("Ask to write to a symbolic buffer")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(1)
|
|
|
|
if isinstance(count, Expression):
|
|
logger.info("Ask to write a symbolic number of bytes ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(2)
|
|
|
|
if isinstance(tx_bytes, Expression):
|
|
logger.info("Ask to return size to a symbolic address ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(3)
|
|
|
|
return super(SDecree, self).sys_transmit(cpu, fd, buf, count, tx_bytes)
|
|
|
|
|
|
def sys_allocate(self, cpu, length, isX, address_p):
|
|
if isinstance(length, Expression):
|
|
logger.info("Ask to ALLOCATE a symbolic number of bytes ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(0)
|
|
if isinstance(isX, Expression):
|
|
logger.info("Ask to ALLOCATE potentially executable or not executable memory")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(1)
|
|
if isinstance(address_p, Expression):
|
|
logger.info("Ask to return ALLOCATE result to a symbolic reference ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(2)
|
|
return super(SDecree, self).sys_allocate(cpu, length, isX, address_p)
|
|
|
|
def sys_deallocate(self, cpu, addr, size):
|
|
if isinstance(addr, Expression):
|
|
logger.info("Ask to DEALLOCATE a symbolic pointer?!")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(0)
|
|
if isinstance(size, Expression):
|
|
logger.info("Ask to DEALLOCATE a symbolic size?!")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(1)
|
|
return super(SDecree, self).sys_deallocate(cpu, addr, size)
|
|
|
|
def sys_random(self, cpu, buf, count, rnd_bytes):
|
|
if isinstance(buf, Expression):
|
|
logger.info("Ask to write random bytes to a symbolic buffer")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(0)
|
|
|
|
if isinstance(count, Expression):
|
|
logger.info("Ask to read a symbolic number of random bytes ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(1)
|
|
|
|
if isinstance(rnd_bytes, Expression):
|
|
logger.info("Ask to return rnd size to a symbolic address ")
|
|
cpu.PC = cpu.PC-cpu.instruction.size
|
|
raise SymbolicSyscallArgument(2)
|
|
|
|
data = []
|
|
for i in xrange(count):
|
|
if False:
|
|
#Too slow for the new age.
|
|
value = self.constraints.new_bitvec(8, name="RANDOM_%04d"%self.random)
|
|
self.constraints.add(symb==cgcrandom.stream[self.random])
|
|
else:
|
|
value = cgcrandom.stream[self.random]
|
|
data.append(value)
|
|
self.random+=1
|
|
|
|
cpu.write_bytes(buf, data)
|
|
if rnd_bytes:
|
|
cpu.write_int(rnd_bytes, len(data), 32)
|
|
logger.info("RANDOM(0x%08x, %d, 0x%08x) -> %d", buf,count,rnd_bytes,len(data))
|
|
self.syscall_trace.append(("_random", -1, data))
|
|
return 0
|
|
|
|
|