C++程序  |  232行  |  8.41 KB

#include "sanitizers.h"

#include <ctype.h>
#include <dirent.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "libcommon/common.h"
#include "libcommon/files.h"
#include "libcommon/log.h"
#include "libcommon/util.h"

/* Stringify */
#define XSTR(x) #x
#define STR(x) XSTR(x)

/*
 * All clang sanitizers, except ASan, can be activated for target binaries
 * with or without the matching runtime library (libcompiler_rt). If runtime
 * libraries are included in target fuzzing environment, we can benefit from the
 * various Die() callbacks and abort/exit logic manipulation. However, some
 * setups (e.g. Android production ARM/ARM64 devices) enable sanitizers, such as
 * UBSan, without the runtime libraries. As such, their default ftrap is activated
 * which is for most cases a SIGABRT. For these cases end-user needs to enable
 * SIGABRT monitoring flag, otherwise these crashes will be missed.
 *
 * Normally SIGABRT is not a wanted signal to monitor for Android, since it produces
 * lots of useless crashes due to way Android process termination hacks work. As
 * a result the sanitizer's 'abort_on_error' flag cannot be utilized since it
 * invokes abort() internally. In order to not lose crashes a custom exitcode can
 * be registered and monitored. Since exitcode is a global flag, it's assumed
 * that target is compiled with only one sanitizer type enabled at a time.
 *
 * For cases where clang runtime library linking is not an option, SIGABRT should
 * be monitored even for noisy targets, such as the Android OS, since no viable
 * alternative exists.
 *
 * 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.
 */

/* 'log_path' output directory for sanitizer reports */
#define kSANLOGDIR "log_path="

/* 'coverage_dir' output directory for coverage data files is set dynamically */
#define kSANCOVDIR "coverage_dir="

/* Raise SIGABRT on error or continue with exitcode logic */
#define kABORT_ENABLED "abort_on_error=1"
#define kABORT_DISABLED "abort_on_error=0"

/*
 * Common sanitizer flags
 *
 * symbolize: Disable symbolication since it changes logs (which are parsed) format
 */
#define kSAN_COMMON "symbolize=0"

/* --{ ASan }-- */
/*
 *Sanitizer specific flags (notice that if enabled 'abort_on_error' has priority
 * over exitcode')
 */
#define kASAN_COMMON_OPTS        \
    "allow_user_segv_handler=1:" \
    "handle_segv=0:"             \
    "allocator_may_return_null=1:" kSAN_COMMON ":exitcode=" STR(HF_SAN_EXIT_CODE)
/* Platform specific flags */
#if defined(__ANDROID__)
/*
 * start_deactivated: Enable on Android to reduce memory usage (useful when not all
 *                    target's DSOs are compiled with sanitizer enabled
 */
#define kASAN_OPTS kASAN_COMMON_OPTS ":start_deactivated=1"
#else
#define kASAN_OPTS kASAN_COMMON_OPTS
#endif

/* --{ UBSan }-- */
#define kUBSAN_OPTS kSAN_COMMON ":exitcode=" STR(HF_SAN_EXIT_CODE)

/* --{ MSan }-- */
#define kMSAN_OPTS \
    kSAN_COMMON ":exit_code=" STR(HF_SAN_EXIT_CODE) ":"                                            \
                                                    "wrap_signals=0:print_stats=1"

/* If no sanitzer support was requested, simply make it use abort() on errors */
#define kSAN_REGULAR                                                 \
    "abort_on_error=1:handle_segv=0:handle_sigbus=0:handle_abort=0:" \
    "handle_sigill=0:handle_sigfpe=0:allocator_may_return_null=1:"   \
    "symbolize=1:detect_leaks=0:disable_coredump=0:log_path=stderr"

/*
 * If the program ends with a signal that ASan does not handle (or can not
 * handle at all, like SIGKILL), coverage data will be lost. This is a big
 * problem on Android, where SIGKILL is a normal way of evicting applications
 * from memory. With 'coverage_direct=1' coverage data is written to a
 * memory-mapped file as soon as it collected. Non-Android targets can disable
 * coverage direct when more coverage data collection methods are implemented.
 */
#define kSAN_COV_OPTS "coverage=1:coverage_direct=1"

static bool sanitizers_Regular(void) {
    if (setenv("ASAN_OPTIONS", kSAN_REGULAR, 1) == -1) {
        PLOG_E("setenv(ASAN_OPTIONS=%s", kSAN_REGULAR);
        return false;
    }
    if (setenv("MSAN_OPTIONS", kSAN_REGULAR, 1) == -1) {
        PLOG_E("setenv(MSAN_OPTIONS=%s", kSAN_REGULAR);
        return false;
    }
    if (setenv("UBSAN_OPTIONS", kSAN_REGULAR, 1) == -1) {
        PLOG_E("setenv(UBSAN_OPTIONS=%s", kSAN_REGULAR);
        return false;
    }
    return true;
}

