diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Runtime/common/fuzz/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fuzz/fuzz-observer.cpp | 1331 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fuzz/fuzz.cpp | 1217 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp | 216 | ||||
-rw-r--r-- | src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp | 1351 |
5 files changed, 4115 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..343a29e9 --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/fuzz-observer.cpp @@ -0,0 +1,1331 @@ +/* $Id: fuzz-observer.cpp $ */ +/** @file + * IPRT - Fuzzing framework API, observer. + */ + +/* + * Copyright (C) 2018-2019 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 + + +/********************************************************************************************************************************* +* 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; + /** Current fuzzer input. */ + RTFUZZINPUT hFuzzInput; + /** Flag whether to keep the input. */ + bool fKeepInput; + /** Flag whether a new input is waiting. */ + volatile bool fNewInput; +} 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; + /** Temp directory for input files. */ + char *pszTmpDir; + /** Results directory. */ + char *pszResultsDir; + /** The binary to run. */ + char *pszBinary; + /** Arguments to run the binary with, terminated by a NULL entry. */ + char **papszArgs; + /** 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; + + +/** + * Stdout/Stderr buffer. + */ +typedef struct RTFUZZOBSSTDOUTERRBUF +{ + /** Current amount buffered. */ + size_t cbBuf; + /** Maxmium amount to buffer. */ + size_t cbBufMax; + /** Base pointer to the data buffer. */ + uint8_t *pbBase; +} RTFUZZOBSSTDOUTERRBUF; +/** Pointer to a stdout/stderr buffer. */ +typedef RTFUZZOBSSTDOUTERRBUF *PRTFUZZOBSSTDOUTERRBUF; + + +/** + * 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 process to monitor. */ + RTPROCESS hProc; + /** Execution time of the process. */ + RTMSINTERVAL msExec; + /** Current input data pointer. */ + uint8_t *pbInputCur; + /** Number of bytes left for the input. */ + size_t cbInputLeft; + /** The stdout data buffer. */ + RTFUZZOBSSTDOUTERRBUF StdOutBuf; + /** The stderr data buffer. */ + RTFUZZOBSSTDOUTERRBUF StdErrBuf; + /** 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; + + +/** + * Initializes the given stdout/stderr buffer. + * + * @returns nothing. + * @param pBuf The buffer to initialize. + */ +static void rtFuzzObsStdOutErrBufInit(PRTFUZZOBSSTDOUTERRBUF 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 rtFuzzObsStdOutErrBufFree(PRTFUZZOBSSTDOUTERRBUF pBuf) +{ + if (pBuf->pbBase) + RTMemFree(pBuf->pbBase); +} + + +/** + * Clears the given stdout/stderr buffer. + * + * @returns nothing. + * @param pBuf The buffer to clear. + */ +static void rtFuzzObsStdOutErrBufClear(PRTFUZZOBSSTDOUTERRBUF pBuf) +{ + pBuf->cbBuf = 0; +} + + +/** + * 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 rtFuzzObsStdOutErrBufFill(PRTFUZZOBSSTDOUTERRBUF 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 stdout/stderr buffer to the given filename. + * + * @returns IPRT status code. + * @param pBuf The buffer to write. + * @param pszFilename The filename to write the buffer to. + */ +static int rtFuzzStdOutErrBufWriteToFile(PRTFUZZOBSSTDOUTERRBUF 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; +} + + +/** + * 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; + rtFuzzObsStdOutErrBufInit(&pExecCtx->StdOutBuf); + rtFuzzObsStdOutErrBufInit(&pExecCtx->StdErrBuf); + + 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); + } + + 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++; + } + + rtFuzzObsStdOutErrBufFree(&pExecCtx->StdOutBuf); + rtFuzzObsStdOutErrBufFree(&pExecCtx->StdErrBuf); + 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) +{ + rtFuzzObsStdOutErrBufClear(&pExecCtx->StdOutBuf); + rtFuzzObsStdOutErrBufClear(&pExecCtx->StdErrBuf); + + int rc = RTProcCreateEx(pThis->pszBinary, &pExecCtx->apszArgs[0], RTENV_DEFAULT, 0 /*fFlags*/, &pExecCtx->StdinHandle, + &pExecCtx->StdoutHandle, &pExecCtx->StderrHandle, 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 = rtFuzzObsStdOutErrBufFill(&pExecCtx->StdOutBuf, pExecCtx->hPipeStdoutR); + AssertRC(rc); + } + else if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDERR) + { + Assert(fEvtsRecv & RTPOLL_EVT_READ); + rc = rtFuzzObsStdOutErrBufFill(&pExecCtx->StdErrBuf, 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)) + 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) +{ + rtFuzzObsStdOutErrBufClear(&pExecCtx->StdOutBuf); + rtFuzzObsStdOutErrBufClear(&pExecCtx->StdErrBuf); + + int rc = RTProcCreateEx(pThis->pszBinary, &pExecCtx->apszArgs[0], RTENV_DEFAULT, 0 /*fFlags*/, &pExecCtx->StdinHandle, + &pExecCtx->StdoutHandle, &pExecCtx->StderrHandle, 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 = RTFuzzCtxStateExport(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 = rtFuzzObsStdOutErrBufFill(&pExecCtx->StdErrBuf, 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)) + { + /* Stdout and Stderr. */ + rc = RTPathJoin(szTmp, sizeof(szTmp), &szPath[0], "stdout"); + AssertRC(rc); + rc = rtFuzzStdOutErrBufWriteToFile(&pExecCtx->StdOutBuf, &szTmp[0]); + if (RT_SUCCESS(rc)) + { + rc = RTPathJoin(szTmp, sizeof(szTmp), &szPath[0], "stderr"); + AssertRC(rc); + rc = rtFuzzStdOutErrBufWriteToFile(&pExecCtx->StdOutBuf, &szTmp[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; + + while (!pObsThrd->fShutdown) + { + char szInput[RTPATH_MAX]; + + /* Wait for work. */ + rc = RTThreadUserWait(hThrd, RT_INDEFINITE_WAIT); + AssertRC(rc); + + if (pObsThrd->fShutdown) + break; + + if (!ASMAtomicXchgBool(&pObsThrd->fNewInput, false)) + continue; + + AssertPtr(pObsThrd->hFuzzInput); + + 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); + + RT_ZERO(szInput); + rc = RTPathJoin(szInput, sizeof(szInput), pThis->pszTmpDir, &szFilename[0]); + AssertRC(rc); + + rc = RTFuzzInputWriteToFile(pObsThrd->hFuzzInput, &szInput[0]); + if (RT_SUCCESS(rc)) + { + RTFUZZOBSVARIABLE aVar[2] = { + { "${INPUT}", sizeof("${INPUT}") - 1, &szInput[0] }, + { NULL, 0, NULL } + }; + rc = rtFuzzObsExecCtxArgvPrepare(pThis, pExecCtx, &aVar[0]); + } + } + else if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_STDIN) + { + rc = RTFuzzInputQueryData(pObsThrd->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)) + { + if (ProcSts.enmReason != RTPROCEXITREASON_NORMAL) + { + ASMAtomicIncU32(&pThis->Stats.cFuzzedInputsCrash); + rc = rtFuzzObsAddInputToResults(pThis, pObsThrd->hFuzzInput, pExecCtx); + } + } + else if (rc == VERR_TIMEOUT) + { + ASMAtomicIncU32(&pThis->Stats.cFuzzedInputsHang); + rc = rtFuzzObsAddInputToResults(pThis, pObsThrd->hFuzzInput, pExecCtx); + } + else + AssertFailed(); + + if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FILE) + RTFileDelete(&szInput[0]); + } + + ASMAtomicBitSet(&pThis->bmEvt, pObsThrd->idObs); + RTSemEventSignal(pThis->hEvtGlobal); + } + + rtFuzzObsExecCtxDestroy(pThis, pExecCtx); + return VINF_SUCCESS; +} + + +/** + * 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]; + + /* Release the old input. */ + if (pObsThrd->hFuzzInput) + { + if (pObsThrd->fKeepInput) + { + int rc2 = RTFuzzInputAddToCtxCorpus(pObsThrd->hFuzzInput); + Assert(RT_SUCCESS(rc2) || rc2 == VERR_ALREADY_EXISTS); RT_NOREF(rc2); + pObsThrd->fKeepInput= false; + } + RTFuzzInputRelease(pObsThrd->hFuzzInput); + } + + rc = RTFuzzCtxInputGenerate(pThis->hFuzzCtx, &pObsThrd->hFuzzInput); + if (RT_SUCCESS(rc)) + { + ASMAtomicWriteBool(&pObsThrd->fNewInput, true); + 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->hFuzzInput = NULL; + pObsThrd->idObs = idObs; + pObsThrd->fShutdown = false; + + 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; +} + + +RTDECL(int) RTFuzzObsCreate(PRTFUZZOBS phFuzzObs) +{ + AssertPtrReturn(phFuzzObs, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + PRTFUZZOBSINT pThis = (PRTFUZZOBSINT)RTMemAllocZ(sizeof(*pThis)); + if (RT_LIKELY(pThis)) + { + pThis->pszBinary = NULL; + pThis->papszArgs = NULL; + 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); + if (RT_SUCCESS(rc)) + { + *phFuzzObs = pThis; + return VINF_SUCCESS; + } + + 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); + 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; + 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) 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); + + /* 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); + if (pThrd->hFuzzInput) + RTFuzzInputRelease(pThrd->hFuzzInput); + } + + 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.cpp b/src/VBox/Runtime/common/fuzz/fuzz.cpp new file mode 100644 index 00000000..a74d130b --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/fuzz.cpp @@ -0,0 +1,1217 @@ +/* $Id: fuzz.cpp $ */ +/** @file + * IPRT - Fuzzing framework API, core. + */ + +/* + * Copyright (C) 2018-2019 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/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/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; + +/** + * A fuzzing input seed. + */ +typedef struct RTFUZZINPUTINT +{ + /** The AVL tree core. */ + AVLU64NODECORE Core; + /** Node for the global list. */ + RTLISTNODE NdInputs; + /** Reference counter. */ + volatile uint32_t cRefs; +/** @todo add magic here (unused padding space on 64-bit hosts). */ + /** The fuzzer this input belongs to. */ + PRTFUZZCTXINT pFuzzer; + /** Complete MD5 hash of the input data. */ + uint8_t abMd5Hash[RTMD5_HASH_SIZE]; + /** Size of the input data. */ + size_t cbInput; + /** Input data - variable in size. */ + uint8_t abInput[1]; +} RTFUZZINPUTINT; +/** Pointer to the internal input state. */ +typedef RTFUZZINPUTINT *PRTFUZZINPUTINT; +/** Pointer to an internal input state pointer. */ +typedef PRTFUZZINPUTINT *PPRTFUZZINPUTINT; + + +/** + * Intermediate indexing structure. + */ +typedef struct RTFUZZINTERMEDIATE +{ + /** The AVL tree core. */ + AVLU64NODECORE Core; + /** The AVL tree for indexing the input seeds (keyed by the lower half of the MD5). */ + AVLU64TREE TreeSeedsLow; +} RTFUZZINTERMEDIATE; +/** Pointer to an intermediate indexing state. */ +typedef RTFUZZINTERMEDIATE *PRTFUZZINTERMEDIATE; +/** Pointer to an intermediate indexing state pointer. */ +typedef PRTFUZZINTERMEDIATE *PPRTFUZZINTERMEDIATE; + +/** + * 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; + /** The AVL tree for indexing the input seeds (keyed by the upper half of the MD5). */ + AVLU64TREE TreeSeedsHigh; + /** Sequential list of all inputs. */ + RTLISTANCHOR LstInputs; + /** Number of inputs currently in the tree. */ + uint32_t cInputs; + /** The maximum size of one input seed to generate. */ + size_t cbInputMax; + /** Behavioral flags. */ + uint32_t fFlagsBehavioral; +} RTFUZZCTXINT; + + +/** + * Available mutators enum. + */ +typedef enum RTFUZZCTXMUTATOR +{ + /** Invalid mutator, not used. */ + RTFUZZCTXMUTATOR_INVALID = 0, + /** Flips a single bit in the input. */ + RTFUZZCTXMUTATOR_BIT_FLIP, + /** Replaces a single byte in the input. */ + RTFUZZCTXMUTATOR_BYTE_REPLACE, + /** Inserts a byte sequence into the input. */ + RTFUZZCTXMUTATOR_BYTE_SEQUENCE_INSERT, + /** Appends a byte sequence to the input. */ + RTFUZZCTXMUTATOR_BYTE_SEQUENCE_APPEND, + /** Deletes a single byte from the input. */ + RTFUZZCTXMUTATOR_BYTE_DELETE, + /** Deletes a sequence of bytes from the input. */ + RTFUZZCTXMUTATOR_BYTE_SEQUENCE_DELETE, + /** Last valid mutator. */ + RTFUZZCTXMUTATOR_LAST = RTFUZZCTXMUTATOR_BYTE_SEQUENCE_DELETE, + /** 32bit hack. */ + RTFUZZCTXMUTATOR_32BIT_HACK = 0x7fffffff +} RTFUZZCTXMUTATOR; +/** Pointer to a mutator enum. */ +typedef RTFUZZCTXMUTATOR *PRTFUZZCTXMUTATOR; + + +/** + * The fuzzer state to be exported - all members are stored in little endian form. + */ +typedef struct RTFUZZCTXSTATE +{ + /** Magic value for identification. */ + uint32_t u32Magic; + /** Size of the PRNG state following in bytes. */ + uint32_t cbPrng; + /** Number of input descriptors following. */ + uint32_t cInputs; + /** Behavioral flags. */ + uint32_t fFlagsBehavioral; + /** Maximum input size to generate. */ + uint64_t cbInputMax; +} RTFUZZCTXSTATE; +/** Pointer to a fuzzing context state. */ +typedef RTFUZZCTXSTATE *PRTFUZZCTXSTATE; + + +/** + * Mutator callback. + * + * @returns IPRT status code. + * @param pThis The fuzzer context instance. + * @param pvBuf The input buffer to mutate. + * @param cbBuf Size of the buffer in bytes. + * @param ppInputMutated Where to store the pointer to the mutated input success. + */ +typedef DECLCALLBACK(int) FNRTFUZZCTXMUTATOR(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, PPRTFUZZINPUTINT ppInputMutated); +/** Pointer to a mutator callback. */ +typedef FNRTFUZZCTXMUTATOR *PFNRTFUZZCTXMUTATOR; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) rtFuzzCtxMutatorBitFlip(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, + PPRTFUZZINPUTINT ppInputMutated); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteReplace(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, + PPRTFUZZINPUTINT ppInputMutated); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceInsert(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, + PPRTFUZZINPUTINT ppInputMutated); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceAppend(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, + PPRTFUZZINPUTINT ppInputMutated); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteDelete(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, + PPRTFUZZINPUTINT ppInputMutated); +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceDelete(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, + PPRTFUZZINPUTINT ppInputMutated); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Array of all available mutators. + */ +static PFNRTFUZZCTXMUTATOR const g_apfnMutators[] = +{ + NULL, + rtFuzzCtxMutatorBitFlip, + rtFuzzCtxMutatorByteReplace, + rtFuzzCtxMutatorByteSequenceInsert, + rtFuzzCtxMutatorByteSequenceAppend, + rtFuzzCtxMutatorByteDelete, + rtFuzzCtxMutatorByteSequenceDelete, + NULL +}; + + + +/** + * Tries to locate an input seed with the given input MD5 seed. + * + * @returns Pointer to the input seed on success or NULL if not found. + * @param pThis The fuzzer context instance. + * @param pbMd5Hash The MD5 hash to search for. + * @param fExact Flag whether to search for an exact match or return the next best fit if no match exists. + * @param ppIntermediate Where to store the pointer to the intermediate layer upon success, optional. + */ +static PRTFUZZINPUTINT rtFuzzCtxInputLocate(PRTFUZZCTXINT pThis, uint8_t *pbMd5Hash, bool fExact, PPRTFUZZINTERMEDIATE ppIntermediate) +{ + PRTFUZZINPUTINT pInput = NULL; + uint64_t u64Md5High = *(uint64_t *)&pbMd5Hash[RTMD5_HASH_SIZE / 2]; + uint64_t u64Md5Low = *(uint64_t *)&pbMd5Hash[0]; + PRTFUZZINTERMEDIATE pIntermediate = (PRTFUZZINTERMEDIATE)RTAvlU64Get(&pThis->TreeSeedsHigh, u64Md5High); + if (!fExact && !pIntermediate) + pIntermediate = (PRTFUZZINTERMEDIATE)RTAvlU64GetBestFit(&pThis->TreeSeedsHigh, u64Md5High, true /*fAbove*/); + if (!fExact && !pIntermediate) + pIntermediate = (PRTFUZZINTERMEDIATE)RTAvlU64GetBestFit(&pThis->TreeSeedsHigh, u64Md5High, false /*fAbove*/); + + if (pIntermediate) + { + /* 2nd level lookup. */ + pInput = (PRTFUZZINPUTINT)RTAvlU64Get(&pIntermediate->TreeSeedsLow, u64Md5Low); + if (!fExact && !pInput) + pInput = (PRTFUZZINPUTINT)RTAvlU64GetBestFit(&pIntermediate->TreeSeedsLow, u64Md5Low, true /*fAbove*/); + if (!fExact && !pInput) + pInput = (PRTFUZZINPUTINT)RTAvlU64GetBestFit(&pIntermediate->TreeSeedsLow, u64Md5Low, false /*fAbove*/); + } + + if (ppIntermediate) + *ppIntermediate = pIntermediate; + + return pInput; +} + + +/** + * Adds the given input to the corpus of the given fuzzer context. + * + * @returns IPRT status code. + * @param pThis The fuzzer context instance. + * @param pInput The input to add. + */ +static int rtFuzzCtxInputAdd(PRTFUZZCTXINT pThis, PRTFUZZINPUTINT pInput) +{ + int rc = VINF_SUCCESS; + uint64_t u64Md5High = *(uint64_t *)&pInput->abMd5Hash[RTMD5_HASH_SIZE / 2]; + uint64_t u64Md5Low = *(uint64_t *)&pInput->abMd5Hash[0]; + + pInput->Core.Key = u64Md5Low; + PRTFUZZINTERMEDIATE pIntermediate = (PRTFUZZINTERMEDIATE)RTAvlU64Get(&pThis->TreeSeedsHigh, u64Md5High); + if (!pIntermediate) + { + pIntermediate = (PRTFUZZINTERMEDIATE)RTMemAllocZ(sizeof(*pIntermediate)); + if (RT_LIKELY(pIntermediate)) + { + pIntermediate->Core.Key = u64Md5High; + pIntermediate->TreeSeedsLow = NULL; + bool fIns = RTAvlU64Insert(&pThis->TreeSeedsHigh, &pIntermediate->Core); + Assert(fIns); RT_NOREF(fIns); + } + else + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + AssertPtr(pIntermediate); + bool fIns = RTAvlU64Insert(&pIntermediate->TreeSeedsLow, &pInput->Core); + if (!fIns) + rc = VERR_ALREADY_EXISTS; + else + { + RTListAppend(&pThis->LstInputs, &pInput->NdInputs); + pThis->cInputs++; + RTFuzzInputRetain(pInput); + } + } + + return rc; +} + + +/** + * Returns a random input from the corpus of the given fuzzer context. + * + * @returns Pointer to a randomly picked input. + * @param pThis The fuzzer context instance. + */ +static PRTFUZZINPUTINT rtFuzzCtxInputPickRnd(PRTFUZZCTXINT pThis) +{ + /* Generate a random MD5 hash and do a non exact localisation. */ + uint8_t abDigestRnd[RTMD5_HASH_SIZE]; + RTRandAdvBytes(pThis->hRand, &abDigestRnd[0], sizeof(abDigestRnd)); + + return rtFuzzCtxInputLocate(pThis, &abDigestRnd[0], false /*fExact*/, NULL /*ppIntermediate*/); +} + + +/** + * Clones a given input. + * + * @returns Pointer to the cloned input or NULL if out of memory. + * @param pThis The fuzzer context instance. + * @param pvBuf The buffer to clone. + * @param cbBuf Size of the buffer in bytes. + */ +static PRTFUZZINPUTINT rtFuzzCtxInputClone(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf) +{ + PRTFUZZINPUTINT pInpClone = (PRTFUZZINPUTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFUZZINPUTINT, abInput[cbBuf])); + if (RT_LIKELY(pInpClone)) + { + pInpClone->cRefs = 1; + pInpClone->pFuzzer = pThis, + pInpClone->cbInput = cbBuf; + memcpy(&pInpClone->abInput[0], pvBuf, cbBuf); + } + + return pInpClone; +} + + +/** + * Creates an empty input seed capable of holding the given number of bytes. + * + * @returns Pointer to the newly created input seed. + * @param pThis The fuzzer context instance. + * @param cbInput Input seed size in bytes. + */ +static PRTFUZZINPUTINT rtFuzzCtxInputCreate(PRTFUZZCTXINT pThis, size_t cbInput) +{ + PRTFUZZINPUTINT pInput = (PRTFUZZINPUTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFUZZINPUTINT, abInput[cbInput])); + if (RT_LIKELY(pInput)) + { + pInput->pFuzzer = pThis; + pInput->cRefs = 1; + pInput->cbInput = cbInput; + } + + return pInput; +} + + +/** + * Destroys the given input. + * + * @returns nothing. + * @param pInput The input to destroy. + */ +static void rtFuzzInputDestroy(PRTFUZZINPUTINT pInput) +{ + RTMemFree(pInput); +} + + +/** + * Destorys the given fuzzer context freeing all allocated resources. + * + * @returns nothing. + * @param pThis The fuzzer context instance. + */ +static void rtFuzzCtxDestroy(PRTFUZZCTXINT pThis) +{ + RT_NOREF(pThis); +} + + +/** + * Mutator callback - flips a single bit in the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorBitFlip(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, PPRTFUZZINPUTINT ppInputMutated) +{ + int rc = VINF_SUCCESS; + PRTFUZZINPUTINT pInputMutated = rtFuzzCtxInputClone(pThis, pvBuf, cbBuf); + if (RT_LIKELY(pInputMutated)) + { + int32_t iBit = RTRandAdvS32Ex(pThis->hRand, 0, (uint32_t)cbBuf * 8 - 1); + ASMBitToggle(&pInputMutated->abInput[0], iBit); + RTMd5(&pInputMutated->abInput[0], pInputMutated->cbInput, &pInputMutated->abMd5Hash[0]); + *ppInputMutated = pInputMutated; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +/** + * Mutator callback - replaces a single byte in the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorByteReplace(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, PPRTFUZZINPUTINT ppInputMutated) +{ + int rc = VINF_SUCCESS; + PRTFUZZINPUTINT pInputMutated = rtFuzzCtxInputClone(pThis, pvBuf, cbBuf); + if (RT_LIKELY(pInputMutated)) + { + uint8_t *pbBuf = (uint8_t *)pvBuf; + uint32_t offByte = RTRandAdvU32Ex(pThis->hRand, 0, (uint32_t)cbBuf - 1); + RTRandAdvBytes(pThis->hRand, &pInputMutated->abInput[offByte], 1); + if (pbBuf[offByte] != pInputMutated->abInput[offByte]) + { + RTMd5(&pInputMutated->abInput[0], pInputMutated->cbInput, &pInputMutated->abMd5Hash[0]); + *ppInputMutated = pInputMutated; + } + else + RTMemFree(pInputMutated); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +/** + * Mutator callback - inserts a byte sequence into the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceInsert(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, PPRTFUZZINPUTINT ppInputMutated) +{ + int rc = VINF_SUCCESS; + if (cbBuf < pThis->cbInputMax) + { + size_t cbInputMutated = RTRandAdvU32Ex(pThis->hRand, (uint32_t)cbBuf + 1, (uint32_t)pThis->cbInputMax); + size_t cbInsert = cbInputMutated - cbBuf; + uint32_t offInsert = RTRandAdvU32Ex(pThis->hRand, 0, (uint32_t)cbBuf); + uint8_t *pbBuf = (uint8_t *)pvBuf; + PRTFUZZINPUTINT pInputMutated = rtFuzzCtxInputCreate(pThis, cbInputMutated); + if (RT_LIKELY(pInputMutated)) + { + if (offInsert) + memcpy(&pInputMutated->abInput[0], pbBuf, offInsert); + RTRandAdvBytes(pThis->hRand, &pInputMutated->abInput[offInsert], cbInsert); + memcpy(&pInputMutated->abInput[offInsert + cbInsert], &pbBuf[offInsert], cbBuf - offInsert); + RTMd5(&pInputMutated->abInput[0], pInputMutated->cbInput, &pInputMutated->abMd5Hash[0]); + *ppInputMutated = pInputMutated; + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * Mutator callback - appends a byte sequence to the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceAppend(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, PPRTFUZZINPUTINT ppInputMutated) +{ + int rc = VINF_SUCCESS; + if (cbBuf < pThis->cbInputMax) + { + size_t cbInputMutated = RTRandAdvU32Ex(pThis->hRand, (uint32_t)cbBuf + 1, (uint32_t)pThis->cbInputMax); + size_t cbInsert = cbInputMutated - cbBuf; + PRTFUZZINPUTINT pInputMutated = rtFuzzCtxInputCreate(pThis, cbInputMutated); + if (RT_LIKELY(pInputMutated)) + { + memcpy(&pInputMutated->abInput[0], pvBuf, cbBuf); + RTRandAdvBytes(pThis->hRand, &pInputMutated->abInput[cbBuf], cbInsert); + RTMd5(&pInputMutated->abInput[0], pInputMutated->cbInput, &pInputMutated->abMd5Hash[0]); + *ppInputMutated = pInputMutated; + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * Mutator callback - deletes a single byte in the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorByteDelete(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, PPRTFUZZINPUTINT ppInputMutated) +{ + int rc = VINF_SUCCESS; + if (cbBuf > 1) + { + uint32_t offDelete = RTRandAdvU32Ex(pThis->hRand, 0, (uint32_t)cbBuf - 1); + uint8_t *pbBuf = (uint8_t *)pvBuf; + PRTFUZZINPUTINT pInputMutated = rtFuzzCtxInputCreate(pThis, cbBuf - 1); + if (RT_LIKELY(pInputMutated)) + { + if (offDelete) + memcpy(&pInputMutated->abInput[0], pbBuf, offDelete); + if (offDelete < cbBuf - 1) + memcpy(&pInputMutated->abInput[offDelete], &pbBuf[offDelete + 1], cbBuf - offDelete - 1); + RTMd5(&pInputMutated->abInput[0], pInputMutated->cbInput, &pInputMutated->abMd5Hash[0]); + *ppInputMutated = pInputMutated; + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * Mutator callback - deletes a byte sequence in the input. + */ +static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceDelete(PRTFUZZCTXINT pThis, const void *pvBuf, size_t cbBuf, PPRTFUZZINPUTINT ppInputMutated) +{ + int rc = VINF_SUCCESS; + if (cbBuf > 1) + { + size_t cbInputMutated = RTRandAdvU32Ex(pThis->hRand, 0, (uint32_t)cbBuf - 1); + size_t cbDel = cbBuf - cbInputMutated; + uint32_t offDel = RTRandAdvU32Ex(pThis->hRand, 0, (uint32_t)(cbBuf - cbDel)); + uint8_t *pbBuf = (uint8_t *)pvBuf; + + PRTFUZZINPUTINT pInputMutated = rtFuzzCtxInputCreate(pThis, cbInputMutated); + if (RT_LIKELY(pInputMutated)) + { + if (offDel) + memcpy(&pInputMutated->abInput[0], pbBuf, offDel); + memcpy(&pInputMutated->abInput[offDel], &pbBuf[offDel + cbDel], cbBuf - offDel - cbDel); + RTMd5(&pInputMutated->abInput[0], pInputMutated->cbInput, &pInputMutated->abMd5Hash[0]); + *ppInputMutated = pInputMutated; + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +static PRTFUZZCTXINT rtFuzzCtxCreateEmpty(void) +{ + PRTFUZZCTXINT pThis = (PRTFUZZCTXINT)RTMemAllocZ(sizeof(*pThis)); + if (RT_LIKELY(pThis)) + { + pThis->u32Magic = RTFUZZCTX_MAGIC; + pThis->cRefs = 1; + pThis->TreeSeedsHigh = NULL; + pThis->cbInputMax = UINT32_MAX; + pThis->cInputs = 0; + pThis->fFlagsBehavioral = 0; + RTListInit(&pThis->LstInputs); + + int rc = RTRandAdvCreateParkMiller(&pThis->hRand); + if (RT_SUCCESS(rc)) + { + RTRandAdvSeed(pThis->hRand, RTTimeSystemNanoTS()); + return pThis; + } + + RTMemFree(pThis); + } + + return NULL; +} + + +RTDECL(int) RTFuzzCtxCreate(PRTFUZZCTX phFuzzCtx) +{ + AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + PRTFUZZCTXINT pThis = rtFuzzCtxCreateEmpty(); + if (RT_LIKELY(pThis)) + { + *phFuzzCtx = pThis; + return VINF_SUCCESS; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +RTDECL(int) RTFuzzCtxCreateFromState(PRTFUZZCTX phFuzzCtx, const void *pvState, size_t cbState) +{ + AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvState, VERR_INVALID_POINTER); + AssertReturn(cbState > 0, VERR_INVALID_PARAMETER); + + 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; +} + + +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 = RTFuzzCtxCreateFromState(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) RTFuzzCtxStateExport(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); + + 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); + StateExport.cbPrng = RT_H2LE_U32((uint32_t)cbPrng); + StateExport.cInputs = RT_H2LE_U32(pThis->cInputs); + StateExport.fFlagsBehavioral = RT_H2LE_U32(pThis->fFlagsBehavioral); + StateExport.cbInputMax = RT_H2LE_U64(pThis->cbInputMax); + + /* Estimate the size of the required state. */ + size_t cbState = sizeof(StateExport) + + cbPrng + + pThis->cInputs * ((pThis->cbInputMax < _1M ? pThis->cbInputMax : _64K) + sizeof(uint32_t)); /* For the size indicator before each input. */ + uint8_t *pbState = (uint8_t *)RTMemAllocZ(cbState); + if (RT_LIKELY(pbState)) + { + size_t offState = 0; + memcpy(pbState, &StateExport, sizeof(StateExport)); + offState += sizeof(StateExport); + memcpy(&pbState[offState], &aszPrngExport[0], cbPrng); + offState += cbPrng; + + /* Export each input. */ + PRTFUZZINPUTINT pIt; + RTListForEach(&pThis->LstInputs, pIt, RTFUZZINPUTINT, NdInputs) + { + /* Ensure buffer size. */ + if (offState + pIt->cbInput + sizeof(uint32_t) > cbState) + { + uint8_t *pbStateNew = (uint8_t *)RTMemRealloc(pbState, cbState + pIt->cbInput + sizeof(uint32_t)); + if (RT_LIKELY(pbStateNew)) + { + pbState = pbStateNew; + cbState += pIt->cbInput + sizeof(uint32_t); + } + else + { + rc = VERR_NO_MEMORY; + break; + } + } + + *(uint32_t *)&pbState[offState] = RT_H2LE_U32((uint32_t)pIt->cbInput); + offState += sizeof(uint32_t); + memcpy(&pbState[offState], &pIt->abInput[0], pIt->cbInput); + offState += pIt->cbInput; + } + + if (RT_SUCCESS(rc)) + { + *ppvState = pbState; + *pcbState = offState; + } + else + RTMemFree(pbState); + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +RTDECL(int) RTFuzzCtxStateExportToFile(RTFUZZCTX hFuzzCtx, const char *pszFilename) +{ + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + + void *pvState = NULL; + size_t cbState = 0; + int rc = RTFuzzCtxStateExport(hFuzzCtx, &pvState, &cbState); + 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, pvState, cbState, NULL); + RTFileClose(hFile); + if (RT_FAILURE(rc)) + RTFileDelete(pszFilename); + } + + RTMemFree(pvState); + } + + 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); + + /* Generate MD5 checksum and try to locate input. */ + int rc = VINF_SUCCESS; + uint8_t abDigest[RTMD5_HASH_SIZE]; + RTMd5(pvInput, cbInput, &abDigest[0]); + + PRTFUZZINPUTINT pInput = rtFuzzCtxInputLocate(pThis, &abDigest[0], true /*fExact*/, NULL /*ppIntermediate*/); + if (!pInput) + { + pInput = (PRTFUZZINPUTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFUZZINPUTINT, abInput[cbInput])); + if (RT_LIKELY(pInput)) + { + pInput->cRefs = 1; + pInput->pFuzzer = pThis; + pInput->cbInput = cbInput; + memcpy(&pInput->abInput[0], pvInput, cbInput); + memcpy(&pInput->abMd5Hash[0], &abDigest[0], sizeof(abDigest)); + rc = rtFuzzCtxInputAdd(pThis, pInput); + if (RT_FAILURE(rc)) + RTMemFree(pInput); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_ALREADY_EXISTS; + + 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; + int rc = RTVfsFileGetSize(hVfsFile, &cbFile); + if (RT_SUCCESS(rc)) + { + PRTFUZZINPUTINT pInput = (PRTFUZZINPUTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFUZZINPUTINT, abInput[cbFile])); + if (RT_LIKELY(pInput)) + { + pInput->cRefs = 1; + pInput->pFuzzer = pThis; + pInput->cbInput = cbFile; + + rc = RTVfsFileRead(hVfsFile, &pInput->abInput[0], cbFile, NULL); + if (RT_SUCCESS(rc)) + { + /* Generate MD5 checksum and try to locate input. */ + uint8_t abDigest[RTMD5_HASH_SIZE]; + RTMd5(&pInput->abInput[0], cbFile, &abDigest[0]); + + if (!rtFuzzCtxInputLocate(pThis, &abDigest[0], true /*fExact*/, NULL /*ppIntermediate*/)) + { + memcpy(&pInput->abMd5Hash[0], &abDigest[0], sizeof(abDigest)); + rc = rtFuzzCtxInputAdd(pThis, pInput); + } + else + rc = VERR_ALREADY_EXISTS; + } + + if (RT_FAILURE(rc)) + RTMemFree(pInput); + } + else + rc = VERR_NO_MEMORY; + } + + 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; + PRTFUZZINPUTINT pSrc = rtFuzzCtxInputPickRnd(pThis); + do + { + RTFUZZCTXMUTATOR enmMutator = (RTFUZZCTXMUTATOR)RTRandAdvU32Ex(pThis->hRand, 1, RTFUZZCTXMUTATOR_LAST); + PRTFUZZINPUTINT pInput = NULL; + rc = g_apfnMutators[enmMutator](pThis, &pSrc->abInput[0], pSrc->cbInput, &pInput); + if ( RT_SUCCESS(rc) + && VALID_PTR(pInput)) + { + if (pThis->fFlagsBehavioral & RTFUZZCTX_F_BEHAVIORAL_ADD_INPUT_AUTOMATICALLY_TO_CORPUS) + rtFuzzCtxInputAdd(pThis, pInput); + *phFuzzInput = pInput; + return rc; + } + } while (++cTries <= 50); + + if (RT_SUCCESS(rc)) + rc = VERR_INVALID_STATE; + + return rc; +} + + +RTDECL(int) RTFuzzCtxMutateBuffer(RTFUZZCTX hFuzzCtx, void *pvBuf, size_t cbBuf, PRTFUZZINPUT phFuzzInput) +{ + int rc = VINF_SUCCESS; + PRTFUZZCTXINT pThis = hFuzzCtx; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(phFuzzInput, VERR_INVALID_POINTER); + + uint32_t cTries = 0; + do + { + RTFUZZCTXMUTATOR enmMutator = (RTFUZZCTXMUTATOR)RTRandAdvU32Ex(pThis->hRand, 1, RTFUZZCTXMUTATOR_LAST); + PRTFUZZINPUTINT pInput = NULL; + rc = g_apfnMutators[enmMutator](pThis, pvBuf, cbBuf, &pInput); + if ( RT_SUCCESS(rc) + && VALID_PTR(pInput)) + { + *phFuzzInput = pInput; + return rc; + } + } while (++cTries <= 50); + + if (RT_SUCCESS(rc)) + rc = VERR_INVALID_STATE; + + return rc; +} + + +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) RTFuzzInputQueryData(RTFUZZINPUT hFuzzInput, void **ppv, size_t *pcb) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(ppv, VERR_INVALID_POINTER); + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + *ppv = &pThis->abInput[0]; + *pcb = pThis->cbInput; + return VINF_SUCCESS; +} + + +RTDECL(int) RTFuzzInputQueryDigestString(RTFUZZINPUT hFuzzInput, char *pszDigest, size_t cchDigest) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pszDigest, VERR_INVALID_POINTER); + AssertReturn(cchDigest >= RTMD5_STRING_LEN + 1, VERR_INVALID_PARAMETER); + + return RTMd5ToString(&pThis->abMd5Hash[0], pszDigest, cchDigest); +} + + +RTDECL(int) RTFuzzInputWriteToFile(RTFUZZINPUT hFuzzInput, const char *pszFilename) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + 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 = RTFileWrite(hFile, &pThis->abInput[0], pThis->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 rtFuzzCtxInputAdd(pThis->pFuzzer, pThis); +} + + +RTDECL(int) RTFuzzInputRemoveFromCtxCorpus(RTFUZZINPUT hFuzzInput) +{ + PRTFUZZINPUTINT pThis = hFuzzInput; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + + 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; + + return rc; +} + diff --git a/src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp b/src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp new file mode 100644 index 00000000..701cdd0b --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp @@ -0,0 +1,216 @@ +/* $Id: fuzzclientcmd.cpp $ */ +/** @file + * IPRT - Fuzzing framework API, fuzzed client command. + */ + +/* + * Copyright (C) 2018-2019 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/getopt.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> + + +/** + * 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; + /** Standard input VFS handle. */ + RTVFSIOSTREAM hVfsStdIn; + /** Standard output VFS handle. */ + RTVFSIOSTREAM hVfsStdOut; +} RTFUZZCMDCLIENT; +/** Pointer to a fuzzing client command state. */ +typedef RTFUZZCMDCLIENT *PRTFUZZCMDCLIENT; + + +/** + * 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 = RTFuzzInputQueryData(hFuzzInput, &pv, &cb); + if (RT_SUCCESS(rc)) + { + char bResp = '.'; + int rc2 = pThis->pfnConsume(pv, cb, pThis->pvUser); + 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 = RTFuzzCtxCreateFromState(&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; +} + + +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 }, + }; + + 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; + + This.pfnConsume = pfnConsume; + This.pvUser = pvUser; + + /* Argument parsing loop. */ + bool fContinue = true; + do + { + RTGETOPTUNION ValueUnion; + int chOpt = RTGetOpt(&GetState, &ValueUnion); + switch (chOpt) + { + case 0: + fContinue = 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) + rcExit = rtFuzzCmdClientRun(&This); + } + 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..b73ac07d --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp @@ -0,0 +1,1351 @@ +/* $Id: fuzzmastercmd.cpp $ */ +/** @file + * IPRT - Fuzzing framework API, master command. + */ + +/* + * Copyright (C) 2018-2019 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/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/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; + /** The fuzzing observer state handle. */ + RTFUZZOBS hFuzzObs; + /** Flag whether fuzzing was started. */ + bool fStarted; +} 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 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 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); + } + + 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); + + 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 = RTFuzzObsCreate(&pFuzzRun->hFuzzObs); + if (RT_SUCCESS(rc)) + { + rc = rtFuzzCmdMasterFuzzRunProcessBinaryCfg(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessArgCfg(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)) + { + /* Create temp directories. */ + char szTmpDir[RTPATH_MAX]; + 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 (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 = RTFuzzObsSetResultDirectory(pFuzzRun->hFuzzObs, szTmpDir); + if (RT_SUCCESS(rc)) + { + /* Start fuzzing. */ + RTListAppend(&pThis->LstFuzzed, &pFuzzRun->NdFuzzed); + rc = RTFuzzObsExecStart(pFuzzRun->hFuzzObs, pFuzzRun->cProcs); + if (RT_SUCCESS(rc)) + pFuzzRun->fStarted = true; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to start fuzzing with %Rrc", rc); + } + else + 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); + } + } + } + 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 = RTFuzzCtxStateExport(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; +} + + +/** + * 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) +{ + PRTFUZZRUN pFuzzRun; + int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + { + RTFUZZOBSSTATS Stats; + rc = RTFuzzObsQueryStats(pFuzzRun->hFuzzObs, &Stats); + if (RT_SUCCESS(rc)) + { + const char s_szStats[] = "{ \"FuzzedInputsPerSec\": %u\n" + " \"FuzzedInputs\": %u\n" + " \"FuzzedInputsHang\": %u\n" + " \"FuzzedInputsCrash\": %u\n}"; + pThis->pszResponse = RTStrAPrintf2(s_szStats, Stats.cFuzzedInputsPerSec, + Stats.cFuzzedInputs, Stats.cFuzzedInputsHang, Stats.cFuzzedInputsCrash); + 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 query fuzzing statistics with %Rrc", rc); + } + + 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)) + { + 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; +} + |