diff --git a/CMakeLists.txt b/CMakeLists.txt index 357a090..32cbc20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,16 @@ cmake_minimum_required(VERSION 2.8) enable_language(C) enable_language(CXX) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +set(CMAKE_C_FLAGS_DEBUG "-O3") +set(CMAKE_C_FLAGS_RELEASE "-O3") + +set(CMAKE_CXX_FLAGS_DEBUG "-O3") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + find_program(PYTHON "python") # Enable the GNU extensions diff --git a/bin/mctest/__main__.py b/bin/mctest/__main__.py index 15be4b5..ee9235b 100644 --- a/bin/mctest/__main__.py +++ b/bin/mctest/__main__.py @@ -14,8 +14,18 @@ # limitations under the License. import angr +import logging import sys +L = logging.getLogger("mctest") +L.setLevel(logging.INFO) + +def hook_function(project, name, cls): + """Hook the function `name` with the SimProcedure `cls`.""" + project.hook(project.kb.labels.lookup(name), + cls(project=project)) + + def read_c_string(state, ea): """Read a concrete NUL-terminated string from `ea`.""" assert isinstance(ea, (int, long)) @@ -31,67 +41,87 @@ def read_c_string(state, ea): return "".join(chars) -def read_uint64_t(state, ea): +def read_uintptr_t(state, ea): """Read a uint64_t value from memory.""" - return state.solver.eval(state.mem[ea].uint64_t.resolved, - cast_to=int) + return state.solver.eval(state.mem[ea].uintptr_t.resolved, cast_to=int) + + +def read_uint32_t(state, ea): + """Read a uint64_t value from memory.""" + return state.solver.eval(state.mem[ea].uint32_t.resolved, cast_to=int) def find_test_cases(project, state): """Find the test case descriptors.""" obj = project.loader.main_object tests = [] + addr_size_bytes = state.arch.bits // 8 for sec in obj.sections: if sec.name != ".mctest_entrypoints": continue + for ea in xrange(sec.vaddr, sec.vaddr + sec.memsize, 32): - test_func_ea = read_uint64_t(state, ea + 0) - test_name_ea = read_uint64_t(state, ea + 8) - file_name_ea = read_uint64_t(state, ea + 16) - file_line_num = read_uint64_t(state, ea + 24) + test_func_ea = read_uintptr_t(state, ea + 0 * addr_size_bytes) + test_name_ea = read_uintptr_t(state, ea + 1 * addr_size_bytes) + file_name_ea = read_uintptr_t(state, ea + 2 * addr_size_bytes) + file_line_num = read_uint32_t(state, ea + 3 * addr_size_bytes) + + if not test_func_ea or \ + not test_name_ea or \ + not file_name_ea or \ + not file_line_num: # `__LINE__` in C always starts at `1` ;-) + continue test_name = read_c_string(state, test_name_ea) file_name = read_c_string(state, file_name_ea) - + L.info("Test case {} at {:x} is at {}:{}".format( + test_name, test_func_ea, file_name, file_line_num)) tests.append((test_func_ea, test_name, file_name, file_line_num)) + return tests -def hook_symbolic_int_func(project, state, name, num_bits): - """Hook a McTest function and make it return a symbolic integer.""" - class Function(angr.SimProcedure): - def run(self): - return self.state.solver.BVS("", num_bits) - - func_name = "McTest_{}".format(name) - ea = project.kb.labels.lookup(func_name) - project.hook(ea, Function(project=project)) +def make_symbolic_input(project, state): + """Fill in the input data array with symbolic data.""" + obj = project.loader.main_object + for sec in obj.sections: + if sec.name == ".mctest_input_data": + data = state.se.Unconstrained('MCTEST_INPUT', sec.memsize * 8) + state.memory.store(sec.vaddr, data) + return data def hook_predicate_int_func(project, state, name, num_bits): """Hook a McTest function that checks whether or not its integer argument is symbolic.""" - class Function(angr.SimProcedure): + class Hook(angr.SimProcedure): def run(self, arg): return int(self.state.se.symbolic(arg)) - - func_name = "McTest_IsSymbolic{}".format(name) - ea = project.kb.labels.lookup(func_name) - project.hook(ea, Function(project=project)) + hook_function(project, "McTest_IsSymbolic{}".format(name), Hook) class Assume(angr.SimProcedure): - """Implements McTest_Assume, which injects a constraint.""" + """Implements _McTest_CanAssume, which tries to inject a constraint.""" def run(self, arg): - if self.state.se.symbolic(arg): - constraint = arg != 0 - eval_res = self.state.se.eval(constraint) - if eval_res: - self.state.add_constraints(constraint) - ret = eval_res - else: - ret = self.state.se.eval(arg) != 0 - return int(ret) + constraint = arg != 0 + self.state.solver.add(constraint) + if not self.state.solver.satisfiable(): + L.error("Failed to assert assumption {}".format(constraint)) + self.exit(2) + + +class Pass(angr.SimProcedure): + """Implements McTest_Pass, which notifies us of a passing test.""" + def run(self): + L.info("Passed test case") + self.exit(0) + + +class Fail(angr.SimProcedure): + """Implements McTest_Fail, which notifies us of a passing test.""" + def run(self): + L.error("Failed test case") + self.exit(1) def main(): @@ -103,7 +133,8 @@ def main(): sys.argv[1], use_sim_procedures=True, translation_cache=True, - support_selfmodifying_code=False) + support_selfmodifying_code=False, + auto_load_libs=False) entry_state = project.factory.entry_state() addr_size_bits = entry_state.arch.bits @@ -119,23 +150,16 @@ def main(): concrete_manager.explore(find=ea_of_main) main_state = concrete_manager.found[0] - #concrete_manager.move(from_stash='found', to_stash='deadended') - - # Hook functions that should now return symbolic values. - hook_symbolic_int_func(project, main_state, 'Bool', 1) - hook_symbolic_int_func(project, main_state, 'Size', addr_size_bits) - hook_symbolic_int_func(project, main_state, 'UInt64', 64) - hook_symbolic_int_func(project, main_state, 'UInt', 32) + # Introduce symbolic input that the tested code will use. + symbolic_input = make_symbolic_input(project, main_state) # Hook predicate functions that should return 1 or 0 depending on whether # or not their argument is symbolic. hook_predicate_int_func(project, main_state, 'UInt', 32) - # Hook the assume function. - project.hook(project.kb.labels.lookup('McTest_Assume'), - Assume(project=project)) - - ea_of_done = project.kb.labels.lookup('McTest_DoneTestCase') + hook_function(project, '_McTest_Assume', Assume) + hook_function(project, 'McTest_Pass', Pass) + hook_function(project, 'McTest_Fail', Fail) # For each test, create a simulation manager whose initial state calls into # the test case function, and returns to `McTest_DoneTestCase`. @@ -143,8 +167,7 @@ def main(): for entry_ea, test_name, file_name, line_num in tests: test_state = project.factory.call_state( entry_ea, - base_state=main_state, - ret_addr=ea_of_done) + base_state=main_state) # NOTE(pag): Enabling Veritesting seems to miss some cases where the # tests fail. @@ -152,13 +175,13 @@ def main(): project=project, active_states=[test_state]) + L.info("Running test case {}".format(test_name)) test_manager.run() for state in test_manager.deadended: last_event = state.history.events[-1] if 'terminate' == last_event.type: code = last_event.objects['exit_code']._model_concrete.value - print "{} in {}:{} terminated with {}".format(test_name, file_name, line_num, code) return 0 diff --git a/examples/OutOfBoundsInt.c b/examples/OutOfBoundsInt.c index ae02008..99d603d 100644 --- a/examples/OutOfBoundsInt.c +++ b/examples/OutOfBoundsInt.c @@ -22,7 +22,7 @@ McTest_EntryPoint(YIsAlwaysPositive) { McTest_Assert(y >= 0); } -McTest_EntryPoint(YIsAlwaysPositive_WillFail) { +McTest_EntryPoint(YIsAlwaysPositive_CanFail) { int x = McTest_IntInRange(-10, 10); int y = x * x * x; McTest_Assert(y >= 0); /* This will fail */ diff --git a/src/include/mctest/McTest.h b/src/include/mctest/McTest.h index 44d5afe..ba1706e 100644 --- a/src/include/mctest/McTest.h +++ b/src/include/mctest/McTest.h @@ -25,6 +25,18 @@ extern "C" { #endif /* __cplusplus */ +/* Return a symbolic value of a given type. */ +extern int McTest_Bool(void); +extern size_t McTest_Size(void); +extern uint64_t McTest_UInt64(void); +extern int64_t McTest_Int64(void); +extern uint32_t McTest_UInt(void); +extern int32_t McTest_Int(void); +extern uint16_t McTest_UShort(void); +extern int16_t McTest_Short(void); +extern uint8_t McTest_UChar(void); +extern int8_t McTest_Char(void); + /* Symbolize the data in the range `[begin, end)`. */ extern void McTest_SymbolizeData(void *begin, void *end); @@ -65,45 +77,23 @@ inline static char *McTest_CStr(size_t len) { /* Creates an assumption about a symbolic value. Returns `1` if the assumption * can hold and was asserted. */ -extern int McTest_Assume(int expr); +extern void _McTest_Assume(int expr); + +#define McTest_Assume(x) _McTest_Assume(!!(x)) + +__attribute__((noreturn)) +extern void McTest_Fail(void); + +__attribute__((noreturn)) +extern void McTest_Pass(void); /* Asserts that `expr` must hold. */ inline static void McTest_Assert(int expr) { - if (McTest_Assume(!expr)) { - abort(); + if (!expr) { + McTest_Fail(); } } -/* Return a symbolic value of a given type. */ -extern int McTest_Bool(void); -extern size_t McTest_Size(void); -extern uint64_t McTest_UInt64(void); -extern uint32_t McTest_UInt(void); - -inline static int64_t McTest_Int64(void) { - return (int64_t) McTest_UInt64(); -} - -inline static int32_t McTest_Int(void) { - return (int32_t) McTest_UInt(); -} - -inline static uint16_t McTest_UShort(void) { - return (uint16_t) McTest_UInt(); -} - -inline static int16_t McTest_Short(void) { - return (int16_t) McTest_UInt(); -} - -inline static unsigned char McTest_UChar(void) { - return (unsigned char) McTest_UInt(); -} - -inline static char McTest_Char(void) { - return (char) McTest_UInt(); -} - /* Return a symbolic value in a the range `[low_inc, high_inc]`. */ #define MCTEST_MAKE_SYMBOLIC_RANGE(Tname, tname) \ inline static tname McTest_ ## Tname ## InRange( \ @@ -124,19 +114,6 @@ MCTEST_MAKE_SYMBOLIC_RANGE(UChar, unsigned char) #undef MCTEST_MAKE_SYMBOLIC_RANGE - -/* Return a symbolic value of a given type. */ -extern int McTest_Bool(void); -extern size_t McTest_Size(void); -extern uint64_t McTest_UInt64(void); -extern int64_t McTest_Int64(void); -extern uint32_t McTest_UInt(void); -extern int32_t McTest_Int(void); -extern uint16_t McTest_UShort(void); -extern int16_t McTest_Short(void); -extern unsigned char McTest_UChar(void); -extern char McTest_Char(void); - /* Predicates to check whether or not a particular value is symbolic */ extern int McTest_IsSymbolicUInt(uint32_t x); @@ -210,29 +187,28 @@ inline static int McTest_IsSymbolicDouble(double x) { #define McTest_EntryPoint(test_name) \ _McTest_EntryPoint(test_name, __FILE__, __LINE__) +struct __attribute__((packed)) McTest_TestInfo { + void (*test_func)(void); + const char *test_name; + const char *file_name; + unsigned line_number; + uint8_t padding[28 - (3 * sizeof(void *))]; +}; + #define _McTest_EntryPoint(test_name, file, line) \ - static void McTest_Run_ ## test_name (void); \ - __attribute__((noinline, used)) \ - MCTEST_EXTERN_C void McTest_Register_ ## test_name (void) { \ - __asm__ __volatile__ ( \ - ".pushsection .mctest_strtab,\"a\" \n" \ - "1: \n" \ - ".asciz \"" _MCTEST_TO_STR(test_name) "\" \n" \ - "2: \n" \ - ".asciz \"" file "\" \n" \ - ".popsection \n" \ - ".pushsection .mctest_entrypoints,\"a\" \n" \ - ".balign 16 \n" \ - ".quad %p0 \n" \ - ".quad 1b \n" \ - ".quad 2b \n" \ - ".quad " _MCTEST_TO_STR(line) " \n" \ - ".popsection \n" \ - : \ - : "i"(McTest_Run_ ## test_name) \ - ); \ + static void McTest_Test_ ## test_name (void); \ + static void McTest_Run_ ## test_name (void) { \ + McTest_Test_ ## test_name(); \ + McTest_Pass(); \ } \ - void McTest_Run_ ## test_name(void) + MCTEST_EXTERN_C struct McTest_TestInfo McTest_Register_ ## test_name \ + __attribute__((section(".mctest_entrypoints"))) = { \ + McTest_Run_ ## test_name, \ + _MCTEST_TO_STR(test_name), \ + file, \ + line \ + }; \ + void McTest_Test_ ## test_name(void) #ifdef __cplusplus diff --git a/src/include/mctest/McTest.hpp b/src/include/mctest/McTest.hpp index 2abecfc..676ae56 100644 --- a/src/include/mctest/McTest.hpp +++ b/src/include/mctest/McTest.hpp @@ -117,32 +117,77 @@ inline static int IsSymbolic(void *x) { return McTest_IsSymbolicPtr(x); } -#if 199711L < __cplusplus +template +class Symbolic { + public: + template + inline Symbolic(Args&& ...args) + : value(std::forward(args)...) {} -/* Returns a symbolic container (where it makes sense). Container entries - * do not necessarily need to be contiguous. */ -template -inline static ContainerT Symbolic(Args&& ...args) { - ContainerT cont(std::forward(args)...); - if (!cont.empty()) { - for (auto &entry : cont) { - auto entry_ptr = &entry; - McTest_SymbolizeData(entry_ptr, &(entry_ptr[1])); + inline Symbolic(void) { + T *val_ptr = &value; + McTest_SymbolizeData(val_ptr, &(val_ptr[1])); + } + + inline operator T (void) const { + return value; + } + + T value; +}; + +template +class SymbolicLinearContainer { + public: + inline explicit Symbolic(size_t len) + : value(len) { + if (len) { + McTest_SymbolizeData(&(str.begin()), &(str.end())); } } - return cont; -} -#endif // 199711L < __cplusplus - -// Returns a symbolic std::string of length `len`. -inline static std::string Str(size_t len) { - std::string str(len); - if (len) { - McTest_SymbolizeData(&(str.begin()), &(str.end())); + inline operator T (void) const { + return value; } - return str; -} + + T value; + + private: + Symblic(void) = delete; +}; + +template <> +class Symbolic : public SymbolicLinearContainer {}; + +template <> +class Symbolic : public SymbolicLinearContainer {}; + +template +class Symbolic> : + public SymbolicLinearContainer> {}; + +#define MAKE_SYMBOL_SPECIALIZATION(Tname, tname) \ + template <> \ + class Symbolic { \ + public: \ + inline Symbolic(void) + : value(McTest_ ## Tname) {} \ + inline operator tname (void) const { \ + return value; \ + } \ + tname value; \ + }; + +MAKE_SYMBOL_SPECIALIZATION(UInt64, uint64_t) +MAKE_SYMBOL_SPECIALIZATION(Int64, int64_t) +MAKE_SYMBOL_SPECIALIZATION(UInt, uint32_t) +MAKE_SYMBOL_SPECIALIZATION(Int, int32_t) +MAKE_SYMBOL_SPECIALIZATION(UShort, uint16_t) +MAKE_SYMBOL_SPECIALIZATION(Short, int16_t) +MAKE_SYMBOL_SPECIALIZATION(UChar, uint8_t) +MAKE_SYMBOL_SPECIALIZATION(Char, int8_t) + +#undef MAKE_SYMBOL_SPECIALIZATION } // namespace mctest diff --git a/src/lib/McTest.c b/src/lib/McTest.c index 3dbf512..9150aaf 100644 --- a/src/lib/McTest.c +++ b/src/lib/McTest.c @@ -22,34 +22,76 @@ extern "C" { #endif /* __cplusplus */ -static uint32_t gPlaybookIndex = 0; -static uint32_t gPlaybook[8192] = {}; +volatile uint8_t McTest_Input[8192] + __attribute__((section(".mctest_input_data"))); + +uint32_t McTest_InputIndex = 0; + +__attribute__((noreturn)) +extern void McTest_Fail(void) { + exit(EXIT_FAILURE); +} + +__attribute__((noreturn)) +extern void McTest_Pass(void) { + exit(EXIT_SUCCESS); +} void McTest_SymbolizeData(void *begin, void *end) { - (void) begin; - (void) end; + uintptr_t begin_addr = (uintptr_t) begin; + uintptr_t end_addr = (uintptr_t) end; + + if (begin_addr > end_addr) { + abort(); + } else if (begin_addr == end_addr) { + return; + } else { + uint8_t *bytes = (uint8_t *) begin; + for (uintptr_t i = 0, max_i = (end_addr - begin_addr); i < max_i; ++i) { + bytes[i] = McTest_Input[McTest_InputIndex++]; + } + } } /* Return a symbolic value of a given type. */ int McTest_Bool(void) { - return 0; + return McTest_Input[McTest_InputIndex++] & 1; } -size_t McTest_Size(void) { - return 0; +#define MAKE_SYMBOL_FUNC(Type, type) \ + type McTest_ ## Type(void) { \ + type val = 0; \ + _Pragma("unroll") \ + for (size_t i = 0; i < sizeof(type); ++i) { \ + val = (val << 8) | ((type) McTest_Input[McTest_InputIndex++]); \ + } \ + return val; \ + } + +MAKE_SYMBOL_FUNC(UInt64, uint64_t) +int64_t McTest_Int64(void) { + return (int64_t) McTest_UInt64(); } -uint64_t McTest_UInt64(void) { - return 0; +MAKE_SYMBOL_FUNC(UInt, uint32_t) +int32_t McTest_Int(void) { + return (int32_t) McTest_UInt(); } -uint32_t McTest_UInt(void) { - return 0; +MAKE_SYMBOL_FUNC(UShort, uint16_t) +int16_t McTest_Short(void) { + return (int16_t) McTest_UShort(); } -int McTest_Assume(int expr) { +MAKE_SYMBOL_FUNC(UChar, uint8_t) +int8_t McTest_Char(void) { + return (int8_t) McTest_UChar(); +} + +#undef MAKE_SYMBOL_FUNC + +void _McTest_Assume(int expr) { assert(expr); - return 1; } int McTest_IsSymbolicUInt(uint32_t x) { @@ -63,11 +105,6 @@ void McTest_DoneTestCase(void) { /* McTest implements the `main` function so that test code can focus on tests */ int main(void) { -#if defined(__x86_64__) || defined(__i386__) || defined(_M_X86) - asm("ud2; .asciz \"I'm McTest'in it\";"); -#else -# error "Unsupported platform (for now)." -#endif return EXIT_SUCCESS; }