blob: 8f1bb25178337705c27890268daea64f24d1da3e [file] [log] [blame]
/*
*
* honggfuzz - architecture dependent code (LINUX)
* -----------------------------------------
*
* Author: Robert Swiecki <swiecki@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 "../common.h"
#include "../arch.h"
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <setjmp.h>
#include <sys/cdefs.h>
#include <sys/personality.h>
#include <sys/ptrace.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include "../files.h"
#include "../linux/perf.h"
#include "../linux/ptrace_utils.h"
#include "../log.h"
#include "../sancov.h"
#include "../subproc.h"
#include "../util.h"
/* Common sanitizer flags */
#if _HF_MONITOR_SIGABRT
#define ABORT_FLAG "abort_on_error=1"
#else
#define ABORT_FLAG "abort_on_error=0"
#endif
/* Size of remote pid cmdline char buffer */
#define _HF_PROC_CMDLINE_SZ 8192
static inline bool arch_shouldAttach(honggfuzz_t * hfuzz, fuzzer_t * fuzzer)
{
if (hfuzz->persistent && fuzzer->linux.attachedPid == fuzzer->pid) {
return false;
}
if (hfuzz->linux.pid > 0 && fuzzer->linux.attachedPid == hfuzz->linux.pid) {
return false;
}
return true;
}
static uint8_t arch_clone_stack[PTHREAD_STACK_MIN * 2];
static int arch_cloneFunc(void *arg)
{
jmp_buf *env_ptr = (jmp_buf *) arg;
longjmp(*env_ptr, 1);
return 0;
}
/* Avoid problem with caching of PID/TID in glibc */
static pid_t arch_clone(uintptr_t flags)
{
if (flags & CLONE_VM) {
LOG_E("Cannot use clone(flags & CLONE_VM)");
return -1;
}
jmp_buf env;
if (setjmp(env) == 0) {
void *stack_mid = &arch_clone_stack[sizeof(arch_clone_stack) / 2];
/* Parent */
return clone(arch_cloneFunc, stack_mid, flags, &env, NULL, NULL);
}
/* Child */
return 0;
}
pid_t arch_fork(honggfuzz_t * hfuzz, fuzzer_t * fuzzer UNUSED)
{
/*
* We need to wait for the child to finish with wait() in case we're fuzzing
* an external process
*/
uintptr_t clone_flags = CLONE_UNTRACED;
if (hfuzz->linux.pid) {
clone_flags = SIGCHLD;
}
pid_t pid = arch_clone(clone_flags);
if (pid > 0 && hfuzz->persistent) {
struct f_owner_ex fown = {.type = F_OWNER_TID,.pid = syscall(__NR_gettid), };
if (fcntl(fuzzer->persistentSock, F_SETOWN_EX, &fown)) {
PLOG_F("fcntl(%d, F_SETOWN_EX)", fuzzer->persistentSock);
}
if (fcntl(fuzzer->persistentSock, F_SETSIG, SIGNAL_WAKE) == -1) {
PLOG_F("fcntl(%d, F_SETSIG, SIGNAL_WAKE)", fuzzer->persistentSock);
}
if (fcntl(fuzzer->persistentSock, F_SETFL, O_ASYNC) == -1) {
PLOG_F("fcntl(%d, F_SETFL, O_ASYNC)", fuzzer->persistentSock);
}
}
return pid;
}
bool arch_launchChild(honggfuzz_t * hfuzz, char *fileName)
{
/*
* Kill the children when fuzzer dies (e.g. due to Ctrl+C)
*/
if (prctl(PR_SET_PDEATHSIG, (long)SIGKILL, 0L, 0L, 0L) == -1) {
PLOG_E("prctl(PR_SET_PDEATHSIG, SIGKILL) failed");
return false;
}
/*
* Kill a process which corrupts its own heap (with ABRT)
*/
if (setenv("MALLOC_CHECK_", "7", 0) == -1) {
PLOG_E("setenv(MALLOC_CHECK_=7) failed");
return false;
}
/*
* Disable ASLR
*/
if (hfuzz->linux.disableRandomization && personality(ADDR_NO_RANDOMIZE) == -1) {
PLOG_E("personality(ADDR_NO_RANDOMIZE) failed");
return false;
}
#define ARGS_MAX 512
char *args[ARGS_MAX + 2];
char argData[PATH_MAX] = { 0 };
int x = 0;
for (x = 0; x < ARGS_MAX && hfuzz->cmdline[x]; x++) {
if (!hfuzz->fuzzStdin && !hfuzz->persistent
&& strcmp(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER) == 0) {
args[x] = (char *)fileName;
} else if (!hfuzz->fuzzStdin && !hfuzz->persistent
&& strstr(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER)) {
const char *off = strstr(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER);
snprintf(argData, PATH_MAX, "%.*s%s", (int)(off - hfuzz->cmdline[x]),
hfuzz->cmdline[x], fileName);
args[x] = argData;
} else {
args[x] = hfuzz->cmdline[x];
}
}
args[x++] = NULL;
LOG_D("Launching '%s' on file '%s'", args[0], hfuzz->persistent ? "PERSISTENT_MODE" : fileName);
/*
* Wait for the ptrace to attach
*/
syscall(__NR_tkill, syscall(__NR_gettid), (uintptr_t) SIGSTOP);
execvp(args[0], args);
PLOG_E("execvp('%s')", args[0]);
return false;
}
static void arch_sigFunc(int signo, siginfo_t * si UNUSED, void *dummy UNUSED)
{
if (signo != SIGNAL_WAKE) {
LOG_E("Signal != SIGNAL_WAKE (%d)", signo);
}
}
static bool arch_setTimer(timer_t * timerid)
{
/*
* Kick in every 200ms, starting with the next second
*/
const struct itimerspec ts = {
.it_value = {.tv_sec = 0,.tv_nsec = 250000000,},
.it_interval = {.tv_sec = 0,.tv_nsec = 250000000,},
};
if (timer_settime(*timerid, 0, &ts, NULL) == -1) {
PLOG_E("timer_settime(arm) failed");
timer_delete(*timerid);
return false;
}
return true;
}
static void arch_checkTimeLimit(honggfuzz_t * hfuzz, fuzzer_t * fuzzer)
{
int64_t curMillis = util_timeNowMillis();
int64_t diffMillis = curMillis - fuzzer->timeStartedMillis;
if (diffMillis > (hfuzz->tmOut * 1000)) {
LOG_W("PID %d took too much time (limit %ld s). Sending SIGKILL",
fuzzer->pid, hfuzz->tmOut);
kill(fuzzer->pid, SIGKILL);
ATOMIC_POST_INC(hfuzz->timeoutedCnt);
}
}
void arch_prepareChild(honggfuzz_t * hfuzz, fuzzer_t * fuzzer)
{
pid_t ptracePid = (hfuzz->linux.pid > 0) ? hfuzz->linux.pid : fuzzer->pid;
pid_t childPid = fuzzer->pid;
if (hfuzz->persistent == false && arch_ptraceWaitForPidStop(childPid) == false) {
LOG_F("PID %d not in a stopped state", childPid);
}
if (arch_shouldAttach(hfuzz, fuzzer) == true) {
if (arch_ptraceAttach(ptracePid) == false) {
LOG_F("arch_ptraceAttach(pid=%d) failed", ptracePid);
}
fuzzer->linux.attachedPid = ptracePid;
}
/* A long-lived process could have already exited, and we wouldn't know */
if (childPid != ptracePid && kill(ptracePid, 0) == -1) {
if (hfuzz->linux.pidFile) {
/* If pid from file, check again for cases of auto-restart daemons that update it */
/*
* TODO: Investigate if we need to delay here, so that target process has
* enough time to restart. Tricky to answer since is target dependent.
*/
if (files_readPidFromFile(hfuzz->linux.pidFile, &hfuzz->linux.pid) == false) {
LOG_F("Failed to read new PID from file - abort");
} else {
if (kill(hfuzz->linux.pid, 0) == -1) {
PLOG_F("Liveness of PID %d read from file questioned - abort",
hfuzz->linux.pid);
} else {
LOG_D("Monitor PID has been updated (pid=%d)", hfuzz->linux.pid);
ptracePid = hfuzz->linux.pid;
}
}
}
}
if (arch_perfEnable(ptracePid, hfuzz, fuzzer) == false) {
LOG_F("Couldn't enable perf counters for pid %d", ptracePid);
}
if (childPid != ptracePid && kill(childPid, SIGCONT) == -1) {
PLOG_F("Restarting PID: %d failed", childPid);
}
arch_ptraceSetCustomPerf(hfuzz, ptracePid, 0ULL);
}
void arch_reapChild(honggfuzz_t * hfuzz, fuzzer_t * fuzzer)
{
pid_t ptracePid = (hfuzz->linux.pid > 0) ? hfuzz->linux.pid : fuzzer->pid;
pid_t childPid = fuzzer->pid;
for (;;) {
if (subproc_persistentModeRoundDone(hfuzz, fuzzer)) {
arch_ptraceGetCustomPerf(hfuzz, ptracePid, &fuzzer->linux.hwCnts.customCnt);
break;
}
int status;
pid_t pid = wait4(-1, &status, __WALL | __WNOTHREAD, NULL);
if (pid == -1 && errno == EINTR) {
if (hfuzz->tmOut) {
arch_checkTimeLimit(hfuzz, fuzzer);
}
continue;
}
if (pid == -1 && errno == ECHILD) {
LOG_D("No more processes to track");
break;
}
if (pid == -1) {
PLOG_F("wait4() failed");
}
char statusStr[4096];
LOG_D("PID '%d' returned with status: %s", pid,
subproc_StatusToStr(status, statusStr, sizeof(statusStr)));
if (hfuzz->persistent == false) {
arch_ptraceGetCustomPerf(hfuzz, ptracePid, &fuzzer->linux.hwCnts.customCnt);
}
if (hfuzz->persistent && pid == fuzzer->persistentPid
&& (WIFEXITED(status) || WIFSIGNALED(status))) {
arch_ptraceAnalyze(hfuzz, status, pid, fuzzer);
fuzzer->persistentPid = 0;
LOG_W("Persistent mode: PID %d exited with status: %s", pid,
subproc_StatusToStr(status, statusStr, sizeof(statusStr)));
break;
}
if (ptracePid == childPid) {
arch_ptraceAnalyze(hfuzz, status, pid, fuzzer);
continue;
}
if (pid == childPid && (WIFEXITED(status) || WIFSIGNALED(status))) {
break;
}
if (pid == childPid) {
continue;
}
arch_ptraceAnalyze(hfuzz, status, pid, fuzzer);
}
#if !_HF_MONITOR_SIGABRT
/*
* There might be cases where ASan instrumented targets crash while generating
* reports for detected errors (inside __asan_report_error() proc). Under such
* scenarios target fails to exit or SIGABRT (AsanDie() proc) as defined in
* ASAN_OPTIONS flags, leaving garbage logs. An attempt is made to parse such
* logs for cases where enough data are written to identify potentially missed
* crashes. If ASan internal error results into a SIGSEGV being raised, it
* will get caught from ptrace API, handling the discovered ASan internal crash.
*/
char crashReport[PATH_MAX] = { 0 };
snprintf(crashReport, sizeof(crashReport), "%s/%s.%d", hfuzz->workDir, kLOGPREFIX, ptracePid);
if (files_exists(crashReport)) {
LOG_W("Un-handled ASan report due to compiler-rt internal error - retry with '%s' (%s)",
crashReport, fuzzer->fileName);
/* Manually set the exitcode to ASan to trigger report parsing */
arch_ptraceExitAnalyze(hfuzz, ptracePid, fuzzer, HF_ASAN_EXIT_CODE);
}
#endif
arch_perfAnalyze(hfuzz, fuzzer);
sancov_Analyze(hfuzz, fuzzer);
}
bool arch_archInit(honggfuzz_t * hfuzz)
{
/* Use it to make %'d work */
setlocale(LC_NUMERIC, "");
if (hfuzz->dynFileMethod != _HF_DYNFILE_NONE) {
unsigned long major = 0, minor = 0;
char *p = NULL;
/*
* Check that Linux kernel is compatible
*
* Compatibility list:
* 1) Perf exclude_callchain_kernel requires kernel >= 3.7
* TODO: Runtime logic to disable it for unsupported kernels
* if it doesn't affect perf counters processing
* 2) If 'PERF_TYPE_HARDWARE' is not supported by kernel, ENOENT
* is returned from perf_event_open(). Unfortunately, no reliable
* way to detect it here. libperf exports some list functions,
* although small guarantees it's installed. Maybe a more targeted
* message at perf_event_open() error handling will help.
* 3) Intel's PT and new Intel BTS format require kernel >= 4.1
*/
unsigned long checkMajor = 3, checkMinor = 7;
if ((hfuzz->dynFileMethod & _HF_DYNFILE_BTS_BLOCK) ||
(hfuzz->dynFileMethod & _HF_DYNFILE_BTS_EDGE) ||
(hfuzz->dynFileMethod & _HF_DYNFILE_IPT_BLOCK)) {
checkMajor = 4;
checkMinor = 1;
}
struct utsname uts;
if (uname(&uts) == -1) {
PLOG_F("uname() failed");
return false;
}
p = uts.release;
major = strtoul(p, &p, 10);
if (*p++ != '.') {
LOG_F("Unsupported kernel version (%s)", uts.release);
return false;
}
minor = strtoul(p, &p, 10);
if ((major < checkMajor) || ((major == checkMajor) && (minor < checkMinor))) {
LOG_E("Kernel version '%s' not supporting chosen perf method", uts.release);
return false;
}
if (arch_perfInit(hfuzz) == false) {
return false;
}
}
#if defined(__ANDROID__) && defined(__arm__)
/*
* For ARM kernels running Android API <= 21, if fuzzing target links to
* libcrypto (OpenSSL), OPENSSL_cpuid_setup initialization is triggering a
* SIGILL/ILLOPC at armv7_tick() due to "mrrc p15, #1, r0, r1, c14)" instruction.
* Setups using BoringSSL (API >= 22) are not affected.
*/
if (setenv("OPENSSL_armcap", OPENSSL_ARMCAP_ABI, 1) == -1) {
PLOG_E("setenv(OPENSSL_armcap) failed");
return false;
}
#endif
/* If read PID from file enable - read current value */
if (hfuzz->linux.pidFile) {
if (files_readPidFromFile(hfuzz->linux.pidFile, &hfuzz->linux.pid) == false) {
LOG_E("Failed to read PID from file");
return false;
}
}
/* If remote pid, resolve command using procfs */
if (hfuzz->linux.pid > 0) {
char procCmd[PATH_MAX] = { 0 };
snprintf(procCmd, sizeof(procCmd), "/proc/%d/cmdline", hfuzz->linux.pid);
hfuzz->linux.pidCmd = malloc(_HF_PROC_CMDLINE_SZ * sizeof(char));
if (!hfuzz->linux.pidCmd) {
PLOG_E("malloc(%zu) failed", (size_t) _HF_PROC_CMDLINE_SZ);
return false;
}
ssize_t sz = files_readFileToBufMax(procCmd, (uint8_t *) hfuzz->linux.pidCmd,
_HF_PROC_CMDLINE_SZ - 1);
if (sz < 1) {
LOG_E("Couldn't read '%s'", procCmd);
free(hfuzz->linux.pidCmd);
return false;
}
/* Make human readable */
for (size_t i = 0; i < ((size_t) sz - 1); i++) {
if (hfuzz->linux.pidCmd[i] == '\0') {
hfuzz->linux.pidCmd[i] = ' ';
}
}
hfuzz->linux.pidCmd[sz] = '\0';
}
/*
* If sanitizer fuzzing enabled increase number of major frames, since top 7-9 frames
* will be occupied with sanitizer symbols if 'abort_on_error' flag is set
*/
#if _HF_MONITOR_SIGABRT
hfuzz->linux.numMajorFrames = 14;
#endif
return true;
}
bool arch_archThreadInit(honggfuzz_t * hfuzz UNUSED, fuzzer_t * fuzzer)
{
struct sigevent sevp = {
.sigev_value.sival_ptr = &fuzzer->timerId,
.sigev_signo = SIGNAL_WAKE,
.sigev_notify = SIGEV_THREAD_ID | SIGEV_SIGNAL,
._sigev_un._tid = syscall(__NR_gettid),
};
if (timer_create(CLOCK_REALTIME, &sevp, &fuzzer->timerId) == -1) {
PLOG_E("timer_create(CLOCK_REALTIME) failed");
return false;
}
sigset_t smask;
sigemptyset(&smask);
struct sigaction sa = {
.sa_sigaction = arch_sigFunc,
.sa_mask = smask,
.sa_flags = SA_SIGINFO,
.sa_restorer = NULL,
};
sigset_t ss;
sigemptyset(&ss);
sigaddset(&ss, SIGNAL_WAKE);
if (sigprocmask(SIG_UNBLOCK, &ss, NULL) != 0) {
PLOG_F("pthread_sigmask(%d, SIG_UNBLOCK)", SIGNAL_WAKE);
}
if (sigaction(SIGNAL_WAKE, &sa, NULL) == -1) {
PLOG_E("sigaction(SIGNAL_WAKE (%d)) failed", SIGNAL_WAKE);
return false;
}
if (arch_setTimer(&(fuzzer->timerId)) == false) {
LOG_F("Couldn't set timer");
}
return true;
}