uint8_t *files_mapFile(char *fileName, off_t * fileSz, int *fd, bool isWritable) { int mmapProt = PROT_READ; if (isWritable) { mmapProt |= PROT_WRITE; } if ((*fd = open(fileName, O_RDONLY)) == -1) { PLOG_W("Couldn't open() '%s' file in R/O mode", fileName); return NULL; } struct stat st; if (fstat(*fd, &st) == -1) { PLOG_W("Couldn't stat() the '%s' file", fileName); close(*fd); return NULL; } uint8_t *buf; if ((buf = mmap(NULL, st.st_size, mmapProt, MAP_PRIVATE, *fd, 0)) == MAP_FAILED) { PLOG_W("Couldn't mmap() the '%s' file", fileName); close(*fd); return NULL; } *fileSz = st.st_size; return buf; }
bool arch_ptraceAttach(pid_t pid) { static const long seize_options = PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEEXIT | PTRACE_O_EXITKILL; if (ptrace(PTRACE_SEIZE, pid, NULL, seize_options) == -1) { PLOG_W("Couldn't ptrace(PTRACE_SEIZE) to pid: %d", pid); return false; } LOG_D("Attached to PID: %d", pid); int tasks[MAX_THREAD_IN_TASK + 1] = { 0 }; if (!arch_listThreads(tasks, MAX_THREAD_IN_TASK, pid)) { LOG_E("Couldn't read thread list for pid '%d'", pid); return false; } for (int i = 0; i < MAX_THREAD_IN_TASK && tasks[i]; i++) { if (tasks[i] == pid) { continue; } if (ptrace(PTRACE_SEIZE, tasks[i], NULL, seize_options) == -1) { PLOG_W("Couldn't ptrace(PTRACE_SEIZE) to pid: %d", tasks[i]); continue; } LOG_D("Attached to PID: %d (thread_group:%d)", tasks[i], pid); } return true; }
bool files_parseBlacklist(honggfuzz_t * hfuzz) { FILE *fBl = fopen(hfuzz->blacklistFile, "rb"); if (fBl == NULL) { PLOG_W("Couldn't open '%s' - R/O mode", hfuzz->blacklistFile); return false; } defer { fclose(fBl); }; char *lineptr = NULL; /* lineptr can be NULL, but it's fine for free() */ defer { free(lineptr); }; size_t n = 0; for (;;) { if (getline(&lineptr, &n, fBl) == -1) { break; } if ((hfuzz->blacklist = util_Realloc(hfuzz->blacklist, (hfuzz->blacklistCnt + 1) * sizeof(hfuzz->blacklist[0]))) == NULL) { PLOG_W("realloc failed (sz=%zu)", (hfuzz->blacklistCnt + 1) * sizeof(hfuzz->blacklist[0])); return false; } hfuzz->blacklist[hfuzz->blacklistCnt] = strtoull(lineptr, 0, 16); LOG_D("Blacklist: loaded %'" PRIu64 "'", hfuzz->blacklist[hfuzz->blacklistCnt]); // Verify entries are sorted so we can use interpolation search if (hfuzz->blacklistCnt > 1) { if (hfuzz->blacklist[hfuzz->blacklistCnt - 1] > hfuzz->blacklist[hfuzz->blacklistCnt]) { LOG_F ("Blacklist file not sorted. Use 'tools/createStackBlacklist.sh' to sort records"); return false; } } hfuzz->blacklistCnt += 1; } if (hfuzz->blacklistCnt > 0) { LOG_I("Loaded %zu stack hash(es) from the blacklist file", hfuzz->blacklistCnt); } else { LOG_F("Empty stack hashes blacklist file '%s'", hfuzz->blacklistFile); } return true; }
bool files_parseDictionary(honggfuzz_t * hfuzz) { FILE *fDict = fopen(hfuzz->dictionaryFile, "rb"); if (fDict == NULL) { PLOG_W("Couldn't open '%s' - R/O mode", hfuzz->dictionaryFile); return false; } defer { fclose(fDict); }; for (;;) { char *lineptr = NULL; size_t n = 0; ssize_t len = getdelim(&lineptr, &n, '\n', fDict); if (len == -1) { break; } if (n > 1 && lineptr[len - 1] == '\n') { lineptr[len - 1] = '\0'; len--; } struct strings_t *str = (struct strings_t *)util_Malloc(sizeof(struct strings_t)); str->len = util_decodeCString(lineptr); str->s = lineptr; hfuzz->dictionaryCnt += 1; TAILQ_INSERT_TAIL(&hfuzz->dictionaryq, str, pointers); LOG_D("Dictionary: loaded word: '%s' (len=%zu)", str->s, str->len); } LOG_I("Loaded %zu words from the dictionary", hfuzz->dictionaryCnt); return true; }
bool files_init(honggfuzz_t * hfuzz) { hfuzz->files = util_Malloc(sizeof(char *)); hfuzz->files[0] = "NONE"; hfuzz->fileCnt = 1; if (hfuzz->externalCommand) { LOG_I ("No input file corpus loaded, the external command '%s' is responsible for creating the fuzz files", hfuzz->externalCommand); return true; } if (!hfuzz->inputDir) { LOG_W("No input file/dir specified"); return false; } struct stat st; if (stat(hfuzz->inputDir, &st) == -1) { PLOG_W("Couldn't stat the input dir '%s'", hfuzz->inputDir); return false; } if (S_ISDIR(st.st_mode)) { return files_readdir(hfuzz); } LOG_W("The initial corpus directory '%s' is not a directory", hfuzz->inputDir); return false; }
static bool cmdlineCheckBinaryType(honggfuzz_t* hfuzz) { int fd; off_t fileSz; uint8_t* map = files_mapFile(hfuzz->exe.cmdline[0], &fileSz, &fd, /* isWriteable= */ false); if (!map) { /* It's not a critical error */ return true; } defer { if (munmap(map, fileSz) == -1) { PLOG_W("munmap(%p, %zu)", map, (size_t)fileSz); } close(fd); }; if (memmem(map, fileSz, _HF_PERSISTENT_SIG, strlen(_HF_PERSISTENT_SIG))) { LOG_I("Persistent signature found in '%s'. Enabling persistent fuzzing mode", hfuzz->exe.cmdline[0]); hfuzz->exe.persistent = true; } if (memmem(map, fileSz, _HF_NETDRIVER_SIG, strlen(_HF_NETDRIVER_SIG))) { LOG_I("NetDriver signature found '%s'", hfuzz->exe.cmdline[0]); hfuzz->exe.netDriver = true; } return true; }
static procMap_t *arch_parsePidMaps(pid_t pid, size_t * mapsCount) { FILE *f = NULL; char fProcMaps[PATH_MAX] = { 0 }; snprintf(fProcMaps, PATH_MAX, "/proc/%d/maps", pid); if ((f = fopen(fProcMaps, "rb")) == NULL) { PLOG_E("Couldn't open '%s' - R/O mode", fProcMaps); return 0; } defer { fclose(f); }; *mapsCount = 0; procMap_t *mapsList = malloc(sizeof(procMap_t)); if (mapsList == NULL) { PLOG_W("malloc(size='%zu')", sizeof(procMap_t)); return NULL; } while (!feof(f)) { char buf[sizeof(procMap_t) + 1]; if (fgets(buf, sizeof(buf), f) == 0) { break; } mapsList[*mapsCount].name[0] = '\0'; sscanf(buf, "%lx-%lx %5s %lx %7s %ld %s", &mapsList[*mapsCount].start, &mapsList[*mapsCount].end, mapsList[*mapsCount].perms, &mapsList[*mapsCount].offset, mapsList[*mapsCount].dev, &mapsList[*mapsCount].inode, mapsList[*mapsCount].name); *mapsCount += 1; if ((mapsList = realloc(mapsList, (*mapsCount + 1) * sizeof(procMap_t))) == NULL) { PLOG_W("realloc failed (sz=%zu)", (*mapsCount + 1) * sizeof(procMap_t)); free(mapsList); return NULL; } } return mapsList; }
bool files_writeBufToFile(char *fileName, uint8_t * buf, size_t fileSz, int flags) { int fd = open(fileName, flags, 0644); if (fd == -1) { PLOG_W("Couldn't open '%s' for R/O", fileName); return false; } defer { close(fd); }; if (files_writeToFd(fd, buf, fileSz) == false) { PLOG_W("Couldn't write '%zu' bytes to file '%s' (fd='%d')", fileSz, fileName, fd); unlink(fileName); return false; } LOG_D("Written '%zu' bytes to '%s'", fileSz, fileName); return true; }
uint8_t *files_mapFileShared(char *fileName, off_t * fileSz, int *fd) { if ((*fd = open(fileName, O_RDONLY)) == -1) { PLOG_W("Couldn't open() '%s' file in R/O mode", fileName); return NULL; } struct stat st; if (fstat(*fd, &st) == -1) { PLOG_W("Couldn't stat() the '%s' file", fileName); close(*fd); return NULL; } uint8_t *buf; if ((buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, *fd, 0)) == MAP_FAILED) { PLOG_W("Couldn't mmap() the '%s' file", fileName); close(*fd); return NULL; } *fileSz = st.st_size; return buf; }
void cgroupFinishFromParent(struct nsjconf_t *nsjconf, pid_t pid) { if (nsjconf->cgroup_mem_max == (size_t) 0) { return; } char mem_cgroup_path[PATH_MAX]; snprintf(mem_cgroup_path, sizeof(mem_cgroup_path), "%s/%s/NSJAIL.%d", nsjconf->cgroup_mem_mount, nsjconf->cgroup_mem_parent, (int)pid); LOG_D("Remove '%s'", mem_cgroup_path); if (rmdir(mem_cgroup_path) == -1) { PLOG_W("rmdir('%s') failed", mem_cgroup_path); } return; }
bool files_writePatternToFd(int fd, off_t size, unsigned char p) { void *buf = malloc(size); if (!buf) { PLOG_W("Couldn't allocate memory"); return false; } defer { free(buf); }; memset(buf, p, (size_t) size); int ret = files_writeToFd(fd, buf, size); return ret; }
bool arch_ptraceWaitForPidStop(pid_t pid) { for (;;) { int status; pid_t ret = wait4(pid, &status, __WALL | WUNTRACED, NULL); if (ret == -1 && errno == EINTR) { continue; } if (ret == -1) { PLOG_W("wait4(pid=%d) failed", pid); return false; } if (!WIFSTOPPED(status)) { LOG_W("PID %d not in a stopped state - status:%d", pid, status); return false; } return true; } }
ssize_t files_readFileToBufMax(char *fileName, uint8_t * buf, size_t fileMaxSz) { int fd = open(fileName, O_RDONLY); if (fd == -1) { PLOG_W("Couldn't open '%s' for R/O", fileName); return -1; } defer { close(fd); }; ssize_t readSz = files_readFromFd(fd, buf, fileMaxSz); if (readSz < 0) { LOG_W("Couldn't read '%s' to a buf", fileName); return -1; } LOG_D("Read '%zu' bytes from '%s'", readSz, fileName); return readSz; }
void util_recoverStdio(void) { int fd = open("/dev/tty", O_RDWR); if (fd == -1) { PLOG_E("Couldn't open '/dev/tty'"); return; } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); if (tcsetpgrp(fd, getpid()) == -1) { PLOG_W("tcsetpgrp(%d) failed", getpid()); } if (fd > 2) { close(fd); } }
static bool files_readdir(honggfuzz_t * hfuzz) { DIR *dir = opendir(hfuzz->inputDir); if (!dir) { PLOG_W("Couldn't open dir '%s'", hfuzz->inputDir); return false; } defer { closedir(dir); }; size_t maxSize = 0UL; unsigned count = 0; for (;;) { errno = 0; struct dirent *res = readdir(dir); if (res == NULL && errno != 0) { PLOG_W("Couldn't read the '%s' dir", hfuzz->inputDir); return false; } if (res == NULL) { break; } char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", hfuzz->inputDir, res->d_name); struct stat st; if (stat(path, &st) == -1) { LOG_W("Couldn't stat() the '%s' file", path); continue; } if (!S_ISREG(st.st_mode)) { LOG_D("'%s' is not a regular file, skipping", path); continue; } if (st.st_size == 0ULL) { LOG_D("'%s' is empty", path); continue; } if (hfuzz->maxFileSz != 0UL && st.st_size > (off_t) hfuzz->maxFileSz) { LOG_W("File '%s' is bigger than maximal defined file size (-F): %" PRId64 " > %" PRId64, path, (int64_t) st.st_size, (int64_t) hfuzz->maxFileSz); continue; } if (!(hfuzz->files = util_Realloc(hfuzz->files, sizeof(char *) * (count + 1)))) { PLOG_W("Couldn't allocate memory"); return false; } if ((size_t) st.st_size > maxSize) { maxSize = st.st_size; } hfuzz->files[count] = util_StrDup(path); hfuzz->fileCnt = ++count; LOG_D("Added '%s' to the list of input files", path); } if (count == 0) { LOG_W("Directory '%s' doesn't contain any regular files", hfuzz->inputDir); return false; } if (hfuzz->maxFileSz == 0UL) { hfuzz->maxFileSz = maxSize; } LOG_I("%zu input files have been added to the list. Max file size: %zu", hfuzz->fileCnt, hfuzz->maxFileSz); return true; }
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; }
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; }
static void arch_ptraceSaveData(honggfuzz_t * hfuzz, pid_t pid, fuzzer_t * fuzzer) { REG_TYPE pc = 0; /* Local copy since flag is overridden for some crashes */ bool saveUnique = hfuzz->saveUnique; char instr[_HF_INSTR_SZ] = "\x00"; siginfo_t si; bzero(&si, sizeof(si)); if (ptrace(PTRACE_GETSIGINFO, pid, 0, &si) == -1) { PLOG_W("Couldn't get siginfo for pid %d", pid); } arch_getInstrStr(pid, &pc, instr); LOG_D("Pid: %d, signo: %d, errno: %d, code: %d, addr: %p, pc: %" REG_PM ", instr: '%s'", pid, si.si_signo, si.si_errno, si.si_code, si.si_addr, pc, instr); if (!SI_FROMUSER(&si) && pc && si.si_addr < hfuzz->linux.ignoreAddr) { LOG_I("'%s' is interesting (%s), but the si.si_addr is %p (below %p), skipping", fuzzer->fileName, arch_sigs[si.si_signo].descr, si.si_addr, hfuzz->linux.ignoreAddr); return; } /* * Unwind and resolve symbols */ /* *INDENT-OFF* */ funcs_t funcs[_HF_MAX_FUNCS] = { [0 ... (_HF_MAX_FUNCS - 1)].pc = NULL, [0 ... (_HF_MAX_FUNCS - 1)].line = 0, [0 ... (_HF_MAX_FUNCS - 1)].func = {'\0'} , }; /* *INDENT-ON* */ #if !defined(__ANDROID__) size_t funcCnt = arch_unwindStack(pid, funcs); arch_bfdResolveSyms(pid, funcs, funcCnt); #else size_t funcCnt = arch_unwindStack(pid, funcs); #endif /* * If unwinder failed (zero frames), use PC from ptrace GETREGS if not zero. * If PC reg zero, temporarily disable uniqueness flag since callstack * hash will be also zero, thus not safe for unique decisions. */ if (funcCnt == 0) { if (pc) { /* Manually update major frame PC & frames counter */ funcs[0].pc = (void *)(uintptr_t) pc; funcCnt = 1; } else { saveUnique = false; } } /* * Temp local copy of previous backtrace value in case worker hit crashes into multiple * tids for same target master thread. Will be 0 for first crash against target. */ uint64_t oldBacktrace = fuzzer->backtrace; /* * Calculate backtrace callstack hash signature */ arch_hashCallstack(hfuzz, fuzzer, funcs, funcCnt, saveUnique); /* * If fuzzing with sanitizer coverage feedback increase crashes counter used * as metric for dynFile evolution */ if (hfuzz->useSanCov) { fuzzer->sanCovCnts.crashesCnt++; } /* * If unique flag is set and single frame crash, disable uniqueness for this crash * to always save (timestamp will be added to the filename) */ if (saveUnique && (funcCnt == 1)) { saveUnique = false; } /* * If worker crashFileName member is set, it means that a tid has already crashed * from target master thread. */ if (fuzzer->crashFileName[0] != '\0') { LOG_D("Multiple crashes detected from worker against attached tids group"); /* * If stackhashes match, don't re-analyze. This will avoid duplicates * and prevent verifier from running multiple passes. Depth of check is * always 1 (last backtrace saved only per target iteration). */ if (oldBacktrace == fuzzer->backtrace) { return; } } /* Increase global crashes counter */ ATOMIC_POST_INC(hfuzz->crashesCnt); /* * Check if stackhash is blacklisted */ if (hfuzz->blacklist && (fastArray64Search(hfuzz->blacklist, hfuzz->blacklistCnt, fuzzer->backtrace) != -1)) { LOG_I("Blacklisted stack hash '%" PRIx64 "', skipping", fuzzer->backtrace); ATOMIC_POST_INC(hfuzz->blCrashesCnt); return; } /* If non-blacklisted crash detected, zero set two MSB */ ATOMIC_POST_ADD(hfuzz->dynFileIterExpire, _HF_DYNFILE_SUB_MASK); void *sig_addr = si.si_addr; if (hfuzz->linux.disableRandomization == false) { pc = 0UL; sig_addr = NULL; } /* User-induced signals don't set si.si_addr */ if (SI_FROMUSER(&si)) { sig_addr = NULL; } /* If dry run mode, copy file with same name into workspace */ if (hfuzz->origFlipRate == 0.0L && hfuzz->useVerifier) { snprintf(fuzzer->crashFileName, sizeof(fuzzer->crashFileName), "%s/%s", hfuzz->workDir, fuzzer->origFileName); } else if (saveUnique) { snprintf(fuzzer->crashFileName, sizeof(fuzzer->crashFileName), "%s/%s.PC.%" REG_PM ".STACK.%" PRIx64 ".CODE.%d.ADDR.%p.INSTR.%s.%s", hfuzz->workDir, arch_sigs[si.si_signo].descr, pc, fuzzer->backtrace, si.si_code, sig_addr, instr, hfuzz->fileExtn); } else { char localtmstr[PATH_MAX]; util_getLocalTime("%F.%H:%M:%S", localtmstr, sizeof(localtmstr), time(NULL)); snprintf(fuzzer->crashFileName, sizeof(fuzzer->crashFileName), "%s/%s.PC.%" REG_PM ".STACK.%" PRIx64 ".CODE.%d.ADDR.%p.INSTR.%s.%s.%d.%s", hfuzz->workDir, arch_sigs[si.si_signo].descr, pc, fuzzer->backtrace, si.si_code, sig_addr, instr, localtmstr, pid, hfuzz->fileExtn); } if (files_exists(fuzzer->crashFileName)) { LOG_I("It seems that '%s' already exists, skipping", fuzzer->crashFileName); // Clear filename so that verifier can understand we hit a duplicate memset(fuzzer->crashFileName, 0, sizeof(fuzzer->crashFileName)); return; } if (files_writeBufToFile (fuzzer->crashFileName, fuzzer->dynamicFile, fuzzer->dynamicFileSz, O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC) == false) { LOG_E("Couldn't copy '%s' to '%s'", fuzzer->fileName, fuzzer->crashFileName); return; } LOG_I("Ok, that's interesting, saved '%s' as '%s'", fuzzer->fileName, fuzzer->crashFileName); ATOMIC_POST_INC(hfuzz->uniqueCrashesCnt); /* If unique crash found, reset dynFile counter */ ATOMIC_CLEAR(hfuzz->dynFileIterExpire); arch_ptraceGenerateReport(pid, fuzzer, funcs, funcCnt, &si, instr); }
/* * 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; }