| #include "sanitizers.h" |
| |
| #include <ctype.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "cmdline.h" |
| #include "libhfcommon/common.h" |
| #include "libhfcommon/log.h" |
| #include "libhfcommon/util.h" |
| |
| /* |
| * Common sanitizer flags if --sanitizers is enabled |
| */ |
| #define kSAN_COMMON \ |
| "symbolize=1:" \ |
| "detect_leaks=0:" \ |
| "disable_coredump=0:" \ |
| "detect_odr_violation=0:" \ |
| "allocator_may_return_null=1:" \ |
| "allow_user_segv_handler=0:" \ |
| "handle_segv=2:" \ |
| "handle_sigbus=2:" \ |
| "handle_abort=2:" \ |
| "handle_sigill=2:" \ |
| "handle_sigfpe=2:" \ |
| "abort_on_error=1" |
| |
| /* --{ ASan }-- */ |
| /* |
| * Sanitizer specific flags (notice that if enabled 'abort_on_error' has priority |
| * over exitcode') |
| */ |
| #define kASAN_OPTS kSAN_COMMON |
| |
| /* --{ UBSan }-- */ |
| #define kUBSAN_OPTS kSAN_COMMON |
| |
| /* --{ MSan }-- */ |
| #define kMSAN_OPTS kSAN_COMMON ":wrap_signals=0:print_stats=1" |
| |
| /* --{ LSan }-- */ |
| #define kLSAN_OPTS kSAN_COMMON |
| |
| /* If no sanitzer support was requested, simply abort() on errors */ |
| #define kSAN_REGULAR \ |
| "symbolize=1:" \ |
| "detect_leaks=0:" \ |
| "disable_coredump=0:" \ |
| "detect_odr_violation=0:" \ |
| "allocator_may_return_null=1:" \ |
| "allow_user_segv_handler=1:" \ |
| "handle_segv=0:" \ |
| "handle_sigbus=0:" \ |
| "handle_abort=0:" \ |
| "handle_sigill=0:" \ |
| "handle_sigfpe=0:" \ |
| "abort_on_error=1" |
| |
| static void sanitizers_AddFlag(honggfuzz_t* hfuzz, const char* env, const char* val) { |
| if (getenv(env)) { |
| LOG_W("The '%s' envar is already set. Not overriding it!", env); |
| return; |
| } |
| |
| char buf[4096] = {}; |
| if (hfuzz->sanitizer.enable) { |
| snprintf(buf, sizeof(buf), "%s=%s:log_path=%s/%s", env, val, hfuzz->io.workDir, kLOGPREFIX); |
| } else { |
| snprintf(buf, sizeof(buf), "%s=%s:log_path=%s/%s", env, kSAN_REGULAR, hfuzz->io.workDir, |
| kLOGPREFIX); |
| } |
| /* |
| * It will make ASAN to start background thread to check RSS mem use, which |
| * will prevent the NetDrvier from using unshare(CLONE_NEWNET), which cannot |
| * be used in multi-threaded contexts |
| */ |
| if (!hfuzz->exe.netDriver && hfuzz->exe.rssLimit) { |
| util_ssnprintf(buf, sizeof(buf), ":soft_rss_limit_mb=%" PRId64, hfuzz->exe.rssLimit); |
| } |
| |
| cmdlineAddEnv(hfuzz, buf); |
| LOG_D("%s", buf); |
| } |
| |
| bool sanitizers_Init(honggfuzz_t* hfuzz) { |
| sanitizers_AddFlag(hfuzz, "ASAN_OPTIONS", kASAN_OPTS); |
| sanitizers_AddFlag(hfuzz, "UBSAN_OPTIONS", kUBSAN_OPTS); |
| sanitizers_AddFlag(hfuzz, "MSAN_OPTIONS", kMSAN_OPTS); |
| sanitizers_AddFlag(hfuzz, "LSAN_OPTIONS", kLSAN_OPTS); |
| |
| return true; |
| } |
| |
| /* Get numeric value of the /proc/<pid>/status "Tgid: <PID>" field */ |
| static pid_t sanitizers_PidForTid(pid_t pid) { |
| char status_path[PATH_MAX]; |
| snprintf(status_path, sizeof(status_path), "/proc/%d/status", (int)pid); |
| FILE* f = fopen(status_path, "rb"); |
| if (!f) { |
| return pid; |
| } |
| defer { |
| fclose(f); |
| }; |
| char* lineptr = NULL; |
| size_t n = 0; |
| defer { |
| free(lineptr); |
| }; |
| |
| while (getline(&lineptr, &n, f) > 0) { |
| int retpid; |
| if (sscanf(lineptr, "Tgid:%d", &retpid) == 1) { |
| LOG_D("Tid %d has Pid %d", (int)pid, retpid); |
| return (pid_t)retpid; |
| } |
| } |
| return pid; |
| } |
| |
| size_t sanitizers_parseReport(run_t* run, pid_t pid, funcs_t* funcs, uint64_t* pc, |
| uint64_t* crashAddr, char description[HF_STR_LEN]) { |
| char crashReport[PATH_MAX]; |
| const char* crashReportCpy = crashReport; |
| |
| /* Under Linux the crash is seen in TID, but the sanitizer report is created for PID */ |
| pid = sanitizers_PidForTid(pid); |
| snprintf( |
| crashReport, sizeof(crashReport), "%s/%s.%d", run->global->io.workDir, kLOGPREFIX, pid); |
| |
| FILE* fReport = fopen(crashReport, "rb"); |
| if (fReport == NULL) { |
| PLOG_D("fopen('%s', 'rb')", crashReport); |
| return 0; |
| } |
| defer { |
| fclose(fReport); |
| if (run->global->sanitizer.del_report) { |
| unlink(crashReportCpy); |
| } |
| }; |
| |
| bool headerFound = false; |
| bool frameFound = false; |
| unsigned int frameIdx = 0; |
| |
| char* lineptr = NULL; |
| size_t n = 0; |
| defer { |
| free(lineptr); |
| }; |
| for (;;) { |
| if (getline(&lineptr, &n, fReport) == -1) { |
| break; |
| } |
| |
| /* First step is to identify header */ |
| if (!headerFound) { |
| int reportpid = 0; |
| if (sscanf(lineptr, "==%d==ERROR: ", &reportpid) != 1) { |
| continue; |
| } |
| if (reportpid != pid) { |
| LOG_W( |
| "SAN report found in '%s', but its PID:%d is different from the needed PID:%d", |
| crashReport, reportpid, pid); |
| break; |
| } |
| headerFound = true; |
| sscanf(lineptr, |
| "==%*d==ERROR: %*[^:]: %*[^ ] on address 0x%" PRIx64 " at pc 0x%" PRIx64, crashAddr, |
| pc); |
| sscanf(lineptr, |
| "==%*d==ERROR: %*[^:]: %*[^ ] on %*s address 0x%" PRIx64 " (pc 0x%" PRIx64, |
| crashAddr, pc); |
| sscanf(lineptr, "==%*d==ERROR: %" HF_XSTR(HF_STR_LEN_MINUS_1) "[^\n]", description); |
| } else { |
| char* pLineLC = lineptr; |
| /* Trim leading spaces */ |
| while (*pLineLC != '\0' && isspace((int)*pLineLC)) { |
| ++pLineLC; |
| } |
| |
| /* End separator for crash thread stack trace is an empty line */ |
| if ((*pLineLC == '\0') && (frameIdx != 0)) { |
| break; |
| } |
| |
| if (sscanf(pLineLC, "#%u", &frameIdx) != 1) { |
| continue; |
| } |
| if (frameIdx >= _HF_MAX_FUNCS) { |
| frameIdx = _HF_MAX_FUNCS - 1; |
| break; |
| } |
| |
| frameFound = true; |
| snprintf(funcs[frameIdx].func, sizeof(funcs[frameIdx].func), "UNKNOWN"); |
| |
| /* |
| * Frames with demangled symbols and with debug info |
| * A::A(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, |
| * std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, |
| * std::char_traits<char>, std::allocator<char> > > >) /home/fuzz/test/fork.cc:12:51 |
| */ |
| if (sscanf(pLineLC, |
| "#%*u 0x%p in %" HF_XSTR(_HF_FUNC_NAME_SZ_MINUS_1) "[^)]) %" HF_XSTR( |
| _HF_FUNC_NAME_SZ_MINUS_1) "[^:]:%zu", |
| &funcs[frameIdx].pc, funcs[frameIdx].func, funcs[frameIdx].file, |
| &funcs[frameIdx].line) == 4) { |
| util_ssnprintf(funcs[frameIdx].func, sizeof(funcs[frameIdx].func), ")"); |
| continue; |
| } |
| |
| /* |
| * Frames with demangled symbols but w/o debug info |
| * #0 0x59d74e in printf_common(void*, char const*, __va_list_tag*) |
| * (/home/smbd/smbd+0x59d74e) |
| */ |
| if (sscanf(pLineLC, "#%*u 0x%p in %" HF_XSTR(_HF_FUNC_NAME_SZ_MINUS_1) "[^)]) (%[^)])", |
| &funcs[frameIdx].pc, funcs[frameIdx].func, funcs[frameIdx].module) == 3) { |
| util_ssnprintf(funcs[frameIdx].func, sizeof(funcs[frameIdx].func), ")"); |
| continue; |
| } |
| /* |
| * Frames with symbols but w/o debug info |
| * #0 0x7ffff59a3668 in start_thread (/lib/x86_64-linux-gnu/libpthread.so.0+0x9668) |
| */ |
| if (sscanf(pLineLC, |
| "#%*u 0x%p in %" HF_XSTR(_HF_FUNC_NAME_SZ_MINUS_1) "s%*[^(](%" HF_XSTR( |
| HF_STR_LEN_MINUS_1) "[^)]", |
| &funcs[frameIdx].pc, funcs[frameIdx].func, funcs[frameIdx].module) == 3) { |
| continue; |
| } |
| /* |
| * Frames with symbols and with debug info |
| * #0 0x1e94738 in smb2_signing_decrypt_pdu /home/test/signing.c:617:3 |
| */ |
| if (sscanf(pLineLC, |
| "#%*u 0x%p in %" HF_XSTR(_HF_FUNC_NAME_SZ_MINUS_1) "[^ ] %" HF_XSTR( |
| HF_STR_LEN_MINUS_1) "[^:\n]:%zu", |
| &funcs[frameIdx].pc, funcs[frameIdx].func, funcs[frameIdx].file, |
| &funcs[frameIdx].line) == 4) { |
| continue; |
| } |
| /* |
| * Frames w/o symbols |
| * #0 0x565584f4 (/mnt/z/test+0x34f4) |
| */ |
| if (sscanf(pLineLC, "#%*u 0x%p%*[^(](%" HF_XSTR(HF_STR_LEN_MINUS_1) "[^)\n]", |
| &funcs[frameIdx].pc, funcs[frameIdx].module) == 2) { |
| continue; |
| } |
| /* |
| * Frames w/o symbols, but with debug info |
| * #0 0x7ffff57cf08f /build/glibc-bBRi4l/.../erms.S:199 |
| */ |
| if (sscanf(pLineLC, "#%*u 0x%p %" HF_XSTR(HF_STR_LEN_MINUS_1) "[^:]:%zu", |
| &funcs[frameIdx].pc, funcs[frameIdx].file, &funcs[frameIdx].line) == 3) { |
| continue; |
| } |
| } |
| } |
| |
| return (!frameFound) ? 0 : (frameIdx + 1); |
| } |
| |
| /* |
| * Size in characters required to store a string representation of a |
| * register value (0xdeadbeef style)) |
| */ |
| #define REGSIZEINCHAR (2 * sizeof(uint64_t) + 3) |
| |
| uint64_t sanitizers_hashCallstack(run_t* run, funcs_t* funcs, size_t funcCnt, bool enableMasking) { |
| size_t numFrames = 7; |
| /* |
| * If sanitizer fuzzing enabled increase number of major frames, since top 7-9 frames will be |
| * occupied with sanitizer runtime library & libc symbols |
| */ |
| if (run->global->sanitizer.enable) { |
| numFrames = 14; |
| } |
| |
| uint64_t hash = 0; |
| for (size_t i = 0; i < funcCnt && i < numFrames; i++) { |
| /* |
| * Convert PC to char array to be compatible with hash function |
| */ |
| char pcStr[REGSIZEINCHAR] = {0}; |
| snprintf(pcStr, REGSIZEINCHAR, "0x%016" PRIx64, (uint64_t)(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; |
| } |
| |
| return hash; |
| } |