Files
manticore/models/decree.py
T
2017-02-13 12:04:15 -05:00

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