| /* |
| * |
| * honggfuzz - routines dealing with subprocesses |
| * ----------------------------------------- |
| * |
| * 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 "subproc.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/resource.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include "arch.h" |
| #include "fuzz.h" |
| #include "libhfcommon/common.h" |
| #include "libhfcommon/files.h" |
| #include "libhfcommon/log.h" |
| #include "libhfcommon/util.h" |
| |
| extern char** environ; |
| |
| const char* subproc_StatusToStr(int status, char* str, size_t len) { |
| if (WIFEXITED(status)) { |
| snprintf(str, len, "EXITED, exit code: %d", WEXITSTATUS(status)); |
| return str; |
| } |
| |
| if (WIFSIGNALED(status)) { |
| snprintf( |
| str, len, "SIGNALED, signal: %d (%s)", WTERMSIG(status), strsignal(WTERMSIG(status))); |
| return str; |
| } |
| if (WIFCONTINUED(status)) { |
| snprintf(str, len, "CONTINUED"); |
| return str; |
| } |
| |
| if (!WIFSTOPPED(status)) { |
| snprintf(str, len, "UNKNOWN STATUS: %d", status); |
| return str; |
| } |
| |
| /* Must be in a stopped state */ |
| if (WSTOPSIG(status) == (SIGTRAP | 0x80)) { |
| snprintf(str, len, "STOPPED (linux syscall): %d (%s)", WSTOPSIG(status), |
| strsignal(WSTOPSIG(status))); |
| return str; |
| } |
| #if defined(PTRACE_EVENT_STOP) |
| #define __LINUX_WPTRACEEVENT(x) ((x & 0xff0000) >> 16) |
| if (WSTOPSIG(status) == SIGTRAP && __LINUX_WPTRACEEVENT(status) != 0) { |
| switch (__LINUX_WPTRACEEVENT(status)) { |
| case PTRACE_EVENT_FORK: |
| snprintf(str, len, "EVENT (Linux) - fork - with signal: %d (%s)", WSTOPSIG(status), |
| strsignal(WSTOPSIG(status))); |
| return str; |
| case PTRACE_EVENT_VFORK: |
| snprintf(str, len, "EVENT (Linux) - vfork - with signal: %d (%s)", WSTOPSIG(status), |
| strsignal(WSTOPSIG(status))); |
| return str; |
| case PTRACE_EVENT_CLONE: |
| snprintf(str, len, "EVENT (Linux) - clone - with signal: %d (%s)", WSTOPSIG(status), |
| strsignal(WSTOPSIG(status))); |
| return str; |
| case PTRACE_EVENT_EXEC: |
| snprintf(str, len, "EVENT (Linux) - exec - with signal: %d (%s)", WSTOPSIG(status), |
| strsignal(WSTOPSIG(status))); |
| return str; |
| case PTRACE_EVENT_VFORK_DONE: |
| snprintf(str, len, "EVENT (Linux) - vfork_done - with signal: %d (%s)", |
| WSTOPSIG(status), strsignal(WSTOPSIG(status))); |
| return str; |
| case PTRACE_EVENT_EXIT: |
| snprintf(str, len, "EVENT (Linux) - exit - with signal: %d (%s)", WSTOPSIG(status), |
| strsignal(WSTOPSIG(status))); |
| return str; |
| case PTRACE_EVENT_SECCOMP: |
| snprintf(str, len, "EVENT (Linux) - seccomp - with signal: %d (%s)", |
| WSTOPSIG(status), strsignal(WSTOPSIG(status))); |
| return str; |
| case PTRACE_EVENT_STOP: |
| snprintf(str, len, "EVENT (Linux) - stop - with signal: %d (%s)", WSTOPSIG(status), |
| strsignal(WSTOPSIG(status))); |
| return str; |
| default: |
| snprintf(str, len, "EVENT (Linux) UNKNOWN (%d): with signal: %d (%s)", |
| __LINUX_WPTRACEEVENT(status), WSTOPSIG(status), strsignal(WSTOPSIG(status))); |
| return str; |
| } |
| } |
| #endif /* defined(PTRACE_EVENT_STOP) */ |
| |
| snprintf( |
| str, len, "STOPPED with signal: %d (%s)", WSTOPSIG(status), strsignal(WSTOPSIG(status))); |
| return str; |
| } |
| |
| bool subproc_persistentModeRoundDone(run_t* run) { |
| if (!run->global->exe.persistent) { |
| return false; |
| } |
| uint8_t rcv; |
| if (recv(run->persistentSock, &rcv, sizeof(rcv), MSG_DONTWAIT) != sizeof(rcv)) { |
| return false; |
| } |
| if (rcv == HFdoneTag) { |
| return true; |
| } |
| LOG_F("Received invalid message from the persistent process: '%c' (0x%" PRIx8 |
| ") , expected '%c' (0x%" PRIx8 ")", |
| rcv, rcv, HFdoneTag, HFdoneTag); |
| return false; |
| } |
| |
| static bool subproc_persistentSendFileIndicator(run_t* run) { |
| uint64_t len = (uint64_t)run->dynamicFileSz; |
| if (!files_sendToSocketNB(run->persistentSock, (uint8_t*)&len, sizeof(len))) { |
| PLOG_W("files_sendToSocketNB(len=%zu)", sizeof(len)); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool subproc_PrepareExecv(run_t* run) { |
| /* |
| * The address space limit. If big enough - roughly the size of RAM used |
| */ |
| #ifdef RLIMIT_AS |
| if (run->global->exe.asLimit) { |
| const struct rlimit rl = { |
| .rlim_cur = run->global->exe.asLimit * 1024ULL * 1024ULL, |
| .rlim_max = run->global->exe.asLimit * 1024ULL * 1024ULL, |
| }; |
| if (setrlimit(RLIMIT_AS, &rl) == -1) { |
| PLOG_W("Couldn't enforce the RLIMIT_AS resource limit, ignoring"); |
| } |
| } |
| #endif /* ifdef RLIMIT_AS */ |
| #ifdef RLIMIT_RSS |
| if (run->global->exe.rssLimit) { |
| const struct rlimit rl = { |
| .rlim_cur = run->global->exe.rssLimit * 1024ULL * 1024ULL, |
| .rlim_max = run->global->exe.rssLimit * 1024ULL * 1024ULL, |
| }; |
| if (setrlimit(RLIMIT_RSS, &rl) == -1) { |
| PLOG_W("Couldn't enforce the RLIMIT_RSS resource limit, ignoring"); |
| } |
| } |
| #endif /* ifdef RLIMIT_RSS */ |
| #ifdef RLIMIT_DATA |
| if (run->global->exe.dataLimit) { |
| const struct rlimit rl = { |
| .rlim_cur = run->global->exe.dataLimit * 1024ULL * 1024ULL, |
| .rlim_max = run->global->exe.dataLimit * 1024ULL * 1024ULL, |
| }; |
| if (setrlimit(RLIMIT_DATA, &rl) == -1) { |
| PLOG_W("Couldn't enforce the RLIMIT_DATA resource limit, ignoring"); |
| } |
| } |
| #endif /* ifdef RLIMIT_DATA */ |
| |
| if (run->global->exe.clearEnv) { |
| environ = NULL; |
| } |
| for (size_t i = 0; i < ARRAYSIZE(run->global->exe.envs) && run->global->exe.envs[i]; i++) { |
| putenv(run->global->exe.envs[i]); |
| } |
| char fuzzNo[128]; |
| snprintf(fuzzNo, sizeof(fuzzNo), "%" PRId32, run->fuzzNo); |
| setenv(_HF_THREAD_NO_ENV, fuzzNo, 1); |
| |
| setsid(); |
| |
| util_closeStdio(/* close_stdin= */ run->global->exe.nullifyStdio, |
| /* close_stdout= */ run->global->exe.nullifyStdio, |
| /* close_stderr= */ run->global->exe.nullifyStdio); |
| |
| /* The bitmap structure */ |
| if (run->global->bbFd != -1 && dup2(run->global->bbFd, _HF_BITMAP_FD) == -1) { |
| PLOG_E("dup2(%d, _HF_BITMAP_FD=%d)", run->global->bbFd, _HF_BITMAP_FD); |
| return false; |
| } |
| |
| /* The input file to _HF_INPUT_FD */ |
| if (run->global->exe.persistent && dup2(run->dynamicFileFd, _HF_INPUT_FD) == -1) { |
| PLOG_E("dup2('%d', _HF_INPUT_FD='%d')", run->dynamicFileFd, _HF_INPUT_FD); |
| return false; |
| } |
| |
| sigset_t sset; |
| sigemptyset(&sset); |
| if (sigprocmask(SIG_SETMASK, &sset, NULL) == -1) { |
| PLOG_W("sigprocmask(empty_set)"); |
| } |
| |
| if (!run->global->exe.persistent) { |
| if ((run->dynamicFileCopyFd = files_writeBufToTmpFile( |
| run->global->io.workDir, run->dynamicFile, run->dynamicFileSz, 0)) == -1) { |
| LOG_E("Couldn't save data to a temporary file"); |
| return false; |
| } |
| if (run->global->exe.fuzzStdin && dup2(run->dynamicFileCopyFd, STDIN_FILENO) == -1) { |
| PLOG_E("dup2(_HF_INPUT_FD=%d, STDIN_FILENO=%d)", run->dynamicFileCopyFd, STDIN_FILENO); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool subproc_New(run_t* run) { |
| run->pid = run->persistentPid; |
| if (run->pid != 0 && run->hasCrashed == false) { |
| return true; |
| } |
| |
| LOG_D("SocketFuzzer: subproc_new: Start New Process"); |
| run->hasCrashed = false; |
| run->tmOutSignaled = false; |
| |
| int sv[2]; |
| if (run->global->exe.persistent) { |
| if (run->persistentSock != -1) { |
| close(run->persistentSock); |
| } |
| |
| int sock_type = SOCK_STREAM; |
| #if defined(SOCK_CLOEXEC) |
| sock_type |= SOCK_CLOEXEC; |
| #endif |
| if (socketpair(AF_UNIX, sock_type, 0, sv) == -1) { |
| PLOG_W("socketpair(AF_UNIX, SOCK_STREAM, 0, sv)"); |
| return false; |
| } |
| run->persistentSock = sv[0]; |
| } |
| |
| LOG_D("Forking new process for thread: %" PRId32, run->fuzzNo); |
| |
| run->pid = arch_fork(run); |
| if (run->pid == -1) { |
| PLOG_E("Couldn't fork"); |
| return false; |
| } |
| /* The child process */ |
| if (!run->pid) { |
| logMutexReset(); |
| /* |
| * Reset sighandlers, and set alarm(1). It's a guarantee against dead-locks |
| * in the child, where we ensure here that the child process will either |
| * execve or get signaled by SIGALRM within 1 second. |
| * |
| * Those deadlocks typically stem from the fact, that malloc() can behave weirdly |
| * when fork()-ing a single thread of a process: e.g. with glibc < 2.24 |
| * (or, Ubuntu's 2.23-0ubuntu6). For more see |
| * http://changelogs.ubuntu.com/changelogs/pool/main/g/glibc/glibc_2.23-0ubuntu7/changelog |
| */ |
| alarm(1); |
| signal(SIGALRM, SIG_DFL); |
| |
| if (run->global->exe.persistent) { |
| if (dup2(sv[1], _HF_PERSISTENT_FD) == -1) { |
| PLOG_F("dup2('%d', '%d')", sv[1], _HF_PERSISTENT_FD); |
| } |
| close(sv[0]); |
| close(sv[1]); |
| } |
| |
| if (!subproc_PrepareExecv(run)) { |
| LOG_E("subproc_PrepareExecv() failed"); |
| exit(EXIT_FAILURE); |
| } |
| if (!arch_launchChild(run)) { |
| LOG_E("Error launching child process"); |
| kill(run->global->threads.mainPid, SIGTERM); |
| _exit(1); |
| } |
| abort(); |
| } |
| |
| /* Parent */ |
| LOG_D("Launched new process, PID: %d, thread: %" PRId32 " (concurrency: %zd)", run->pid, |
| run->fuzzNo, run->global->threads.threadsMax); |
| if (run->global->socketFuzzer) { |
| // Dont know why, but this is important |
| run->persistentPid = run->pid; |
| } |
| |
| if (run->global->exe.persistent) { |
| close(sv[1]); |
| LOG_I("Persistent mode: Launched new persistent PID: %d", (int)run->pid); |
| run->persistentPid = run->pid; |
| |
| } |
| |
| arch_prepareParentAfterFork(run); |
| |
| return true; |
| } |
| |
| bool subproc_Run(run_t* run) { |
| run->timeStartedMillis = util_timeNowMillis(); |
| |
| if (!subproc_New(run)) { |
| LOG_E("subproc_New()"); |
| return false; |
| } |
| |
| arch_prepareParent(run); |
| |
| if (run->global->exe.persistent && !subproc_persistentSendFileIndicator(run)) { |
| LOG_W("Could not send file size to the persistent process"); |
| kill(run->persistentPid, SIGKILL); |
| } |
| arch_reapChild(run); |
| |
| return true; |
| } |
| |
| uint8_t subproc_System(run_t* run, const char* const argv[]) { |
| pid_t pid = arch_fork(run); |
| if (pid == -1) { |
| PLOG_E("Couldn't fork"); |
| return 255; |
| } |
| if (!pid) { |
| logMutexReset(); |
| |
| setsid(); |
| util_closeStdio( |
| /* close_stdin= */ true, /* close_stdout= */ false, /* close_stderr= */ false); |
| |
| sigset_t sset; |
| sigemptyset(&sset); |
| if (sigprocmask(SIG_SETMASK, &sset, NULL) == -1) { |
| PLOG_W("sigprocmask(empty_set)"); |
| } |
| |
| execv(argv[0], (char* const*)&argv[0]); |
| PLOG_F("Couldn't execute '%s'", argv[0]); |
| return 255; |
| } |
| |
| int status; |
| int flags = 0; |
| #if defined(__WNOTHREAD) |
| flags |= __WNOTHREAD; |
| #endif /* defined(__WNOTHREAD) */ |
| |
| for (;;) { |
| int ret = wait4(pid, &status, flags, NULL); |
| if (ret == -1 && errno == EINTR) { |
| continue; |
| } |
| if (ret == -1) { |
| PLOG_E("wait4() for process PID: %d", (int)pid); |
| return 255; |
| } |
| if (ret != pid) { |
| LOG_E("wait4() returned %d, but waited for %d", ret, (int)pid); |
| return 255; |
| } |
| if (WIFSIGNALED(status)) { |
| LOG_E("Command '%s' terminated with signal: %d", argv[0], WTERMSIG(status)); |
| return (100 + WTERMSIG(status)); |
| } |
| if (WIFEXITED(status)) { |
| if (WEXITSTATUS(status) == 0) { |
| return 0U; |
| } |
| LOG_E("Command '%s' returned with exit code %d", argv[0], WEXITSTATUS(status)); |
| return 1U; |
| } |
| |
| LOG_D("wait4() returned with status: %d", status); |
| } |
| } |
| |
| void subproc_checkTimeLimit(run_t* run) { |
| if (run->global->timing.tmOut == 0) { |
| return; |
| } |
| |
| int64_t curMillis = util_timeNowMillis(); |
| int64_t diffMillis = curMillis - run->timeStartedMillis; |
| |
| if (run->tmOutSignaled && (diffMillis > ((run->global->timing.tmOut + 1) * 1000))) { |
| /* Has this instance been already signaled due to timeout? Just, SIGKILL it */ |
| LOG_W("PID %d has already been signaled due to timeout. Killing it with SIGKILL", run->pid); |
| kill(run->pid, SIGKILL); |
| return; |
| } |
| |
| if ((diffMillis > (run->global->timing.tmOut * 1000)) && !run->tmOutSignaled) { |
| run->tmOutSignaled = true; |
| LOG_W("PID %d took too much time (limit %ld s). Killing it with %s", run->pid, |
| (long)run->global->timing.tmOut, |
| run->global->timing.tmoutVTALRM ? "SIGVTALRM" : "SIGKILL"); |
| if (run->global->timing.tmoutVTALRM) { |
| kill(run->pid, SIGVTALRM); |
| } else { |
| kill(run->pid, SIGKILL); |
| } |
| ATOMIC_POST_INC(run->global->cnts.timeoutedCnt); |
| } |
| } |
| |
| void subproc_checkTermination(run_t* run) { |
| if (fuzz_isTerminating()) { |
| LOG_D("Killing PID: %d", (int)run->pid); |
| kill(run->pid, SIGKILL); |
| } |
| } |