Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions reproducers/1370-minimal.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.section .text
.global _start

_bb_0x401040:
blsiq %rax, %rbx
ret

_start:
call _setup_dyn
call _setup_regs
call _bb_0x401040
call _exit

_exit:
movq $0, %rdi
movq $60, %rax
syscall

_alloc:
movq $4096, %rsi
movq $(PROT_READ | PROT_WRITE), %rdx
movq $(MAP_PRIVATE | MAP_ANONYMOUS), %r10
movq $-1, %r8
movq $0, %r9
movq $syscall_mmap, %rax
syscall
ret

_setup_regs:
mov $0x0, %rax
ret

_setup_dyn:
ret

.section .data
PROT_READ = 0x1
PROT_WRITE = 0x2
MAP_PRIVATE = 0x2
MAP_ANONYMOUS = 0x20
syscall_mmap = 9

_setup_mem:

Binary file added reproducers/1371-minimal
Binary file not shown.
Binary file added reproducers/1372-minimal
Binary file not shown.
Binary file added reproducers/1374-minimal
Binary file not shown.
Binary file added reproducers/1376-minimal
Binary file not shown.
Binary file added reproducers/1377-minimal
Binary file not shown.
Binary file added reproducers/2175-minimal
Binary file not shown.
12 changes: 1 addition & 11 deletions reproducers/issue-508.c
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/random.h>

int main() {
int mem = 0x12345678;
int buf = 0;
getrandom(&buf, sizeof(buf), 0);
register long rax asm("rax") = 0x1234567812345678;
register int edi asm("edi") = buf;
register int edi asm("edi") = 0x2345678;
asm("cmpxchg %[edi],%[mem]"
: [ mem ] "+m"(mem), [ rax ] "+r"(rax)
: [ edi ] "r"(edi));
long rax2 = rax;
printf("rax2 = %lx\n", rax2);
printf("rand= %d\n", buf);
}

20 changes: 20 additions & 0 deletions src/focaccia/qemu/_qemu_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
'snap': None,
}

verbosity = {
'info': ErrorTypes.INFO,
'warning': ErrorTypes.POSSIBLE,
'error': ErrorTypes.CONFIRMED,
}

def match_event(event: Event, target: ReadableProgramState) -> bool:
# Match just on PC
debug(f'Matching for PC {hex(target.read_pc())} with event {hex(event.pc)}')
Expand Down Expand Up @@ -597,6 +603,20 @@ def main():
except Exception as e:
raise Exception(f'Unable to serialize snapshots to file {args.output}: {e}')

if args.reproducer:
from focaccia.reproducer import Reproducer
try:
for r in res:
errs = [e for e in r['errors'] if e.severity >= verbosity[args.error_level]]
if not errs:
continue

rep = Reproducer(symb_transforms.env.binary_name, symb_transforms.env.argv, symb_transforms.env.envp, r['snap'], r['ref'])
with open(args.reproducer, 'w') as file:
file.write(rep.asm())
except Exception as e:
raise Exception(f'Unable to generate reproducer: {e}')

if __name__ == "__main__":
main()

56 changes: 47 additions & 9 deletions src/focaccia/reproducer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

from .lldb_target import LLDBConcreteTarget
from .native.lldb_target import LLDBLocalTarget
from .snapshot import ProgramState
from .symbolic import SymbolicTransform, eval_symbol
from .arch import x86

import re

class ReproducerMemoryError(Exception):
pass
class ReproducerBasicBlockError(Exception):
Expand All @@ -12,22 +14,60 @@ class ReproducerRegisterError(Exception):
pass

class Reproducer():
def __init__(self, oracle: str, argv: str, snap: ProgramState, sym: SymbolicTransform) -> None:
def __init__(self, oracle: str, argv: str, envp: str, snap: ProgramState, sym: SymbolicTransform) -> None:

target = LLDBConcreteTarget(oracle)
target = LLDBLocalTarget(oracle, argv, envp)

