diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
commit | 4035b1bfb1e5843a539a8b624d21952b756974d1 (patch) | |
tree | f1e9cd5bf548cbc57ff2fddfb2b4aa9ae95587e2 /src/VBox/Runtime/common/fuzz | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 6.1.22-dfsg.upstream/6.1.22-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Runtime/common/fuzz')
-rw-r--r-- | src/VBox/Runtime/common/fuzz/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fuzz/fuzz-observer.cpp | 1393 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fuzz/fuzz-target-recorder.cpp | 791 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fuzz/fuzz.cpp | 2181 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp | 317 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp | 1861 |
6 files changed, 6543 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/fuzz/Makefile.kup b/src/VBox/Runtime/common/fuzz/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/Makefile.kup diff --git a/src/VBox/Runtime/common/fuzz/fuzz-observer.cpp b/src/VBox/Runtime/common/fuzz/fuzz-observer.cpp new file mode 100644 index 00000000..3d230931 --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/fuzz-observer.cpp @@ -0,0 +1,1393 @@ +/* $Id: fuzz-observer.cpp $ */ +/** @file + * IPRT - Fuzzing framework API, observer. + */ + +/* + * Copyright (C) 2018-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/fuzz.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/env.h> +#include <iprt/file.h> +#include <iprt/md5.h> +#include <iprt/mem.h> +#include <iprt/mp.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include <iprt/thread.h> + + +/** Poll ID for the reading end of the stdout pipe from the client process. */ +#define RTFUZZOBS_EXEC_CTX_POLL_ID_STDOUT 0 +/** Poll ID for the reading end of the stderr pipe from the client process. */ +#define RTFUZZOBS_EXEC_CTX_POLL_ID_STDERR 1 +/** Poll ID for the writing end of the stdin pipe to the client process. */ +#define RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN 2 + +/** Length of the input queue for an observer thread. */ +# define RTFUZZOBS_THREAD_INPUT_QUEUE_MAX UINT32_C(5) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to the internal fuzzing observer state. */ +typedef struct RTFUZZOBSINT *PRTFUZZOBSINT; + + +/** + * Observer thread state for one process. + */ +typedef struct RTFUZZOBSTHRD +{ + /** The thread handle. */ + RTTHREAD hThread; + /** The observer ID. */ + uint32_t idObs; + /** Flag whether to shutdown. */ + volatile bool fShutdown; + /** Pointer to te global observer state. */ + PRTFUZZOBSINT pFuzzObs; + /** Number of inputs in the queue. */ + volatile uint32_t cInputs; + /** Where to insert the next input. */ + volatile uint32_t offQueueInputW; + /** Where to retrieve the next input from. */ + volatile uint32_t offQueueInputR; + /** The input queue for this thread. */ + RTFUZZINPUT ahQueueInput[RTFUZZOBS_THREAD_INPUT_QUEUE_MAX]; +} RTFUZZOBSTHRD; +/** Pointer to an observer thread state. */ +typedef RTFUZZOBSTHRD *PRTFUZZOBSTHRD; + + +/** + * Internal fuzzing observer state. + */ +typedef struct RTFUZZOBSINT +{ + /** The fuzzing context used for this observer. */ + RTFUZZCTX hFuzzCtx; + /** The target state recorder. */ + RTFUZZTGTREC hTgtRec; + /** Temp directory for input files. */ + char *pszTmpDir; + /** Results directory. */ + char *pszResultsDir; + /** The binary to run. */ + char *pszBinary; + /** The filename path of the binary. */ + const char *pszBinaryFilename; + /** Arguments to run the binary with, terminated by a NULL entry. */ + char **papszArgs; + /** The environment to use for the target. */ + RTENV hEnv; + /** Any configured sanitizers. */ + uint32_t fSanitizers; + /** Sanitizer related options set in the environment block. */ + char *pszSanitizerOpts; + /** Number of arguments. */ + uint32_t cArgs; + /** Maximum time to wait for the client to terminate until it is considered hung and killed. */ + RTMSINTERVAL msWaitMax; + /** The channel the binary expects the input. */ + RTFUZZOBSINPUTCHAN enmInputChan; + /** Flag whether to shutdown the master and all workers. */ + volatile bool fShutdown; + /** Global observer thread handle. */ + RTTHREAD hThreadGlobal; + /** The event semaphore handle for the global observer thread. */ + RTSEMEVENT hEvtGlobal; + /** Notification event bitmap. */ + volatile uint64_t bmEvt; + /** Number of threads created - one for each process. */ + uint32_t cThreads; + /** Pointer to the array of observer thread states. */ + PRTFUZZOBSTHRD paObsThreads; + /** Timestamp of the last stats query. */ + uint64_t tsLastStats; + /** Last number of fuzzed inputs per second if we didn't gather enough data in between + * statistic queries. */ + uint32_t cFuzzedInputsPerSecLast; + /** Fuzzing statistics. */ + RTFUZZOBSSTATS Stats; +} RTFUZZOBSINT; + + +/** + * Worker execution context. + */ +typedef struct RTFUZZOBSEXECCTX +{ + /** The stdout pipe handle - reading end. */ + RTPIPE hPipeStdoutR; + /** The stdout pipe handle - writing end. */ + RTPIPE hPipeStdoutW; + /** The stderr pipe handle - reading end. */ + RTPIPE hPipeStderrR; + /** The stderr pipe handle - writing end. */ + RTPIPE hPipeStderrW; + /** The stdin pipe handle - reading end. */ + RTPIPE hPipeStdinR; + /** The stind pipe handle - writing end. */ + RTPIPE hPipeStdinW; + /** The stdout handle. */ + RTHANDLE StdoutHandle; + /** The stderr handle. */ + RTHANDLE StderrHandle; + /** The stdin handle. */ + RTHANDLE StdinHandle; + /** The pollset to monitor. */ + RTPOLLSET hPollSet; + /** The environment block to use. */ + RTENV hEnv; + /** The process to monitor. */ + RTPROCESS hProc; + /** Execution time of the process. */ + RTMSINTERVAL msExec; + /** The recording state handle. */ + RTFUZZTGTSTATE hTgtState; + /** Current input data pointer. */ + uint8_t *pbInputCur; + /** Number of bytes left for the input. */ + size_t cbInputLeft; + /** Modified arguments vector - variable in size. */ + char *apszArgs[1]; +} RTFUZZOBSEXECCTX; +/** Pointer to an execution context. */ +typedef RTFUZZOBSEXECCTX *PRTFUZZOBSEXECCTX; +/** Pointer to an execution context pointer. */ +typedef PRTFUZZOBSEXECCTX *PPRTFUZZOBSEXECCTX; + + +/** + * A variable descriptor. + */ +typedef struct RTFUZZOBSVARIABLE +{ + /** The variable. */ + const char *pszVar; + /** Length of the variable in characters - excluding the terminator. */ + uint32_t cchVar; + /** The replacement value. */ + const char *pszVal; +} RTFUZZOBSVARIABLE; +/** Pointer to a variable descriptor. */ +typedef RTFUZZOBSVARIABLE *PRTFUZZOBSVARIABLE; + + + +/** + * Replaces a variable with its value. + * + * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY. + * @param ppszNew In/Out. + * @param pcchNew In/Out. (Messed up on failure.) + * @param offVar Variable offset. + * @param cchVar Variable length. + * @param pszValue The value. + * @param cchValue Value length. + */ +static int rtFuzzObsReplaceStringVariable(char **ppszNew, size_t *pcchNew, size_t offVar, size_t cchVar, + const char *pszValue, size_t cchValue) +{ + size_t const cchAfter = *pcchNew - offVar - cchVar; + if (cchVar < cchValue) + { + *pcchNew += cchValue - cchVar; + int rc = RTStrRealloc(ppszNew, *pcchNew + 1); + if (RT_FAILURE(rc)) + return rc; + } + + char *pszNew = *ppszNew; + memmove(&pszNew[offVar + cchValue], &pszNew[offVar + cchVar], cchAfter + 1); + memcpy(&pszNew[offVar], pszValue, cchValue); + return VINF_SUCCESS; +} + + +/** + * Replace the variables found in the source string, returning a new string that + * lives on the string heap. + * + * @returns IPRT status code. + * @param pszSrc The source string. + * @param paVars Pointer to the array of known variables. + * @param ppszNew Where to return the new string. + */ +static int rtFuzzObsReplaceStringVariables(const char *pszSrc, PRTFUZZOBSVARIABLE paVars, char **ppszNew) +{ + /* Lazy approach that employs memmove. */ + int rc = VINF_SUCCESS; + size_t cchNew = strlen(pszSrc); + char *pszNew = RTStrDup(pszSrc); + + if (paVars) + { + char *pszDollar = pszNew; + while ((pszDollar = strchr(pszDollar, '$')) != NULL) + { + if (pszDollar[1] == '{') + { + const char *pszEnd = strchr(&pszDollar[2], '}'); + if (pszEnd) + { + size_t const cchVar = pszEnd - pszDollar + 1; /* includes "${}" */ + size_t offDollar = pszDollar - pszNew; + PRTFUZZOBSVARIABLE pVar = paVars; + while (pVar->pszVar != NULL) + { + if ( cchVar == pVar->cchVar + && !memcmp(pszDollar, pVar->pszVar, cchVar)) + { + size_t const cchValue = strlen(pVar->pszVal); + rc = rtFuzzObsReplaceStringVariable(&pszNew, &cchNew, offDollar, + cchVar, pVar->pszVal, cchValue); + offDollar += cchValue; + break; + } + + pVar++; + } + + pszDollar = &pszNew[offDollar]; + + if (RT_FAILURE(rc)) + { + RTStrFree(pszNew); + *ppszNew = NULL; + return rc; + } + } + } + } + } + + *ppszNew = pszNew; + return rc; +} + +/** + * Prepares the argument vector for the child process. + * + * @returns IPRT status code. + * @param pThis The internal fuzzing observer state. + * @param pExecCtx The execution context to prepare the argument vector for. + * @param paVars Pointer to the array of known variables. + */ +static int rtFuzzObsExecCtxArgvPrepare(PRTFUZZOBSINT pThis, PRTFUZZOBSEXECCTX pExecCtx, PRTFUZZOBSVARIABLE paVars) +{ + int rc = VINF_SUCCESS; + for (unsigned i = 0; i < pThis->cArgs && RT_SUCCESS(rc); i++) + rc = rtFuzzObsReplaceStringVariables(pThis->papszArgs[i], paVars, &pExecCtx->apszArgs[i]); + + return rc; +} + + +/** + * Creates a new execution context. + * + * @returns IPRT status code. + * @param ppExecCtx Where to store the pointer to the execution context on success. + * @param pThis The internal fuzzing observer state. + */ +static int rtFuzzObsExecCtxCreate(PPRTFUZZOBSEXECCTX ppExecCtx, PRTFUZZOBSINT pThis) +{ + int rc = VINF_SUCCESS; + PRTFUZZOBSEXECCTX pExecCtx = (PRTFUZZOBSEXECCTX)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFUZZOBSEXECCTX, apszArgs[pThis->cArgs + 1])); + if (RT_LIKELY(pExecCtx)) + { + pExecCtx->hPipeStdoutR = NIL_RTPIPE; + pExecCtx->hPipeStdoutW = NIL_RTPIPE; + pExecCtx->hPipeStderrR = NIL_RTPIPE; + pExecCtx->hPipeStderrW = NIL_RTPIPE; + pExecCtx->hPipeStdinR = NIL_RTPIPE; + pExecCtx->hPipeStdinW = NIL_RTPIPE; + pExecCtx->hPollSet = NIL_RTPOLLSET; + pExecCtx->hProc = NIL_RTPROCESS; + pExecCtx->msExec = 0; + + rc = RTEnvClone(&pExecCtx->hEnv, pThis->hEnv); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzTgtRecorderCreateNewState(pThis->hTgtRec, &pExecCtx->hTgtState); + if (RT_SUCCESS(rc)) + { + rc = RTPollSetCreate(&pExecCtx->hPollSet); + if (RT_SUCCESS(rc)) + { + rc = RTPipeCreate(&pExecCtx->hPipeStdoutR, &pExecCtx->hPipeStdoutW, RTPIPE_C_INHERIT_WRITE); + if (RT_SUCCESS(rc)) + { + RTHANDLE Handle; + Handle.enmType = RTHANDLETYPE_PIPE; + Handle.u.hPipe = pExecCtx->hPipeStdoutR; + rc = RTPollSetAdd(pExecCtx->hPollSet, &Handle, RTPOLL_EVT_READ, RTFUZZOBS_EXEC_CTX_POLL_ID_STDOUT); + AssertRC(rc); + + rc = RTPipeCreate(&pExecCtx->hPipeStderrR, &pExecCtx->hPipeStderrW, RTPIPE_C_INHERIT_WRITE); + if (RT_SUCCESS(rc)) + { + Handle.u.hPipe = pExecCtx->hPipeStderrR; + rc = RTPollSetAdd(pExecCtx->hPollSet, &Handle, RTPOLL_EVT_READ, RTFUZZOBS_EXEC_CTX_POLL_ID_STDERR); + AssertRC(rc); + + /* Create the stdin pipe handles if not a file input. */ + if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_STDIN || pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT) + { + rc = RTPipeCreate(&pExecCtx->hPipeStdinR, &pExecCtx->hPipeStdinW, RTPIPE_C_INHERIT_READ); + if (RT_SUCCESS(rc)) + { + pExecCtx->StdinHandle.enmType = RTHANDLETYPE_PIPE; + pExecCtx->StdinHandle.u.hPipe = pExecCtx->hPipeStdinR; + + Handle.u.hPipe = pExecCtx->hPipeStdinW; + rc = RTPollSetAdd(pExecCtx->hPollSet, &Handle, RTPOLL_EVT_WRITE, RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN); + AssertRC(rc); + } + } + else + { + pExecCtx->StdinHandle.enmType = RTHANDLETYPE_PIPE; + pExecCtx->StdinHandle.u.hPipe = NIL_RTPIPE; + } + + if (RT_SUCCESS(rc)) + { + pExecCtx->StdoutHandle.enmType = RTHANDLETYPE_PIPE; + pExecCtx->StdoutHandle.u.hPipe = pExecCtx->hPipeStdoutW; + pExecCtx->StderrHandle.enmType = RTHANDLETYPE_PIPE; + pExecCtx->StderrHandle.u.hPipe = pExecCtx->hPipeStderrW; + *ppExecCtx = pExecCtx; + return VINF_SUCCESS; + } + + RTPipeClose(pExecCtx->hPipeStderrR); + RTPipeClose(pExecCtx->hPipeStderrW); + } + + RTPipeClose(pExecCtx->hPipeStdoutR); + RTPipeClose(pExecCtx->hPipeStdoutW); + } + + RTPollSetDestroy(pExecCtx->hPollSet); + } + + RTFuzzTgtStateRelease(pExecCtx->hTgtState); + } + + RTEnvDestroy(pExecCtx->hEnv); + } + + RTMemFree(pExecCtx); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +/** + * Destroys the given execution context. + * + * @returns nothing. + * @param pThis The internal fuzzing observer state. + * @param pExecCtx The execution context to destroy. + */ +static void rtFuzzObsExecCtxDestroy(PRTFUZZOBSINT pThis, PRTFUZZOBSEXECCTX pExecCtx) +{ + RTPipeClose(pExecCtx->hPipeStdoutR); + RTPipeClose(pExecCtx->hPipeStdoutW); + RTPipeClose(pExecCtx->hPipeStderrR); + RTPipeClose(pExecCtx->hPipeStderrW); + + if ( pThis->enmInputChan == RTFUZZOBSINPUTCHAN_STDIN + || pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT) + { + RTPipeClose(pExecCtx->hPipeStdinR); + RTPipeClose(pExecCtx->hPipeStdinW); + } + + RTPollSetDestroy(pExecCtx->hPollSet); + char **ppszArg = &pExecCtx->apszArgs[0]; + while (*ppszArg != NULL) + { + RTStrFree(*ppszArg); + ppszArg++; + } + + if (pExecCtx->hTgtState != NIL_RTFUZZTGTSTATE) + RTFuzzTgtStateRelease(pExecCtx->hTgtState); + RTEnvDestroy(pExecCtx->hEnv); + RTMemFree(pExecCtx); +} + + +/** + * Runs the client binary pumping all data back and forth waiting for the client to finish. + * + * @returns IPRT status code. + * @retval VERR_TIMEOUT if the client didn't finish in the given deadline and was killed. + * @param pThis The internal fuzzing observer state. + * @param pExecCtx The execution context. + * @param pProcStat Where to store the process exit status on success. + */ +static int rtFuzzObsExecCtxClientRun(PRTFUZZOBSINT pThis, PRTFUZZOBSEXECCTX pExecCtx, PRTPROCSTATUS pProcStat) +{ + int rc = RTProcCreateEx(pThis->pszBinary, &pExecCtx->apszArgs[0], pExecCtx->hEnv, 0 /*fFlags*/, &pExecCtx->StdinHandle, + &pExecCtx->StdoutHandle, &pExecCtx->StderrHandle, NULL, NULL, NULL, &pExecCtx->hProc); + if (RT_SUCCESS(rc)) + { + uint64_t tsMilliesStart = RTTimeSystemMilliTS(); + for (;;) + { + /* Wait a bit for something to happen on one of the pipes. */ + uint32_t fEvtsRecv = 0; + uint32_t idEvt = 0; + rc = RTPoll(pExecCtx->hPollSet, 10 /*cMillies*/, &fEvtsRecv, &idEvt); + if (RT_SUCCESS(rc)) + { + if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDOUT) + { + Assert(fEvtsRecv & RTPOLL_EVT_READ); + rc = RTFuzzTgtStateAppendStdoutFromPipe(pExecCtx->hTgtState, pExecCtx->hPipeStdoutR); + AssertRC(rc); + } + else if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDERR) + { + Assert(fEvtsRecv & RTPOLL_EVT_READ); + + rc = RTFuzzTgtStateAppendStderrFromPipe(pExecCtx->hTgtState, pExecCtx->hPipeStderrR); + AssertRC(rc); + } + else if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN) + { + /* Feed the next input. */ + Assert(fEvtsRecv & RTPOLL_EVT_WRITE); + size_t cbWritten = 0; + rc = RTPipeWrite(pExecCtx->hPipeStdinW, pExecCtx->pbInputCur, pExecCtx->cbInputLeft, &cbWritten); + if (RT_SUCCESS(rc)) + { + pExecCtx->cbInputLeft -= cbWritten; + if (!pExecCtx->cbInputLeft) + { + /* Close stdin pipe. */ + rc = RTPollSetRemove(pExecCtx->hPollSet, RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN); + AssertRC(rc); + RTPipeClose(pExecCtx->hPipeStdinW); + } + } + } + else + AssertMsgFailed(("Invalid poll ID returned: %u!\n", idEvt)); + } + else + Assert(rc == VERR_TIMEOUT); + + /* Check the process status. */ + rc = RTProcWait(pExecCtx->hProc, RTPROCWAIT_FLAGS_NOBLOCK, pProcStat); + if (RT_SUCCESS(rc)) + { + /* Add the coverage report to the sanitizer if enabled. */ + if (pThis->fSanitizers & RTFUZZOBS_SANITIZER_F_SANCOV) + { + char szSanCovReport[RTPATH_MAX]; + ssize_t cch = RTStrPrintf2(&szSanCovReport[0], sizeof(szSanCovReport), + "%s%c%s.%u.sancov", + pThis->pszTmpDir, RTPATH_SLASH, + pThis->pszBinaryFilename, pExecCtx->hProc); + Assert(cch > 0); RT_NOREF(cch); + rc = RTFuzzTgtStateAddSanCovReportFromFile(pExecCtx->hTgtState, &szSanCovReport[0]); + RTFileDelete(&szSanCovReport[0]); + } + break; + } + else + { + Assert(rc == VERR_PROCESS_RUNNING); + /* Check whether we reached the limit. */ + if (RTTimeSystemMilliTS() - tsMilliesStart > pThis->msWaitMax) + { + rc = VERR_TIMEOUT; + break; + } + } + } /* for (;;) */ + + /* Kill the process on a timeout. */ + if (rc == VERR_TIMEOUT) + { + int rc2 = RTProcTerminate(pExecCtx->hProc); + AssertRC(rc2); + } + } + + return rc; +} + + +/** + * Runs the fuzzing aware client binary pumping all data back and forth waiting for the client to crash. + * + * @returns IPRT status code. + * @retval VERR_TIMEOUT if the client didn't finish in the given deadline and was killed. + * @param pThis The internal fuzzing observer state. + * @param pExecCtx The execution context. + * @param pProcStat Where to store the process exit status on success. + */ +static int rtFuzzObsExecCtxClientRunFuzzingAware(PRTFUZZOBSINT pThis, PRTFUZZOBSEXECCTX pExecCtx, PRTPROCSTATUS pProcStat) +{ + int rc = RTProcCreateEx(pThis->pszBinary, &pExecCtx->apszArgs[0], pExecCtx->hEnv, 0 /*fFlags*/, &pExecCtx->StdinHandle, + &pExecCtx->StdoutHandle, &pExecCtx->StderrHandle, NULL, NULL, NULL, &pExecCtx->hProc); + if (RT_SUCCESS(rc)) + { + /* Send the initial fuzzing context state over to the client. */ + void *pvState = NULL; + size_t cbState = 0; + rc = RTFuzzCtxStateExportToMem(pThis->hFuzzCtx, &pvState, &cbState); + if (RT_SUCCESS(rc)) + { + uint32_t cbStateWr = (uint32_t)cbState; + rc = RTPipeWriteBlocking(pExecCtx->hPipeStdinW, &cbStateWr, sizeof(cbStateWr), NULL); + rc = RTPipeWriteBlocking(pExecCtx->hPipeStdinW, pvState, cbState, NULL); + if (RT_SUCCESS(rc)) + { + rc = RTPollSetRemove(pExecCtx->hPollSet, RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN); + AssertRC(rc); + + uint64_t tsMilliesLastSignal = RTTimeSystemMilliTS(); + uint32_t cFuzzedInputs = 0; + for (;;) + { + /* Wait a bit for something to happen on one of the pipes. */ + uint32_t fEvtsRecv = 0; + uint32_t idEvt = 0; + rc = RTPoll(pExecCtx->hPollSet, 10 /*cMillies*/, &fEvtsRecv, &idEvt); + if (RT_SUCCESS(rc)) + { + if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDOUT) + { + Assert(fEvtsRecv & RTPOLL_EVT_READ); + for (;;) + { + char achBuf[512]; + size_t cbRead = 0; + rc = RTPipeRead(pExecCtx->hPipeStdoutR, &achBuf[0], sizeof(achBuf), &cbRead); + if (RT_SUCCESS(rc)) + { + if (!cbRead) + break; + + tsMilliesLastSignal = RTTimeMilliTS(); + for (unsigned i = 0; i < cbRead; i++) + { + ASMAtomicIncU32(&pThis->Stats.cFuzzedInputs); + ASMAtomicIncU32(&pThis->Stats.cFuzzedInputsPerSec); + + if (achBuf[i] == '.') + cFuzzedInputs++; + else if (achBuf[i] == 'A') + { + /** @todo Advance our fuzzer to get the added input. */ + } + } + } + else + break; + } + AssertRC(rc); + } + else if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDERR) + { + Assert(fEvtsRecv & RTPOLL_EVT_READ); + rc = RTFuzzTgtStateAppendStderrFromPipe(pExecCtx->hTgtState, pExecCtx->hPipeStderrR); + AssertRC(rc); + } + else + AssertMsgFailed(("Invalid poll ID returned: %u!\n", idEvt)); + } + else + Assert(rc == VERR_TIMEOUT); + + /* Check the process status. */ + rc = RTProcWait(pExecCtx->hProc, RTPROCWAIT_FLAGS_NOBLOCK, pProcStat); + if (RT_SUCCESS(rc)) + break; + else + { + Assert(rc == VERR_PROCESS_RUNNING); + /* Check when the last response from the client was. */ + if (RTTimeSystemMilliTS() - tsMilliesLastSignal > pThis->msWaitMax) + { + rc = VERR_TIMEOUT; + break; + } + } + } /* for (;;) */ + + /* Kill the process on a timeout. */ + if (rc == VERR_TIMEOUT) + { + int rc2 = RTProcTerminate(pExecCtx->hProc); + AssertRC(rc2); + } + } + } + } + + RTHANDLE Handle; + Handle.enmType = RTHANDLETYPE_PIPE; + Handle.u.hPipe = pExecCtx->hPipeStdinW; + rc = RTPollSetAdd(pExecCtx->hPollSet, &Handle, RTPOLL_EVT_WRITE, RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN); + AssertRC(rc); + + return rc; +} + + +/** + * Adds the input to the results directory. + * + * @returns IPRT status code. + * @param pThis The internal fuzzing observer state. + * @param hFuzzInput Fuzzing input handle to write. + * @param pExecCtx Execution context. + */ +static int rtFuzzObsAddInputToResults(PRTFUZZOBSINT pThis, RTFUZZINPUT hFuzzInput, PRTFUZZOBSEXECCTX pExecCtx) +{ + char aszDigest[RTMD5_STRING_LEN + 1]; + int rc = RTFuzzInputQueryDigestString(hFuzzInput, &aszDigest[0], sizeof(aszDigest)); + if (RT_SUCCESS(rc)) + { + /* Create a directory. */ + char szPath[RTPATH_MAX]; + rc = RTPathJoin(szPath, sizeof(szPath), pThis->pszResultsDir, &aszDigest[0]); + AssertRC(rc); + + rc = RTDirCreate(&szPath[0], 0700, 0 /*fCreate*/); + if (RT_SUCCESS(rc)) + { + /* Write the input. */ + char szTmp[RTPATH_MAX]; + rc = RTPathJoin(szTmp, sizeof(szTmp), &szPath[0], "input"); + AssertRC(rc); + + rc = RTFuzzInputWriteToFile(hFuzzInput, &szTmp[0]); + if (RT_SUCCESS(rc)) + rc = RTFuzzTgtStateDumpToDir(pExecCtx->hTgtState, &szPath[0]); + } + } + + return rc; +} + + +/** + * Fuzzing observer worker loop. + * + * @returns IPRT status code. + * @param hThrd The thread handle. + * @param pvUser Opaque user data. + */ +static DECLCALLBACK(int) rtFuzzObsWorkerLoop(RTTHREAD hThrd, void *pvUser) +{ + PRTFUZZOBSTHRD pObsThrd = (PRTFUZZOBSTHRD)pvUser; + PRTFUZZOBSINT pThis = pObsThrd->pFuzzObs; + PRTFUZZOBSEXECCTX pExecCtx = NULL; + + int rc = rtFuzzObsExecCtxCreate(&pExecCtx, pThis); + if (RT_FAILURE(rc)) + return rc; + + char szInput[RTPATH_MAX]; RT_ZERO(szInput); + if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FILE) + { + char szFilename[32]; + + ssize_t cbBuf = RTStrPrintf2(&szFilename[0], sizeof(szFilename), "%u", pObsThrd->idObs); + Assert(cbBuf > 0); RT_NOREF(cbBuf); + + rc = RTPathJoin(szInput, sizeof(szInput), pThis->pszTmpDir, &szFilename[0]); + AssertRC(rc); + + RTFUZZOBSVARIABLE aVar[2] = + { + { "${INPUT}", sizeof("${INPUT}") - 1, &szInput[0] }, + { NULL, 0, NULL } + }; + rc = rtFuzzObsExecCtxArgvPrepare(pThis, pExecCtx, &aVar[0]); + if (RT_FAILURE(rc)) + return rc; + } + + while (!pObsThrd->fShutdown) + { + /* Wait for work. */ + if (!ASMAtomicReadU32(&pObsThrd->cInputs)) + { + rc = RTThreadUserWait(hThrd, RT_INDEFINITE_WAIT); + AssertRC(rc); + } + + if (pObsThrd->fShutdown) + break; + + if (!ASMAtomicReadU32(&pObsThrd->cInputs)) + continue; + + uint32_t offRead = ASMAtomicReadU32(&pObsThrd->offQueueInputR); + RTFUZZINPUT hFuzzInput = pObsThrd->ahQueueInput[offRead]; + + ASMAtomicDecU32(&pObsThrd->cInputs); + offRead = (offRead + 1) % RT_ELEMENTS(pObsThrd->ahQueueInput); + ASMAtomicWriteU32(&pObsThrd->offQueueInputR, offRead); + if (!ASMAtomicBitTestAndSet(&pThis->bmEvt, pObsThrd->idObs)) + RTSemEventSignal(pThis->hEvtGlobal); + + if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FILE) + rc = RTFuzzInputWriteToFile(hFuzzInput, &szInput[0]); + else if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_STDIN) + { + rc = RTFuzzInputQueryBlobData(hFuzzInput, (void **)&pExecCtx->pbInputCur, &pExecCtx->cbInputLeft); + if (RT_SUCCESS(rc)) + rc = rtFuzzObsExecCtxArgvPrepare(pThis, pExecCtx, NULL); + } + + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS ProcSts; + if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT) + rc = rtFuzzObsExecCtxClientRunFuzzingAware(pThis, pExecCtx, &ProcSts); + else + { + rc = rtFuzzObsExecCtxClientRun(pThis, pExecCtx, &ProcSts); + ASMAtomicIncU32(&pThis->Stats.cFuzzedInputs); + ASMAtomicIncU32(&pThis->Stats.cFuzzedInputsPerSec); + } + + if (RT_SUCCESS(rc)) + { + rc = RTFuzzTgtStateAddProcSts(pExecCtx->hTgtState, &ProcSts); + AssertRC(rc); + + if (ProcSts.enmReason != RTPROCEXITREASON_NORMAL) + { + ASMAtomicIncU32(&pThis->Stats.cFuzzedInputsCrash); + rc = rtFuzzObsAddInputToResults(pThis, hFuzzInput, pExecCtx); + } + } + else if (rc == VERR_TIMEOUT) + { + ASMAtomicIncU32(&pThis->Stats.cFuzzedInputsHang); + rc = rtFuzzObsAddInputToResults(pThis, hFuzzInput, pExecCtx); + } + else + AssertFailed(); + + /* + * Check whether we reached an unknown target state and add the input to the + * corpus in that case. + */ + rc = RTFuzzTgtStateAddToRecorder(pExecCtx->hTgtState); + if (RT_SUCCESS(rc)) + { + /* Add to corpus and create a new target state for the next run. */ + RTFuzzInputAddToCtxCorpus(hFuzzInput); + RTFuzzTgtStateRelease(pExecCtx->hTgtState); + pExecCtx->hTgtState = NIL_RTFUZZTGTSTATE; + rc = RTFuzzTgtRecorderCreateNewState(pThis->hTgtRec, &pExecCtx->hTgtState); + AssertRC(rc); + } + else + { + Assert(rc == VERR_ALREADY_EXISTS); + /* Reset the state for the next run. */ + rc = RTFuzzTgtStateReset(pExecCtx->hTgtState); + AssertRC(rc); + } + RTFuzzInputRelease(hFuzzInput); + + if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FILE) + RTFileDelete(&szInput[0]); + } + } + + rtFuzzObsExecCtxDestroy(pThis, pExecCtx); + return VINF_SUCCESS; +} + + +/** + * Fills the input queue of the given observer thread until it is full. + * + * @returns IPRT status code. + * @param pThis Pointer to the observer instance data. + * @param pObsThrd The observer thread instance to fill. + */ +static int rtFuzzObsMasterInputQueueFill(PRTFUZZOBSINT pThis, PRTFUZZOBSTHRD pObsThrd) +{ + int rc = VINF_SUCCESS; + uint32_t cInputsAdded = 0; + uint32_t cInputsAdd = RTFUZZOBS_THREAD_INPUT_QUEUE_MAX - ASMAtomicReadU32(&pObsThrd->cInputs); + uint32_t offW = ASMAtomicReadU32(&pObsThrd->offQueueInputW); + + while ( cInputsAdded < cInputsAdd + && RT_SUCCESS(rc)) + { + RTFUZZINPUT hFuzzInput = NIL_RTFUZZINPUT; + rc = RTFuzzCtxInputGenerate(pThis->hFuzzCtx, &hFuzzInput); + if (RT_SUCCESS(rc)) + { + pObsThrd->ahQueueInput[offW] = hFuzzInput; + offW = (offW + 1) % RTFUZZOBS_THREAD_INPUT_QUEUE_MAX; + cInputsAdded++; + } + } + + ASMAtomicWriteU32(&pObsThrd->offQueueInputW, offW); + ASMAtomicAddU32(&pObsThrd->cInputs, cInputsAdded); + + return rc; +} + + +/** + * Fuzzing observer master worker loop. + * + * @returns IPRT status code. + * @param hThread The thread handle. + * @param pvUser Opaque user data. + */ +static DECLCALLBACK(int) rtFuzzObsMasterLoop(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + int rc = VINF_SUCCESS; + PRTFUZZOBSINT pThis = (PRTFUZZOBSINT)pvUser; + + RTThreadUserSignal(hThread); + + while ( !pThis->fShutdown + && RT_SUCCESS(rc)) + { + uint64_t bmEvt = ASMAtomicXchgU64(&pThis->bmEvt, 0); + uint32_t idxObs = 0; + while (bmEvt != 0) + { + if (bmEvt & 0x1) + { + /* Create a new input for this observer and kick it. */ + PRTFUZZOBSTHRD pObsThrd = &pThis->paObsThreads[idxObs]; + + rc = rtFuzzObsMasterInputQueueFill(pThis, pObsThrd); + if (RT_SUCCESS(rc)) + RTThreadUserSignal(pObsThrd->hThread); + } + + idxObs++; + bmEvt >>= 1; + } + + rc = RTSemEventWait(pThis->hEvtGlobal, RT_INDEFINITE_WAIT); + } + + return VINF_SUCCESS; +} + + +/** + * Initializes the given worker thread structure. + * + * @returns IPRT status code. + * @param pThis The internal fuzzing observer state. + * @param iObs Observer ID. + * @param pObsThrd The observer thread structure. + */ +static int rtFuzzObsWorkerThreadInit(PRTFUZZOBSINT pThis, uint32_t idObs, PRTFUZZOBSTHRD pObsThrd) +{ + pObsThrd->pFuzzObs = pThis; + pObsThrd->idObs = idObs; + pObsThrd->fShutdown = false; + pObsThrd->cInputs = 0; + pObsThrd->offQueueInputW = 0; + pObsThrd->offQueueInputR = 0; + + ASMAtomicBitSet(&pThis->bmEvt, idObs); + return RTThreadCreate(&pObsThrd->hThread, rtFuzzObsWorkerLoop, pObsThrd, 0, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, "Fuzz-Worker"); +} + + +/** + * Creates the given amount of worker threads and puts them into waiting state. + * + * @returns IPRT status code. + * @param pThis The internal fuzzing observer state. + * @param cThreads Number of worker threads to create. + */ +static int rtFuzzObsWorkersCreate(PRTFUZZOBSINT pThis, uint32_t cThreads) +{ + int rc = VINF_SUCCESS; + PRTFUZZOBSTHRD paObsThreads = (PRTFUZZOBSTHRD)RTMemAllocZ(cThreads * sizeof(RTFUZZOBSTHRD)); + if (RT_LIKELY(paObsThreads)) + { + for (unsigned i = 0; i < cThreads && RT_SUCCESS(rc); i++) + { + rc = rtFuzzObsWorkerThreadInit(pThis, i, &paObsThreads[i]); + if (RT_FAILURE(rc)) + { + /* Rollback. */ + + } + } + + if (RT_SUCCESS(rc)) + { + pThis->paObsThreads = paObsThreads; + pThis->cThreads = cThreads; + } + else + RTMemFree(paObsThreads); + } + + return rc; +} + + +/** + * Creates the global worker thread managing the input creation and other worker threads. + * + * @returns IPRT status code. + * @param pThis The internal fuzzing observer state. + */ +static int rtFuzzObsMasterCreate(PRTFUZZOBSINT pThis) +{ + pThis->fShutdown = false; + + int rc = RTSemEventCreate(&pThis->hEvtGlobal); + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&pThis->hThreadGlobal, rtFuzzObsMasterLoop, pThis, 0, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, "Fuzz-Master"); + if (RT_SUCCESS(rc)) + { + RTThreadUserWait(pThis->hThreadGlobal, RT_INDEFINITE_WAIT); + } + else + { + RTSemEventDestroy(pThis->hEvtGlobal); + pThis->hEvtGlobal = NIL_RTSEMEVENT; + } + } + + return rc; +} + + +/** + * Sets up any configured sanitizers to cooperate with the observer. + * + * @returns IPRT status code. + * @param pThis The internal fuzzing observer state. + */ +static int rtFuzzObsSetupSanitizerCfg(PRTFUZZOBSINT pThis) +{ + int rc = VINF_SUCCESS; + bool fSep = false; + + if (pThis->fSanitizers & RTFUZZOBS_SANITIZER_F_ASAN) + { + /* + * Need to set abort_on_error=1 in ASAN_OPTIONS or + * the sanitizer will call exit() instead of abort() and we + * don't catch invalid memory accesses. + */ + rc = RTStrAAppend(&pThis->pszSanitizerOpts, "abort_on_error=1"); + fSep = true; + } + + if ( RT_SUCCESS(rc) + && (pThis->fSanitizers & RTFUZZOBS_SANITIZER_F_SANCOV)) + { + /* + * The coverage sanitizer will dump coverage information into a file + * on process exit. Need to configure the directory where to dump it. + */ + char aszSanCovCfg[_4K]; + ssize_t cch = RTStrPrintf2(&aszSanCovCfg[0], sizeof(aszSanCovCfg), + "%scoverage=1:coverage_dir=%s", + fSep ? ":" : "", pThis->pszTmpDir); + if (cch > 0) + rc = RTStrAAppend(&pThis->pszSanitizerOpts, &aszSanCovCfg[0]); + else + rc = VERR_BUFFER_OVERFLOW; + fSep = true; + } + + if ( RT_SUCCESS(rc) + && pThis->pszSanitizerOpts) + { + /* Add it to the environment. */ + if (pThis->hEnv == RTENV_DEFAULT) + { + /* Clone the environment to keep the default one untouched. */ + rc = RTEnvClone(&pThis->hEnv, RTENV_DEFAULT); + } + if (RT_SUCCESS(rc)) + rc = RTEnvSetEx(pThis->hEnv, "ASAN_OPTIONS", pThis->pszSanitizerOpts); + } + + return rc; +} + + +RTDECL(int) RTFuzzObsCreate(PRTFUZZOBS phFuzzObs, RTFUZZCTXTYPE enmType, uint32_t fTgtRecFlags) +{ + AssertPtrReturn(phFuzzObs, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + PRTFUZZOBSINT pThis = (PRTFUZZOBSINT)RTMemAllocZ(sizeof(*pThis)); + if (RT_LIKELY(pThis)) + { + pThis->pszBinary = NULL; + pThis->pszBinaryFilename = NULL; + pThis->papszArgs = NULL; + pThis->hEnv = RTENV_DEFAULT; + pThis->msWaitMax = 1000; + pThis->hThreadGlobal = NIL_RTTHREAD; + pThis->hEvtGlobal = NIL_RTSEMEVENT; + pThis->bmEvt = 0; + pThis->cThreads = 0; + pThis->paObsThreads = NULL; + pThis->tsLastStats = RTTimeMilliTS(); + pThis->Stats.cFuzzedInputsPerSec = 0; + pThis->Stats.cFuzzedInputs = 0; + pThis->Stats.cFuzzedInputsHang = 0; + pThis->Stats.cFuzzedInputsCrash = 0; + rc = RTFuzzCtxCreate(&pThis->hFuzzCtx, enmType); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzTgtRecorderCreate(&pThis->hTgtRec, fTgtRecFlags); + if (RT_SUCCESS(rc)) + { + *phFuzzObs = pThis; + return VINF_SUCCESS; + } + RTFuzzCtxRelease(pThis->hFuzzCtx); + } + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTFuzzObsDestroy(RTFUZZOBS hFuzzObs) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + RTFuzzObsExecStop(hFuzzObs); + + /* Clean up all acquired resources. */ + for (unsigned i = 0; i < pThis->cArgs; i++) + RTStrFree(pThis->papszArgs[i]); + + RTMemFree(pThis->papszArgs); + + if (pThis->hEvtGlobal != NIL_RTSEMEVENT) + RTSemEventDestroy(pThis->hEvtGlobal); + + if (pThis->pszResultsDir) + RTStrFree(pThis->pszResultsDir); + if (pThis->pszTmpDir) + RTStrFree(pThis->pszTmpDir); + if (pThis->pszBinary) + RTStrFree(pThis->pszBinary); + if (pThis->pszSanitizerOpts) + RTStrFree(pThis->pszSanitizerOpts); + if (pThis->hEnv != RTENV_DEFAULT) + { + RTEnvDestroy(pThis->hEnv); + pThis->hEnv = RTENV_DEFAULT; + } + RTFuzzTgtRecorderRelease(pThis->hTgtRec); + RTFuzzCtxRelease(pThis->hFuzzCtx); + RTMemFree(pThis); + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzObsQueryCtx(RTFUZZOBS hFuzzObs, PRTFUZZCTX phFuzzCtx) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER); + + RTFuzzCtxRetain(pThis->hFuzzCtx); + *phFuzzCtx = pThis->hFuzzCtx; + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzObsQueryStats(RTFUZZOBS hFuzzObs, PRTFUZZOBSSTATS pStats) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pStats, VERR_INVALID_POINTER); + + uint64_t tsStatsQuery = RTTimeMilliTS(); + uint32_t cFuzzedInputsPerSec = ASMAtomicXchgU32(&pThis->Stats.cFuzzedInputsPerSec, 0); + + pStats->cFuzzedInputsCrash = ASMAtomicReadU32(&pThis->Stats.cFuzzedInputsCrash); + pStats->cFuzzedInputsHang = ASMAtomicReadU32(&pThis->Stats.cFuzzedInputsHang); + pStats->cFuzzedInputs = ASMAtomicReadU32(&pThis->Stats.cFuzzedInputs); + uint64_t cPeriodSec = (tsStatsQuery - pThis->tsLastStats) / 1000; + if (cPeriodSec) + { + pStats->cFuzzedInputsPerSec = cFuzzedInputsPerSec / cPeriodSec; + pThis->cFuzzedInputsPerSecLast = pStats->cFuzzedInputsPerSec; + pThis->tsLastStats = tsStatsQuery; + } + else + pStats->cFuzzedInputsPerSec = pThis->cFuzzedInputsPerSecLast; + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzObsSetTmpDirectory(RTFUZZOBS hFuzzObs, const char *pszTmp) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pszTmp, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + pThis->pszTmpDir = RTStrDup(pszTmp); + if (!pThis->pszTmpDir) + rc = VERR_NO_STR_MEMORY; + return rc; +} + + +RTDECL(int) RTFuzzObsSetResultDirectory(RTFUZZOBS hFuzzObs, const char *pszResults) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pszResults, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + pThis->pszResultsDir = RTStrDup(pszResults); + if (!pThis->pszResultsDir) + rc = VERR_NO_STR_MEMORY; + return rc; +} + + +RTDECL(int) RTFuzzObsSetTestBinary(RTFUZZOBS hFuzzObs, const char *pszBinary, RTFUZZOBSINPUTCHAN enmInputChan) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pszBinary, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + pThis->enmInputChan = enmInputChan; + pThis->pszBinary = RTStrDup(pszBinary); + if (RT_UNLIKELY(!pThis->pszBinary)) + rc = VERR_NO_STR_MEMORY; + else + pThis->pszBinaryFilename = RTPathFilename(pThis->pszBinary); + return rc; +} + + +RTDECL(int) RTFuzzObsSetTestBinaryArgs(RTFUZZOBS hFuzzObs, const char * const *papszArgs, unsigned cArgs) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + int rc = VINF_SUCCESS; + char **papszArgsOld = pThis->papszArgs; + if (papszArgs) + { + pThis->papszArgs = (char **)RTMemAllocZ(sizeof(char **) * (cArgs + 1)); + if (RT_LIKELY(pThis->papszArgs)) + { + for (unsigned i = 0; i < cArgs; i++) + { + pThis->papszArgs[i] = RTStrDup(papszArgs[i]); + if (RT_UNLIKELY(!pThis->papszArgs[i])) + { + while (i > 0) + { + i--; + RTStrFree(pThis->papszArgs[i]); + } + break; + } + } + + if (RT_FAILURE(rc)) + RTMemFree(pThis->papszArgs); + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + pThis->papszArgs = papszArgsOld; + else + pThis->cArgs = cArgs; + } + else + { + pThis->papszArgs = NULL; + pThis->cArgs = 0; + if (papszArgsOld) + { + char **ppsz = papszArgsOld; + while (*ppsz != NULL) + { + RTStrFree(*ppsz); + ppsz++; + } + RTMemFree(papszArgsOld); + } + } + + return rc; +} + + +RTDECL(int) RTFuzzObsSetTestBinaryEnv(RTFUZZOBS hFuzzObs, RTENV hEnv) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + pThis->hEnv = hEnv; + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzObsSetTestBinarySanitizers(RTFUZZOBS hFuzzObs, uint32_t fSanitizers) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + pThis->fSanitizers = fSanitizers; + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzObsSetTestBinaryTimeout(RTFUZZOBS hFuzzObs, RTMSINTERVAL msTimeoutMax) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + pThis->msWaitMax = msTimeoutMax; + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzObsExecStart(RTFUZZOBS hFuzzObs, uint32_t cProcs) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(cProcs <= sizeof(uint64_t) * 8, VERR_INVALID_PARAMETER); + AssertReturn( pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FILE + || pThis->pszTmpDir != NULL, + VERR_INVALID_STATE); + + int rc = VINF_SUCCESS; + if (!cProcs) + cProcs = RT_MIN(RTMpGetPresentCoreCount(), sizeof(uint64_t) * 8); + + rc = rtFuzzObsSetupSanitizerCfg(pThis); + if (RT_SUCCESS(rc)) + { + /* Spin up the worker threads first. */ + rc = rtFuzzObsWorkersCreate(pThis, cProcs); + if (RT_SUCCESS(rc)) + { + /* Spin up the global thread. */ + rc = rtFuzzObsMasterCreate(pThis); + } + } + + return rc; +} + + +RTDECL(int) RTFuzzObsExecStop(RTFUZZOBS hFuzzObs) +{ + PRTFUZZOBSINT pThis = hFuzzObs; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + /* Wait for the master thread to terminate. */ + if (pThis->hThreadGlobal != NIL_RTTHREAD) + { + ASMAtomicXchgBool(&pThis->fShutdown, true); + RTSemEventSignal(pThis->hEvtGlobal); + RTThreadWait(pThis->hThreadGlobal, RT_INDEFINITE_WAIT, NULL); + pThis->hThreadGlobal = NIL_RTTHREAD; + } + + /* Destroy the workers. */ + if (pThis->paObsThreads) + { + for (unsigned i = 0; i < pThis->cThreads; i++) + { + PRTFUZZOBSTHRD pThrd = &pThis->paObsThreads[i]; + ASMAtomicXchgBool(&pThrd->fShutdown, true); + RTThreadUserSignal(pThrd->hThread); + RTThreadWait(pThrd->hThread, RT_INDEFINITE_WAIT, NULL); + } + + RTMemFree(pThis->paObsThreads); + pThis->paObsThreads = NULL; + pThis->cThreads = 0; + } + + RTSemEventDestroy(pThis->hEvtGlobal); + pThis->hEvtGlobal = NIL_RTSEMEVENT; + return VINF_SUCCESS; +} + diff --git a/src/VBox/Runtime/common/fuzz/fuzz-target-recorder.cpp b/src/VBox/Runtime/common/fuzz/fuzz-target-recorder.cpp new file mode 100644 index 00000000..a46790ba --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/fuzz-target-recorder.cpp @@ -0,0 +1,791 @@ +/* $Id: fuzz-target-recorder.cpp $ */ +/** @file + * IPRT - Fuzzing framework API, target state recorder. + */ + +/* + * Copyright (C) 2019-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/fuzz.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/avl.h> +#include <iprt/crc.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to the internal fuzzed target recorder state. */ +typedef struct RTFUZZTGTRECINT *PRTFUZZTGTRECINT; + + +/** + * Stdout/Stderr buffer. + */ +typedef struct RTFUZZTGTSTDOUTERRBUF +{ + /** Current amount buffered. */ + size_t cbBuf; + /** Maxmium amount to buffer. */ + size_t cbBufMax; + /** Base pointer to the data buffer. */ + uint8_t *pbBase; +} RTFUZZTGTSTDOUTERRBUF; +/** Pointer to a stdout/stderr buffer. */ +typedef RTFUZZTGTSTDOUTERRBUF *PRTFUZZTGTSTDOUTERRBUF; + + +/** + * Internal fuzzed target state. + */ +typedef struct RTFUZZTGTSTATEINT +{ + /** Node for the list of states. */ + RTLISTNODE NdStates; + /** Checksum for the state. */ + uint64_t uChkSum; + /** Magic identifying the structure. */ + uint32_t u32Magic; + /** Reference counter. */ + volatile uint32_t cRefs; + /** The owning recorder instance. */ + PRTFUZZTGTRECINT pTgtRec; + /** Flag whether the state is finalized. */ + bool fFinalized; + /** Flag whether the state is contained in the recorded set. */ + bool fInRecSet; + /** The stdout data buffer. */ + RTFUZZTGTSTDOUTERRBUF StdOutBuf; + /** The stderr data buffer. */ + RTFUZZTGTSTDOUTERRBUF StdErrBuf; + /** Process status. */ + RTPROCSTATUS ProcSts; + /** Coverage report buffer. */ + void *pvCovReport; + /** Size of the coverage report in bytes. */ + size_t cbCovReport; + /** Number of traced edges. */ + size_t cEdges; +} RTFUZZTGTSTATEINT; +/** Pointer to an internal fuzzed target state. */ +typedef RTFUZZTGTSTATEINT *PRTFUZZTGTSTATEINT; + + +/** + * Recorder states node in the AVL tree. + */ +typedef struct RTFUZZTGTRECNODE +{ + /** The AVL tree core (keyed by checksum). */ + AVLU64NODECORE Core; + /** The list anchor for the individual states. */ + RTLISTANCHOR LstStates; +} RTFUZZTGTRECNODE; +/** Pointer to a recorder states node. */ +typedef RTFUZZTGTRECNODE *PRTFUZZTGTRECNODE; + + +/** + * Edge information node. + */ +typedef struct RTFUZZTGTEDGE +{ + /** The AVL tree core (keyed by offset). */ + AVLU64NODECORE Core; + /** Number of times the edge was hit. */ + volatile uint64_t cHits; +} RTFUZZTGTEDGE; +/** Pointer to a edge information node. */ +typedef RTFUZZTGTEDGE *PRTFUZZTGTEDGE; + + +/** + * Internal fuzzed target recorder state. + */ +typedef struct RTFUZZTGTRECINT +{ + /** Magic value for identification. */ + uint32_t u32Magic; + /** Reference counter. */ + volatile uint32_t cRefs; + /** Flags passed when the recorder was created. */ + uint32_t fRecFlags; + /** Semaphore protecting the states tree. */ + RTSEMRW hSemRwStates; + /** The AVL tree for indexing the recorded state (keyed by stdout/stderr buffer size). */ + AVLU64TREE TreeStates; + /** Semaphore protecting the edges tree. */ + RTSEMRW hSemRwEdges; + /** The AVL tree for discovered edges when coverage reports are collected. */ + AVLU64TREE TreeEdges; + /** Number of edges discovered so far. */ + volatile uint64_t cEdges; + /** The discovered offset width. */ + volatile uint32_t cbCovOff; +} RTFUZZTGTRECINT; + + +/** SanCov magic for 64bit offsets. */ +#define SANCOV_MAGIC_64 UINT64_C(0xc0bfffffffffff64) +/** SanCov magic for 32bit offsets. */ +#define SANCOV_MAGIC_32 UINT64_C(0xc0bfffffffffff32) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Initializes the given stdout/stderr buffer. + * + * @returns nothing. + * @param pBuf The buffer to initialize. + */ +static void rtFuzzTgtStdOutErrBufInit(PRTFUZZTGTSTDOUTERRBUF pBuf) +{ + pBuf->cbBuf = 0; + pBuf->cbBufMax = 0; + pBuf->pbBase = NULL; +} + + +/** + * Frees all allocated resources in the given stdout/stderr buffer. + * + * @returns nothing. + * @param pBuf The buffer to free. + */ +static void rtFuzzTgtStdOutErrBufFree(PRTFUZZTGTSTDOUTERRBUF pBuf) +{ + if (pBuf->pbBase) + RTMemFree(pBuf->pbBase); +} + + +/** + * Fills the given stdout/stderr buffer from the given pipe. + * + * @returns IPRT status code. + * @param pBuf The buffer to fill. + * @param hPipeRead The pipe to read from. + */ +static int rtFuzzTgtStdOutErrBufFillFromPipe(PRTFUZZTGTSTDOUTERRBUF pBuf, RTPIPE hPipeRead) +{ + int rc = VINF_SUCCESS; + + size_t cbRead = 0; + size_t cbThisRead = 0; + do + { + cbThisRead = pBuf->cbBufMax - pBuf->cbBuf; + if (!cbThisRead) + { + /* Try to increase the buffer. */ + uint8_t *pbNew = (uint8_t *)RTMemRealloc(pBuf->pbBase, pBuf->cbBufMax + _4K); + if (RT_LIKELY(pbNew)) + { + pBuf->cbBufMax += _4K; + pBuf->pbBase = pbNew; + } + cbThisRead = pBuf->cbBufMax - pBuf->cbBuf; + } + + if (cbThisRead) + { + rc = RTPipeRead(hPipeRead, pBuf->pbBase + pBuf->cbBuf, cbThisRead, &cbRead); + if (RT_SUCCESS(rc)) + pBuf->cbBuf += cbRead; + } + else + rc = VERR_NO_MEMORY; + } while ( RT_SUCCESS(rc) + && cbRead == cbThisRead); + + return rc; +} + + +/** + * Writes the given buffer to the given file. + * + * @returns IPRT status code. + * @param pBuf The buffer to write. + * @param pszFilename Where to write the buffer. + */ +static int rtFuzzTgtStateStdOutErrBufWriteToFile(PRTFUZZTGTSTDOUTERRBUF pBuf, const char *pszFilename) +{ + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, pBuf->pbBase, pBuf->cbBuf, NULL); + AssertRC(rc); + RTFileClose(hFile); + + if (RT_FAILURE(rc)) + RTFileDelete(pszFilename); + } + + return rc; +} + + +/** + * Scans the given target state for newly discovered edges in the coverage report. + * + * @returns IPRT status code. + * @param pThis The fuzzer target recorder instance. + * @param pTgtState The target state to check. + */ +static int rtFuzzTgtRecScanStateForNewEdges(PRTFUZZTGTRECINT pThis, PRTFUZZTGTSTATEINT pTgtState) +{ + int rc = VINF_SUCCESS; + + if (pTgtState->pvCovReport) + { + rc = RTSemRWRequestRead(pThis->hSemRwEdges, RT_INDEFINITE_WAIT); AssertRC(rc); + + uint32_t cbCovOff = ASMAtomicReadU32(&pThis->cbCovOff); + Assert(cbCovOff != 0); + + uint8_t *pbCovCur = (uint8_t *)pTgtState->pvCovReport; + size_t cEdgesLeft = pTgtState->cbCovReport / cbCovOff; + while (cEdgesLeft) + { + uint64_t offCur = cbCovOff == sizeof(uint64_t) + ? *(uint64_t *)pbCovCur + : *(uint32_t *)pbCovCur; + + PRTFUZZTGTEDGE pEdge = (PRTFUZZTGTEDGE)RTAvlU64Get(&pThis->TreeEdges, offCur); + if (!pEdge) + { + /* New edge discovered, allocate and add. */ + rc = RTSemRWReleaseRead(pThis->hSemRwEdges); AssertRC(rc); + + pEdge = (PRTFUZZTGTEDGE)RTMemAllocZ(sizeof(RTFUZZTGTEDGE)); + if (RT_LIKELY(pEdge)) + { + pEdge->Core.Key = offCur; + pEdge->cHits = 1; + rc = RTSemRWRequestWrite(pThis->hSemRwEdges, RT_INDEFINITE_WAIT); AssertRC(rc); + + bool fIns = RTAvlU64Insert(&pThis->TreeEdges, &pEdge->Core); + if (!fIns) + { + /* Someone raced us, free and query again. */ + RTMemFree(pEdge); + pEdge = (PRTFUZZTGTEDGE)RTAvlU64Get(&pThis->TreeEdges, offCur); + AssertPtr(pEdge); + + ASMAtomicIncU64(&pEdge->cHits); + } + else + ASMAtomicIncU64(&pThis->cEdges); + + rc = RTSemRWReleaseWrite(pThis->hSemRwEdges); AssertRC(rc); + rc = RTSemRWRequestRead(pThis->hSemRwEdges, RT_INDEFINITE_WAIT); AssertRC(rc); + } + else + { + rc = RTSemRWRequestRead(pThis->hSemRwEdges, RT_INDEFINITE_WAIT); + AssertRC(rc); + + rc = VERR_NO_MEMORY; + break; + } + } + else + ASMAtomicIncU64(&pEdge->cHits); + + pbCovCur += cbCovOff; + cEdgesLeft--; + } + + rc = RTSemRWReleaseRead(pThis->hSemRwEdges); AssertRC(rc); + } + + return rc; +} + + +/** + * Destorys the given fuzzer target recorder freeing all allocated resources. + * + * @returns nothing. + * @param pThis The fuzzer target recorder instance. + */ +static void rtFuzzTgtRecDestroy(PRTFUZZTGTRECINT pThis) +{ + RT_NOREF(pThis); +} + + +/** + * Destroys the given fuzzer target state freeing all allocated resources. + * + * @returns nothing. + * @param pThis The fuzzed target state instance. + */ +static void rtFuzzTgtStateDestroy(PRTFUZZTGTSTATEINT pThis) +{ + pThis->u32Magic = ~(uint32_t)0; /** @todo Dead magic */ + rtFuzzTgtStdOutErrBufFree(&pThis->StdOutBuf); + rtFuzzTgtStdOutErrBufFree(&pThis->StdErrBuf); + RTMemFree(pThis); +} + + +/** + * Compares two given target states, checking whether they match. + * + * @returns Flag whether the states are identical. + * @param pThis Target state 1. + * @param pThat Target state 2. + */ +static bool rtFuzzTgtStateDoMatch(PRTFUZZTGTSTATEINT pThis, PRTFUZZTGTSTATEINT pThat) +{ + PRTFUZZTGTRECINT pTgtRec = pThis->pTgtRec; + Assert(pTgtRec == pThat->pTgtRec); + + if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_STDOUT) + && ( pThis->StdOutBuf.cbBuf != pThat->StdOutBuf.cbBuf + || ( pThis->StdOutBuf.cbBuf > 0 + && memcmp(pThis->StdOutBuf.pbBase, pThat->StdOutBuf.pbBase, pThis->StdOutBuf.cbBuf)))) + return false; + + if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_STDERR) + && ( pThis->StdErrBuf.cbBuf != pThat->StdErrBuf.cbBuf + || ( pThis->StdErrBuf.cbBuf > 0 + && memcmp(pThis->StdErrBuf.pbBase, pThat->StdErrBuf.pbBase, pThis->StdErrBuf.cbBuf)))) + return false; + + if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_PROCSTATUS) + && memcmp(&pThis->ProcSts, &pThat->ProcSts, sizeof(RTPROCSTATUS))) + return false; + + if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_SANCOV) + && ( pThis->cbCovReport != pThat->cbCovReport + || ( pThis->cbCovReport > 0 + && memcmp(pThis->pvCovReport, pThat->pvCovReport, pThis->cbCovReport)))) + return false; + + return true; +} + + +RTDECL(int) RTFuzzTgtRecorderCreate(PRTFUZZTGTREC phFuzzTgtRec, uint32_t fRecFlags) +{ + AssertPtrReturn(phFuzzTgtRec, VERR_INVALID_POINTER); + AssertReturn(!(fRecFlags & ~RTFUZZTGT_REC_STATE_F_VALID), VERR_INVALID_PARAMETER); + + int rc; + PRTFUZZTGTRECINT pThis = (PRTFUZZTGTRECINT)RTMemAllocZ(sizeof(*pThis)); + if (RT_LIKELY(pThis)) + { + pThis->u32Magic = 0; /** @todo */ + pThis->cRefs = 1; + pThis->TreeStates = NULL; + pThis->TreeEdges = NULL; + pThis->cbCovOff = 0; + pThis->fRecFlags = fRecFlags; + + rc = RTSemRWCreate(&pThis->hSemRwStates); + if (RT_SUCCESS(rc)) + { + rc = RTSemRWCreate(&pThis->hSemRwEdges); + if (RT_SUCCESS(rc)) + { + *phFuzzTgtRec = pThis; + return VINF_SUCCESS; + } + + RTSemRWDestroy(pThis->hSemRwStates); + } + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(uint32_t) RTFuzzTgtRecorderRetain(RTFUZZTGTREC hFuzzTgtRec) +{ + PRTFUZZTGTRECINT pThis = hFuzzTgtRec; + + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + + +RTDECL(uint32_t) RTFuzzTgtRecorderRelease(RTFUZZTGTREC hFuzzTgtRec) +{ + PRTFUZZTGTRECINT pThis = hFuzzTgtRec; + if (pThis == NIL_RTFUZZTGTREC) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + rtFuzzTgtRecDestroy(pThis); + return cRefs; +} + + +RTDECL(int) RTFuzzTgtRecorderCreateNewState(RTFUZZTGTREC hFuzzTgtRec, PRTFUZZTGTSTATE phFuzzTgtState) +{ + PRTFUZZTGTRECINT pThis = hFuzzTgtRec; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(phFuzzTgtState, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + PRTFUZZTGTSTATEINT pState = (PRTFUZZTGTSTATEINT)RTMemAllocZ(sizeof(*pState)); + if (RT_LIKELY(pState)) + { + pState->u32Magic = 0; /** @todo */ + pState->cRefs = 1; + pState->pTgtRec = pThis; + pState->fFinalized = false; + rtFuzzTgtStdOutErrBufInit(&pState->StdOutBuf); + rtFuzzTgtStdOutErrBufInit(&pState->StdErrBuf); + *phFuzzTgtState = pState; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(uint32_t) RTFuzzTgtStateRetain(RTFUZZTGTSTATE hFuzzTgtState) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + + +RTDECL(uint32_t) RTFuzzTgtStateRelease(RTFUZZTGTSTATE hFuzzTgtState) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + if (pThis == NIL_RTFUZZTGTSTATE) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0 && !pThis->fInRecSet) + rtFuzzTgtStateDestroy(pThis); + return cRefs; +} + + +RTDECL(int) RTFuzzTgtStateReset(RTFUZZTGTSTATE hFuzzTgtState) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + /* Clear the buffers. */ + pThis->StdOutBuf.cbBuf = 0; + pThis->StdErrBuf.cbBuf = 0; + RT_ZERO(pThis->ProcSts); + if (pThis->pvCovReport) + RTMemFree(pThis->pvCovReport); + pThis->pvCovReport = NULL; + pThis->fFinalized = false; + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzTgtStateFinalize(RTFUZZTGTSTATE hFuzzTgtState) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + /* Create the checksum. */ + PRTFUZZTGTRECINT pTgtRec = pThis->pTgtRec; + uint64_t uChkSum = RTCrc64Start(); + if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_STDOUT) + && pThis->StdOutBuf.cbBuf) + uChkSum = RTCrc64Process(uChkSum, pThis->StdOutBuf.pbBase, pThis->StdOutBuf.cbBuf); + if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_STDERR) + && pThis->StdErrBuf.cbBuf) + uChkSum = RTCrc64Process(uChkSum, pThis->StdErrBuf.pbBase, pThis->StdErrBuf.cbBuf); + if (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_PROCSTATUS) + uChkSum = RTCrc64Process(uChkSum, &pThis->ProcSts, sizeof(RTPROCSTATUS)); + if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_SANCOV) + && pThis->pvCovReport) + uChkSum = RTCrc64Process(uChkSum, pThis->pvCovReport, pThis->cbCovReport); + + pThis->uChkSum = RTCrc64Finish(uChkSum); + pThis->fFinalized = true; + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzTgtStateAddToRecorder(RTFUZZTGTSTATE hFuzzTgtState) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + if (!pThis->fFinalized) + { + int rc = RTFuzzTgtStateFinalize(pThis); + if (RT_FAILURE(rc)) + return rc; + } + + PRTFUZZTGTRECINT pTgtRec = pThis->pTgtRec; + + /* Try to find a node matching the stdout and sterr sizes first. */ + int rc = RTSemRWRequestRead(pTgtRec->hSemRwStates, RT_INDEFINITE_WAIT); AssertRC(rc); + PRTFUZZTGTRECNODE pNode = (PRTFUZZTGTRECNODE)RTAvlU64Get(&pTgtRec->TreeStates, pThis->uChkSum); + if (pNode) + { + /* Traverse the states and check if any matches the stdout and stderr buffers exactly. */ + PRTFUZZTGTSTATEINT pIt; + bool fMatchFound = false; + RTListForEach(&pNode->LstStates, pIt, RTFUZZTGTSTATEINT, NdStates) + { + if (rtFuzzTgtStateDoMatch(pThis, pIt)) + { + fMatchFound = true; + break; + } + } + + rc = RTSemRWReleaseRead(pTgtRec->hSemRwStates); AssertRC(rc); + if (!fMatchFound) + { + rc = RTSemRWRequestWrite(pTgtRec->hSemRwStates, RT_INDEFINITE_WAIT); AssertRC(rc); + RTListAppend(&pNode->LstStates, &pThis->NdStates); + rc = RTSemRWReleaseWrite(pTgtRec->hSemRwStates); AssertRC(rc); + pThis->fInRecSet = true; + } + else + rc = VERR_ALREADY_EXISTS; + } + else + { + rc = RTSemRWReleaseRead(pTgtRec->hSemRwStates); AssertRC(rc); + + /* No node found, create new one and insert in to the tree right away. */ + pNode = (PRTFUZZTGTRECNODE)RTMemAllocZ(sizeof(*pNode)); + if (RT_LIKELY(pNode)) + { + pNode->Core.Key = pThis->uChkSum; + RTListInit(&pNode->LstStates); + RTListAppend(&pNode->LstStates, &pThis->NdStates); + rc = RTSemRWRequestWrite(pTgtRec->hSemRwStates, RT_INDEFINITE_WAIT); AssertRC(rc); + bool fIns = RTAvlU64Insert(&pTgtRec->TreeStates, &pNode->Core); + if (!fIns) + { + /* Someone raced us, get the new node and append there. */ + RTMemFree(pNode); + pNode = (PRTFUZZTGTRECNODE)RTAvlU64Get(&pTgtRec->TreeStates, pThis->uChkSum); + AssertPtr(pNode); + RTListAppend(&pNode->LstStates, &pThis->NdStates); + } + rc = RTSemRWReleaseWrite(pTgtRec->hSemRwStates); AssertRC(rc); + pThis->fInRecSet = true; + } + else + rc = VERR_NO_MEMORY; + } + + if ( RT_SUCCESS(rc) + && pThis->fInRecSet) + rc = rtFuzzTgtRecScanStateForNewEdges(pTgtRec, pThis); + + return rc; +} + + +RTDECL(int) RTFuzzTgtStateAppendStdoutFromBuf(RTFUZZTGTSTATE hFuzzTgtState, const void *pvStdOut, size_t cbStdOut) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER); + + RT_NOREF(pvStdOut, cbStdOut); + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(int) RTFuzzTgtStateAppendStderrFromBuf(RTFUZZTGTSTATE hFuzzTgtState, const void *pvStdErr, size_t cbStdErr) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER); + + RT_NOREF(pvStdErr, cbStdErr); + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(int) RTFuzzTgtStateAppendStdoutFromPipe(RTFUZZTGTSTATE hFuzzTgtState, RTPIPE hPipe) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER); + + return rtFuzzTgtStdOutErrBufFillFromPipe(&pThis->StdOutBuf, hPipe); +} + + +RTDECL(int) RTFuzzTgtStateAppendStderrFromPipe(RTFUZZTGTSTATE hFuzzTgtState, RTPIPE hPipe) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER); + + return rtFuzzTgtStdOutErrBufFillFromPipe(&pThis->StdErrBuf, hPipe); +} + + +RTDECL(int) RTFuzzTgtStateAddSanCovReportFromFile(RTFUZZTGTSTATE hFuzzTgtState, const char *pszFilename) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER); + + uint8_t *pbSanCov = NULL; + size_t cbSanCov = 0; + int rc = RTFileReadAll(pszFilename, (void **)&pbSanCov, &cbSanCov); + if (RT_SUCCESS(rc)) + { + /* Check for the magic identifying whether the offsets are 32bit or 64bit. */ + if ( cbSanCov >= sizeof(uint64_t) + && ( *(uint64_t *)pbSanCov == SANCOV_MAGIC_64 + || *(uint64_t *)pbSanCov == SANCOV_MAGIC_32)) + { + uint32_t cbCovOff = sizeof(uint32_t); + if (*(uint64_t *)pbSanCov == SANCOV_MAGIC_64) + cbCovOff = sizeof(uint64_t); + + uint32_t cbCovDet = ASMAtomicReadU32(&pThis->pTgtRec->cbCovOff); + if (!cbCovDet) + { + /* Set the detected offset width. */ + if (!ASMAtomicCmpXchgU32(&pThis->pTgtRec->cbCovOff, cbCovOff, 0)) + { + /* Someone raced us, check again. */ + cbCovDet = ASMAtomicReadU32(&pThis->pTgtRec->cbCovOff); + Assert(cbCovDet != 0); + } + else + cbCovDet = cbCovOff; + } + + if (cbCovDet == cbCovOff) + { + /* + * Just copy the offsets into the state for now. Now further analysis + * is happening right now, just checking whether the content changed for + * the states.to spot newly discovered edges. + */ + pThis->cbCovReport = cbSanCov - sizeof(uint64_t); + pThis->pvCovReport = RTMemDup(pbSanCov + sizeof(uint64_t), pThis->cbCovReport); + if (!pThis->pvCovReport) + { + pThis->cbCovReport = 0; + rc = VERR_NO_MEMORY; + } + } + else + rc = VERR_INVALID_STATE; /* Mixing 32bit and 64bit offsets shouldn't happen, is not supported. */ + } + else + rc = VERR_INVALID_STATE; + RTFileReadAllFree(pbSanCov, cbSanCov); + } + return rc; +} + + +RTDECL(int) RTFuzzTgtStateAddProcSts(RTFUZZTGTSTATE hFuzzTgtState, PCRTPROCSTATUS pProcSts) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pProcSts, VERR_INVALID_POINTER); + AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER); + + pThis->ProcSts = *pProcSts; + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzTgtStateDumpToDir(RTFUZZTGTSTATE hFuzzTgtState, const char *pszDirPath) +{ + PRTFUZZTGTSTATEINT pThis = hFuzzTgtState; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pszDirPath, VERR_INVALID_POINTER); + AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER); + + int rc = VINF_SUCCESS; + char szPath[RTPATH_MAX]; + if (pThis->StdOutBuf.cbBuf) + { + rc = RTPathJoin(szPath, sizeof(szPath), pszDirPath, "stdout"); AssertRC(rc); + if (RT_SUCCESS(rc)) + rc = rtFuzzTgtStateStdOutErrBufWriteToFile(&pThis->StdOutBuf, &szPath[0]); + } + + if ( RT_SUCCESS(rc) + && pThis->StdErrBuf.cbBuf) + { + rc = RTPathJoin(szPath, sizeof(szPath), pszDirPath, "stderr"); AssertRC(rc); + if (RT_SUCCESS(rc)) + rc = rtFuzzTgtStateStdOutErrBufWriteToFile(&pThis->StdErrBuf, &szPath[0]); + } + + return rc; +} + diff --git a/src/VBox/Runtime/common/fuzz/fuzz.cpp b/src/VBox/Runtime/common/fuzz/fuzz.cpp new file mode 100644 index 00000000..579b792d --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/fuzz.cpp @@ -0,0 +1,2181 @@ +/* $Id: fuzz.cpp $ */ +/** @file + * IPRT - Fuzzing framework API, core. + */ + +/* + * Copyright (C) 2018-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/fuzz.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/avl.h> +#include <iprt/critsect.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/list.h> +#include <iprt/md5.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/rand.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include <iprt/vfs.h> + + +#define RTFUZZCTX_MAGIC UINT32_C(0xdeadc0de) /** @todo */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to the internal fuzzer state. */ +typedef struct RTFUZZCTXINT *PRTFUZZCTXINT; +/** Pointer to a fuzzed mutation. */ +typedef struct RTFUZZMUTATION *PRTFUZZMUTATION; +/** Pointer to a fuzzed mutation pointer. */ +typedef PRTFUZZMUTATION *PPRTFUZZMUTATION; +/** Pointer to a const mutation. */ +typedef const struct RTFUZZMUTATION *PCRTFUZZMUTATION; + + +/** + * Mutator class. + */ +typedef enum RTFUZZMUTATORCLASS +{ + /** Invalid class, do not use. */ + RTFUZZMUTATORCLASS_INVALID = 0, + /** Mutator operates on single bits. */ + RTFUZZMUTATORCLASS_BITS, + /** Mutator operates on bytes (single or multiple). */ + RTFUZZMUTATORCLASS_BYTES, + /** Mutator interpretes data as integers and operates on them. */ + RTFUZZMUTATORCLASS_INTEGERS, + /** Mutator uses multiple mutations to create new mutations. */ + RTFUZZMUTATORCLASS_MUTATORS, + /** 32bit hack. */ + RTFUZZMUTATORCLASS_32BIT_HACK = 0x7fffffff +} RTFUZZMUTATORCLASS; + + +/** + * Mutator preparation callback. + * + * @returns IPRT status code. + * @param pThis The fuzzer context instance. + * @param offStart Where the mutation should start. + * @param pMutationParent The parent mutation to start working from. + * @param ppMutation Where to store the created mutation on success. + */ +typedef DECLCALLBACK(int) FNRTFUZZCTXMUTATORPREP(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation); +/** Pointer to a mutator preparation callback. */ +typedef FNRTFUZZCTXMUTATORPREP *PFNRTFUZZCTXMUTATORPREP; + + +/** + * Mutator execution callback. + * + * @returns IPRT status code. + * @param pThis The fuzzer context instance. + * @param pMutation The mutation to work on. + * @param pvMutation Mutation dependent data. + * @param pbBuf The buffer to work on. + * @param cbBuf Size of the remaining buffer. + */ +typedef DECLCALLBACK(int) FNRTFUZZCTXMUTATOREXEC(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf); +/** Pointer to a mutator execution callback. */ +typedef FNRTFUZZCTXMUTATOREXEC *PFNRTFUZZCTXMUTATOREXEC; + + +/** + * Mutator export callback. + * + * @returns IPRT status code. + * @param pThis The fuzzer context instance. + * @param pMutation The mutation to work on. + * @param pvMutation Mutation dependent data. + * @param pfnExport The export callback. + * @param pvUser Opaque user data to pass to the export callback. + */ +typedef DECLCALLBACK(int) FNRTFUZZCTXMUTATOREXPORT(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + PFNRTFUZZCTXEXPORT pfnExport, void *pvUser); +/** Pointer to a mutator export callback. */ +typedef FNRTFUZZCTXMUTATOREXPORT *PFNRTFUZZCTXMUTATOREXPORT; + + +/** + * Mutator import callback. + * + * @returns IPRT status code. + * @param pThis The fuzzer context instance. + * @param pMutation The mutation to work on. + * @param pvMutation Mutation dependent data. + * @param pfnExport The import callback. + * @param pvUser Opaque user data to pass to the import callback. + */ +typedef DECLCALLBACK(int) FNRTFUZZCTXMUTATORIMPORT(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, void *pvMutation, + PFNRTFUZZCTXIMPORT pfnImport, void *pvUser); +/** Pointer to a mutator import callback. */ +typedef FNRTFUZZCTXMUTATORIMPORT *PFNRTFUZZCTXMUTATORIMPORT; + + +/** + * A fuzzing mutator descriptor. + */ +typedef struct RTFUZZMUTATOR +{ + /** Id of the mutator. */ + const char *pszId; + /** Mutator description. */ + const char *pszDesc; + /** Mutator index. */ + uint32_t uMutator; + /** Mutator class. */ + RTFUZZMUTATORCLASS enmClass; + /** Additional flags for the mutator, controlling the behavior. */ + uint64_t fFlags; + /** The preparation callback. */ + PFNRTFUZZCTXMUTATORPREP pfnPrep; + /** The execution callback. */ + PFNRTFUZZCTXMUTATOREXEC pfnExec; + /** The export callback. */ + PFNRTFUZZCTXMUTATOREXPORT pfnExport; + /** The import callback. */ + PFNRTFUZZCTXMUTATORIMPORT pfnImport; +} RTFUZZMUTATOR; +/** Pointer to a fuzzing mutator descriptor. */ +typedef RTFUZZMUTATOR *PRTFUZZMUTATOR; +/** Pointer to a const fuzzing mutator descriptor. */ +typedef const RTFUZZMUTATOR *PCRTFUZZMUTATOR; + +/** The special corpus mutator. */ +#define RTFUZZMUTATOR_ID_CORPUS UINT32_C(0xffffffff) + +/** Mutator always works from the end of the buffer (no starting offset generation). */ +#define RTFUZZMUTATOR_F_END_OF_BUF RT_BIT_64(0) +/** Default flags. */ +#define RTFUZZMUTATOR_F_DEFAULT (0) + + +/** + * A fuzzed mutation. + */ +typedef struct RTFUZZMUTATION +{ + /** The AVL tree core. */ + AVLU64NODECORE Core; + /** The list node if the mutation has the mutated + * data allocated. */ + RTLISTNODE NdAlloc; + /** Magic identifying this structure. */ + uint32_t u32Magic; + /** Reference counter. */ + volatile uint32_t cRefs; + /** The fuzzer this mutation belongs to. */ + PRTFUZZCTXINT pFuzzer; + /** Parent mutation (no reference is held), NULL means root or original data. */ + PRTFUZZMUTATION pMutationParent; + /** Mutation level. */ + uint32_t iLvl; + /** The mutator causing this mutation, NULL if original input data. */ + PCRTFUZZMUTATOR pMutator; + /** Byte offset where the mutation starts. */ + uint64_t offMutation; + /** Size of the generated input data in bytes after the mutation was applied. */ + size_t cbInput; + /** Size of the mutation dependent data. */ + size_t cbMutation; + /** Size allocated for the input. */ + size_t cbAlloc; + /** Pointer to the input data if created. */ + void *pvInput; + /** Flag whether the mutation is contained in the tree of the context. */ + bool fInTree; + /** Flag whether the mutation input data is cached. */ + bool fCached; + /** Mutation dependent data, variable in size. */ + uint8_t abMutation[1]; +} RTFUZZMUTATION; + + +/** + * A fuzzing input seed. + */ +typedef struct RTFUZZINPUTINT +{ + /** Magic identifying this structure. */ + uint32_t u32Magic; + /** Reference counter. */ + volatile uint32_t cRefs; + /** The fuzzer this input belongs to. */ + PRTFUZZCTXINT pFuzzer; + /** The top mutation to work from (reference held). */ + PRTFUZZMUTATION pMutationTop; + /** Fuzzer context type dependent data. */ + union + { + /** Blob data. */ + struct + { + /** Pointer to the input data if created. */ + void *pvInput; + } Blob; + /** Stream state. */ + struct + { + /** Number of bytes seen so far. */ + size_t cbSeen; + } Stream; + } u; +} RTFUZZINPUTINT; +/** Pointer to the internal input state. */ +typedef RTFUZZINPUTINT *PRTFUZZINPUTINT; +/** Pointer to an internal input state pointer. */ +typedef PRTFUZZINPUTINT *PPRTFUZZINPUTINT; + + +/** + * The fuzzer state. + */ +typedef struct RTFUZZCTXINT +{ + /** Magic value for identification. */ + uint32_t u32Magic; + /** Reference counter. */ + volatile uint32_t cRefs; + /** The random number generator. */ + RTRAND hRand; + /** Fuzzing context type. */ + RTFUZZCTXTYPE enmType; + /** Semaphore protecting the mutations tree. */ + RTSEMRW hSemRwMutations; + /** The AVL tree for indexing the mutations (keyed by counter). */ + AVLU64TREE TreeMutations; + /** Number of inputs currently in the tree. */ + volatile uint64_t cMutations; + /** The maximum size of one input seed to generate. */ + size_t cbInputMax; + /** Behavioral flags. */ + uint32_t fFlagsBehavioral; + /** Number of enabled mutators. */ + uint32_t cMutators; + /** Pointer to the mutator descriptors. */ + PRTFUZZMUTATOR paMutators; + /** Maximum amount of bytes of mutated inputs to cache. */ + size_t cbMutationsAllocMax; + /** Current amount of bytes of cached mutated inputs. */ + size_t cbMutationsAlloc; + /** List of mutators having data allocated currently. */ + RTLISTANCHOR LstMutationsAlloc; + /** Critical section protecting the allocation list. */ + RTCRITSECT CritSectAlloc; + /** Total number of bytes of memory currently allocated in total for this context. */ + volatile size_t cbMemTotal; +} RTFUZZCTXINT; + + +/** + * The fuzzer state to be exported - all members are stored in little endian form. + */ +typedef struct RTFUZZCTXSTATE +{ + /** Magic value for identification. */ + uint32_t u32Magic; + /** Context type. */ + uint32_t uCtxType; + /** Size of the PRNG state following in bytes. */ + uint32_t cbPrng; + /** Number of mutator descriptors following. */ + uint32_t cMutators; + /** Number of mutation descriptors following. */ + uint32_t cMutations; + /** Behavioral flags. */ + uint32_t fFlagsBehavioral; + /** Maximum input size to generate. */ + uint64_t cbInputMax; +} RTFUZZCTXSTATE; +/** Pointer to a fuzzing context state. */ +typedef RTFUZZCTXSTATE *PRTFUZZCTXSTATE; + +/** BLOB context type. */ +#define RTFUZZCTX_STATE_TYPE_BLOB UINT32_C(0) +/** Stream context type. */ +#define RTFUZZCTX_STATE_TYPE_STREAM UINT32_C(1) + + +/** + * The fuzzer mutation state to be exported - all members are stored in little endian form. + */ +typedef struct RTFUZZMUTATIONSTATE +{ + /** The mutation identifier. */ + uint64_t u64Id; + /** The mutation identifier of the parent, 0 for no parent. */ + uint64_t u64IdParent; + /** The byte offset where the mutation starts. */ + uint64_t u64OffMutation; + /** Size of input data after mutation was applied. */ + uint64_t cbInput; + /** Size of mutation dependent data following. */ + uint64_t cbMutation; + /** The mutator ID. */ + uint32_t u32IdMutator; + /** The mutation level. */ + uint32_t iLvl; + /** Magic value for identification. */ + uint32_t u32Magic; +} RTFUZZMUTATIONSTATE; + + +/** + * Fuzzing context memory header. + */ +typedef struct RTFUZZMEMHDR +{ + /** Size of the memory area following. */ + size_t cb; +#if HC_ARCH_BITS == 32 + /** Some padding. */ + uint32_t uPadding0; +#elif HC_ARCH_BITS == 64 + /** Some padding. */ + uint64_t uPadding0; +#else +# error "Port me" +#endif +} RTFUZZMEMHDR; +/** Pointer to a memory header. */ +typedef RTFUZZMEMHDR *PRTFUZZMEMHDR; + + +/** + * Fuzzing context export AVL arguments. + */ +typedef struct RTFUZZEXPORTARGS +{ + /** Pointer to the export callback. */ + PFNRTFUZZCTXEXPORT pfnExport; + /** Opaque user data to pass to the callback. */ + void *pvUser; +} RTFUZZEXPORTARGS; +/** Pointer to the export arguments. */ +typedef RTFUZZEXPORTARGS *PRTFUZZEXPORTARGS; +/** Pointer to the constant export arguments. */ +typedef const RTFUZZEXPORTARGS *PCRTFUZZEXPORTARGS; + + +/** + * Integer replacing mutator additional data. + */ +typedef struct RTFUZZMUTATORINTEGER +{ + /** The integer class. */ + uint8_t uIntClass; + /** Flag whether to do a byte swap. */ + bool fByteSwap; + /** The index into the class specific array. */ + uint16_t idxInt; +} RTFUZZMUTATORINTEGER; +/** Pointer to additional integer replacing mutator data. */ +typedef RTFUZZMUTATORINTEGER *PRTFUZZMUTATORINTEGER; +/** Pointer to constant additional integer replacing mutator data. */ +typedef const RTFUZZMUTATORINTEGER *PCRTFUZZMUTATORINTEGER; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) rtFuzzCtxMutatorBitFlipPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteReplacePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteInsertPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceInsertAppendPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteDeletePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceDeletePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation); +static DECLCALLBACK(int) rtFuzzCtxMutatorIntegerReplacePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation); +static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation); + +static DECLCALLBACK(int) rtFuzzCtxMutatorCorpusExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf); +static DECLCALLBACK(int) rtFuzzCtxMutatorBitFlipExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteReplaceExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteInsertExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceInsertAppendExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteDeleteExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceDeleteExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf); +static DECLCALLBACK(int) rtFuzzCtxMutatorIntegerReplaceExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf); +static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf); + +static DECLCALLBACK(int) rtFuzzCtxMutatorExportDefault(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + PFNRTFUZZCTXEXPORT pfnExport, void *pvUser); +static DECLCALLBACK(int) rtFuzzCtxMutatorImportDefault(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, void *pvMutation, + PFNRTFUZZCTXIMPORT pfnImport, void *pvUser); + +static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverExport(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + PFNRTFUZZCTXEXPORT pfnExport, void *pvUser); +static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverImport(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, void *pvMutation, + PFNRTFUZZCTXIMPORT pfnImport, void *pvUser); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Signed 8bit interesting values. */ +static int8_t s_ai8Interesting[] = { INT8_MIN, INT8_MIN + 1, -1, 0, 1, INT8_MAX - 1, INT8_MAX }; +/** Unsigned 8bit interesting values. */ +static uint8_t s_au8Interesting[] = { 0, 1, UINT8_MAX - 1, UINT8_MAX }; +/** Signed 16bit interesting values. */ +static int16_t s_ai16Interesting[] = { INT16_MIN, INT16_MIN + 1, -1, 0, 1, INT16_MAX - 1, INT16_MAX }; +/** Unsigned 16bit interesting values. */ +static uint16_t s_au16Interesting[] = { 0, 1, UINT16_MAX - 1, UINT16_MAX }; +/** Signed 32bit interesting values. */ +static int32_t s_ai32Interesting[] = { INT32_MIN, INT32_MIN + 1, -1, 0, 1, INT32_MAX - 1, INT32_MAX }; +/** Unsigned 32bit interesting values. */ +static uint32_t s_au32Interesting[] = { 0, 1, UINT32_MAX - 1, UINT32_MAX }; +/** Signed 64bit interesting values. */ +static int64_t s_ai64Interesting[] = { INT64_MIN, INT64_MIN + 1, -1, 0, 1, INT64_MAX - 1, INT64_MAX }; +/** Unsigned 64bit interesting values. */ +static uint64_t s_au64Interesting[] = { 0, 1, UINT64_MAX - 1, UINT64_MAX }; + + +/** + * The special corpus mutator for the original data. + */ +static RTFUZZMUTATOR const g_MutatorCorpus = +{ + /** pszId */ + "Corpus", + /** pszDesc */ + "Special mutator, which is assigned to the initial corpus", + /** uMutator. */ + RTFUZZMUTATOR_ID_CORPUS, + /** enmClass. */ + RTFUZZMUTATORCLASS_BYTES, + /** fFlags */ + RTFUZZMUTATOR_F_DEFAULT, + /** pfnPrep */ + NULL, + /** pfnExec */ + rtFuzzCtxMutatorCorpusExec, + /** pfnExport */ + rtFuzzCtxMutatorExportDefault, + /** pfnImport */ + rtFuzzCtxMutatorImportDefault +}; + +/** + * Array of all available mutators. + */ +static RTFUZZMUTATOR const g_aMutators[] = +{ + /* pszId pszDesc uMutator enmClass fFlags pfnPrep pfnExec pfnExport pfnImport */ + { "BitFlip", "Flips a single bit in the input", 0, RTFUZZMUTATORCLASS_BITS, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorBitFlipPrep, rtFuzzCtxMutatorBitFlipExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault }, + { "ByteReplace", "Replaces a single byte in the input", 1, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorByteReplacePrep, rtFuzzCtxMutatorByteReplaceExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault }, + { "ByteInsert", "Inserts a single byte sequence into the input", 2, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorByteInsertPrep, rtFuzzCtxMutatorByteInsertExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault }, + { "ByteSeqIns", "Inserts a byte sequence in the input", 3, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorByteSequenceInsertAppendPrep, rtFuzzCtxMutatorByteSequenceInsertAppendExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault }, + { "ByteSeqApp", "Appends a byte sequence to the input", 4, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_END_OF_BUF, rtFuzzCtxMutatorByteSequenceInsertAppendPrep, rtFuzzCtxMutatorByteSequenceInsertAppendExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault }, + { "ByteDelete", "Deletes a single byte sequence from the input", 5, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorByteDeletePrep, rtFuzzCtxMutatorByteDeleteExec, NULL, NULL }, + { "ByteSeqDel", "Deletes a byte sequence from the input", 6, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorByteSequenceDeletePrep, rtFuzzCtxMutatorByteSequenceDeleteExec, NULL, NULL }, + { "IntReplace", "Replaces a possible integer with an interesting one", 7, RTFUZZMUTATORCLASS_INTEGERS, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorIntegerReplacePrep, rtFuzzCtxMutatorIntegerReplaceExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault }, + { "MutCrossover", "Creates a crossover of two other mutations", 8, RTFUZZMUTATORCLASS_MUTATORS, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorCrossoverPrep, rtFuzzCtxMutatorCrossoverExec, rtFuzzCtxMutatorCrossoverExport, rtFuzzCtxMutatorCrossoverImport } +}; + + +/** + * Allocates the given number of bytes. + * + * @returns Pointer to the allocated memory + * @param pThis The fuzzer context instance. + * @param cb How much to allocate. + */ +static void *rtFuzzCtxMemoryAlloc(PRTFUZZCTXINT pThis, size_t cb) +{ + AssertReturn(cb > 0, NULL); + + PRTFUZZMEMHDR pMemHdr = (PRTFUZZMEMHDR)RTMemAllocZ(cb + sizeof(RTFUZZMEMHDR)); + if (RT_LIKELY(pMemHdr)) + { + pMemHdr->cb = cb; + size_t cbIgn = ASMAtomicAddZ(&pThis->cbMemTotal, cb + sizeof(RTFUZZMEMHDR)); RT_NOREF(cbIgn); + return pMemHdr + 1; + } + + return NULL; +} + + +/** + * Frees the given memory. + * + * @returns nothing. + * @param pThis The fuzzer context instance. + * @param pv Pointer to the memory area to free. + */ +static void rtFuzzCtxMemoryFree(PRTFUZZCTXINT pThis, void *pv) +{ + AssertReturnVoid(pv != NULL); + PRTFUZZMEMHDR pMemHdr = ((PRTFUZZMEMHDR)pv) - 1; + + size_t cbIgn = ASMAtomicSubZ(&pThis->cbMemTotal, pMemHdr->cb + sizeof(RTFUZZMEMHDR)); RT_NOREF(cbIgn); + RTMemFree(pMemHdr); +} + + +/** + * Frees the cached inputs until the given amount is free. + * + * @returns Whether the amount of memory is free. + * @param pThis The fuzzer context instance. + * @param cb How many bytes to reclaim + */ +static bool rtFuzzCtxMutationAllocReclaim(PRTFUZZCTXINT pThis, size_t cb) +{ + while ( !RTListIsEmpty(&pThis->LstMutationsAlloc) + && pThis->cbMutationsAlloc + cb > pThis->cbMutationsAllocMax) + { + PRTFUZZMUTATION pMutation = RTListGetLast(&pThis->LstMutationsAlloc, RTFUZZMUTATION, NdAlloc); + AssertPtr(pMutation); + AssertPtr(pMutation->pvInput); + + rtFuzzCtxMemoryFree(pThis, pMutation->pvInput); + pThis->cbMutationsAlloc -= pMutation->cbAlloc; + pMutation->pvInput = NULL; + pMutation->cbAlloc = 0; + pMutation->fCached = false; + RTListNodeRemove(&pMutation->NdAlloc); + } + + return pThis->cbMutationsAlloc + cb <= pThis->cbMutationsAllocMax; +} + + +/** + * Updates the cache status of the given mutation. + * + * @returns nothing. + * @param pThis The fuzzer context instance. + * @param pMutation The mutation to update. + */ +static void rtFuzzCtxMutationMaybeEnterCache(PRTFUZZCTXINT pThis, PRTFUZZMUTATION pMutation) +{ + RTCritSectEnter(&pThis->CritSectAlloc); + + /* Initial corpus mutations are not freed. */ + if ( pMutation->pvInput + && pMutation->pMutator != &g_MutatorCorpus) + { + Assert(!pMutation->fCached); + + if (rtFuzzCtxMutationAllocReclaim(pThis, pMutation->cbAlloc)) + { + RTListPrepend(&pThis->LstMutationsAlloc, &pMutation->NdAlloc); + pThis->cbMutationsAlloc += pMutation->cbAlloc; + pMutation->fCached = true; + } + else + { + rtFuzzCtxMemoryFree(pThis, pMutation->pvInput); + pMutation->pvInput = NULL; + pMutation->cbAlloc = 0; + pMutation->fCached = false; + } + } + RTCritSectLeave(&pThis->CritSectAlloc); +} + + +/** + * Removes a cached mutation from the cache. + * + * @returns nothing. + * @param pThis The fuzzer context instance. + * @param pMutation The mutation to remove. + */ +static void rtFuzzCtxMutationCacheRemove(PRTFUZZCTXINT pThis, PRTFUZZMUTATION pMutation) +{ + RTCritSectEnter(&pThis->CritSectAlloc); + if (pMutation->fCached) + { + RTListNodeRemove(&pMutation->NdAlloc); + pThis->cbMutationsAlloc -= pMutation->cbAlloc; + pMutation->fCached = false; + } + RTCritSectLeave(&pThis->CritSectAlloc); +} + + +/** + * Destroys the given mutation. + * + * @returns nothing. + * @param pMutation The mutation to destroy. + */ +static void rtFuzzMutationDestroy(PRTFUZZMUTATION pMutation) +{ + if (pMutation->pvInput) + { + rtFuzzCtxMemoryFree(pMutation->pFuzzer, pMutation->pvInput); + if (pMutation->fCached) + { + RTCritSectEnter(&pMutation->pFuzzer->CritSectAlloc); + RTListNodeRemove(&pMutation->NdAlloc); + pMutation->pFuzzer->cbMutationsAlloc -= pMutation->cbAlloc; + RTCritSectLeave(&pMutation->pFuzzer->CritSectAlloc); + } + pMutation->pvInput = NULL; + pMutation->cbAlloc = 0; + pMutation->fCached = false; + } + rtFuzzCtxMemoryFree(pMutation->pFuzzer, pMutation); +} + + +/** + * Retains an external reference to the given mutation. + * + * @returns New reference count on success. + * @param pMutation The mutation to retain. + */ +static uint32_t rtFuzzMutationRetain(PRTFUZZMUTATION pMutation) +{ + uint32_t cRefs = ASMAtomicIncU32(&pMutation->cRefs); + AssertMsg( ( cRefs > 1 + || pMutation->fInTree) + && cRefs < _1M, ("%#x %p\n", cRefs, pMutation)); + + if (cRefs == 1) + rtFuzzCtxMutationCacheRemove(pMutation->pFuzzer, pMutation); + return cRefs; +} + + +/** + * Releases an external reference from the given mutation. + * + * @returns New reference count on success. + * @param pMutation The mutation to retain. + */ +static uint32_t rtFuzzMutationRelease(PRTFUZZMUTATION pMutation) +{ + uint32_t cRefs = ASMAtomicDecU32(&pMutation->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pMutation)); + + if (cRefs == 0) + { + if (!pMutation->fInTree) + rtFuzzMutationDestroy(pMutation); + else + rtFuzzCtxMutationMaybeEnterCache(pMutation->pFuzzer, pMutation); + } + + return cRefs; +} + + +/** + * Adds the given mutation to the corpus of the given fuzzer context. + * + * @returns IPRT status code. + * @param pThis The fuzzer context instance. + * @param pMutation The mutation to add. + */ +static int rtFuzzCtxMutationAdd(PRTFUZZCTXINT pThis, PRTFUZZMUTATION pMutation) +{ + int rc = VINF_SUCCESS; + + pMutation->Core.Key = ASMAtomicIncU64(&pThis->cMutations); + rc = RTSemRWRequestWrite(pThis->hSemRwMutations, RT_INDEFINITE_WAIT); + AssertRC(rc); RT_NOREF(rc); + bool fIns = RTAvlU64Insert(&pThis->TreeMutations, &pMutation->Core); + Assert(fIns); RT_NOREF(fIns); + rc = RTSemRWReleaseWrite(pThis->hSemRwMutations); + AssertRC(rc); RT_NOREF(rc); + + pMutation->fInTree = true; + return rc; +} + + +/** + * Locates the mutation with the given key. + * + * @returns Pointer to the mutation if found or NULL otherwise. + * @param pThis The fuzzer context instance. + * @param uKey The key to locate. + */ +static PRTFUZZMUTATION rtFuzzCtxMutationLocate(PRTFUZZCTXINT pThis, uint64_t uKey) +{ + int rc = RTSemRWRequestRead(pThis->hSemRwMutations, RT_INDEFINITE_WAIT); + AssertRC(rc); RT_NOREF(rc); + + /* + * Using best fit getter here as there might be a racing mutation insertion and the mutation counter has increased + * already but the mutation is not yet in the tree. + */ + PRTFUZZMUTATION pMutation = (PRTFUZZMUTATION)RTAvlU64GetBestFit(&pThis->TreeMutations, uKey, false /*fAbove*/); + if (RT_LIKELY(pMutation)) + rtFuzzMutationRetain(pMutation); + + rc = RTSemRWReleaseRead(pThis->hSemRwMutations); + AssertRC(rc); RT_NOREF(rc); + + return pMutation; +} + + +/** + * Returns a random mutation from the corpus of the given fuzzer context. + * + * @returns Pointer to a randomly picked mutation (reference count is increased). + * @param pThis The fuzzer context instance. + */ +static PRTFUZZMUTATION rtFuzzCtxMutationPickRnd(PRTFUZZCTXINT pThis) +{ + uint64_t idxMutation = RTRandAdvU64Ex(pThis->hRand, 1, ASMAtomicReadU64(&pThis->cMutations)); + return rtFuzzCtxMutationLocate(pThis, idxMutation); +} + + +/** + * Creates a new mutation capable of holding the additional number of bytes. + * + * @returns Pointer to the newly created mutation or NULL if out of memory. + * @param pThis The fuzzer context instance. + * @param offMutation The starting offset for the mutation. + * @param pMutationParent The parent mutation, can be NULL. + * @param cbAdditional Additional number of bytes to allocate after the core structure. + * @param ppvMutation Where to store the pointer to the mutation dependent data on success. + */ +static PRTFUZZMUTATION rtFuzzMutationCreate(PRTFUZZCTXINT pThis, uint64_t offMutation, PRTFUZZMUTATION pMutationParent, + size_t cbAdditional, void **ppvMutation) +{ + PRTFUZZMUTATION pMutation = (PRTFUZZMUTATION)rtFuzzCtxMemoryAlloc(pThis, sizeof(RTFUZZMUTATION) + cbAdditional); + if (RT_LIKELY(pMutation)) + { + pMutation->u32Magic = 0; /** @todo */ + pMutation->pFuzzer = pThis; + pMutation->cRefs = 1; + pMutation->iLvl = 0; + pMutation->offMutation = offMutation; + pMutation->pMutationParent = pMutationParent; + pMutation->cbMutation = cbAdditional; + pMutation->fInTree = false; + pMutation->fCached = false; + pMutation->pvInput = NULL; + pMutation->cbInput = 0; + pMutation->cbAlloc = 0; + + if (pMutationParent) + pMutation->iLvl = pMutationParent->iLvl + 1; + if (ppvMutation) + *ppvMutation = &pMutation->abMutation[0]; + } + + return pMutation; +} + + +/** + * Destroys the given fuzzer context freeing all allocated resources. + * + * @returns nothing. + * @param pThis The fuzzer context instance. + */ +static void rtFuzzCtxDestroy(PRTFUZZCTXINT pThis) +{ + RT_NOREF(pThis); +} + + +/** + * Creates the final input data applying all accumulated mutations. + * + * @returns IPRT status code. + * @param pMutation The mutation to finalize. + */ +static int rtFuzzMutationDataFinalize(PRTFUZZMUTATION pMutation) +{ + if (pMutation->pvInput) + return VINF_SUCCESS; + + /* Traverse the mutations top to bottom and insert into the array. */ + int rc = VINF_SUCCESS; + uint32_t idx = pMutation->iLvl + 1; + PRTFUZZMUTATION *papMutations = (PRTFUZZMUTATION *)RTMemTmpAlloc(idx * sizeof(PCRTFUZZMUTATION)); + if (RT_LIKELY(papMutations)) + { + PRTFUZZMUTATION pMutationCur = pMutation; + size_t cbAlloc = 0; + + /* + * As soon as a mutation with allocated input data is encountered the insertion is + * stopped as it contains all necessary mutated inputs we can start from. + */ + while (idx > 0) + { + rtFuzzMutationRetain(pMutationCur); + papMutations[idx - 1] = pMutationCur; + cbAlloc = RT_MAX(cbAlloc, pMutationCur->cbInput); + if (pMutationCur->pvInput) + { + idx--; + break; + } + pMutationCur = pMutationCur->pMutationParent; + idx--; + } + + pMutation->cbAlloc = cbAlloc; + uint8_t *pbBuf = (uint8_t *)rtFuzzCtxMemoryAlloc(pMutation->pFuzzer, cbAlloc); + if (RT_LIKELY(pbBuf)) + { + pMutation->pvInput = pbBuf; + + /* Copy the initial input data. */ + size_t cbInputNow = papMutations[idx]->cbInput; + memcpy(pbBuf, papMutations[idx]->pvInput, cbInputNow); + rtFuzzMutationRelease(papMutations[idx]); + + for (uint32_t i = idx + 1; i < pMutation->iLvl + 1; i++) + { + PRTFUZZMUTATION pCur = papMutations[i]; + pCur->pMutator->pfnExec(pCur->pFuzzer, pCur, (void *)&pCur->abMutation[0], + pbBuf + pCur->offMutation, + cbInputNow - pCur->offMutation); + + cbInputNow = pCur->cbInput; + rtFuzzMutationRelease(pCur); + } + + Assert(cbInputNow == pMutation->cbInput); + } + else + rc = VERR_NO_MEMORY; + + RTMemTmpFree(papMutations); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +/** + * Default mutator export callback (just writing the raw data). + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorExportDefault(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + PFNRTFUZZCTXEXPORT pfnExport, void *pvUser) +{ + return pfnExport(pThis, pvMutation, pMutation->cbMutation, pvUser); +} + + +/** + * Default mutator import callback (just reading the raw data). + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorImportDefault(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, void *pvMutation, + PFNRTFUZZCTXIMPORT pfnImport, void *pvUser) +{ + return pfnImport(pThis, pvMutation, pMutation->cbMutation, NULL, pvUser); +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorCorpusExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf) +{ + RT_NOREF(pThis, cbBuf, pvMutation); + memcpy(pbBuf, pvMutation, pMutation->cbInput); + return VINF_SUCCESS; +} + + +/** + * Mutator callback - flips a single bit in the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorBitFlipPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation) +{ + int rc = VINF_SUCCESS; + uint8_t *pidxBitFlip = 0; + PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, sizeof(*pidxBitFlip), (void **)&pidxBitFlip); + if (RT_LIKELY(pMutation)) + { + pMutation->cbInput = pMutationParent->cbInput; /* Bit flips don't change the input size. */ + *pidxBitFlip = (uint8_t)RTRandAdvU32Ex(pThis->hRand, 0, sizeof(uint8_t) * 8 - 1); + *ppMutation = pMutation; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorBitFlipExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf) +{ + RT_NOREF(pThis, cbBuf, pMutation); + uint8_t idxBitFlip = *(uint8_t *)pvMutation; + ASMBitToggle(pbBuf, idxBitFlip); + return VINF_SUCCESS; +} + + +/** + * Mutator callback - replaces a single byte in the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorByteReplacePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation) +{ + int rc = VINF_SUCCESS; + uint8_t *pbReplace = 0; + PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, sizeof(*pbReplace), (void **)&pbReplace); + if (RT_LIKELY(pMutation)) + { + pMutation->cbInput = pMutationParent->cbInput; /* Byte replacements don't change the input size. */ + RTRandAdvBytes(pThis->hRand, pbReplace, 1); /** @todo Filter out same values. */ + *ppMutation = pMutation; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorByteReplaceExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf) +{ + RT_NOREF(pThis, cbBuf, pMutation); + uint8_t bReplace = *(uint8_t *)pvMutation; + *pbBuf = bReplace; + return VINF_SUCCESS; +} + + +/** + * Mutator callback - inserts a single byte into the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorByteInsertPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation) +{ + int rc = VINF_SUCCESS; + uint8_t *pbInsert = 0; + if (pMutationParent->cbInput < pThis->cbInputMax) + { + PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, 1 /*cbAdditional*/, (void **)&pbInsert); + if (RT_LIKELY(pMutation)) + { + pMutation->cbInput = pMutationParent->cbInput + 1; + RTRandAdvBytes(pThis->hRand, pbInsert, 1); + *ppMutation = pMutation; + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorByteInsertExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf) +{ + RT_NOREF(pThis, pMutation, pvMutation); + + /* Just move the residual data one byte to the back. */ + memmove(pbBuf + 1, pbBuf, cbBuf); + *pbBuf = *(uint8_t *)pvMutation; + return VINF_SUCCESS; +} + + +/** + * Mutator callback - inserts a byte sequence into the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceInsertAppendPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation) +{ + int rc = VINF_SUCCESS; + if (pMutationParent->cbInput < pThis->cbInputMax) + { + size_t cbInputMutated = (size_t)RTRandAdvU64Ex(pThis->hRand, pMutationParent->cbInput + 1, pThis->cbInputMax); + size_t cbInsert = cbInputMutated - pMutationParent->cbInput; + uint8_t *pbAdd = NULL; + + PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, cbInsert, (void **)&pbAdd); + if (RT_LIKELY(pMutation)) + { + pMutation->cbInput = cbInputMutated; + RTRandAdvBytes(pThis->hRand, pbAdd, cbInsert); + *ppMutation = pMutation; + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceInsertAppendExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf) +{ + RT_NOREF(pThis); + size_t cbInsert = pMutation->cbInput - pMutation->pMutationParent->cbInput; + + /* Move any remaining data to the end. */ + if (cbBuf) + memmove(pbBuf + cbInsert, pbBuf, cbBuf); + + memcpy(pbBuf, pvMutation, cbInsert); + return VINF_SUCCESS; +} + + +/** + * Mutator callback - deletes a single byte in the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorByteDeletePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation) +{ + int rc = VINF_SUCCESS; + if (pMutationParent->cbInput - offStart >= 1) + { + PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, 0 /*cbAdditional*/, NULL); + if (RT_LIKELY(pMutation)) + { + pMutation->cbInput = pMutationParent->cbInput - 1; + *ppMutation = pMutation; + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorByteDeleteExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf) +{ + RT_NOREF(pThis, pMutation, pvMutation); + + /* Just move the residual data to the front. */ + memmove(pbBuf, pbBuf + 1, cbBuf - 1); + return VINF_SUCCESS; +} + + +/** + * Mutator callback - deletes a byte sequence in the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceDeletePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation) +{ + int rc = VINF_SUCCESS; + if ( pMutationParent->cbInput > offStart + && pMutationParent->cbInput > 1) + { + size_t cbInputMutated = (size_t)RTRandAdvU64Ex(pThis->hRand, offStart, pMutationParent->cbInput - 1); + + PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, 0 /*cbAdditional*/, NULL); + if (RT_LIKELY(pMutation)) + { + pMutation->cbInput = cbInputMutated; + *ppMutation = pMutation; + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceDeleteExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf) +{ + RT_NOREF(pThis, pvMutation); + Assert(pMutation->pMutationParent->cbInput > pMutation->cbInput); + size_t cbDel = pMutation->pMutationParent->cbInput - pMutation->cbInput; + + /* Just move the residual data to the front. */ + memmove(pbBuf, pbBuf + cbDel, cbBuf - cbDel); + return VINF_SUCCESS; +} + + +/** + * Mutator callback - replaces a possible integer with something interesting. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorIntegerReplacePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation) +{ + int rc = VINF_SUCCESS; + PRTFUZZMUTATORINTEGER pMutInt = NULL; + PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, sizeof(*pMutInt), (void **)&pMutInt); + if (RT_LIKELY(pMutation)) + { + size_t cbLeft = pMutationParent->cbInput - offStart; + uint32_t uClassMax = 0; + + switch (cbLeft) + { + case 1: + uClassMax = 1; + break; + case 2: + case 3: + uClassMax = 3; + break; + case 4: + case 5: + case 6: + case 7: + uClassMax = 5; + break; + default: + uClassMax = 7; + break; + } + + pMutInt->uIntClass = (uint8_t)RTRandAdvU32Ex(pThis->hRand, 0, uClassMax); + pMutInt->fByteSwap = RT_BOOL(RTRandAdvU32Ex(pThis->hRand, 0, 1)); + + switch (pMutInt->uIntClass) + { + case 0: + pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_ai8Interesting) - 1); + break; + case 1: + pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_au8Interesting) - 1); + break; + case 2: + pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_ai16Interesting) - 1); + break; + case 3: + pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_au16Interesting) - 1); + break; + case 4: + pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_ai32Interesting) - 1); + break; + case 5: + pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_au32Interesting) - 1); + break; + case 6: + pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_ai64Interesting) - 1); + break; + case 7: + pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_au64Interesting) - 1); + break; + default: + AssertReleaseFailed(); + } + + pMutation->cbInput = pMutationParent->cbInput; + *ppMutation = pMutation; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorIntegerReplaceExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf) +{ + RT_NOREF(pThis, pMutation, cbBuf); + union + { + int8_t i8; + uint8_t u8; + int16_t i16; + uint16_t u16; + int32_t i32; + uint32_t u32; + int64_t i64; + uint64_t u64; + } Int; + PCRTFUZZMUTATORINTEGER pMutInt = (PCRTFUZZMUTATORINTEGER)pvMutation; + size_t cb = 0; + + switch (pMutInt->uIntClass) + { + case 0: + Int.i8 = s_ai8Interesting[pMutInt->idxInt]; + cb = 1; + break; + case 1: + Int.u8 = s_au8Interesting[pMutInt->idxInt]; + cb = 1; + break; + case 2: + Int.i16 = s_ai16Interesting[pMutInt->idxInt]; + cb = 2; + if (pMutInt->fByteSwap) + Int.u16 = RT_BSWAP_U16(Int.u16); + break; + case 3: + Int.u16 = s_au16Interesting[pMutInt->idxInt]; + cb = 2; + if (pMutInt->fByteSwap) + Int.u16 = RT_BSWAP_U16(Int.u16); + break; + case 4: + Int.i32 = s_ai32Interesting[pMutInt->idxInt]; + cb = 4; + if (pMutInt->fByteSwap) + Int.u32 = RT_BSWAP_U32(Int.u32); + break; + case 5: + Int.u32 = s_au32Interesting[pMutInt->idxInt]; + cb = 4; + if (pMutInt->fByteSwap) + Int.u32 = RT_BSWAP_U32(Int.u32); + break; + case 6: + Int.i64 = s_ai64Interesting[pMutInt->idxInt]; + cb = 8; + if (pMutInt->fByteSwap) + Int.u64 = RT_BSWAP_U64(Int.u64); + break; + case 7: + Int.u64 = s_au64Interesting[pMutInt->idxInt]; + cb = 8; + if (pMutInt->fByteSwap) + Int.u64 = RT_BSWAP_U64(Int.u64); + break; + default: + AssertReleaseFailed(); + } + + memcpy(pbBuf, &Int, cb); + return VINF_SUCCESS; +} + + +/** + * Mutator callback - crosses over two mutations at the given point. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent, + PPRTFUZZMUTATION ppMutation) +{ + int rc = VINF_SUCCESS; + + if (pThis->cMutations > 1) + { + uint64_t *pidxMutCrossover = NULL; + PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, sizeof(*pidxMutCrossover), (void **)&pidxMutCrossover); + if (RT_LIKELY(pMutation)) + { + uint32_t cTries = 10; + PRTFUZZMUTATION pMutCrossover = NULL; + /* + * Pick a random mutation to crossover with (making sure it is not the current one + * or the crossover point is beyond the end of input). + */ + do + { + if (pMutCrossover) + rtFuzzMutationRelease(pMutCrossover); + pMutCrossover = rtFuzzCtxMutationPickRnd(pThis); + cTries--; + } while ( ( pMutCrossover == pMutationParent + || offStart >= pMutCrossover->cbInput) + && cTries > 0); + + if (cTries) + { + pMutation->cbInput = pMutCrossover->cbInput; + *pidxMutCrossover = pMutCrossover->Core.Key; + *ppMutation = pMutation; + } + else + rtFuzzMutationDestroy(pMutation); + + rtFuzzMutationRelease(pMutCrossover); + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + uint8_t *pbBuf, size_t cbBuf) +{ + RT_NOREF(cbBuf); + uint64_t idxMutCrossover = *(uint64_t *)pvMutation; + + PRTFUZZMUTATION pMutCrossover = rtFuzzCtxMutationLocate(pThis, idxMutCrossover); + int rc = rtFuzzMutationDataFinalize(pMutCrossover); + if (RT_SUCCESS(rc)) + { + memcpy(pbBuf, (uint8_t *)pMutCrossover->pvInput + pMutation->offMutation, + pMutCrossover->cbInput - pMutation->offMutation); + rtFuzzMutationRelease(pMutCrossover); + } + + return rc; +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverExport(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation, + PFNRTFUZZCTXEXPORT pfnExport, void *pvUser) +{ + RT_NOREF(pMutation); + + uint64_t idxMutCrossover = *(uint64_t *)pvMutation; + idxMutCrossover = RT_H2LE_U64(idxMutCrossover); + return pfnExport(pThis, &idxMutCrossover, sizeof(idxMutCrossover), pvUser); +} + + +static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverImport(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, void *pvMutation, + PFNRTFUZZCTXIMPORT pfnImport, void *pvUser) +{ + RT_NOREF(pMutation); + + uint64_t uKey = 0; + int rc = pfnImport(pThis, &uKey, sizeof(uKey), NULL, pvUser); + if (RT_SUCCESS(rc)) + { + uKey = RT_LE2H_U64(uKey); + *(uint64_t *)pvMutation = uKey; + } + + return rc; +} + + +/** + * Creates an empty fuzzing context. + * + * @returns IPRT status code. + * @param ppThis Where to store the pointer to the internal fuzzing context instance on success. + * @param enmType Fuzzing context type. + */ +static int rtFuzzCtxCreateEmpty(PRTFUZZCTXINT *ppThis, RTFUZZCTXTYPE enmType) +{ + int rc; + PRTFUZZCTXINT pThis = (PRTFUZZCTXINT)RTMemAllocZ(sizeof(*pThis)); + if (RT_LIKELY(pThis)) + { + pThis->u32Magic = RTFUZZCTX_MAGIC; + pThis->cRefs = 1; + pThis->enmType = enmType; + pThis->TreeMutations = NULL; + pThis->cbInputMax = UINT32_MAX; + pThis->cMutations = 0; + pThis->fFlagsBehavioral = 0; + pThis->cbMutationsAllocMax = _1G; + pThis->cbMemTotal = 0; + RTListInit(&pThis->LstMutationsAlloc); + + /* Copy the default mutator descriptors over. */ + pThis->paMutators = (PRTFUZZMUTATOR)RTMemAllocZ(RT_ELEMENTS(g_aMutators) * sizeof(RTFUZZMUTATOR)); + if (RT_LIKELY(pThis->paMutators)) + { + pThis->cMutators = RT_ELEMENTS(g_aMutators); + memcpy(&pThis->paMutators[0], &g_aMutators[0], RT_ELEMENTS(g_aMutators) * sizeof(RTFUZZMUTATOR)); + + rc = RTSemRWCreate(&pThis->hSemRwMutations); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pThis->CritSectAlloc); + if (RT_SUCCESS(rc)) + { + rc = RTRandAdvCreateParkMiller(&pThis->hRand); + if (RT_SUCCESS(rc)) + { + RTRandAdvSeed(pThis->hRand, RTTimeSystemNanoTS()); + *ppThis = pThis; + return VINF_SUCCESS; + } + + RTCritSectDelete(&pThis->CritSectAlloc); + } + + RTSemRWDestroy(pThis->hSemRwMutations); + } + } + else + rc = VERR_NO_MEMORY; + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +/** + * Destroys the given fuzzing input. + * + * @returns nothing. + * @param pThis The fuzzing input to destroy. + */ +static void rtFuzzInputDestroy(PRTFUZZINPUTINT pThis) +{ + PRTFUZZCTXINT pFuzzer = pThis->pFuzzer; + + rtFuzzMutationRelease(pThis->pMutationTop); + rtFuzzCtxMemoryFree(pFuzzer, pThis); + RTFuzzCtxRelease(pFuzzer); +} + + +RTDECL(int) RTFuzzCtxCreate(PRTFUZZCTX phFuzzCtx, RTFUZZCTXTYPE enmType) +{ + AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER); + + return rtFuzzCtxCreateEmpty(phFuzzCtx, enmType); +} + + +RTDECL(int) RTFuzzCtxCreateFromState(PRTFUZZCTX phFuzzCtx, PFNRTFUZZCTXIMPORT pfnImport, void *pvUser) +{ + AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pfnImport, VERR_INVALID_POINTER); + +#if 0 + int rc = VINF_SUCCESS; + if (cbState >= sizeof(RTFUZZCTXSTATE)) + { + RTFUZZCTXSTATE StateImport; + + memcpy(&StateImport, pvState, sizeof(RTFUZZCTXSTATE)); + if ( RT_LE2H_U32(StateImport.u32Magic) == RTFUZZCTX_MAGIC + && RT_LE2H_U32(StateImport.cbPrng) <= cbState - sizeof(RTFUZZCTXSTATE)) + { + PRTFUZZCTXINT pThis = rtFuzzCtxCreateEmpty(); + if (RT_LIKELY(pThis)) + { + pThis->cbInputMax = (size_t)RT_LE2H_U64(StateImport.cbInputMax); + pThis->fFlagsBehavioral = RT_LE2H_U32(StateImport.fFlagsBehavioral); + + uint8_t *pbState = (uint8_t *)pvState; + uint32_t cInputs = RT_LE2H_U32(StateImport.cInputs); + rc = RTRandAdvRestoreState(pThis->hRand, (const char *)&pbState[sizeof(RTFUZZCTXSTATE)]); + if (RT_SUCCESS(rc)) + { + /* Go through the inputs and add them. */ + pbState += sizeof(RTFUZZCTXSTATE) + RT_LE2H_U32(StateImport.cbPrng); + cbState -= sizeof(RTFUZZCTXSTATE) + RT_LE2H_U32(StateImport.cbPrng); + + uint32_t idx = 0; + while ( idx < cInputs + && RT_SUCCESS(rc)) + { + size_t cbInput = 0; + if (cbState >= sizeof(uint32_t)) + { + memcpy(&cbInput, pbState, sizeof(uint32_t)); + cbInput = RT_LE2H_U32(cbInput); + pbState += sizeof(uint32_t); + } + + if ( cbInput + && cbInput <= cbState) + { + PRTFUZZINPUTINT pInput = rtFuzzCtxInputCreate(pThis, cbInput); + if (RT_LIKELY(pInput)) + { + memcpy(&pInput->abInput[0], pbState, cbInput); + RTMd5(&pInput->abInput[0], pInput->cbInput, &pInput->abMd5Hash[0]); + rc = rtFuzzCtxInputAdd(pThis, pInput); + if (RT_FAILURE(rc)) + RTMemFree(pInput); + pbState += cbInput; + } + } + else + rc = VERR_INVALID_STATE; + + idx++; + } + + if (RT_SUCCESS(rc)) + { + *phFuzzCtx = pThis; + return VINF_SUCCESS; + } + } + + rtFuzzCtxDestroy(pThis); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_MAGIC; + } + else + rc = VERR_INVALID_MAGIC; + + return rc; +#else + RT_NOREF(pvUser); + return VERR_NOT_IMPLEMENTED; +#endif +} + + +RTDECL(int) RTFuzzCtxCreateFromStateMem(PRTFUZZCTX phFuzzCtx, const void *pvState, size_t cbState) +{ + AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvState, VERR_INVALID_POINTER); + AssertPtrReturn(cbState, VERR_INVALID_POINTER); + + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(int) RTFuzzCtxCreateFromStateFile(PRTFUZZCTX phFuzzCtx, const char *pszFilename) +{ + AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + + void *pv = NULL; + size_t cb = 0; + int rc = RTFileReadAll(pszFilename, &pv, &cb); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzCtxCreateFromStateMem(phFuzzCtx, pv, cb); + RTFileReadAllFree(pv, cb); + } + + return rc; +} + + +RTDECL(uint32_t) RTFuzzCtxRetain(RTFUZZCTX hFuzzCtx) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + + +RTDECL(uint32_t) RTFuzzCtxRelease(RTFUZZCTX hFuzzCtx) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + if (pThis == NIL_RTFUZZCTX) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + rtFuzzCtxDestroy(pThis); + return cRefs; +} + + +RTDECL(int) RTFuzzCtxQueryStats(RTFUZZCTX hFuzzCtx, PRTFUZZCTXSTATS pStats) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pStats, VERR_INVALID_POINTER); + + pStats->cbMemory = ASMAtomicReadZ(&pThis->cbMemTotal); + pStats->cMutations = ASMAtomicReadU64(&pThis->cMutations); + return VINF_SUCCESS; +} + + +/** + * Fuzzing context export callback for a single mutation. + */ +static DECLCALLBACK(int) rtFuzzCtxStateExportMutations(PAVLU64NODECORE pCore, void *pvParam) +{ + PRTFUZZMUTATION pMutation = (PRTFUZZMUTATION)pCore; + PCRTFUZZMUTATOR pMutator = pMutation->pMutator; + PCRTFUZZEXPORTARGS pArgs = (PCRTFUZZEXPORTARGS)pvParam; + RTFUZZMUTATIONSTATE MutationState; + + MutationState.u64Id = RT_H2LE_U64(pMutation->Core.Key); + if (pMutation->pMutationParent) + MutationState.u64IdParent = RT_H2LE_U64(pMutation->pMutationParent->Core.Key); + else + MutationState.u64IdParent = RT_H2LE_U64(0); + MutationState.u64OffMutation = RT_H2LE_U64(pMutation->offMutation); + MutationState.cbInput = RT_H2LE_U64((uint64_t)pMutation->cbInput); + MutationState.cbMutation = RT_H2LE_U64((uint64_t)pMutation->cbMutation); + MutationState.u32IdMutator = RT_H2LE_U32(pMutator->uMutator); + MutationState.iLvl = RT_H2LE_U32(pMutation->iLvl); + MutationState.u32Magic = RT_H2LE_U32(pMutation->u32Magic); + + int rc = pArgs->pfnExport(pMutation->pFuzzer, &MutationState, sizeof(MutationState), pArgs->pvUser); + if ( RT_SUCCESS(rc) + && pMutator->pfnExport) + rc = pMutator->pfnExport(pMutation->pFuzzer, pMutation, &pMutation->abMutation[0], pArgs->pfnExport, pArgs->pvUser); + return rc; +} + + +RTDECL(int) RTFuzzCtxStateExport(RTFUZZCTX hFuzzCtx, PFNRTFUZZCTXEXPORT pfnExport, void *pvUser) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pfnExport, VERR_INVALID_POINTER); + + char aszPrngExport[_4K]; /* Should be plenty of room here. */ + size_t cbPrng = sizeof(aszPrngExport); + int rc = RTRandAdvSaveState(pThis->hRand, &aszPrngExport[0], &cbPrng); + if (RT_SUCCESS(rc)) + { + RTFUZZCTXSTATE StateExport; + + StateExport.u32Magic = RT_H2LE_U32(RTFUZZCTX_MAGIC); + switch (pThis->enmType) + { + case RTFUZZCTXTYPE_BLOB: + StateExport.uCtxType = RT_H2LE_U32(RTFUZZCTX_STATE_TYPE_BLOB); + break; + case RTFUZZCTXTYPE_STREAM: + StateExport.uCtxType = RT_H2LE_U32(RTFUZZCTX_STATE_TYPE_STREAM); + break; + default: + AssertFailed(); + break; + } + StateExport.cbPrng = RT_H2LE_U32((uint32_t)cbPrng); + StateExport.cMutations = RT_H2LE_U32(pThis->cMutations); + StateExport.cMutators = RT_H2LE_U32(pThis->cMutators); + StateExport.fFlagsBehavioral = RT_H2LE_U32(pThis->fFlagsBehavioral); + StateExport.cbInputMax = RT_H2LE_U64(pThis->cbInputMax); + + /* Write the context state and PRNG state first. */ + rc = pfnExport(pThis, &StateExport, sizeof(StateExport), pvUser); + if (RT_SUCCESS(rc)) + rc = pfnExport(pThis, &aszPrngExport[0], cbPrng, pvUser); + if (RT_SUCCESS(rc)) + { + /* Write the mutator descriptors next. */ + for (uint32_t i = 0; i < pThis->cMutators && RT_SUCCESS(rc); i++) + { + PRTFUZZMUTATOR pMutator = &pThis->paMutators[i]; + uint32_t cchId = (uint32_t)strlen(pMutator->pszId) + 1; + uint32_t cchIdW = RT_H2LE_U32(cchId); + + rc = pfnExport(pThis, &cchIdW, sizeof(cchIdW), pvUser); + if (RT_SUCCESS(rc)) + rc = pfnExport(pThis, &pMutator->pszId[0], cchId, pvUser); + } + } + + /* Write the mutations last. */ + if (RT_SUCCESS(rc)) + { + RTFUZZEXPORTARGS Args; + + Args.pfnExport = pfnExport; + Args.pvUser = pvUser; + rc = RTAvlU64DoWithAll(&pThis->TreeMutations, true /*fFromLeft*/, rtFuzzCtxStateExportMutations, &Args); + } + } + + return rc; +} + + +RTDECL(int) RTFuzzCtxStateExportToMem(RTFUZZCTX hFuzzCtx, void **ppvState, size_t *pcbState) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(ppvState, VERR_INVALID_POINTER); + AssertPtrReturn(pcbState, VERR_INVALID_POINTER); + + return VERR_NOT_IMPLEMENTED; +} + + +/** + * Export to file callback. + */ +static DECLCALLBACK(int) rtFuzzCtxStateExportFile(RTFUZZCTX hFuzzCtx, const void *pvBuf, size_t cbWrite, void *pvUser) +{ + RT_NOREF(hFuzzCtx); + + RTFILE hFile = (RTFILE)pvUser; + return RTFileWrite(hFile, pvBuf, cbWrite, NULL); +} + + +RTDECL(int) RTFuzzCtxStateExportToFile(RTFUZZCTX hFuzzCtx, const char *pszFilename) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzCtxStateExport(hFuzzCtx, rtFuzzCtxStateExportFile, hFile); + RTFileClose(hFile); + if (RT_FAILURE(rc)) + RTFileDelete(pszFilename); + } + + return rc; +} + + +RTDECL(int) RTFuzzCtxCorpusInputAdd(RTFUZZCTX hFuzzCtx, const void *pvInput, size_t cbInput) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pvInput, VERR_INVALID_POINTER); + AssertReturn(cbInput, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + void *pvCorpus = NULL; + PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, 0, NULL, cbInput, &pvCorpus); + if (RT_LIKELY(pMutation)) + { + pMutation->pMutator = &g_MutatorCorpus; + pMutation->cbInput = cbInput; + pMutation->pvInput = pvCorpus; + memcpy(pvCorpus, pvInput, cbInput); + rc = rtFuzzCtxMutationAdd(pThis, pMutation); + if (RT_FAILURE(rc)) + rtFuzzMutationDestroy(pMutation); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTFuzzCtxCorpusInputAddFromFile(RTFUZZCTX hFuzzCtx, const char *pszFilename) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + + void *pv = NULL; + size_t cb = 0; + int rc = RTFileReadAll(pszFilename, &pv, &cb); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzCtxCorpusInputAdd(hFuzzCtx, pv, cb); + RTFileReadAllFree(pv, cb); + } + + return rc; +} + + +RTDECL(int) RTFuzzCtxCorpusInputAddFromVfsFile(RTFUZZCTX hFuzzCtx, RTVFSFILE hVfsFile) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(hVfsFile != NIL_RTVFSFILE, VERR_INVALID_HANDLE); + + uint64_t cbFile = 0; + void *pvCorpus = NULL; + int rc = RTVfsFileQuerySize(hVfsFile, &cbFile); + if (RT_SUCCESS(rc)) + { + PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, 0, NULL, cbFile, &pvCorpus); + if (RT_LIKELY(pMutation)) + { + pMutation->pMutator = &g_MutatorCorpus; + pMutation->cbInput = cbFile; + pMutation->pvInput = pvCorpus; + rc = RTVfsFileRead(hVfsFile, pvCorpus, cbFile, NULL); + if (RT_SUCCESS(rc)) + rc = rtFuzzCtxMutationAdd(pThis, pMutation); + + if (RT_FAILURE(rc)) + rtFuzzMutationDestroy(pMutation); + } + } + + return rc; +} + + +RTDECL(int) RTFuzzCtxCorpusInputAddFromDirPath(RTFUZZCTX hFuzzCtx, const char *pszDirPath) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pszDirPath, VERR_INVALID_POINTER); + + RTDIR hDir; + int rc = RTDirOpen(&hDir, pszDirPath); + if (RT_SUCCESS(rc)) + { + for (;;) + { + RTDIRENTRY DirEntry; + rc = RTDirRead(hDir, &DirEntry, NULL); + if (RT_FAILURE(rc)) + break; + + /* Skip '.', '..' and other non-files. */ + if ( DirEntry.enmType != RTDIRENTRYTYPE_UNKNOWN + && DirEntry.enmType != RTDIRENTRYTYPE_FILE) + continue; + if (RTDirEntryIsStdDotLink(&DirEntry)) + continue; + + /* Compose the full path, result 'unknown' entries and skip non-files. */ + char szFile[RTPATH_MAX]; + RT_ZERO(szFile); + rc = RTPathJoin(szFile, sizeof(szFile), pszDirPath, DirEntry.szName); + if (RT_FAILURE(rc)) + break; + + if (DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN) + { + RTDirQueryUnknownType(szFile, false, &DirEntry.enmType); + if (DirEntry.enmType != RTDIRENTRYTYPE_FILE) + continue; + } + + /* Okay, it's a file we can add. */ + rc = RTFuzzCtxCorpusInputAddFromFile(hFuzzCtx, szFile); + if (RT_FAILURE(rc)) + break; + } + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + RTDirClose(hDir); + } + + return rc; +} + + +RTDECL(int) RTFuzzCtxCfgSetInputSeedMaximum(RTFUZZCTX hFuzzCtx, size_t cbMax) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + pThis->cbInputMax = cbMax; + return VINF_SUCCESS; +} + + +RTDECL(size_t) RTFuzzCtxCfgGetInputSeedMaximum(RTFUZZCTX hFuzzCtx) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, 0); + + return pThis->cbInputMax; +} + + +RTDECL(int) RTFuzzCtxCfgSetBehavioralFlags(RTFUZZCTX hFuzzCtx, uint32_t fFlags) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(!(fFlags & ~RTFUZZCTX_F_BEHAVIORAL_VALID), VERR_INVALID_PARAMETER); + + pThis->fFlagsBehavioral = fFlags; + return VINF_SUCCESS; +} + + +RTDECL(uint32_t) RTFuzzCfgGetBehavioralFlags(RTFUZZCTX hFuzzCtx) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, 0); + + return pThis->fFlagsBehavioral; +} + + +RTDECL(int) RTFuzzCtxCfgSetTmpDirectory(RTFUZZCTX hFuzzCtx, const char *pszPathTmp) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pszPathTmp, VERR_INVALID_POINTER); + + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(const char *) RTFuzzCtxCfgGetTmpDirectory(RTFUZZCTX hFuzzCtx) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, NULL); + + return NULL; +} + + +RTDECL(int) RTFuzzCtxReseed(RTFUZZCTX hFuzzCtx, uint64_t uSeed) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + RTRandAdvSeed(pThis->hRand, uSeed); + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzCtxInputGenerate(RTFUZZCTX hFuzzCtx, PRTFUZZINPUT phFuzzInput) +{ + int rc = VINF_SUCCESS; + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(phFuzzInput, VERR_INVALID_POINTER); + + uint32_t cTries = 0; + PRTFUZZMUTATION pMutationParent = rtFuzzCtxMutationPickRnd(pThis); + do + { + uint32_t idxMutator = RTRandAdvU32Ex(pThis->hRand, 0, pThis->cMutators - 1); + PCRTFUZZMUTATOR pMutator = &pThis->paMutators[idxMutator]; + PRTFUZZMUTATION pMutation = NULL; + + uint64_t offStart = 0; + if (!(pMutator->fFlags & RTFUZZMUTATOR_F_END_OF_BUF)) + offStart = RTRandAdvU64Ex(pThis->hRand, 0, pMutationParent->cbInput - 1); + else + offStart = pMutationParent->cbInput; + + rc = pMutator->pfnPrep(pThis, offStart, pMutationParent, &pMutation); + if ( RT_SUCCESS(rc) + && VALID_PTR(pMutation)) + { + pMutation->pMutator = pMutator; + + if (pThis->fFlagsBehavioral & RTFUZZCTX_F_BEHAVIORAL_ADD_INPUT_AUTOMATICALLY_TO_CORPUS) + rtFuzzCtxMutationAdd(pThis, pMutation); + + /* Create a new input. */ + PRTFUZZINPUTINT pInput = (PRTFUZZINPUTINT)rtFuzzCtxMemoryAlloc(pThis, sizeof(RTFUZZINPUTINT)); + if (RT_LIKELY(pInput)) + { + pInput->u32Magic = 0; /** @todo */ + pInput->cRefs = 1; + pInput->pFuzzer = pThis; + pInput->pMutationTop = pMutation; + RTFuzzCtxRetain(pThis); + + rtFuzzMutationRelease(pMutationParent); + *phFuzzInput = pInput; + return rc; + } + else + rc = VERR_NO_MEMORY; + } + } while (++cTries <= 50); + + rtFuzzMutationRelease(pMutationParent); + if (RT_SUCCESS(rc)) + rc = VERR_INVALID_STATE; + + return rc; +} + + +RTDECL(int) RTFuzzInputQueryBlobData(RTFUZZINPUT hFuzzInput, void **ppv, size_t *pcb) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->pFuzzer->enmType == RTFUZZCTXTYPE_BLOB, VERR_INVALID_STATE); + + int rc = VINF_SUCCESS; + if (!pThis->pMutationTop->pvInput) + rc = rtFuzzMutationDataFinalize(pThis->pMutationTop); + + if (RT_SUCCESS(rc)) + { + *ppv = pThis->pMutationTop->pvInput; + *pcb = pThis->pMutationTop->cbInput; + } + + return rc; +} + + +RTDECL(int) RTFuzzInputMutateStreamData(RTFUZZINPUT hFuzzInput, void *pvBuf, size_t cbBuf) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->pFuzzer->enmType == RTFUZZCTXTYPE_STREAM, VERR_INVALID_STATE); + + RT_NOREF(pvBuf, cbBuf); + return VERR_NOT_IMPLEMENTED; +} + + +RTDECL(uint32_t) RTFuzzInputRetain(RTFUZZINPUT hFuzzInput) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + + +RTDECL(uint32_t) RTFuzzInputRelease(RTFUZZINPUT hFuzzInput) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + if (pThis == NIL_RTFUZZINPUT) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + rtFuzzInputDestroy(pThis); + return cRefs; +} + + +RTDECL(int) RTFuzzInputQueryDigestString(RTFUZZINPUT hFuzzInput, char *pszDigest, size_t cchDigest) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->pFuzzer->enmType == RTFUZZCTXTYPE_BLOB, VERR_INVALID_STATE); + AssertPtrReturn(pszDigest, VERR_INVALID_POINTER); + AssertReturn(cchDigest >= RTMD5_STRING_LEN + 1, VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + if (!pThis->pMutationTop->pvInput) + rc = rtFuzzMutationDataFinalize(pThis->pMutationTop); + + if (RT_SUCCESS(rc)) + { + uint8_t abHash[RTMD5_HASH_SIZE]; + RTMd5(pThis->pMutationTop->pvInput, pThis->pMutationTop->cbInput, &abHash[0]); + rc = RTMd5ToString(&abHash[0], pszDigest, cchDigest); + } + + return rc; +} + + +RTDECL(int) RTFuzzInputWriteToFile(RTFUZZINPUT hFuzzInput, const char *pszFilename) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->pFuzzer->enmType == RTFUZZCTXTYPE_BLOB, VERR_INVALID_STATE); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + if (!pThis->pMutationTop->pvInput) + rc = rtFuzzMutationDataFinalize(pThis->pMutationTop); + + if (RT_SUCCESS(rc)) + { + RTFILE hFile; + rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, pThis->pMutationTop->pvInput, pThis->pMutationTop->cbInput, NULL); + AssertRC(rc); + RTFileClose(hFile); + + if (RT_FAILURE(rc)) + RTFileDelete(pszFilename); + } + } + + return rc; +} + + +RTDECL(int) RTFuzzInputAddToCtxCorpus(RTFUZZINPUT hFuzzInput) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + return rtFuzzCtxMutationAdd(pThis->pFuzzer, pThis->pMutationTop); +} + + +RTDECL(int) RTFuzzInputRemoveFromCtxCorpus(RTFUZZINPUT hFuzzInput) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + +#if 0 + int rc = VINF_SUCCESS; + PRTFUZZINTERMEDIATE pIntermediate = NULL; + PRTFUZZINPUTINT pInputLoc = rtFuzzCtxInputLocate(pThis->pFuzzer, &pThis->abMd5Hash[0], true /*fExact*/, + &pIntermediate); + if (pInputLoc) + { + AssertPtr(pIntermediate); + Assert(pInputLoc == pThis); + + uint64_t u64Md5Low = *(uint64_t *)&pThis->abMd5Hash[0]; + RTAvlU64Remove(&pIntermediate->TreeSeedsLow, u64Md5Low); + RTFuzzInputRelease(hFuzzInput); + } + else + rc = VERR_NOT_FOUND; +#endif + + return VERR_NOT_IMPLEMENTED; +} + diff --git a/src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp b/src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp new file mode 100644 index 00000000..c4973d9a --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp @@ -0,0 +1,317 @@ +/* $Id: fuzzclientcmd.cpp $ */ +/** @file + * IPRT - Fuzzing framework API, fuzzed client command. + */ + +/* + * Copyright (C) 2018-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/fuzz.h> +#include "internal/iprt.h" + +#include <iprt/assert.h> +#include <iprt/buildconfig.h> +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/ldr.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/types.h> +#include <iprt/vfs.h> + + + +typedef DECLCALLBACK(int) FNLLVMFUZZERTESTONEINPUT(const uint8_t *pbData, size_t cbData); +typedef FNLLVMFUZZERTESTONEINPUT *PFNLLVMFUZZERTESTONEINPUT; + + +/** + * Fuzzing client command state. + */ +typedef struct RTFUZZCMDCLIENT +{ + /** Our own fuzzing context containing all the data. */ + RTFUZZCTX hFuzzCtx; + /** Consumption callback. */ + PFNFUZZCLIENTCONSUME pfnConsume; + /** Opaque user data to pass to the consumption callback. */ + void *pvUser; + /** The LLVM libFuzzer compatible entry point if configured */ + PFNLLVMFUZZERTESTONEINPUT pfnLlvmFuzzerTestOneInput; + /** The selected input channel. */ + RTFUZZOBSINPUTCHAN enmInputChan; + /** Standard input VFS handle. */ + RTVFSIOSTREAM hVfsStdIn; + /** Standard output VFS handle. */ + RTVFSIOSTREAM hVfsStdOut; +} RTFUZZCMDCLIENT; +/** Pointer to a fuzzing client command state. */ +typedef RTFUZZCMDCLIENT *PRTFUZZCMDCLIENT; + + + +/** + * Runs the appropriate consumption callback with the provided data. + * + * @returns Status code, 0 for success. + * @param pThis The fuzzing client command state. + * @param pvData The data to consume. + * @param cbData Size of the data in bytes. + */ +static int rtFuzzCmdClientConsume(PRTFUZZCMDCLIENT pThis, const void *pvData, size_t cbData) +{ + if (pThis->pfnLlvmFuzzerTestOneInput) + return pThis->pfnLlvmFuzzerTestOneInput((const uint8_t *)pvData, cbData); + else + return pThis->pfnConsume(pvData, cbData, pThis->pvUser); +} + + +/** + * The fuzzing client mainloop. + * + * @returns IPRT status code. + * @param pThis The fuzzing client command state. + */ +static int rtFuzzCmdClientMainloop(PRTFUZZCMDCLIENT pThis) +{ + int rc = VINF_SUCCESS; + bool fShutdown = false; + + while ( !fShutdown + && RT_SUCCESS(rc)) + { + RTFUZZINPUT hFuzzInput; + + rc = RTFuzzCtxInputGenerate(pThis->hFuzzCtx, &hFuzzInput); + if (RT_SUCCESS(rc)) + { + void *pv = NULL; + size_t cb = 0; + rc = RTFuzzInputQueryBlobData(hFuzzInput, &pv, &cb); + if (RT_SUCCESS(rc)) + { + char bResp = '.'; + int rc2 = rtFuzzCmdClientConsume(pThis, pv, cb); + if (RT_SUCCESS(rc2)) + { + rc = RTFuzzInputAddToCtxCorpus(hFuzzInput); + bResp = 'A'; + } + + if (RT_SUCCESS(rc)) + rc = RTVfsIoStrmWrite(pThis->hVfsStdOut, &bResp, 1, true /*fBlocking*/, NULL); + } + + RTFuzzInputRelease(hFuzzInput); + } + } + + return rc; +} + + +/** + * Run the fuzzing client. + * + * @returns Process exit status. + * @param pThis The fuzzing client command state. + */ +static RTEXITCODE rtFuzzCmdClientRun(PRTFUZZCMDCLIENT pThis) +{ + int rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT, 0, true /*fLeaveOpen*/, &pThis->hVfsStdIn); + if (RT_SUCCESS(rc)) + { + rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, 0, true /*fLeaveOpen*/, &pThis->hVfsStdOut); + if (RT_SUCCESS(rc)) + { + /* Read the initial input fuzzer state from the standard input. */ + uint32_t cbFuzzCtxState; + rc = RTVfsIoStrmRead(pThis->hVfsStdIn, &cbFuzzCtxState, sizeof(cbFuzzCtxState), true /*fBlocking*/, NULL); + if (RT_SUCCESS(rc)) + { + void *pvFuzzCtxState = RTMemAllocZ(cbFuzzCtxState); + if (RT_LIKELY(pvFuzzCtxState)) + { + rc = RTVfsIoStrmRead(pThis->hVfsStdIn, pvFuzzCtxState, cbFuzzCtxState, true /*fBlocking*/, NULL); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzCtxCreateFromStateMem(&pThis->hFuzzCtx, pvFuzzCtxState, cbFuzzCtxState); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdClientMainloop(pThis); + } + + RTMemFree(pvFuzzCtxState); + } + else + rc = VERR_NO_MEMORY; + } + } + } + + if (RT_SUCCESS(rc)) + return RTEXITCODE_SUCCESS; + + return RTEXITCODE_FAILURE; +} + + +/** + * Run a single iteration of the fuzzing client and return. + * + * @returns Process exit status. + * @param pThis The fuzzing client command state. + */ +static RTEXITCODE rtFuzzCmdClientRunFile(PRTFUZZCMDCLIENT pThis, const char *pszFilename) +{ + void *pv = NULL; + size_t cbFile = 0; + int rc = RTFileReadAll(pszFilename, &pv, &cbFile); + if (RT_SUCCESS(rc)) + { + rtFuzzCmdClientConsume(pThis, pv, cbFile); + RTFileReadAllFree(pv, cbFile); + return RTEXITCODE_SUCCESS; + } + + return RTEXITCODE_FAILURE; +} + + +RTR3DECL(RTEXITCODE) RTFuzzCmdFuzzingClient(unsigned cArgs, char **papszArgs, PFNFUZZCLIENTCONSUME pfnConsume, void *pvUser) +{ + /* + * Parse the command line. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--help", 'h', RTGETOPT_REQ_NOTHING }, + { "--version", 'V', RTGETOPT_REQ_NOTHING }, + { "--llvm-input", 'l', RTGETOPT_REQ_STRING }, + { "--file", 'f', RTGETOPT_REQ_STRING }, + }; + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, + RTGETOPTINIT_FLAGS_OPTS_FIRST); + if (RT_SUCCESS(rc)) + { + /* Option variables: */ + RTFUZZCMDCLIENT This; + RTLDRMOD hLlvmMod = NIL_RTLDRMOD; + const char *pszFilename = NULL; + + This.pfnConsume = pfnConsume; + This.pvUser = pvUser; + This.enmInputChan = RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT; + + /* Argument parsing loop. */ + bool fContinue = true; + bool fExit = false; + do + { + RTGETOPTUNION ValueUnion; + int chOpt = RTGetOpt(&GetState, &ValueUnion); + switch (chOpt) + { + case 0: + fContinue = false; + break; + + case 'f': + { + pszFilename = ValueUnion.psz; + This.enmInputChan = RTFUZZOBSINPUTCHAN_FILE; + break; + } + + case 'l': + { + /* + * Load the indicated library and try to resolve LLVMFuzzerTestOneInput, + * which will act as the input callback. + */ + rc = RTLdrLoad(ValueUnion.psz, &hLlvmMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hLlvmMod, "LLVMFuzzerTestOneInput", (void **)&This.pfnLlvmFuzzerTestOneInput); + if (RT_FAILURE(rc)) + rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to query '%s' from '%s': %Rrc", + "LLVMFuzzerTestOneInput", + ValueUnion.psz, + rc); + } + break; + } + + case 'h': + RTPrintf("Usage: to be written\nOption dump:\n"); + for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++) + RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong); + fContinue = false; + fExit = true; + break; + + case 'V': + RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision()); + fContinue = false; + fExit = true; + break; + + default: + rcExit = RTGetOptPrintError(chOpt, &ValueUnion); + fContinue = false; + break; + } + } while (fContinue); + + if ( rcExit == RTEXITCODE_SUCCESS + && !fExit) + { + switch (This.enmInputChan) + { + case RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT: + rcExit = rtFuzzCmdClientRun(&This); + break; + case RTFUZZOBSINPUTCHAN_FILE: + rcExit = rtFuzzCmdClientRunFile(&This, pszFilename); + break; + default: + rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Input channel unknown/not implemented yet"); + } + } + + if (hLlvmMod != NIL_RTLDRMOD) + RTLdrClose(hLlvmMod); + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc); + return rcExit; +} + diff --git a/src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp b/src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp new file mode 100644 index 00000000..44ee27b7 --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp @@ -0,0 +1,1861 @@ +/* $Id: fuzzmastercmd.cpp $ */ +/** @file + * IPRT - Fuzzing framework API, master command. + */ + +/* + * Copyright (C) 2018-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/fuzz.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/base64.h> +#include <iprt/buildconfig.h> +#include <iprt/ctype.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/json.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/tcp.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/vfs.h> +#include <iprt/zip.h> + + +/** + * A running fuzzer state. + */ +typedef struct RTFUZZRUN +{ + /** List node. */ + RTLISTNODE NdFuzzed; + /** Identifier. */ + char *pszId; + /** Number of processes. */ + uint32_t cProcs; + /** Target recorder flags. */ + uint32_t fTgtRecFlags; + /** The fuzzing observer state handle. */ + RTFUZZOBS hFuzzObs; + /** Flag whether fuzzing was started. */ + bool fStarted; + /** Time when this run was created. */ + RTTIME TimeCreated; + /** Millisecond timestamp when the run was created. */ + uint64_t tsCreatedMs; +} RTFUZZRUN; +/** Pointer to a running fuzzer state. */ +typedef RTFUZZRUN *PRTFUZZRUN; + + +/** + * Fuzzing master command state. + */ +typedef struct RTFUZZCMDMASTER +{ + /** List of running fuzzers. */ + RTLISTANCHOR LstFuzzed; + /** The port to listen on. */ + uint16_t uPort; + /** The TCP server for requests. */ + PRTTCPSERVER hTcpSrv; + /** The root temp directory. */ + const char *pszTmpDir; + /** The root results directory. */ + const char *pszResultsDir; + /** Flag whether to shutdown. */ + bool fShutdown; + /** The response message. */ + char *pszResponse; +} RTFUZZCMDMASTER; +/** Pointer to a fuzzing master command state. */ +typedef RTFUZZCMDMASTER *PRTFUZZCMDMASTER; + + +/** + * Wrapper around RTErrInfoSetV / RTMsgErrorV. + * + * @returns @a rc + * @param pErrInfo Extended error info. + * @param rc The return code. + * @param pszFormat The message format. + * @param ... The message format arguments. + */ +static int rtFuzzCmdMasterErrorRc(PRTERRINFO pErrInfo, int rc, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + if (pErrInfo) + RTErrInfoSetV(pErrInfo, rc, pszFormat, va); + else + RTMsgErrorV(pszFormat, va); + va_end(va); + return rc; +} + + +/** + * Returns a running fuzzer state by the given ID. + * + * @returns Pointer to the running fuzzer state or NULL if not found. + * @param pThis The fuzzing master command state. + * @param pszId The ID to look for. + */ +static PRTFUZZRUN rtFuzzCmdMasterGetFuzzerById(PRTFUZZCMDMASTER pThis, const char *pszId) +{ + PRTFUZZRUN pIt = NULL; + RTListForEach(&pThis->LstFuzzed, pIt, RTFUZZRUN, NdFuzzed) + { + if (!RTStrCmp(pIt->pszId, pszId)) + return pIt; + } + + return NULL; +} + + +#if 0 /* unused */ +/** + * Processes and returns the value of the given config item in the JSON request. + * + * @returns IPRT status code. + * @param ppszStr Where to store the pointer to the string on success. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessCfgString(char **ppszStr, const char *pszCfgItem, RTJSONVAL hJsonCfg, PRTERRINFO pErrInfo) +{ + int rc = RTJsonValueQueryStringByName(hJsonCfg, pszCfgItem, ppszStr); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query string value of \"%s\"", pszCfgItem); + + return rc; +} + + +/** + * Processes and returns the value of the given config item in the JSON request. + * + * @returns IPRT status code. + * @param pfVal Where to store the config value on success. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessCfgBool(bool *pfVal, const char *pszCfgItem, RTJSONVAL hJsonCfg, PRTERRINFO pErrInfo) +{ + int rc = RTJsonValueQueryBooleanByName(hJsonCfg, pszCfgItem, pfVal); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query boolean value of \"%s\"", pszCfgItem); + + return rc; +} + + +/** + * Processes and returns the value of the given config item in the JSON request. + * + * @returns IPRT status code. + * @param pfVal Where to store the config value on success. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param fDef Default value if the item wasn't found. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessCfgBoolDef(bool *pfVal, const char *pszCfgItem, RTJSONVAL hJsonCfg, bool fDef, PRTERRINFO pErrInfo) +{ + int rc = RTJsonValueQueryBooleanByName(hJsonCfg, pszCfgItem, pfVal); + if (rc == VERR_NOT_FOUND) + { + *pfVal = fDef; + rc = VINF_SUCCESS; + } + else if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query boolean value of \"%s\"", pszCfgItem); + + return rc; +} +#endif + + +/** + * Processes and returns the value of the given config item in the JSON request. + * + * @returns IPRT status code. + * @param pcbVal Where to store the config value on success. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param cbDef Default value if the item wasn't found. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessCfgSizeDef(size_t *pcbVal, const char *pszCfgItem, RTJSONVAL hJsonCfg, size_t cbDef, PRTERRINFO pErrInfo) +{ + *pcbVal = cbDef; /* Make GCC 6.3.0 happy. */ + + int64_t i64Val = 0; + int rc = RTJsonValueQueryIntegerByName(hJsonCfg, pszCfgItem, &i64Val); + if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; + else if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query size_t value of \"%s\"", pszCfgItem); + else if (i64Val < 0 || (size_t)i64Val != (uint64_t)i64Val) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_OUT_OF_RANGE, "JSON request malformed: Integer \"%s\" is out of range", pszCfgItem); + else + *pcbVal = (size_t)i64Val; + + return rc; +} + + +/** + * Processes and returns the value of the given config item in the JSON request. + * + * @returns IPRT status code. + * @param pcbVal Where to store the config value on success. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param cbDef Default value if the item wasn't found. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessCfgU32Def(uint32_t *pu32Val, const char *pszCfgItem, RTJSONVAL hJsonCfg, uint32_t u32Def, PRTERRINFO pErrInfo) +{ + int64_t i64Val = 0; + int rc = RTJsonValueQueryIntegerByName(hJsonCfg, pszCfgItem, &i64Val); + if (rc == VERR_NOT_FOUND) + { + *pu32Val = u32Def; + rc = VINF_SUCCESS; + } + else if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query uint32_t value of \"%s\"", pszCfgItem); + else if (i64Val < 0 || (uint32_t)i64Val != (uint64_t)i64Val) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_OUT_OF_RANGE, "JSON request malformed: Integer \"%s\" is out of range", pszCfgItem); + else + *pu32Val = (uint32_t)i64Val; + + return rc; +} + + +/** + * Returns the configured input channel for the binary under test. + * + * @returns Selected input channel or RTFUZZOBSINPUTCHAN_INVALID if an error occurred. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param enmChanDef Default value if the item wasn't found. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static RTFUZZOBSINPUTCHAN rtFuzzCmdMasterFuzzRunProcessCfgGetInputChan(const char *pszCfgItem, RTJSONVAL hJsonCfg, RTFUZZOBSINPUTCHAN enmChanDef, PRTERRINFO pErrInfo) +{ + RTFUZZOBSINPUTCHAN enmInputChan = RTFUZZOBSINPUTCHAN_INVALID; + + RTJSONVAL hJsonVal; + int rc = RTJsonValueQueryByName(hJsonCfg, pszCfgItem, &hJsonVal); + if (rc == VERR_NOT_FOUND) + enmInputChan = enmChanDef; + else if (RT_SUCCESS(rc)) + { + const char *pszBinary = RTJsonValueGetString(hJsonVal); + if (pszBinary) + { + if (!RTStrCmp(pszBinary, "File")) + enmInputChan = RTFUZZOBSINPUTCHAN_FILE; + else if (!RTStrCmp(pszBinary, "Stdin")) + enmInputChan = RTFUZZOBSINPUTCHAN_STDIN; + else if (!RTStrCmp(pszBinary, "FuzzingAware")) + enmInputChan = RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT; + else + rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_PARAMETER, "JSON request malformed: \"%s\" for \"%s\" is not known", pszCfgItem, pszBinary); + } + else + rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"%s\" is not a string", pszCfgItem); + + RTJsonValueRelease(hJsonVal); + } + else + rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query \"%s\"", pszCfgItem); + + return enmInputChan; +} + + +/** + * Processes binary related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessBinaryCfg(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonVal; + int rc = RTJsonValueQueryByName(hJsonRoot, "BinaryPath", &hJsonVal); + if (RT_SUCCESS(rc)) + { + const char *pszBinary = RTJsonValueGetString(hJsonVal); + if (RT_LIKELY(pszBinary)) + { + RTFUZZOBSINPUTCHAN enmInputChan = rtFuzzCmdMasterFuzzRunProcessCfgGetInputChan("InputChannel", hJsonRoot, RTFUZZOBSINPUTCHAN_STDIN, pErrInfo); + if (enmInputChan != RTFUZZOBSINPUTCHAN_INVALID) + { + rc = RTFuzzObsSetTestBinary(pFuzzRun->hFuzzObs, pszBinary, enmInputChan); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to add the binary path for the fuzzing run"); + } + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"BinaryPath\" is not a string"); + RTJsonValueRelease(hJsonVal); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query value of \"BinaryPath\""); + + return rc; +} + + +/** + * Processes argument related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessArgCfg(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValArgArray; + int rc = RTJsonValueQueryByName(hJsonRoot, "Arguments", &hJsonValArgArray); + if (RT_SUCCESS(rc)) + { + unsigned cArgs = 0; + rc = RTJsonValueQueryArraySize(hJsonValArgArray, &cArgs); + if (RT_SUCCESS(rc)) + { + if (cArgs > 0) + { + const char **papszArgs = (const char **)RTMemAllocZ(cArgs * sizeof(const char *)); + RTJSONVAL *pahJsonVal = (RTJSONVAL *)RTMemAllocZ(cArgs * sizeof(RTJSONVAL)); + if (RT_LIKELY(papszArgs && pahJsonVal)) + { + unsigned idx = 0; + + for (idx = 0; idx < cArgs && RT_SUCCESS(rc); idx++) + { + rc = RTJsonValueQueryByIndex(hJsonValArgArray, idx, &pahJsonVal[idx]); + if (RT_SUCCESS(rc)) + { + papszArgs[idx] = RTJsonValueGetString(pahJsonVal[idx]); + if (RT_UNLIKELY(!papszArgs[idx])) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Argument %u is not a string", idx); + } + } + + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsSetTestBinaryArgs(pFuzzRun->hFuzzObs, papszArgs, cArgs); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to set arguments for the fuzzing run"); + } + + /* Release queried values. */ + while (idx > 0) + { + RTJsonValueRelease(pahJsonVal[idx - 1]); + idx--; + } + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_MEMORY, "Out of memory allocating memory for the argument vector"); + + if (papszArgs) + RTMemFree(papszArgs); + if (pahJsonVal) + RTMemFree(pahJsonVal); + } + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"Arguments\" is not an array"); + RTJsonValueRelease(hJsonValArgArray); + } + + return rc; +} + + +/** + * Processes process environment related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessEnvironment(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValEnv; + int rc = RTJsonValueQueryByName(hJsonRoot, "Env", &hJsonValEnv); + if (RT_SUCCESS(rc)) + { + bool fReplaceEnv = false; /* false means to append everything to the default block. */ + + rc = RTJsonValueQueryBooleanByName(hJsonRoot, "EnvReplace", &fReplaceEnv); + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + { + RTJSONIT hEnvIt; + RTENV hEnv = NULL; + + if (fReplaceEnv) + rc = RTEnvCreate(&hEnv); + else + rc = RTEnvClone(&hEnv, RTENV_DEFAULT); + + if (RT_SUCCESS(rc)) + { + rc = RTJsonIteratorBeginArray(hJsonValEnv, &hEnvIt); + if (RT_SUCCESS(rc)) + { + do + { + RTJSONVAL hVal; + rc = RTJsonIteratorQueryValue(hEnvIt, &hVal, NULL); + if (RT_SUCCESS(rc)) + { + const char *pszVar = RTJsonValueGetString(hVal); + if (RT_LIKELY(pszVar)) + rc = RTEnvPutEx(hEnv, pszVar); + RTJsonValueRelease(hVal); + } + rc = RTJsonIteratorNext(hEnvIt); + } while (RT_SUCCESS(rc)); + + if ( rc == VERR_JSON_IS_EMPTY + || rc == VERR_JSON_ITERATOR_END) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to parse environment"); + + RTJsonIteratorFree(hEnvIt); + } + else if (rc == VERR_JSON_IS_EMPTY) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"Environment\" is not an array"); + + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsSetTestBinaryEnv(pFuzzRun->hFuzzObs, hEnv); + AssertRC(rc); + } + else if (hEnv) + RTEnvDestroy(hEnv); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to create environment block"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query \"EnvReplace\""); + + RTJsonValueRelease(hJsonValEnv); + } + else if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; /* Just keep using the default environment. */ + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query the \"Environment\""); + + return rc; +} + + +/** + * Processes process environment related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessSanitizers(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValSan; + int rc = RTJsonValueQueryByName(hJsonRoot, "Sanitizers", &hJsonValSan); + if (RT_SUCCESS(rc)) + { + uint32_t fSanitizers = 0; + RTJSONIT hSanIt; + rc = RTJsonIteratorBeginArray(hJsonValSan, &hSanIt); + if (RT_SUCCESS(rc)) + { + do + { + RTJSONVAL hVal; + rc = RTJsonIteratorQueryValue(hSanIt, &hVal, NULL); + if (RT_SUCCESS(rc)) + { + const char *pszSan = RTJsonValueGetString(hVal); + if (!RTStrICmp(pszSan, "Asan")) + fSanitizers |= RTFUZZOBS_SANITIZER_F_ASAN; + else if (!RTStrICmp(pszSan, "SanCov")) + fSanitizers |= RTFUZZOBS_SANITIZER_F_SANCOV; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NOT_FOUND, "JSON request malformed: The sanitizer '%s' is not known", pszSan); + RTJsonValueRelease(hVal); + } + rc = RTJsonIteratorNext(hSanIt); + } while (RT_SUCCESS(rc)); + + if ( rc == VERR_JSON_IS_EMPTY + || rc == VERR_JSON_ITERATOR_END) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to parse sanitizers"); + + RTJsonIteratorFree(hSanIt); + } + else if (rc == VERR_JSON_IS_EMPTY) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"Sanitizers\" is not an array"); + + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsSetTestBinarySanitizers(pFuzzRun->hFuzzObs, fSanitizers); + AssertRC(rc); + } + + RTJsonValueRelease(hJsonValSan); + } + else if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; /* Just keep using the defaults. */ + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query the \"Sanitizers\""); + + return rc; +} + + +/** + * Processes the given seed and adds it to the input corpus. + * + * @returns IPRT status code. + * @param hFuzzCtx The fuzzing context handle. + * @param pszCompression Compression used for the seed. + * @param pszSeed The seed as a base64 encoded string. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessSeed(RTFUZZCTX hFuzzCtx, const char *pszCompression, const char *pszSeed, PRTERRINFO pErrInfo) +{ + int rc = VINF_SUCCESS; + ssize_t cbSeedDecoded = RTBase64DecodedSize(pszSeed, NULL); + if (cbSeedDecoded > 0) + { + uint8_t *pbSeedDecoded = (uint8_t *)RTMemAllocZ(cbSeedDecoded); + if (RT_LIKELY(pbSeedDecoded)) + { + rc = RTBase64Decode(pszSeed, pbSeedDecoded, cbSeedDecoded, NULL, NULL); + if (RT_SUCCESS(rc)) + { + /* Decompress if applicable. */ + if (!RTStrICmp(pszCompression, "None")) + rc = RTFuzzCtxCorpusInputAdd(hFuzzCtx, pbSeedDecoded, cbSeedDecoded); + else + { + RTVFSIOSTREAM hVfsIosSeed; + rc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pbSeedDecoded, cbSeedDecoded, &hVfsIosSeed); + if (RT_SUCCESS(rc)) + { + RTVFSIOSTREAM hVfsDecomp = NIL_RTVFSIOSTREAM; + + if (!RTStrICmp(pszCompression, "Gzip")) + rc = RTZipGzipDecompressIoStream(hVfsIosSeed, RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR, &hVfsDecomp); + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Compression \"%s\" is not known", pszCompression); + + if (RT_SUCCESS(rc)) + { + RTVFSFILE hVfsFile; + rc = RTVfsMemFileCreate(hVfsDecomp, 2 * _1M, &hVfsFile); + if (RT_SUCCESS(rc)) + { + rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + { + /* The VFS file contains the buffer for the seed now. */ + rc = RTFuzzCtxCorpusInputAddFromVfsFile(hFuzzCtx, hVfsFile); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to add input seed"); + RTVfsFileRelease(hVfsFile); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to seek to the beginning of the seed"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to decompress input seed"); + + RTVfsIoStrmRelease(hVfsDecomp); + } + + RTVfsIoStrmRelease(hVfsIosSeed); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create I/O stream from seed buffer"); + } + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to decode the seed string"); + + RTMemFree(pbSeedDecoded); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_MEMORY, "Request error: Failed to allocate %zd bytes of memory for the seed", cbSeedDecoded); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: Couldn't find \"Seed\" doesn't contain a base64 encoded value"); + + return rc; +} + + +/** + * Processes a signle input seed for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonSeed The seed node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessInputSeedSingle(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonSeed, PRTERRINFO pErrInfo) +{ + RTFUZZCTX hFuzzCtx; + int rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonValComp; + rc = RTJsonValueQueryByName(hJsonSeed, "Compression", &hJsonValComp); + if (RT_SUCCESS(rc)) + { + const char *pszCompression = RTJsonValueGetString(hJsonValComp); + if (RT_LIKELY(pszCompression)) + { + RTJSONVAL hJsonValSeed; + rc = RTJsonValueQueryByName(hJsonSeed, "Seed", &hJsonValSeed); + if (RT_SUCCESS(rc)) + { + const char *pszSeed = RTJsonValueGetString(hJsonValSeed); + if (RT_LIKELY(pszSeed)) + rc = rtFuzzCmdMasterFuzzRunProcessSeed(hFuzzCtx, pszCompression, pszSeed, pErrInfo); + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"Seed\" value is not a string"); + + RTJsonValueRelease(hJsonValSeed); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Seed\" value"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"Compression\" value is not a string"); + + RTJsonValueRelease(hJsonValComp); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Compression\" value"); + + RTFuzzCtxRelease(hFuzzCtx); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to query fuzzing context from observer"); + + return rc; +} + + +/** + * Processes the given seed file and adds it to the input corpus. + * + * @returns IPRT status code. + * @param hFuzzCtx The fuzzing context handle. + * @param pszCompression Compression used for the seed. + * @param pszSeed The seed as a base64 encoded string. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessSeedFile(RTFUZZCTX hFuzzCtx, const char *pszCompression, const char *pszFile, PRTERRINFO pErrInfo) +{ + int rc = VINF_SUCCESS; + + /* Decompress if applicable. */ + if (!RTStrICmp(pszCompression, "None")) + rc = RTFuzzCtxCorpusInputAddFromFile(hFuzzCtx, pszFile); + else + { + RTVFSIOSTREAM hVfsIosSeed; + rc = RTVfsIoStrmOpenNormal(pszFile, RTFILE_O_OPEN | RTFILE_O_READ, &hVfsIosSeed); + if (RT_SUCCESS(rc)) + { + RTVFSIOSTREAM hVfsDecomp = NIL_RTVFSIOSTREAM; + + if (!RTStrICmp(pszCompression, "Gzip")) + rc = RTZipGzipDecompressIoStream(hVfsIosSeed, RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR, &hVfsDecomp); + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Compression \"%s\" is not known", pszCompression); + + if (RT_SUCCESS(rc)) + { + RTVFSFILE hVfsFile; + rc = RTVfsMemFileCreate(hVfsDecomp, 2 * _1M, &hVfsFile); + if (RT_SUCCESS(rc)) + { + rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + { + /* The VFS file contains the buffer for the seed now. */ + rc = RTFuzzCtxCorpusInputAddFromVfsFile(hFuzzCtx, hVfsFile); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to add input seed"); + RTVfsFileRelease(hVfsFile); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to seek to the beginning of the seed"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to decompress input seed"); + + RTVfsIoStrmRelease(hVfsDecomp); + } + + RTVfsIoStrmRelease(hVfsIosSeed); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create I/O stream from seed buffer"); + } + + return rc; +} + + +/** + * Processes a signle input seed given as a file path for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonSeed The seed node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessInputSeedFileSingle(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonSeed, PRTERRINFO pErrInfo) +{ + RTFUZZCTX hFuzzCtx; + int rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonValComp; + rc = RTJsonValueQueryByName(hJsonSeed, "Compression", &hJsonValComp); + if (RT_SUCCESS(rc)) + { + const char *pszCompression = RTJsonValueGetString(hJsonValComp); + if (RT_LIKELY(pszCompression)) + { + RTJSONVAL hJsonValFile; + rc = RTJsonValueQueryByName(hJsonSeed, "File", &hJsonValFile); + if (RT_SUCCESS(rc)) + { + const char *pszFile = RTJsonValueGetString(hJsonValFile); + if (RT_LIKELY(pszFile)) + rc = rtFuzzCmdMasterFuzzRunProcessSeedFile(hFuzzCtx, pszCompression, pszFile, pErrInfo); + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"File\" value is not a string"); + + RTJsonValueRelease(hJsonValFile); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"File\" value"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"Compression\" value is not a string"); + + RTJsonValueRelease(hJsonValComp); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Compression\" value"); + + RTFuzzCtxRelease(hFuzzCtx); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to query fuzzing context from observer"); + + return rc; +} + + +/** + * Processes input seed related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessInputSeeds(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValSeedArray; + int rc = RTJsonValueQueryByName(hJsonRoot, "InputSeeds", &hJsonValSeedArray); + if (RT_SUCCESS(rc)) + { + RTJSONIT hIt; + rc = RTJsonIteratorBegin(hJsonValSeedArray, &hIt); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonInpSeed; + while ( RT_SUCCESS(rc) + && RTJsonIteratorQueryValue(hIt, &hJsonInpSeed, NULL) != VERR_JSON_ITERATOR_END) + { + rc = rtFuzzCmdMasterFuzzRunProcessInputSeedSingle(pFuzzRun, hJsonInpSeed, pErrInfo); + RTJsonValueRelease(hJsonInpSeed); + if (RT_FAILURE(rc)) + break; + rc = RTJsonIteratorNext(hIt); + } + + if (rc == VERR_JSON_ITERATOR_END) + rc = VINF_SUCCESS; + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to create array iterator"); + + RTJsonValueRelease(hJsonValSeedArray); + } + else if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + { + rc = RTJsonValueQueryByName(hJsonRoot, "InputSeedFiles", &hJsonValSeedArray); + if (RT_SUCCESS(rc)) + { + RTJSONIT hIt; + rc = RTJsonIteratorBegin(hJsonValSeedArray, &hIt); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonInpSeed; + while ( RT_SUCCESS(rc) + && RTJsonIteratorQueryValue(hIt, &hJsonInpSeed, NULL) != VERR_JSON_ITERATOR_END) + { + rc = rtFuzzCmdMasterFuzzRunProcessInputSeedFileSingle(pFuzzRun, hJsonInpSeed, pErrInfo); + RTJsonValueRelease(hJsonInpSeed); + if (RT_FAILURE(rc)) + break; + rc = RTJsonIteratorNext(hIt); + } + + if (rc == VERR_JSON_ITERATOR_END) + rc = VINF_SUCCESS; + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to create array iterator"); + + RTJsonValueRelease(hJsonValSeedArray); + } + else if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; + } + + return rc; +} + + +/** + * Processes miscellaneous config items. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessMiscCfg(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + size_t cbTmp; + int rc = rtFuzzCmdMasterFuzzRunProcessCfgSizeDef(&cbTmp, "InputSeedMax", hJsonRoot, 0, pErrInfo); + if (RT_SUCCESS(rc)) + { + RTFUZZCTX hFuzzCtx; + rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx); + AssertRC(rc); + + rc = RTFuzzCtxCfgSetInputSeedMaximum(hFuzzCtx, cbTmp); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to set maximum input seed size to %zu", cbTmp); + } + + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessCfgU32Def(&pFuzzRun->cProcs, "FuzzingProcs", hJsonRoot, 0, pErrInfo); + if (RT_SUCCESS(rc)) + { + uint32_t msTimeoutMax = 0; + rc = rtFuzzCmdMasterFuzzRunProcessCfgU32Def(&msTimeoutMax, "TimeoutMax", hJsonRoot, 1000, pErrInfo); + if (RT_SUCCESS(rc)) + rc = RTFuzzObsSetTestBinaryTimeout(pFuzzRun->hFuzzObs, msTimeoutMax); + } + + return rc; +} + + +/** + * Processes target recording related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessTgtRecFlags(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValTgt; + int rc = RTJsonValueQueryByName(hJsonRoot, "TgtRec", &hJsonValTgt); + if (RT_SUCCESS(rc)) + { + uint32_t fTgtRecFlags = 0; + RTJSONIT hTgtIt; + rc = RTJsonIteratorBeginArray(hJsonValTgt, &hTgtIt); + if (RT_SUCCESS(rc)) + { + do + { + RTJSONVAL hVal; + rc = RTJsonIteratorQueryValue(hTgtIt, &hVal, NULL); + if (RT_SUCCESS(rc)) + { + const char *pszTgtRec = RTJsonValueGetString(hVal); + if (!RTStrICmp(pszTgtRec, "StdOut")) + fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_STDOUT; + else if (!RTStrICmp(pszTgtRec, "StdErr")) + fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_STDERR; + else if (!RTStrICmp(pszTgtRec, "ProcSts")) + fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_PROCSTATUS; + else if (!RTStrICmp(pszTgtRec, "SanCov")) + fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_SANCOV; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NOT_FOUND, "JSON request malformed: The recording flags '%s' is not known", pszTgtRec); + RTJsonValueRelease(hVal); + } + rc = RTJsonIteratorNext(hTgtIt); + } while (RT_SUCCESS(rc)); + + if ( rc == VERR_JSON_IS_EMPTY + || rc == VERR_JSON_ITERATOR_END) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to parse target recording flags"); + + RTJsonIteratorFree(hTgtIt); + } + else if (rc == VERR_JSON_IS_EMPTY) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"TgtRec\" is not an array"); + + pFuzzRun->fTgtRecFlags = fTgtRecFlags; + + RTJsonValueRelease(hJsonValTgt); + } + else if (rc == VERR_NOT_FOUND) + { + pFuzzRun->fTgtRecFlags = RTFUZZTGT_REC_STATE_F_PROCSTATUS; + rc = VINF_SUCCESS; /* Just keep using the defaults. */ + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query \"TgtRec\""); + + return rc; +} + + +/** + * Sets up the directories for the given fuzzing run. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param pFuzzRun The fuzzing run to setup the directories for. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunSetupDirectories(PRTFUZZCMDMASTER pThis, PRTFUZZRUN pFuzzRun, PRTERRINFO pErrInfo) +{ + /* Create temp directories. */ + char szTmpDir[RTPATH_MAX]; + int rc = RTPathJoin(&szTmpDir[0], sizeof(szTmpDir), pThis->pszTmpDir, pFuzzRun->pszId); + AssertRC(rc); + rc = RTDirCreate(szTmpDir, 0700, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_SET + | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); + if (rc == VERR_ALREADY_EXISTS) + { + /* Clear the directory. */ + rc = RTDirRemoveRecursive(szTmpDir, RTDIRRMREC_F_CONTENT_ONLY); + } + + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsSetTmpDirectory(pFuzzRun->hFuzzObs, szTmpDir); + if (RT_SUCCESS(rc)) + { + rc = RTPathJoin(&szTmpDir[0], sizeof(szTmpDir), pThis->pszResultsDir, pFuzzRun->pszId); + AssertRC(rc); + rc = RTDirCreate(szTmpDir, 0700, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_SET + | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); + if (RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS) + { + rc = RTFuzzObsSetResultDirectory(pFuzzRun->hFuzzObs, szTmpDir); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to set results directory to %s", szTmpDir); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create results directory %s", szTmpDir); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to set temporary directory to %s", szTmpDir); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create temporary directory %s", szTmpDir); + + return rc; +} + + +/** + * Creates a new fuzzing run with the given ID. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param pszId The ID to use. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterCreateFuzzRunWithId(PRTFUZZCMDMASTER pThis, const char *pszId, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + int rc = VINF_SUCCESS; + PRTFUZZRUN pFuzzRun = (PRTFUZZRUN)RTMemAllocZ(sizeof(*pFuzzRun)); + if (RT_LIKELY(pFuzzRun)) + { + pFuzzRun->pszId = RTStrDup(pszId); + if (RT_LIKELY(pFuzzRun->pszId)) + { + rc = rtFuzzCmdMasterFuzzRunProcessTgtRecFlags(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsCreate(&pFuzzRun->hFuzzObs, RTFUZZCTXTYPE_BLOB, pFuzzRun->fTgtRecFlags); + if (RT_SUCCESS(rc)) + { + rc = rtFuzzCmdMasterFuzzRunProcessBinaryCfg(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessArgCfg(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessEnvironment(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessInputSeeds(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessMiscCfg(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessSanitizers(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunSetupDirectories(pThis, pFuzzRun, pErrInfo); + + if (RT_SUCCESS(rc)) + { + /* Start fuzzing. */ + RTListAppend(&pThis->LstFuzzed, &pFuzzRun->NdFuzzed); + rc = RTFuzzObsExecStart(pFuzzRun->hFuzzObs, pFuzzRun->cProcs); + if (RT_SUCCESS(rc)) + { + RTTIMESPEC TimeSpec; + RTTimeNow(&TimeSpec); + RTTimeLocalExplode(&pFuzzRun->TimeCreated, &TimeSpec); + pFuzzRun->tsCreatedMs = RTTimeMilliTS(); + pFuzzRun->fStarted = true; + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to start fuzzing with %Rrc", rc); + } + } + } + } + else + rc = VERR_NO_STR_MEMORY; + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_MEMORY, "Request error: Out of memory allocating the fuzzer state"); + + return rc; +} + + +/** + * Resolves the fuzzing run from the given ID config item and the given JSON request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonRoot The root node of the JSON request. + * @param pszIdItem The JSON item which contains the ID of the fuzzing run. + * @param ppFuzzRun Where to store the pointer to the fuzzing run on success. + */ +static int rtFuzzCmdMasterQueryFuzzRunFromJson(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, const char *pszIdItem, PRTERRINFO pErrInfo, + PRTFUZZRUN *ppFuzzRun) +{ + RTJSONVAL hJsonValId; + int rc = RTJsonValueQueryByName(hJsonRoot, pszIdItem, &hJsonValId); + if (RT_SUCCESS(rc)) + { + const char *pszId = RTJsonValueGetString(hJsonValId); + if (pszId) + { + PRTFUZZRUN pFuzzRun = rtFuzzCmdMasterGetFuzzerById(pThis, pszId); + if (pFuzzRun) + *ppFuzzRun = pFuzzRun; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NOT_FOUND, "Request error: The ID \"%s\" wasn't found", pszId); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Id\" is not a string value"); + + RTJsonValueRelease(hJsonValId); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Id\" value"); + return rc; +} + + +/** + * Processes the "StartFuzzing" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqStart(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValId; + int rc = RTJsonValueQueryByName(hJsonRoot, "Id", &hJsonValId); + if (RT_SUCCESS(rc)) + { + const char *pszId = RTJsonValueGetString(hJsonValId); + if (pszId) + { + PRTFUZZRUN pFuzzRun = rtFuzzCmdMasterGetFuzzerById(pThis, pszId); + if (!pFuzzRun) + rc = rtFuzzCmdMasterCreateFuzzRunWithId(pThis, pszId, hJsonRoot, pErrInfo); + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_ALREADY_EXISTS, "Request error: The ID \"%s\" is already registered", pszId); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Id\" is not a string value"); + + RTJsonValueRelease(hJsonValId); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Id\" value"); + return rc; +} + + +/** + * Processes the "StopFuzzing" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqStop(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + PRTFUZZRUN pFuzzRun; + int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + { + RTListNodeRemove(&pFuzzRun->NdFuzzed); + RTFuzzObsExecStop(pFuzzRun->hFuzzObs); + RTFuzzObsDestroy(pFuzzRun->hFuzzObs); + RTStrFree(pFuzzRun->pszId); + RTMemFree(pFuzzRun); + } + + return rc; +} + + +/** + * Processes the "SuspendFuzzing" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqSuspend(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + PRTFUZZRUN pFuzzRun; + int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + { + if (pFuzzRun->fStarted) + { + rc = RTFuzzObsExecStop(pFuzzRun->hFuzzObs); + if (RT_SUCCESS(rc)) + pFuzzRun->fStarted = false; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Suspending the fuzzing process failed"); + } + } + + return rc; +} + + +/** + * Processes the "ResumeFuzzing" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqResume(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + PRTFUZZRUN pFuzzRun; + int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + { + if (!pFuzzRun->fStarted) + { + rc = rtFuzzCmdMasterFuzzRunProcessCfgU32Def(&pFuzzRun->cProcs, "FuzzingProcs", hJsonRoot, pFuzzRun->cProcs, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsExecStart(pFuzzRun->hFuzzObs, pFuzzRun->cProcs); + if (RT_SUCCESS(rc)) + pFuzzRun->fStarted = true; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Resuming the fuzzing process failed"); + } + } + } + + return rc; +} + + +/** + * Processes the "SaveFuzzingState" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqSaveState(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + PRTFUZZRUN pFuzzRun; + int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + { + /* Suspend fuzzing, save and resume if not stopped. */ + if (pFuzzRun->fStarted) + { + rc = RTFuzzObsExecStop(pFuzzRun->hFuzzObs); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Suspending the fuzzing process failed"); + } + + if (RT_SUCCESS(rc)) + { + RTFUZZCTX hFuzzCtx; + rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx); + AssertRC(rc); + + void *pvState = NULL; + size_t cbState = 0; + rc = RTFuzzCtxStateExportToMem(hFuzzCtx, &pvState, &cbState); + if (RT_SUCCESS(rc)) + { + /* Encode to base64. */ + size_t cbStateStr = RTBase64EncodedLength(cbState) + 1; + char *pszState = (char *)RTMemAllocZ(cbStateStr); + if (pszState) + { + rc = RTBase64Encode(pvState, cbState, pszState, cbStateStr, &cbStateStr); + if (RT_SUCCESS(rc)) + { + /* Strip all new lines from the srting. */ + size_t offStr = 0; + while (offStr < cbStateStr) + { +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + char *pszEol = strchr(&pszState[offStr], '\r'); +#else + char *pszEol = strchr(&pszState[offStr], '\n'); +#endif + if (pszEol) + { + offStr += pszEol - &pszState[offStr]; + memmove(pszEol, &pszEol[RTBASE64_EOL_SIZE], cbStateStr - offStr - RTBASE64_EOL_SIZE); + cbStateStr -= RTBASE64_EOL_SIZE; + } + else + break; + } + + const char s_szState[] = "{ \"State\": %s }"; + pThis->pszResponse = RTStrAPrintf2(s_szState, pszState); + if (RT_UNLIKELY(!pThis->pszResponse)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_BUFFER_OVERFLOW, "Request error: Response data buffer overflow", rc); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to encode the state as a base64 string"); + RTMemFree(pszState); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_STR_MEMORY, "Request error: Failed to allocate a state string for the response"); + RTMemFree(pvState); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Exporting the state failed"); + } + + if (pFuzzRun->fStarted) + { + int rc2 = RTFuzzObsExecStart(pFuzzRun->hFuzzObs, pFuzzRun->cProcs); + if (RT_FAILURE(rc2)) + rtFuzzCmdMasterErrorRc(pErrInfo, rc2, "Request error: Resuming the fuzzing process failed"); + } + } + + return rc; +} + + +/** + * Queries the statistics for the given fuzzing run and adds the result to the response. + * + * @returns IPRT static code. + * @param pThis The fuzzing master command state. + * @param pFuzzRun The fuzzing run. + * @param pszIndent Indentation to use. + * @param fLast Flags whether this is the last element in the list. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessQueryRunStats(PRTFUZZCMDMASTER pThis, PRTFUZZRUN pFuzzRun, + const char *pszIndent, bool fLast, PRTERRINFO pErrInfo) +{ + RTFUZZOBSSTATS ObsStats; + RTFUZZCTXSTATS CtxStats; + RTFUZZCTX hFuzzCtx; + RT_ZERO(ObsStats); RT_ZERO(CtxStats); + + int rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzCtxQueryStats(hFuzzCtx, &CtxStats); + RTFuzzCtxRelease(hFuzzCtx); + } + + if (RT_SUCCESS(rc)) + rc = RTFuzzObsQueryStats(pFuzzRun->hFuzzObs, &ObsStats); + if (RT_SUCCESS(rc)) + { + const char s_szStatsFmt[] = "%s{ \n" + "%s \"Id\": \"%s\"\n" + "%s \"TimeCreated\": \"%s\"\n" + "%s \"UptimeSec\": %llu\n" + "%s \"FuzzedInputsPerSec\": %u\n" + "%s \"FuzzedInputs\": %u\n" + "%s \"FuzzedInputsHang\": %u\n" + "%s \"FuzzedInputsCrash\": %u\n" + "%s \"MemoryUsage\": %zu\n" + "%s \"CorpusSize\": %llu\n" + "%s}%s\n"; + char aszTime[_1K]; RT_ZERO(aszTime); + char aszStats[_4K]; RT_ZERO(aszStats); + + if (RTTimeToString(&pFuzzRun->TimeCreated, aszTime, sizeof(aszTime))) + { + ssize_t cchStats = RTStrPrintf2(&aszStats[0], sizeof(aszStats), + s_szStatsFmt, pszIndent, + pszIndent, pFuzzRun->pszId, + pszIndent, aszTime, + pszIndent, (RTTimeMilliTS() - pFuzzRun->tsCreatedMs) / RT_MS_1SEC_64, + pszIndent, ObsStats.cFuzzedInputsPerSec, + pszIndent, ObsStats.cFuzzedInputs, + pszIndent, ObsStats.cFuzzedInputsHang, + pszIndent, ObsStats.cFuzzedInputsCrash, + pszIndent, CtxStats.cbMemory, + pszIndent, CtxStats.cMutations, + pszIndent, fLast ? "" : ","); + if (RT_LIKELY(cchStats > 0)) + { + rc = RTStrAAppend(&pThis->pszResponse, &aszStats[0]); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to build statistics response", rc); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_BUFFER_OVERFLOW, "Request error: Response data buffer overflow"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_BUFFER_OVERFLOW, "Request error: Buffer overflow conerting time to string"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to query fuzzing statistics with %Rrc", rc); + + return rc; +} + + +/** + * Processes the "QueryStats" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqQueryStats(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValId; + int rc = RTJsonValueQueryByName(hJsonRoot, "Id", &hJsonValId); + if (RT_SUCCESS(rc)) + { + RTJsonValueRelease(hJsonValId); + PRTFUZZRUN pFuzzRun; + rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterProcessQueryRunStats(pThis, pFuzzRun, " ", + true /*fLast*/, pErrInfo); + } + else if (rc == VERR_NOT_FOUND) + { + /* Id is not there, so collect statistics of all running jobs. */ + rc = RTStrAAppend(&pThis->pszResponse, " [\n"); + if (RT_SUCCESS(rc)) + { + PRTFUZZRUN pRun = NULL; + RTListForEach(&pThis->LstFuzzed, pRun, RTFUZZRUN, NdFuzzed) + { + bool fLast = RTListNodeIsLast(&pThis->LstFuzzed, &pRun->NdFuzzed); + rc = rtFuzzCmdMasterProcessQueryRunStats(pThis, pRun, " ", fLast, pErrInfo); + if (RT_FAILURE(rc)) + break; + } + if (RT_SUCCESS(rc)) + rc = RTStrAAppend(&pThis->pszResponse, " ]\n"); + } + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't get \"Id\" value"); + + return rc; +} + + +/** + * Processes a JSON request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReq(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValReq; + int rc = RTJsonValueQueryByName(hJsonRoot, "Request", &hJsonValReq); + if (RT_SUCCESS(rc)) + { + const char *pszReq = RTJsonValueGetString(hJsonValReq); + if (pszReq) + { + if (!RTStrCmp(pszReq, "StartFuzzing")) + rc = rtFuzzCmdMasterProcessJsonReqStart(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "StopFuzzing")) + rc = rtFuzzCmdMasterProcessJsonReqStop(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "SuspendFuzzing")) + rc = rtFuzzCmdMasterProcessJsonReqSuspend(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "ResumeFuzzing")) + rc = rtFuzzCmdMasterProcessJsonReqResume(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "SaveFuzzingState")) + rc = rtFuzzCmdMasterProcessJsonReqSaveState(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "QueryStats")) + rc = rtFuzzCmdMasterProcessJsonReqQueryStats(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "Shutdown")) + pThis->fShutdown = true; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Request\" contains unknown value \"%s\"", pszReq); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Request\" is not a string value"); + + RTJsonValueRelease(hJsonValReq); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Request\" value"); + + return rc; +} + + +/** + * Loads a fuzzing configuration for immediate startup from the given file. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param pszFuzzCfg The fuzzing config to load. + */ +static int rtFuzzCmdMasterFuzzCfgLoadFromFile(PRTFUZZCMDMASTER pThis, const char *pszFuzzCfg) +{ + RTJSONVAL hJsonRoot; + int rc = RTJsonParseFromFile(&hJsonRoot, pszFuzzCfg, NULL); + if (RT_SUCCESS(rc)) + { + rc = rtFuzzCmdMasterProcessJsonReqStart(pThis, hJsonRoot, NULL); + RTJsonValueRelease(hJsonRoot); + } + else + rc = rtFuzzCmdMasterErrorRc(NULL, rc, "JSON request malformed: Couldn't load file \"%s\"", pszFuzzCfg); + + return rc; +} + + +/** + * Destroys all running fuzzers for the given master state. + * + * @returns nothing. + * @param pThis The fuzzing master command state. + */ +static void rtFuzzCmdMasterDestroy(PRTFUZZCMDMASTER pThis) +{ + RT_NOREF(pThis); +} + + +/** + * Sends an ACK response to the client. + * + * @returns nothing. + * @param hSocket The socket handle to send the ACK to. + * @param pszResponse Additional response data. + */ +static void rtFuzzCmdMasterTcpSendAck(RTSOCKET hSocket, const char *pszResponse) +{ + const char s_szSucc[] = "{ \"Status\": \"ACK\" }\n"; + const char s_szSuccResp[] = "{ \"Status\": \"ACK\"\n \"Response\":\n"; + const char s_szSuccRespClose[] = "\n }\n"; + if (pszResponse) + { + RTSGSEG aSegs[3]; + RTSGBUF SgBuf; + aSegs[0].pvSeg = (void *)s_szSuccResp; + aSegs[0].cbSeg = sizeof(s_szSuccResp) - 1; + aSegs[1].pvSeg = (void *)pszResponse; + aSegs[1].cbSeg = strlen(pszResponse); + aSegs[2].pvSeg = (void *)s_szSuccRespClose; + aSegs[2].cbSeg = sizeof(s_szSuccRespClose) - 1; + + RTSgBufInit(&SgBuf, &aSegs[0], RT_ELEMENTS(aSegs)); + RTTcpSgWrite(hSocket, &SgBuf); + } + else + RTTcpWrite(hSocket, s_szSucc, sizeof(s_szSucc)); +} + + +/** + * Sends an NACK response to the client. + * + * @returns nothing. + * @param hSocket The socket handle to send the ACK to. + * @param pErrInfo Optional error information to send along. + */ +static void rtFuzzCmdMasterTcpSendNAck(RTSOCKET hSocket, PRTERRINFO pErrInfo) +{ + const char s_szFail[] = "{ \"Status\": \"NACK\" }\n"; + const char s_szFailInfo[] = "{ \"Status\": \"NACK\"\n \"Information\": \"%s\" }\n"; + + if (pErrInfo) + { + char szTmp[_1K]; + ssize_t cchResp = RTStrPrintf2(szTmp, sizeof(szTmp), s_szFailInfo, pErrInfo->pszMsg); + if (cchResp > 0) + RTTcpWrite(hSocket, szTmp, cchResp); + else + RTTcpWrite(hSocket, s_szFail, strlen(s_szFail)); + } + else + RTTcpWrite(hSocket, s_szFail, strlen(s_szFail)); +} + + +/** + * TCP server serving callback for a single connection. + * + * @returns IPRT status code. + * @param hSocket The socket handle of the connection. + * @param pvUser Opaque user data. + */ +static DECLCALLBACK(int) rtFuzzCmdMasterTcpServe(RTSOCKET hSocket, void *pvUser) +{ + PRTFUZZCMDMASTER pThis = (PRTFUZZCMDMASTER)pvUser; + size_t cbReqMax = _32K; + size_t cbReq = 0; + uint8_t *pbReq = (uint8_t *)RTMemAllocZ(cbReqMax); + + if (RT_LIKELY(pbReq)) + { + uint8_t *pbCur = pbReq; + + for (;;) + { + size_t cbThisRead = cbReqMax - cbReq; + int rc = RTTcpRead(hSocket, pbCur, cbThisRead, &cbThisRead); + if ( RT_SUCCESS(rc) + && cbThisRead) + { + cbReq += cbThisRead; + + /* Check for a zero terminator marking the end of the request. */ + uint8_t *pbEnd = (uint8_t *)memchr(pbCur, 0, cbThisRead); + if (pbEnd) + { + /* Adjust request size, data coming after the zero terminiator is ignored right now. */ + cbReq -= cbThisRead - (pbEnd - pbCur) + 1; + + RTJSONVAL hJsonReq; + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + + rc = RTJsonParseFromBuf(&hJsonReq, pbReq, cbReq, &ErrInfo.Core); + if (RT_SUCCESS(rc)) + { + rc = rtFuzzCmdMasterProcessJsonReq(pThis, hJsonReq, &ErrInfo.Core); + if (RT_SUCCESS(rc)) + rtFuzzCmdMasterTcpSendAck(hSocket, pThis->pszResponse); + else + rtFuzzCmdMasterTcpSendNAck(hSocket, &ErrInfo.Core); + RTJsonValueRelease(hJsonReq); + } + else + rtFuzzCmdMasterTcpSendNAck(hSocket, &ErrInfo.Core); + + if (pThis->pszResponse) + { + RTStrFree(pThis->pszResponse); + pThis->pszResponse = NULL; + } + break; + } + else if (cbReq == cbReqMax) + { + /* Try to increase the buffer. */ + uint8_t *pbReqNew = (uint8_t *)RTMemRealloc(pbReq, cbReqMax + _32K); + if (RT_LIKELY(pbReqNew)) + { + cbReqMax += _32K; + pbReq = pbReqNew; + pbCur = pbReq + cbReq; + } + else + rtFuzzCmdMasterTcpSendNAck(hSocket, NULL); + } + else + pbCur += cbThisRead; + } + else + break; + } + } + else + rtFuzzCmdMasterTcpSendNAck(hSocket, NULL); + + if (pbReq) + RTMemFree(pbReq); + + return pThis->fShutdown ? VERR_TCP_SERVER_STOP : VINF_SUCCESS; +} + + +/** + * Mainloop for the fuzzing master. + * + * @returns Process exit code. + * @param pThis The fuzzing master command state. + * @param pszLoadCfg Initial config to load. + */ +static RTEXITCODE rtFuzzCmdMasterRun(PRTFUZZCMDMASTER pThis, const char *pszLoadCfg) +{ + if (pszLoadCfg) + { + int rc = rtFuzzCmdMasterFuzzCfgLoadFromFile(pThis, pszLoadCfg); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + } + + /* Start up the control server. */ + int rc = RTTcpServerCreateEx(NULL, pThis->uPort, &pThis->hTcpSrv); + if (RT_SUCCESS(rc)) + { + do + { + rc = RTTcpServerListen(pThis->hTcpSrv, rtFuzzCmdMasterTcpServe, pThis); + } while (rc != VERR_TCP_SERVER_STOP); + } + + RTTcpServerDestroy(pThis->hTcpSrv); + rtFuzzCmdMasterDestroy(pThis); + return RTEXITCODE_SUCCESS; +} + + +RTR3DECL(RTEXITCODE) RTFuzzCmdMaster(unsigned cArgs, char **papszArgs) +{ + /* + * Parse the command line. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--fuzz-config", 'c', RTGETOPT_REQ_STRING }, + { "--temp-dir", 't', RTGETOPT_REQ_STRING }, + { "--results-dir", 'r', RTGETOPT_REQ_STRING }, + { "--listen-port", 'p', RTGETOPT_REQ_UINT16 }, + { "--daemonize", 'd', RTGETOPT_REQ_NOTHING }, + { "--daemonized", 'Z', RTGETOPT_REQ_NOTHING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING }, + { "--version", 'V', RTGETOPT_REQ_NOTHING }, + }; + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, + RTGETOPTINIT_FLAGS_OPTS_FIRST); + if (RT_SUCCESS(rc)) + { + /* Option variables: */ + bool fDaemonize = false; + bool fDaemonized = false; + const char *pszLoadCfg = NULL; + RTFUZZCMDMASTER This; + + RTListInit(&This.LstFuzzed); + This.hTcpSrv = NIL_RTTCPSERVER; + This.uPort = 4242; + This.pszTmpDir = NULL; + This.pszResultsDir = NULL; + This.fShutdown = false; + This.pszResponse = NULL; + + /* Argument parsing loop. */ + bool fContinue = true; + do + { + RTGETOPTUNION ValueUnion; + int chOpt = RTGetOpt(&GetState, &ValueUnion); + switch (chOpt) + { + case 0: + fContinue = false; + break; + + case 'c': + pszLoadCfg = ValueUnion.psz; + break; + + case 'p': + This.uPort = ValueUnion.u16; + break; + + case 't': + This.pszTmpDir = ValueUnion.psz; + break; + + case 'r': + This.pszResultsDir = ValueUnion.psz; + break; + + case 'd': + fDaemonize = true; + break; + + case 'Z': + fDaemonized = true; + fDaemonize = false; + break; + + case 'h': + RTPrintf("Usage: to be written\nOption dump:\n"); + for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++) + RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong); + fContinue = false; + break; + + case 'V': + RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision()); + fContinue = false; + break; + + default: + rcExit = RTGetOptPrintError(chOpt, &ValueUnion); + fContinue = false; + break; + } + } while (fContinue); + + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Daemonize ourselves if asked to. + */ + if (fDaemonize) + { + rc = RTProcDaemonize(papszArgs, "--daemonized"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize: %Rrc\n", rc); + } + else + rcExit = rtFuzzCmdMasterRun(&This, pszLoadCfg); + } + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc); + return rcExit; +} + |