static bool arch_listThreads(int tasks[], size_t thrSz, int pid) { char path[512]; snprintf(path, sizeof(path), "/proc/%d/task", pid); /* An optimization, the number of threads is st.st_nlink - 2 (. and ..) */ struct stat st; if (stat(path, &st) != -1) { if (st.st_nlink == 3) { tasks[0] = pid; tasks[1] = 0; return true; } } size_t count = 0; DIR *dir = opendir(path); if (!dir) { PLOG_E("Couldn't open dir '%s'", path); return false; } defer { closedir(dir); }; for (;;) { errno = 0; struct dirent *res = readdir(dir); if (res == NULL && errno != 0) { PLOG_E("Couldn't read contents of '%s'", path); return false; } if (res == NULL) { break; } pid_t pid = (pid_t) strtol(res->d_name, (char **)NULL, 10); if (pid == 0) { LOG_D("The following dir entry couldn't be converted to pid_t '%s'", res->d_name); continue; } tasks[count++] = pid; LOG_D("Added pid '%d' from '%s/%s'", pid, path, res->d_name); if (count >= thrSz) { break; } } PLOG_D("Total number of threads in pid '%d': '%zd'", pid, count); tasks[count + 1] = 0; if (count < 1) { return false; } return true; }
static bool arch_listThreads(int tasks[], size_t thrSz, int pid) { size_t count = 0; char path[512]; snprintf(path, sizeof(path), "/proc/%d/task", pid); DIR *dir = opendir(path); if (!dir) { PLOG_E("Couldn't open dir '%s'", path); return false; } for (;;) { struct dirent de, *res; if (readdir_r(dir, &de, &res) > 0) { PLOG_E("Couldn't read contents of '%s'", path); closedir(dir); return false; } if (res == NULL) { break; } pid_t pid = (pid_t) strtol(res->d_name, (char **)NULL, 10); if (pid == 0) { LOG_D("The following dir entry couldn't be converted to pid_t '%s'", res->d_name); continue; } tasks[count++] = pid; LOG_D("Added pid '%d' from '%s/%s'", pid, path, res->d_name); if (count >= thrSz) { break; } } closedir(dir); PLOG_D("Total number of threads in pid '%d': '%zd'", pid, count); tasks[count + 1] = 0; if (count < 1) { return false; } return true; }
/* * dstExists argument can be used by caller for cases where existing destination * file requires special handling (e.g. save unique crashes) */ bool files_copyFile(const char *source, const char *destination, bool * dstExists) { if (dstExists) *dstExists = false; if (link(source, destination) == 0) { return true; } else { if (errno == EEXIST) { // Should kick-in before MAC, so avoid the hassle if (dstExists) *dstExists = true; return false; } else { PLOG_D("Couldn't link '%s' as '%s'", source, destination); /* * Don't fail yet as we might have a running env which doesn't allow * hardlinks (e.g. SELinux) */ } } // Now try with a verbose POSIX alternative int inFD, outFD, dstOpenFlags; mode_t dstFilePerms; // O_EXCL is important for saving unique crashes dstOpenFlags = O_CREAT | O_WRONLY | O_EXCL; dstFilePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; inFD = open(source, O_RDONLY); if (inFD == -1) { PLOG_D("Couldn't open '%s' source", source); return false; } defer { close(inFD); }; struct stat inSt; if (fstat(inFD, &inSt) == -1) { PLOG_W("Couldn't fstat(fd='%d' fileName='%s')", inFD, source); return false; } outFD = open(destination, dstOpenFlags, dstFilePerms); if (outFD == -1) { if (errno == EEXIST) { if (dstExists) *dstExists = true; } PLOG_D("Couldn't open '%s' destination", destination); return false; } defer { close(outFD); }; uint8_t *inFileBuf = malloc(inSt.st_size); if (!inFileBuf) { PLOG_W("malloc(%zu) failed", (size_t) inSt.st_size); return false; } defer { free(inFileBuf); }; ssize_t readSz = files_readFromFd(inFD, inFileBuf, (size_t) inSt.st_size); if (readSz < 0) { PLOG_W("Couldn't read '%s' to a buf", source); return false; } if (files_writeToFd(outFD, inFileBuf, readSz) == false) { PLOG_W("Couldn't write '%zu' bytes to file '%s' (fd='%d')", (size_t) readSz, destination, outFD); unlink(destination); return false; } return true; }
static int arch_parseAsanReport(honggfuzz_t * hfuzz, pid_t pid, funcs_t * funcs, void **crashAddr, char **op) { char crashReport[PATH_MAX] = { 0 }; const char *const crashReportCpy = crashReport; snprintf(crashReport, sizeof(crashReport), "%s/%s.%d", hfuzz->workDir, kLOGPREFIX, pid); FILE *fReport = fopen(crashReport, "rb"); if (fReport == NULL) { PLOG_D("Couldn't open '%s' - R/O mode", crashReport); return -1; } defer { fclose(fReport); }; defer { unlink(crashReportCpy); }; char header[35] = { 0 }; snprintf(header, sizeof(header), "==%d==ERROR: AddressSanitizer:", pid); size_t headerSz = strlen(header); bool headerFound = false; uint8_t frameIdx = 0; char framePrefix[5] = { 0 }; snprintf(framePrefix, sizeof(framePrefix), "#%" PRIu8, frameIdx); char *lineptr = NULL, *cAddr = NULL; size_t n = 0; defer { free(lineptr); }; for (;;) { if (getline(&lineptr, &n, fReport) == -1) { break; } /* First step is to identify header */ if (headerFound == false) { if ((strlen(lineptr) > headerSz) && (strncmp(header, lineptr, headerSz) == 0)) { headerFound = true; /* Parse crash address */ cAddr = strstr(lineptr, "address 0x"); if (cAddr) { cAddr = cAddr + strlen("address "); char *endOff = strchr(cAddr, ' '); cAddr[endOff - cAddr] = '\0'; *crashAddr = (void *)((size_t) strtoull(cAddr, NULL, 16)); } else { *crashAddr = 0x0; } } continue; } else { char *pLineLC = lineptr; /* Trim leading spaces */ while (*pLineLC != '\0' && isspace(*pLineLC)) { ++pLineLC; } /* Separator for crash thread stack trace is an empty line (after trmming \n */ if ((*pLineLC == '\0') && (frameIdx != 0)) { break; } /* Basic length checks */ if (strlen(pLineLC) < 10) { continue; } /* If available parse the type of error (READ/WRITE) */ if (cAddr && strstr(pLineLC, cAddr)) { if (strncmp(pLineLC, "READ", 4) == 0) { *op = "READ"; } else if (strncmp(pLineLC, "WRITE", 5) == 0) { *op = "WRITE"; } cAddr = NULL; } /* Check for crash thread frames */ if (strncmp(pLineLC, framePrefix, strlen(framePrefix)) == 0) { /* Abort if max depth */ if (frameIdx >= _HF_MAX_FUNCS) { break; } /* * Frames have following format: #0 0xaa860177 (/system/lib/libc.so+0x196177) */ char *savePtr = NULL; strtok_r(pLineLC, " ", &savePtr); funcs[frameIdx].pc = (void *)((size_t) strtoull(strtok_r(NULL, " ", &savePtr), NULL, 16)); /* DSO & code offset parsing */ char *targetStr = strtok_r(NULL, " ", &savePtr); char *startOff = strchr(targetStr, '(') + 1; char *plusOff = strchr(targetStr, '+'); char *endOff = strrchr(targetStr, ')'); targetStr[endOff - startOff] = '\0'; if ((startOff == NULL) || (endOff == NULL) || (plusOff == NULL)) { LOG_D("Invalid ASan report entry (%s)", lineptr); } else { size_t dsoSz = MIN(sizeof(funcs[frameIdx].func), (size_t) (plusOff - startOff)); memcpy(funcs[frameIdx].func, startOff, dsoSz); char *codeOff = targetStr + (plusOff - startOff) + 1; funcs[frameIdx].line = strtoull(codeOff, NULL, 16); } frameIdx++; snprintf(framePrefix, sizeof(framePrefix), "#%" PRIu8, frameIdx); } } } return frameIdx; }
static size_t arch_getProcMem(pid_t pid, uint8_t * buf, size_t len, REG_TYPE pc) { /* * Let's try process_vm_readv first */ const struct iovec local_iov = { .iov_base = buf, .iov_len = len, }; const struct iovec remote_iov = { .iov_base = (void *)(uintptr_t) pc, .iov_len = len, }; if (process_vm_readv(pid, &local_iov, 1, &remote_iov, 1, 0) == (ssize_t) len) { return len; } // Debug if failed since it shouldn't happen very often PLOG_D("process_vm_readv() failed"); /* * Ok, let's do it via ptrace() then. * len must be aligned to the sizeof(long) */ int cnt = len / sizeof(long); size_t memsz = 0; for (int x = 0; x < cnt; x++) { uint8_t *addr = (uint8_t *) (uintptr_t) pc + (int)(x * sizeof(long)); long ret = ptrace(PTRACE_PEEKDATA, pid, addr, NULL); if (errno != 0) { PLOG_W("Couldn't PT_READ_D on pid %d, addr: %p", pid, addr); break; } memsz += sizeof(long); memcpy(&buf[x * sizeof(long)], &ret, sizeof(long)); } return memsz; } void arch_ptraceGetCustomPerf(honggfuzz_t * hfuzz, pid_t pid, uint64_t * cnt UNUSED) { if ((hfuzz->dynFileMethod & _HF_DYNFILE_CUSTOM) == 0) { return; } if (hfuzz->persistent) { ptrace(PTRACE_INTERRUPT, pid, 0, 0); arch_ptraceWaitForPidStop(pid); } defer { if (hfuzz->persistent) { ptrace(PTRACE_CONT, pid, 0, 0); } }; #if defined(__x86_64__) struct user_regs_struct_64 regs; if (ptrace(PTRACE_GETREGS, pid, 0, ®s) != -1) { *cnt = regs.gs_base; return; } #endif /* defined(__x86_64__) */ *cnt = 0ULL; } void arch_ptraceSetCustomPerf(honggfuzz_t * hfuzz, pid_t pid, uint64_t cnt UNUSED) { if ((hfuzz->dynFileMethod & _HF_DYNFILE_CUSTOM) == 0) { return; } if (hfuzz->persistent) { ptrace(PTRACE_INTERRUPT, pid, 0, 0); arch_ptraceWaitForPidStop(pid); } defer { if (hfuzz->persistent) { ptrace(PTRACE_CONT, pid, 0, 0); } }; #if defined(__x86_64__) struct user_regs_struct_64 regs; if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) { return; } regs.gs_base = cnt; if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) { return; } #endif /* defined(__x86_64__) */ } static size_t arch_getPC(pid_t pid, REG_TYPE * pc, REG_TYPE * status_reg UNUSED) { /* * Some old ARM android kernels are failing with PTRACE_GETREGS to extract * the correct register values if struct size is bigger than expected. As such the * 32/64-bit multiplexing trick is not working for them in case PTRACE_GETREGSET * fails or is not implemented. To cover such cases we explicitly define * the struct size to 32bit version for arm CPU. */ #if defined(__arm__) struct user_regs_struct_32 regs; #else HEADERS_STRUCT regs; #endif struct iovec pt_iov = { .iov_base = ®s, .iov_len = sizeof(regs), }; if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &pt_iov) == -1L) { PLOG_D("ptrace(PTRACE_GETREGSET) failed"); // If PTRACE_GETREGSET fails, try PTRACE_GETREGS if available #if PTRACE_GETREGS_AVAILABLE if (ptrace(PTRACE_GETREGS, pid, 0, ®s)) { PLOG_D("ptrace(PTRACE_GETREGS) failed"); LOG_W("ptrace PTRACE_GETREGSET & PTRACE_GETREGS failed to extract target registers"); return 0; } #else return 0; #endif } #if defined(__i386__) || defined(__x86_64__) /* * 32-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_32)) { struct user_regs_struct_32 *r32 = (struct user_regs_struct_32 *)®s; *pc = r32->eip; *status_reg = r32->eflags; return pt_iov.iov_len; } /* * 64-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_64)) { struct user_regs_struct_64 *r64 = (struct user_regs_struct_64 *)®s; *pc = r64->ip; *status_reg = r64->flags; return pt_iov.iov_len; } LOG_W("Unknown registers structure size: '%zd'", pt_iov.iov_len); return 0; #endif /* defined(__i386__) || defined(__x86_64__) */ #if defined(__arm__) || defined(__aarch64__) /* * 32-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_32)) { struct user_regs_struct_32 *r32 = (struct user_regs_struct_32 *)®s; #ifdef __ANDROID__ *pc = r32->ARM_pc; *status_reg = r32->ARM_cpsr; #else *pc = r32->uregs[ARM_pc]; *status_reg = r32->uregs[ARM_cpsr]; #endif return pt_iov.iov_len; } /* * 64-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_64)) { struct user_regs_struct_64 *r64 = (struct user_regs_struct_64 *)®s; *pc = r64->pc; *status_reg = r64->pstate; return pt_iov.iov_len; } LOG_W("Unknown registers structure size: '%zd'", pt_iov.iov_len); return 0; #endif /* defined(__arm__) || defined(__aarch64__) */ #if defined(__powerpc64__) || defined(__powerpc__) /* * 32-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_32)) { struct user_regs_struct_32 *r32 = (struct user_regs_struct_32 *)®s; *pc = r32->nip; return pt_iov.iov_len; } /* * 64-bit */ if (pt_iov.iov_len == sizeof(struct user_regs_struct_64)) { struct user_regs_struct_64 *r64 = (struct user_regs_struct_64 *)®s; *pc = r64->nip; return pt_iov.iov_len; } LOG_W("Unknown registers structure size: '%zd'", pt_iov.iov_len); return 0; #endif /* defined(__powerpc64__) || defined(__powerpc__) */ LOG_D("Unknown/unsupported CPU architecture"); return 0; } static void arch_getInstrStr(pid_t pid, REG_TYPE * pc, char *instr) { /* * We need a value aligned to 8 * which is sizeof(long) on 64bit CPU archs (on most of them, I hope;) */ uint8_t buf[MAX_INSTR_SZ]; size_t memsz; REG_TYPE status_reg = 0; snprintf(instr, _HF_INSTR_SZ, "%s", "[UNKNOWN]"); size_t pcRegSz = arch_getPC(pid, pc, &status_reg); if (!pcRegSz) { LOG_W("Current architecture not supported for disassembly"); return; } if ((memsz = arch_getProcMem(pid, buf, sizeof(buf), *pc)) == 0) { snprintf(instr, _HF_INSTR_SZ, "%s", "[NOT_MMAPED]"); return; } #if !defined(__ANDROID__) arch_bfdDisasm(pid, buf, memsz, instr); #else cs_arch arch; cs_mode mode; #if defined(__arm__) || defined(__aarch64__) arch = (pcRegSz == sizeof(struct user_regs_struct_64)) ? CS_ARCH_ARM64 : CS_ARCH_ARM; if (arch == CS_ARCH_ARM) { mode = (status_reg & 0x20) ? CS_MODE_THUMB : CS_MODE_ARM; } else { mode = CS_MODE_ARM; } #elif defined(__i386__) || defined(__x86_64__) arch = CS_ARCH_X86; mode = (pcRegSz == sizeof(struct user_regs_struct_64)) ? CS_MODE_64 : CS_MODE_32; #else LOG_E("Unknown/Unsupported Android CPU architecture"); #endif csh handle; cs_err err = cs_open(arch, mode, &handle); if (err != CS_ERR_OK) { LOG_W("Capstone initialization failed: '%s'", cs_strerror(err)); return; } cs_insn *insn; size_t count = cs_disasm(handle, buf, sizeof(buf), *pc, 0, &insn); if (count < 1) { LOG_W("Couldn't disassemble the assembler instructions' stream: '%s'", cs_strerror(cs_errno(handle))); cs_close(&handle); return; } snprintf(instr, _HF_INSTR_SZ, "%s %s", insn[0].mnemonic, insn[0].op_str); cs_free(insn, count); cs_close(&handle); #endif /* defined(__ANDROID__) */ for (int x = 0; instr[x] && x < _HF_INSTR_SZ; x++) { if (instr[x] == '/' || instr[x] == '\\' || isspace(instr[x]) || !isprint(instr[x])) { instr[x] = '_'; } } return; } static void arch_hashCallstack(honggfuzz_t * hfuzz, fuzzer_t * fuzzer, funcs_t * funcs, size_t funcCnt, bool enableMasking) { uint64_t hash = 0; for (size_t i = 0; i < funcCnt && i < hfuzz->linux.numMajorFrames; i++) { /* * Convert PC to char array to be compatible with hash function */ char pcStr[REGSIZEINCHAR] = { 0 }; snprintf(pcStr, REGSIZEINCHAR, REG_PD REG_PM, (REG_TYPE) (long)funcs[i].pc); /* * Hash the last three nibbles */ hash ^= util_hash(&pcStr[strlen(pcStr) - 3], 3); } /* * If only one frame, hash is not safe to be used for uniqueness. We mask it * here with a constant prefix, so analyzers can pick it up and create filenames * accordingly. 'enableMasking' is controlling masking for cases where it should * not be enabled (e.g. fuzzer worker is from verifier). */ if (enableMasking && funcCnt == 1) { hash |= _HF_SINGLE_FRAME_MASK; } fuzzer->backtrace = hash; }
bool arch_launchChild(honggfuzz_t * hfuzz, char *fileName) { #define ARGS_MAX 512 char *args[ARGS_MAX + 2]; char argData[PATH_MAX] = { 0 }; int x; for (x = 0; x < ARGS_MAX && hfuzz->cmdline[x]; x++) { if (!hfuzz->fuzzStdin && strcmp(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER) == 0) { args[x] = fileName; } else if (!hfuzz->fuzzStdin && 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], fileName); /* * Set timeout (prof), real timeout (2*prof), and rlimit_cpu (2*prof) */ if (hfuzz->tmOut) { struct itimerval it; /* * The hfuzz->tmOut is real CPU usage time... */ it.it_value.tv_sec = hfuzz->tmOut; it.it_value.tv_usec = 0; it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0; if (setitimer(ITIMER_PROF, &it, NULL) == -1) { PLOG_E("Couldn't set the ITIMER_PROF timer"); return false; } /* * ...so, if a process sleeps, this one should * trigger a signal... */ it.it_value.tv_sec = hfuzz->tmOut * 2UL; it.it_value.tv_usec = 0; it.it_interval.tv_sec = 0; it.it_interval.tv_usec = 0; if (setitimer(ITIMER_REAL, &it, NULL) == -1) { PLOG_E("Couldn't set the ITIMER_REAL timer"); return false; } /* * ..if a process sleeps and catches SIGPROF/SIGALRM * rlimits won't help either */ struct rlimit rl; rl.rlim_cur = hfuzz->tmOut * 2; rl.rlim_max = hfuzz->tmOut * 2; if (setrlimit(RLIMIT_CPU, &rl) == -1) { PLOG_E("Couldn't enforce the RLIMIT_CPU resource limit"); return false; } } /* * The address space limit. If big enough - roughly the size of RAM used */ if (hfuzz->asLimit) { struct rlimit rl; rl.rlim_cur = hfuzz->asLimit * 1024UL * 1024UL; rl.rlim_max = hfuzz->asLimit * 1024UL * 1024UL; if (setrlimit(RLIMIT_AS, &rl) == -1) { PLOG_D("Couldn't enforce the RLIMIT_AS resource limit, ignoring"); } } if (hfuzz->nullifyStdio) { util_nullifyStdio(); } if (hfuzz->fuzzStdin) { /* * Uglyyyyyy ;) */ if (!util_redirectStdin(fileName)) { return false; } } for (size_t i = 0; i < ARRAYSIZE(hfuzz->envs) && hfuzz->envs[i]; i++) { putenv(hfuzz->envs[i]); } execvp(args[0], args); util_recoverStdio(); LOG_F("Failed to create new '%s' process", args[0]); return false; }
static bool containDropPrivs(struct nsjconf_t *nsjconf) { /* * Best effort because of /proc/self/setgroups */ gid_t *group_list = NULL; if (setgroups(0, group_list) == -1) { PLOG_D("setgroups(NULL) failed"); } if (setresgid(nsjconf->inside_gid, nsjconf->inside_gid, nsjconf->inside_gid) == -1) { PLOG_E("setresgid(%u)", nsjconf->inside_gid); return false; } if (setresuid(nsjconf->inside_uid, nsjconf->inside_uid, nsjconf->inside_uid) == -1) { PLOG_E("setresuid(%u)", nsjconf->inside_uid); return false; } #ifndef PR_SET_NO_NEW_PRIVS #define PR_SET_NO_NEW_PRIVS 38 #endif if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { /* Only new kernels support it */ PLOG_W("prctl(PR_SET_NO_NEW_PRIVS, 1)"); } if (nsjconf->keep_caps == false) { for (unsigned long i = 0; i < 128UL; i++) { /* * Number of capabilities differs between kernels, so * wait for the first one which returns EINVAL */ if (prctl(PR_CAPBSET_DROP, i, 0UL, 0UL, 0UL) == -1 && errno == EINVAL) { break; } } if (prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0) == -1) { PLOG_E("prctl(PR_SET_KEEPCAPS, 0)"); return false; } struct __user_cap_header_struct cap_hdr = { .version = _LINUX_CAPABILITY_VERSION_3, .pid = 0, }; const struct __user_cap_data_struct cap_data[_LINUX_CAPABILITY_U32S_3] = { [0 ... (_LINUX_CAPABILITY_U32S_3 - 1)].inheritable = 0U, [0 ... (_LINUX_CAPABILITY_U32S_3 - 1)].effective = 0U, [0 ... (_LINUX_CAPABILITY_U32S_3 - 1)].permitted = 0U, }; if (syscall(__NR_capset, &cap_hdr, &cap_data) == -1) { PLOG_E("capset()"); return false; } } return true; } static bool containPrepareEnv(struct nsjconf_t *nsjconf) { if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) == -1) { PLOG_E("prctl(PR_SET_PDEATHSIG, SIGKILL)"); return false; } if (nsjconf->personality && personality(nsjconf->personality) == -1) { PLOG_E("personality(%lx)", nsjconf->personality); return false; } errno = 0; if (setpriority(PRIO_PROCESS, 0, 19) == -1 && errno != 0) { PLOG_W("setpriority(19)"); } if (nsjconf->skip_setsid == false) { setsid(); } return true; } static bool containInitMountNs(struct nsjconf_t *nsjconf) { return mountInitNs(nsjconf); } static bool containSetLimits(struct nsjconf_t *nsjconf) { struct rlimit64 rl; rl.rlim_cur = rl.rlim_max = nsjconf->rl_as; if (prlimit64(0, RLIMIT_AS, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_AS, %" PRIu64 ")", nsjconf->rl_as); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_core; if (prlimit64(0, RLIMIT_CORE, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_CORE, %" PRIu64 ")", nsjconf->rl_core); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_cpu; if (prlimit64(0, RLIMIT_CPU, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_CPU, %" PRIu64 ")", nsjconf->rl_cpu); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_fsize; if (prlimit64(0, RLIMIT_FSIZE, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_FSIZE, %" PRIu64 ")", nsjconf->rl_fsize); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_nofile; if (prlimit64(0, RLIMIT_NOFILE, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_NOFILE, %" PRIu64 ")", nsjconf->rl_nofile); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_nproc; if (prlimit64(0, RLIMIT_NPROC, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_NPROC, %" PRIu64 ")", nsjconf->rl_nproc); return false; } rl.rlim_cur = rl.rlim_max = nsjconf->rl_stack; if (prlimit64(0, RLIMIT_STACK, &rl, NULL) == -1) { PLOG_E("prlimit64(0, RLIMIT_STACK, %" PRIu64 ")", nsjconf->rl_stack); return false; } return true; } static bool containMakeFdsCOENaive(void) { // Don't use getrlimit(RLIMIT_NOFILE) here, as it can return an artifically small value // (e.g. 32), which could be smaller than a maximum assigned number to file-descriptors // in this process. Just use some reasonably sane value (e.g. 1024) for (unsigned fd = (STDERR_FILENO + 1); fd < 1024; fd++) { int flags = fcntl(fd, F_GETFD, 0); if (flags == -1) { continue; } fcntl(fd, F_SETFD, flags | FD_CLOEXEC); LOG_D("Set fd '%d' flag to FD_CLOEXEC", fd); } return true; } static bool containMakeFdsCOEProc(void) { /* Make all fds above stderr close-on-exec */ DIR *dir = opendir("/proc/self/fd"); if (dir == NULL) { PLOG_D("opendir('/proc/self/fd')"); return false; } defer { closedir(dir); }; for (;;) { errno = 0; struct dirent *entry = readdir(dir); if (entry == NULL && errno != 0) { PLOG_D("readdir('/proc/self/fd')"); return false; } if (entry == NULL) { break; } if (strcmp(".", entry->d_name) == 0) { continue; } if (strcmp("..", entry->d_name) == 0) { continue; } int fd = strtoul(entry->d_name, NULL, 10); if (errno == EINVAL) { LOG_W("Cannot convert /proc/self/fd/%s to a number", entry->d_name); continue; } if (fd > STDERR_FILENO) { int flags = fcntl(fd, F_GETFD, 0); if (flags == -1) { PLOG_D("fcntl(fd, F_GETFD, 0)"); return false; } fcntl(fd, F_SETFD, flags | FD_CLOEXEC); LOG_D("Set fd '%d' flag to FD_CLOEXEC", fd); } } return true; }
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_", "3", 1) == -1) { PLOG_E("setenv(MALLOC_CHECK_=3) failed"); return false; } /* * Tell asan to ignore SEGVs */ if (setenv ("ASAN_OPTIONS", "allow_user_segv_handler=1:handle_segv=0:abort_on_error=1:allocator_may_return_null=1", 1) == -1) { PLOG_E("setenv(ASAN_OPTIONS) failed"); return false; } const char *msan_options = "exit_code=" HF_MSAN_EXIT_CODE_STR ":report_umrs=0:wrap_signals=0:print_stats=1"; if (hfuzz->msanReportUMRS == true) { msan_options = "exit_code=" HF_MSAN_EXIT_CODE_STR ":report_umrs=1:wrap_signals=0:print_stats=1"; } if (setenv("MSAN_OPTIONS", msan_options, 1) == -1) { PLOG_E("setenv(MSAN_OPTIONS) failed"); return false; } /* * Disable ASLR */ if (hfuzz->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; for (x = 0; x < ARGS_MAX && hfuzz->cmdline[x]; x++) { if (!hfuzz->fuzzStdin && strcmp(hfuzz->cmdline[x], _HF_FILE_PLACEHOLDER) == 0) { args[x] = fileName; } else if (!hfuzz->fuzzStdin && 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], fileName); /* * Set timeout (prof), real timeout (2*prof), and rlimit_cpu (2*prof) */ if (hfuzz->tmOut) { /* * Set the CPU rlimit to twice the value of the time-out */ struct rlimit rl = { .rlim_cur = hfuzz->tmOut * 2, .rlim_max = hfuzz->tmOut * 2, }; if (setrlimit(RLIMIT_CPU, &rl) == -1) { PLOG_E("Couldn't enforce the RLIMIT_CPU resource limit"); return false; } } /* * The address space limit. If big enough - roughly the size of RAM used */ if (hfuzz->asLimit) { struct rlimit64 rl = { .rlim_cur = hfuzz->asLimit * 1024ULL * 1024ULL, .rlim_max = hfuzz->asLimit * 1024ULL * 1024ULL, }; if (prlimit64(0, RLIMIT_AS, &rl, NULL) == -1) { PLOG_D("Couldn't enforce the RLIMIT_AS resource limit, ignoring"); } } for (size_t i = 0; i < ARRAYSIZE(hfuzz->envs) && hfuzz->envs[i]; i++) { putenv(hfuzz->envs[i]); } if (hfuzz->nullifyStdio) { util_nullifyStdio(); } if (hfuzz->fuzzStdin) { /* * Uglyyyyyy ;) */ if (!util_redirectStdin(fileName)) { return false; } } /* * Wait for the ptrace to attach */ syscall(__NR_tkill, syscall(__NR_gettid), SIGSTOP); execvp(args[0], args); util_recoverStdio(); LOG_F("Failed to create new '%s' process", args[0]); return false; } static void arch_sigFunc(int signo, siginfo_t * si, void *dummy) { if (signo != SIGALRM) { LOG_E("Signal != SIGALRM (%d)", signo); } return; if (si == NULL) { return; } if (dummy == NULL) { return; } } static void arch_removeTimer(timer_t * timerid) { timer_delete(*timerid); } static bool arch_setTimer(timer_t * timerid) { struct sigevent sevp = { .sigev_value.sival_ptr = timerid, .sigev_signo = SIGALRM, .sigev_notify = SIGEV_THREAD_ID | SIGEV_SIGNAL, ._sigev_un._tid = syscall(__NR_gettid), }; if (timer_create(CLOCK_REALTIME, &sevp, timerid) == -1) { PLOG_E("timer_create(CLOCK_REALTIME) failed"); return false; } /* * Kick in every 200ms, starting with the next second */ const struct itimerspec ts = { .it_value = {.tv_sec = 1,.tv_nsec = 0}, .it_interval = {.tv_sec = 0,.tv_nsec = 200000000,}, }; if (timer_settime(*timerid, 0, &ts, NULL) == -1) { PLOG_E("timer_settime() failed"); timer_delete(*timerid); return false; } sigset_t smask; sigemptyset(&smask); struct sigaction sa = { .sa_handler = NULL, .sa_sigaction = arch_sigFunc, .sa_mask = smask, .sa_flags = SA_SIGINFO, .sa_restorer = NULL, }; if (sigaction(SIGALRM, &sa, NULL) == -1) { PLOG_E("sigaction(SIGALRM) failed"); 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); __sync_fetch_and_add(&hfuzz->timeoutedCnt, 1UL); } }