self.pc = snap.read_register("pc")
self.bb = target.get_basic_block_inst(self.pc)
self.sl = target.get_symbol_limit()
self.snap = snap
self.sym = sym

def replace_mem_access(self, instruction) -> str:
instr = instruction.split(' ')
res = instr[0] + " "
pattern = r'^([^\(]*)?\(([^\)]+)\)$'
try:
for i in instr[1:]:
match = re.match(pattern, i.strip(','))
if not match:
res += i.strip(',') + ", "
continue
displacement_str = match.group(1)
if displacement_str == "":
displacement = 0
elif displacement_str.startswith('0x'):
displacement = int(displacement_str, 16)
elif displacement_str.startswith('-0x'):
displacement = -int(displacement_str[1:], 16)
else:
displacement = int(displacement_str)

inner_parts = match.group(2).split(',')
base_val = self.snap.read_register(inner_parts[0].strip(" %").upper())
index_val = 0
scale_val = 0

if len(inner_parts) > 1: # (base, index)
index_val = self.snap.read_register(inner_parts[1].strip(" %").upper())

if len(inner_parts) > 2: # (base, index, scale)
scale_val = int(inner_parts[2].strip())

address = displacement + base_val + (index_val * scale_val)
res += "_" + hex(address) + ", "
except:
raise ReproducerBasicBlockError(f'{hex(self.pc)}\n{self.snap}\n{self.sym}\n{self.bb}')
return res[:-2]

def get_bb(self) -> str:
try:
asm = ""
asm += f'_bb_{hex(self.pc)}:\n'
for i in self.bb[:-1]:
asm += f'{i}\n'
asm += f'{self.replace_mem_access(i)}\n'
break
asm += f'ret\n'
asm += f'\n'

Expand All @@ -36,7 +76,7 @@ def get_bb(self) -> str:
raise ReproducerBasicBlockError(f'{hex(self.pc)}\n{self.snap}\n{self.sym}\n{self.bb}')

def get_regs(self) -> str:
general_regs = ['RIP', 'RAX', 'RBX','RCX','RDX', 'RSI','RDI','RBP','RSP','R8','R9','R10','R11','R12','R13','R14','R15',]
general_regs = ['RIP', 'RAX', 'RBX','RCX','RDX', 'RSI','RDI','RBP','R8','R9','R10','R11','R12','R13','R14','R15',]
flag_regs = ['CF', 'PF', 'AF', 'ZF', 'SF', 'TF', 'IF', 'DF', 'OF', 'IOPL', 'NT',]
eflag_regs = ['RF', 'VM', 'AC', 'VIF', 'VIP', 'ID',]

Expand All @@ -62,15 +102,14 @@ def get_regs(self) -> str:
def get_mem(self) -> str:
try:
asm = ""
asm += f'_setup_mem:\n'
for mem in self.sym.get_used_memory_addresses():
addr = eval_symbol(mem.ptr, self.snap)
val = self.snap.read_memory(addr, int(mem.size/8))

if addr < self.sl:
asm += f'.org {hex(addr)}\n'
asm += f'_{hex(addr)}:\n'
for b in val:
asm += f'.byte ${hex(b)}\n'
asm += f'\t.byte {hex(b)}\n'
asm += f'\n'

return asm
Expand Down Expand Up @@ -140,7 +179,6 @@ def get_code(self) -> str:
asm += f'.section .text\n'
asm += f'.global _start\n'
asm += f'\n'
asm += f'.org {hex(self.pc)}\n'
asm += self.get_bb()
asm += self.get_start()
asm += self.get_exit()
Expand Down
4 changes: 4 additions & 0 deletions src/focaccia/tools/validate_qemu.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ def make_argparser():
help='GDB binary to invoke.')
prog.add_argument('--deterministic-log', default=None,
help='The directory containing rr traces')
prog.add_argument('--reproducer',
type=str,
help='Generate repoducer executables for detected'
' errors.')
return prog

def quoted(s: str) -> str:
Expand Down