bool sanitizers_Init(honggfuzz_t* hfuzz) {
    if (hfuzz->linux.pid > 0) {
        return true;
    }

    if (hfuzz->enableSanitizers == false) {
        return sanitizers_Regular();
    }

    /* Set sanitizer flags once to avoid performance overhead per worker spawn */
    size_t flagsSz = 0;

    /* Larger constant combination + 2 dynamic paths */
    size_t bufSz = sizeof(kASAN_OPTS) + 1 + sizeof(kABORT_ENABLED) + 1 + sizeof(kSANLOGDIR) +
                   PATH_MAX + 1 + sizeof(kSANCOVDIR) + PATH_MAX + 1;
    char* san_opts = util_Calloc(bufSz);
    defer { free(san_opts); };

    char* abortFlag;
    if (hfuzz->monitorSIGABRT) {
        abortFlag = kABORT_ENABLED;
    } else {
        abortFlag = kABORT_DISABLED;
    }

    /* Address Sanitizer (ASan) */
    if (hfuzz->useSanCov) {
        snprintf(san_opts, bufSz, "%s:%s:%s:%s%s/%s:%s%s/%s", kASAN_OPTS, abortFlag, kSAN_COV_OPTS,
            kSANCOVDIR, hfuzz->io.workDir, _HF_SANCOV_DIR, kSANLOGDIR, hfuzz->io.workDir,
            kLOGPREFIX);
    } else {
        snprintf(san_opts, bufSz, "%s:%s:%s%s/%s", kASAN_OPTS, abortFlag, kSANLOGDIR,
            hfuzz->io.workDir, kLOGPREFIX);
    }

    flagsSz = strlen(san_opts) + 1;
    hfuzz->sanOpts.asanOpts = util_Calloc(flagsSz);
    memcpy(hfuzz->sanOpts.asanOpts, san_opts, flagsSz);
    LOG_D("ASAN_OPTIONS=%s", hfuzz->sanOpts.asanOpts);

    /* Undefined Behavior Sanitizer (UBSan) */
    memset(san_opts, 0, bufSz);
    if (hfuzz->useSanCov) {
        snprintf(san_opts, bufSz, "%s:%s:%s:%s%s/%s:%s%s/%s", kUBSAN_OPTS, abortFlag, kSAN_COV_OPTS,
            kSANCOVDIR, hfuzz->io.workDir, _HF_SANCOV_DIR, kSANLOGDIR, hfuzz->io.workDir,
            kLOGPREFIX);
    } else {
        snprintf(san_opts, bufSz, "%s:%s:%s%s/%s", kUBSAN_OPTS, abortFlag, kSANLOGDIR,
            hfuzz->io.workDir, kLOGPREFIX);
    }

    flagsSz = strlen(san_opts) + 1;
    hfuzz->sanOpts.ubsanOpts = util_Calloc(flagsSz);
    memcpy(hfuzz->sanOpts.ubsanOpts, san_opts, flagsSz);
    LOG_D("UBSAN_OPTIONS=%s", hfuzz->sanOpts.ubsanOpts);

    /* Memory Sanitizer (MSan) */
    memset(san_opts, 0, bufSz);

    if (hfuzz->useSanCov) {
        snprintf(san_opts, bufSz, "%s:%s:%s:%s%s/%s:%s%s/%s", kMSAN_OPTS, abortFlag, kSAN_COV_OPTS,
            kSANCOVDIR, hfuzz->io.workDir, _HF_SANCOV_DIR, kSANLOGDIR, hfuzz->io.workDir,
            kLOGPREFIX);
    } else {
        snprintf(san_opts, bufSz, "%s:%s:%s%s/%s", kMSAN_OPTS, abortFlag, kSANLOGDIR,
            hfuzz->io.workDir, kLOGPREFIX);
    }

    flagsSz = strlen(san_opts) + 1;
    hfuzz->sanOpts.msanOpts = util_Calloc(flagsSz);
    memcpy(hfuzz->sanOpts.msanOpts, san_opts, flagsSz);
    LOG_D("MSAN_OPTIONS=%s", hfuzz->sanOpts.msanOpts);

    return true;
}

bool sanitizers_prepareExecve(run_t* run) {
    /* Address Sanitizer (ASan) */
    if (run->global->sanOpts.asanOpts) {
        if (setenv("ASAN_OPTIONS", run->global->sanOpts.asanOpts, 1) == -1) {
            PLOG_E("setenv(ASAN_OPTIONS) failed");
            return false;
        }
    }

    /* Memory Sanitizer (MSan) */
    if (run->global->sanOpts.msanOpts) {
        if (setenv("MSAN_OPTIONS", run->global->sanOpts.msanOpts, 1) == -1) {
            PLOG_E("setenv(MSAN_OPTIONS) failed");
            return false;
        }
    }

    /* Undefined Behavior Sanitizer (UBSan) */
    if (run->global->sanOpts.ubsanOpts) {
        if (setenv("UBSAN_OPTIONS", run->global->sanOpts.ubsanOpts, 1) == -1) {
            PLOG_E("setenv(UBSAN_OPTIONS) failed");
            return false;
        }
    }

    return true;
}