blob: 572ca48418c9510af182a91eb46ad1252b2610e1 [file] [log] [blame]
/*
*
* honggfuzz - architecture dependent code (MAC OS X)
* -----------------------------------------
*
* Author: Robert Swiecki <swiecki@google.com> Felix Gröbert
* <groebert@google.com>
*
* Copyright 2010-2015 by Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
*/
#include "arch.h"
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "honggfuzz.h"
#include "libcommon/common.h"
#include "libcommon/files.h"
#include "libcommon/log.h"
#include "libcommon/util.h"
#include "sancov.h"
#include "subproc.h"
#include <mach/i386/thread_status.h>
#include <mach/mach.h>
#include <mach/mach_types.h>
#include <mach/mach_vm.h>
#include <mach/task_info.h>
#include <pthread.h>
#include <servers/bootstrap.h>
#include "mach_exc.h"
#include "mach_excServer.h"
#import <Foundation/Foundation.h>
/*
* Interface to third_party/CrashReport_*.o
*/
@interface CrashReport : NSObject
- (id)initWithTask:(task_t)task
exceptionType:(exception_type_t)anExceptionType
exceptionCode:(mach_exception_data_t)anExceptionCode
exceptionCodeCount:(mach_msg_type_number_t)anExceptionCodeCount
thread:(thread_t)thread
threadStateFlavor:(thread_state_flavor_t)aThreadStateFlavor
threadState:(thread_state_data_t)aThreadState
threadStateCount:(mach_msg_type_number_t)aThreadStateCount;
@end
/*
* Global to have exception port available in the collection thread
*/
static mach_port_t g_exception_port = MACH_PORT_NULL;
/*
* From xnu/bsd/sys/proc_internal.h
*/
#define PID_MAX 99999
/*
* Global to store crash info in exception handler thread
*/
run_t g_fuzzer_crash_information[PID_MAX + 1];
/*
* Global to store the CrashWrangler generated callstack from
* the exception handler thread
*/
static char* g_fuzzer_crash_callstack[PID_MAX + 1];
/*
* Global to have a unique service name for each honggfuzz process
*/
char g_service_name[256];
struct {
bool important;
const char* descr;
} arch_sigs[NSIG];
__attribute__((constructor)) void arch_initSigs(void) {
for (int x = 0; x < NSIG; x++) arch_sigs[x].important = false;
arch_sigs[SIGILL].important = true;
arch_sigs[SIGILL].descr = "SIGILL";
arch_sigs[SIGFPE].important = true;
arch_sigs[SIGFPE].descr = "SIGFPE";
arch_sigs[SIGSEGV].important = true;
arch_sigs[SIGSEGV].descr = "SIGSEGV";
arch_sigs[SIGBUS].important = true;
arch_sigs[SIGBUS].descr = "SIGBUS";
/* Is affected from monitorSIGABRT flag */
arch_sigs[SIGABRT].important = true;
arch_sigs[SIGABRT].descr = "SIGABRT";
/* Is affected from tmoutVTALRM flag */
arch_sigs[SIGVTALRM].important = false;
arch_sigs[SIGVTALRM].descr = "SIGVTALRM";
}
const char* exception_to_string(int exception) {
switch (exception) {
case EXC_BAD_ACCESS:
return "EXC_BAD_ACCESS";
case EXC_BAD_INSTRUCTION:
return "EXC_BAD_INSTRUCTION";
case EXC_ARITHMETIC:
return "EXC_ARITHMETIC";
case EXC_EMULATION:
return "EXC_EMULATION";
case EXC_SOFTWARE:
return "EXC_SOFTWARE";
case EXC_BREAKPOINT:
return "EXC_BREAKPOINT";
case EXC_SYSCALL:
return "EXC_SYSCALL";
case EXC_MACH_SYSCALL:
return "EXC_MACH_SYSCALL";
case EXC_RPC_ALERT:
return "EXC_RPC_ALERT";
case EXC_CRASH:
return "EXC_CRASH";
}
return "UNKNOWN";
}
static void arch_generateReport(run_t* run, int termsig) {
run->report[0] = '\0';
util_ssnprintf(run->report, sizeof(run->report), "ORIG_FNAME: %s\n", run->origFileName);
util_ssnprintf(run->report, sizeof(run->report), "FUZZ_FNAME: %s\n", run->crashFileName);
util_ssnprintf(run->report, sizeof(run->report), "PID: %d\n", run->pid);
util_ssnprintf(
run->report, sizeof(run->report), "SIGNAL: %s (%d)\n", arch_sigs[termsig].descr, termsig);
util_ssnprintf(
run->report, sizeof(run->report), "EXCEPTION: %s\n", exception_to_string(run->exception));
util_ssnprintf(run->report, sizeof(run->report), "FAULT ADDRESS: %p\n", run->access);
util_ssnprintf(run->report, sizeof(run->report), "CRASH FRAME PC: %p\n", run->pc);
util_ssnprintf(run->report, sizeof(run->report), "STACK HASH: %016llx\n", run->backtrace);
if (g_fuzzer_crash_callstack[run->pid]) {
util_ssnprintf(
run->report, sizeof(run->report), "STACK: \n%s\n", g_fuzzer_crash_callstack[run->pid]);
} else {
util_ssnprintf(run->report, sizeof(run->report), "STACK: \n Callstack not available.\n");
}
return;
}
/*
* Returns true if a process exited (so, presumably, we can delete an input
* file)
*/
static bool arch_analyzeSignal(run_t* run, int status) {
/*
* Resumed by delivery of SIGCONT
*/
if (WIFCONTINUED(status)) {
return false;
}
if (WIFEXITED(status) || WIFSIGNALED(status)) {
sancov_Analyze(run);
}
/*
* Boring, the process just exited
*/
if (WIFEXITED(status)) {
LOG_D("Process (pid %d) exited normally with status %d", run->pid, WEXITSTATUS(status));
return true;
}
/*
* Shouldn't really happen, but, well..
*/
if (!WIFSIGNALED(status)) {
LOG_E("Process (pid %d) exited with the following status %d, please report that as a bug",
run->pid, status);
return true;
}
int termsig = WTERMSIG(status);
LOG_D("Process (pid %d) killed by signal %d '%s'", run->pid, termsig, strsignal(termsig));
if (!arch_sigs[termsig].important) {
LOG_D("It's not that important signal, skipping");
return true;
}
/*
* Signal is interesting
*/
/*
* Increase crashes counter presented by ASCII display
*/
ATOMIC_POST_INC(run->global->cnts.crashesCnt);
/*
* Get data from exception handler
*/
run->pc = g_fuzzer_crash_information[run->pid].pc;
run->exception = g_fuzzer_crash_information[run->pid].exception;
run->access = g_fuzzer_crash_information[run->pid].access;
run->backtrace = g_fuzzer_crash_information[run->pid].backtrace;
defer {
if (g_fuzzer_crash_callstack[run->pid]) {
free(g_fuzzer_crash_callstack[run->pid]);
g_fuzzer_crash_callstack[run->pid] = NULL;
}
};
/*
* Check if stackhash is blacklisted
*/
if (run->global->blacklist && (fastArray64Search(run->global->blacklist,
run->global->blacklistCnt, run->backtrace) != -1)) {
LOG_I("Blacklisted stack hash '%" PRIx64 "', skipping", run->backtrace);
ATOMIC_POST_INC(run->global->cnts.blCrashesCnt);
return true;
}
/* If dry run mode, copy file with same name into workspace */
if (run->global->mutationsPerRun == 0U && run->global->useVerifier) {
snprintf(run->crashFileName, sizeof(run->crashFileName), "%s/%s", run->global->io.crashDir,
run->origFileName);
} else if (run->global->io.saveUnique) {
snprintf(run->crashFileName, sizeof(run->crashFileName),
"%s/%s.%s.PC.%.16llx.STACK.%.16llx.ADDR.%.16llx.%s", run->global->io.crashDir,
arch_sigs[termsig].descr, exception_to_string(run->exception), run->pc, run->backtrace,
run->access, run->global->io.fileExtn);
} else {
char localtmstr[PATH_MAX];
util_getLocalTime("%F.%H.%M.%S", localtmstr, sizeof(localtmstr), time(NULL));
snprintf(run->crashFileName, sizeof(run->crashFileName),
"%s/%s.%s.PC.%.16llx.STACK.%.16llx.ADDR.%.16llx.TIME.%s.PID.%.5d.%s",
run->global->io.crashDir, arch_sigs[termsig].descr, exception_to_string(run->exception),
run->pc, run->backtrace, run->access, localtmstr, run->pid, run->global->io.fileExtn);
}
if (files_exists(run->crashFileName)) {
LOG_I("It seems that '%s' already exists, skipping", run->crashFileName);
// Clear filename so that verifier can understand we hit a duplicate
memset(run->crashFileName, 0, sizeof(run->crashFileName));
return true;
}
if (files_writeBufToFile(run->crashFileName, run->dynamicFile, run->dynamicFileSz,
O_CREAT | O_EXCL | O_WRONLY) == false) {
LOG_E("Couldn't copy '%s' to '%s'", run->fileName, run->crashFileName);
return true;
}
LOG_I("Ok, that's interesting, saved '%s' as '%s'", run->fileName, run->crashFileName);
ATOMIC_POST_INC(run->global->cnts.uniqueCrashesCnt);
/* If unique crash found, reset dynFile counter */
ATOMIC_CLEAR(run->global->dynFileIterExpire);
arch_generateReport(run, termsig);
return true;
}
pid_t arch_fork(run_t* run UNUSED) { return fork(); }
bool arch_launchChild(run_t* run) {
#define ARGS_MAX 512
const char* args[ARGS_MAX + 2];
char argData[PATH_MAX] = {0};
int x;
for (x = 0; x < ARGS_MAX && run->global->exe.cmdline[x]; x++) {
if (!run->global->exe.fuzzStdin &&
strcmp(run->global->exe.cmdline[x], _HF_FILE_PLACEHOLDER) == 0) {
args[x] = run->fileName;
} else if (!run->global->exe.fuzzStdin &&
strstr(run->global->exe.cmdline[x], _HF_FILE_PLACEHOLDER)) {
const char* off = strstr(run->global->exe.cmdline[x], _HF_FILE_PLACEHOLDER);
snprintf(argData, PATH_MAX, "%.*s%s", (int)(off - run->global->exe.cmdline[x]),
run->global->exe.cmdline[x], run->fileName);
args[x] = argData;
} else {
args[x] = run->global->exe.cmdline[x];
}
}
args[x++] = NULL;
LOG_D("Launching '%s' on file '%s'", args[0], run->fileName);
/*
* Get child's bootstrap port.
*/
mach_port_t child_bootstrap = MACH_PORT_NULL;
if (task_get_bootstrap_port(mach_task_self(), &child_bootstrap) != KERN_SUCCESS) {
return false;
}
/*
* Get exception port.
*/
mach_port_t exception_port = MACH_PORT_NULL;
if (bootstrap_look_up(child_bootstrap, g_service_name, &exception_port) != KERN_SUCCESS) {
return false;
}
/*
* Here we register the exception port in the child
*/
if (task_set_exception_ports(mach_task_self(), EXC_MASK_CRASH, exception_port,
EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
MACHINE_THREAD_STATE) != KERN_SUCCESS) {
return false;
}
/* alarm persists across forks, so disable it here */
alarm(0);
execvp(args[0], (char* const*)args);
alarm(1);
return false;
}
void arch_prepareParent(run_t* run UNUSED) {}
void arch_prepareParentAfterFork(run_t* run UNUSED) {}
void arch_reapChild(run_t* run) {
/*
* First check manually if we have expired children
*/
subproc_checkTimeLimit(run);
/*
* Now check for signals using wait4
*/
int options = WUNTRACED;
if (run->global->timing.tmOut) {
options |= WNOHANG;
}
for (;;) {
int status = 0;
while (wait4(run->pid, &status, options, NULL) != run->pid) {
if (run->global->timin.tmOut) {
subproc_checkTimeLimit(run);
usleep(0.20 * 1000000);
}
}
char strStatus[4096];
LOG_D("Process (pid %d) came back with status: %s", run->pid,
subproc_StatusToStr(status, strStatus, sizeof(strStatus)));
if (arch_analyzeSignal(run, status)) {
return;
}
}
}
void* wait_for_exception() {
while (1) {
mach_msg_server_once(mach_exc_server, 4096, g_exception_port, MACH_MSG_OPTION_NONE);
}
}
/*
* Called once before fuzzing starts. Prepare mach ports for attaching crash reporter.
*/
bool arch_archInit(honggfuzz_t* hfuzz) {
char plist[PATH_MAX];
snprintf(plist, sizeof(plist), "/Users/%s/Library/Preferences/com.apple.DebugSymbols.plist",
getlogin());
if (files_exists(plist)) {
LOG_W(
"honggfuzz won't work if DBGShellCommands are set in "
"~/Library/Preferences/com.apple.DebugSymbols.plist");
}
/*
* Allocate exception port.
*/
if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &g_exception_port) !=
KERN_SUCCESS) {
return false;
}
/*
* Insert exception receive port.
*/
if (mach_port_insert_right(mach_task_self(), g_exception_port, g_exception_port,
MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) {
return false;
}
/*
* Get bootstrap port.
*/
mach_port_t bootstrap = MACH_PORT_NULL;
if (task_get_bootstrap_port(mach_task_self(), &bootstrap) != KERN_SUCCESS) {
return false;
}
/*
* Generate and register exception port service.
*/
snprintf(g_service_name, sizeof(g_service_name), "com.google.code.honggfuzz.%d",
(int)util_rndGet(0, 999999));
if (bootstrap_check_in(bootstrap, g_service_name, &g_exception_port) != KERN_SUCCESS) {
return false;
}
/*
* Create a collection thread to catch the exceptions from the
* children
*/
pthread_t exception_thread;
if (pthread_create(&exception_thread, NULL, wait_for_exception, 0)) {
LOG_F("Parent: could not create thread to wait for child's exception");
return false;
}
if (pthread_detach(exception_thread)) {
LOG_F("Parent: could not detach thread to wait for child's exception");
return false;
}
/* Default is true for all platforms except Android */
arch_sigs[SIGABRT].important = hfuzz->monitorSIGABRT;
/* Default is false */
arch_sigs[SIGVTALRM].important = hfuzz->tmoutVTALRM;
return true;
}
#ifdef DEBUG
/*
* Write the crash report to DEBUG
*/
static void write_crash_report(thread_port_t thread, task_port_t task, exception_type_t exception,
mach_exception_data_t code, mach_msg_type_number_t code_count, int* flavor,
thread_state_t in_state, mach_msg_type_number_t in_state_count) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
CrashReport* _crashReport = nil;
_crashReport = [[CrashReport alloc] initWithTask:task
exceptionType:exception
exceptionCode:code
exceptionCodeCount:code_count
thread:thread
threadStateFlavor:*flavor
threadState:(thread_state_t)in_state
threadStateCount:in_state_count];
NSString* crashDescription = [_crashReport description];
char* description = (char*)[crashDescription UTF8String];
LOG_D("CrashReport: %s", description);
[_crashReport release];
[pool drain];
}
#endif
/* Hash the callstack in an unique way */
static uint64_t hash_callstack(thread_port_t thread, task_port_t task, exception_type_t exception,
mach_exception_data_t code, mach_msg_type_number_t code_count, int* flavor,
thread_state_t in_state, mach_msg_type_number_t in_state_count) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
CrashReport* _crashReport = nil;
_crashReport = [[CrashReport alloc] initWithTask:task
exceptionType:exception
exceptionCode:code
exceptionCodeCount:code_count
thread:thread
threadStateFlavor:*flavor
threadState:(thread_state_t)in_state
threadStateCount:in_state_count];
NSString* crashDescription = [_crashReport description];
char* description = (char*)[crashDescription UTF8String];
/*
* The callstack begins with the following word
*/
char* callstack = strstr(description, "Crashed:");
if (callstack == NULL) {
LOG_F("Could not find callstack in crash report %s", description);
}
/*
* Scroll forward to the next newline
*/
char* callstack_start = strstr(callstack, "\n");
if (callstack_start == NULL) {
LOG_F("Could not find callstack start in crash report %s", description);
}
/*
* Skip the newline
*/
callstack_start++;
/*
* Determine the end of the callstack
*/
char* callstack_end = strstr(callstack_start, "\n\nThread");
if (callstack_end == NULL) {
LOG_F("Could not find callstack end in crash report %s", description);
}
if (callstack_end <= callstack_start) {
LOG_F("Malformed callstack: %s", description);
}
/*
* Check for too large callstack.
*/
#define MAX_CALLSTACK_SIZE 4096
const size_t callstack_size = (callstack_end - callstack_start);
if (callstack_size > MAX_CALLSTACK_SIZE) {
LOG_W("Too large callstack (%zu bytes), truncating to %d bytes", callstack_size,
MAX_CALLSTACK_SIZE);
callstack_start[MAX_CALLSTACK_SIZE] = '\0';
callstack_end = callstack_start + MAX_CALLSTACK_SIZE;
}
pid_t pid;
pid_for_task(task, &pid);
char** buf = &g_fuzzer_crash_callstack[pid];
/*
* Check for memory leaks. This shouldn't happen.
*/
if (*buf) {
LOG_E("Memory leak: arch_analyzeSignal didn't free previous callstack");
free(*buf);
*buf = NULL;
}
/*
* Copy the CrashWrangler formatted callstack and make sure
* it's NULL-terminated.
*/
*callstack_end = '\0';
*buf = util_StrDup(callstack_start);
/*
*
* For each line, we only take the last three nibbles from the
* address.
*
* Sample outputs:
*
* 0 libsystem_kernel.dylib 0x00007fff80514d46 __kill + 10
* 1 libsystem_c.dylib 0x00007fff85731ec0 __abort + 193
* 2 libsystem_c.dylib 0x00007fff85732d17 __stack_chk_fail + 195
* 3 stack_buffer_overflow64-stripped 0x000000010339def5 0x10339d000 + 3829
* 4 ??? 0x4141414141414141 0 + 4702111234474983745
*
* 0 libsystem_kernel.dylib 0x00007fff80514d46 __kill + 10
* 1 libsystem_c.dylib 0x00007fff85731ec0 __abort + 193
* 2 libsystem_c.dylib 0x00007fff85732d17 __stack_chk_fail + 195
* 3 stack_buffer_overflow64 0x0000000108f41ef5 main + 133
* 4 ??? 0x4141414141414141 0 + 4702111234474983745
*
* 0 libsystem_kernel.dylib 0x940023ba __kill + 10
* 1 libsystem_kernel.dylib 0x940014bc kill$UNIX2003 + 32
* 2 libsystem_c.dylib 0x926f362e __abort + 246
* 3 libsystem_c.dylib 0x926c2b60 __chk_fail + 49
* 4 libsystem_c.dylib 0x926c2bf9 __memset_chk + 53
* 5 stack_buffer_overflow32-stripped 0x00093ee5 0x93000 + 3813
* 6 libdyld.dylib 0x978c6725 start + 1
*
* 0 libsystem_kernel.dylib 0x940023ba __kill + 10
* 1 libsystem_kernel.dylib 0x940014bc kill$UNIX2003 + 32
* 2 libsystem_c.dylib 0x926f362e __abort + 246
* 3 libsystem_c.dylib 0x926c2b60 __chk_fail + 49
* 4 libsystem_c.dylib 0x926c2bf9 __memset_chk + 53
* 5 stack_buffer_overflow32 0x0003cee5 main + 117
* 6 libdyld.dylib 0x978c6725 start + 1
*
*/
uint64_t hash = 0;
char* pos = callstack_start;
/*
* Go through each line until we run out of lines
*/
while (strstr(pos, "\t") != NULL) {
/*
* Format: dylib spaces tab address space symbol space plus space offset
* Scroll pos forward to the last three nibbles of the address.
*/
if ((pos = strstr(pos, "\t")) == NULL) break;
if ((pos = strstr(pos, " ")) == NULL) break;
pos = pos - 3;
/*
* Hash the last three nibbles
*/
hash ^= util_hash(pos, 3);
/*
* Scroll pos one forward to skip the current tab
*/
pos++;
}
LOG_D("Callstack hash %llu", hash);
[_crashReport release];
[pool drain];
return hash;
}
kern_return_t catch_mach_exception_raise(mach_port_t exception_port, mach_port_t thread,
mach_port_t task, exception_type_t exception, mach_exception_data_t code,
mach_msg_type_number_t codeCnt) {
LOG_F("This function should never get called");
return KERN_SUCCESS;
}
kern_return_t catch_mach_exception_raise_state(mach_port_t exception_port,
exception_type_t exception, const mach_exception_data_t code, mach_msg_type_number_t codeCnt,
int* flavor, const thread_state_t old_state, mach_msg_type_number_t old_stateCnt,
thread_state_t new_state, mach_msg_type_number_t* new_stateCnt) {
LOG_F("This function should never get called");
return KERN_SUCCESS;
}
kern_return_t catch_mach_exception_raise_state_identity(
__attribute__((unused)) exception_port_t exception_port, thread_port_t thread, task_port_t task,
exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t code_count,
int* flavor, thread_state_t in_state, mach_msg_type_number_t in_state_count,
thread_state_t out_state, mach_msg_type_number_t* out_state_count) {
if (exception != EXC_CRASH) {
LOG_F("Got non EXC_CRASH! This should not happen.");
}
/*
* We will save our results to the honggfuzz_t global
*/
pid_t pid;
pid_for_task(task, &pid);
LOG_D("Crash of pid %d", pid);
run_t* run = &g_fuzzer_crash_information[pid];
/*
* Get program counter.
* Cast to void* in order to silence the alignment warnings
*/
x86_thread_state_t* platform_in_state = ((x86_thread_state_t*)(void*)in_state);
if (x86_THREAD_STATE32 == platform_in_state->tsh.flavor) {
run->pc = platform_in_state->uts.ts32.__eip;
} else {
run->pc = platform_in_state->uts.ts64.__rip;
}
/*
* Get the exception type
*/
exception_type_t exception_type = ((code[0] >> 20) & 0x0F);
if (exception_type == 0) {
exception_type = EXC_CRASH;
}
run->exception = exception_type;
/*
* Get the access address.
*/
mach_exception_data_type_t exception_data[2];
memcpy(exception_data, code, sizeof(exception_data));
exception_data[0] = (code[0] & ~(0x00000000FFF00000));
exception_data[1] = code[1];
mach_exception_data_type_t access_address = exception_data[1];
run->access = (uint64_t)access_address;
/*
* Get a hash of the callstack
*/
uint64_t hash =
hash_callstack(thread, task, exception, code, code_count, flavor, in_state, in_state_count);
run->backtrace = hash;
#ifdef DEBUG
write_crash_report(thread, task, exception, code, code_count, flavor, in_state, in_state_count);
#endif
/*
* Cleanup
*/
if (mach_port_deallocate(mach_task_self(), task) != KERN_SUCCESS) {
LOG_W("Exception Handler: Could not deallocate task");
}
if (mach_port_deallocate(mach_task_self(), thread) != KERN_SUCCESS) {
LOG_W("Exception Handler: Could not deallocate thread");
}
/*
* KERN_SUCCESS indicates that this should not be forwarded to other crash
* handlers
*/
return KERN_SUCCESS;
}
bool arch_archThreadInit(run_t* run UNUSED) { return true; }