diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp')
-rw-r--r-- | src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp | 1877 |
1 files changed, 1877 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp b/src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp new file mode 100644 index 00000000..562218e1 --- /dev/null +++ b/src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp @@ -0,0 +1,1877 @@ +/* $Id: fuzzmastercmd.cpp $ */ +/** @file + * IPRT - Fuzzing framework API, master command. + */ + +/* + * Copyright (C) 2018-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/fuzz.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/base64.h> +#include <iprt/buildconfig.h> +#include <iprt/ctype.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/json.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/tcp.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/vfs.h> +#include <iprt/zip.h> + + +/** + * A running fuzzer state. + */ +typedef struct RTFUZZRUN +{ + /** List node. */ + RTLISTNODE NdFuzzed; + /** Identifier. */ + char *pszId; + /** Number of processes. */ + uint32_t cProcs; + /** Target recorder flags. */ + uint32_t fTgtRecFlags; + /** The fuzzing observer state handle. */ + RTFUZZOBS hFuzzObs; + /** Flag whether fuzzing was started. */ + bool fStarted; + /** Time when this run was created. */ + RTTIME TimeCreated; + /** Millisecond timestamp when the run was created. */ + uint64_t tsCreatedMs; +} RTFUZZRUN; +/** Pointer to a running fuzzer state. */ +typedef RTFUZZRUN *PRTFUZZRUN; + + +/** + * Fuzzing master command state. + */ +typedef struct RTFUZZCMDMASTER +{ + /** List of running fuzzers. */ + RTLISTANCHOR LstFuzzed; + /** The port to listen on. */ + uint16_t uPort; + /** The TCP server for requests. */ + PRTTCPSERVER hTcpSrv; + /** The root temp directory. */ + const char *pszTmpDir; + /** The root results directory. */ + const char *pszResultsDir; + /** Flag whether to shutdown. */ + bool fShutdown; + /** The response message. */ + char *pszResponse; +} RTFUZZCMDMASTER; +/** Pointer to a fuzzing master command state. */ +typedef RTFUZZCMDMASTER *PRTFUZZCMDMASTER; + + +/** + * Wrapper around RTErrInfoSetV / RTMsgErrorV. + * + * @returns @a rc + * @param pErrInfo Extended error info. + * @param rc The return code. + * @param pszFormat The message format. + * @param ... The message format arguments. + */ +static int rtFuzzCmdMasterErrorRc(PRTERRINFO pErrInfo, int rc, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + if (pErrInfo) + RTErrInfoSetV(pErrInfo, rc, pszFormat, va); + else + RTMsgErrorV(pszFormat, va); + va_end(va); + return rc; +} + + +/** + * Returns a running fuzzer state by the given ID. + * + * @returns Pointer to the running fuzzer state or NULL if not found. + * @param pThis The fuzzing master command state. + * @param pszId The ID to look for. + */ +static PRTFUZZRUN rtFuzzCmdMasterGetFuzzerById(PRTFUZZCMDMASTER pThis, const char *pszId) +{ + PRTFUZZRUN pIt = NULL; + RTListForEach(&pThis->LstFuzzed, pIt, RTFUZZRUN, NdFuzzed) + { + if (!RTStrCmp(pIt->pszId, pszId)) + return pIt; + } + + return NULL; +} + + +#if 0 /* unused */ +/** + * Processes and returns the value of the given config item in the JSON request. + * + * @returns IPRT status code. + * @param ppszStr Where to store the pointer to the string on success. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessCfgString(char **ppszStr, const char *pszCfgItem, RTJSONVAL hJsonCfg, PRTERRINFO pErrInfo) +{ + int rc = RTJsonValueQueryStringByName(hJsonCfg, pszCfgItem, ppszStr); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query string value of \"%s\"", pszCfgItem); + + return rc; +} + + +/** + * Processes and returns the value of the given config item in the JSON request. + * + * @returns IPRT status code. + * @param pfVal Where to store the config value on success. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessCfgBool(bool *pfVal, const char *pszCfgItem, RTJSONVAL hJsonCfg, PRTERRINFO pErrInfo) +{ + int rc = RTJsonValueQueryBooleanByName(hJsonCfg, pszCfgItem, pfVal); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query boolean value of \"%s\"", pszCfgItem); + + return rc; +} + + +/** + * Processes and returns the value of the given config item in the JSON request. + * + * @returns IPRT status code. + * @param pfVal Where to store the config value on success. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param fDef Default value if the item wasn't found. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessCfgBoolDef(bool *pfVal, const char *pszCfgItem, RTJSONVAL hJsonCfg, bool fDef, PRTERRINFO pErrInfo) +{ + int rc = RTJsonValueQueryBooleanByName(hJsonCfg, pszCfgItem, pfVal); + if (rc == VERR_NOT_FOUND) + { + *pfVal = fDef; + rc = VINF_SUCCESS; + } + else if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query boolean value of \"%s\"", pszCfgItem); + + return rc; +} +#endif + + +/** + * Processes and returns the value of the given config item in the JSON request. + * + * @returns IPRT status code. + * @param pcbVal Where to store the config value on success. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param cbDef Default value if the item wasn't found. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessCfgSizeDef(size_t *pcbVal, const char *pszCfgItem, RTJSONVAL hJsonCfg, size_t cbDef, PRTERRINFO pErrInfo) +{ + *pcbVal = cbDef; /* Make GCC 6.3.0 happy. */ + + int64_t i64Val = 0; + int rc = RTJsonValueQueryIntegerByName(hJsonCfg, pszCfgItem, &i64Val); + if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; + else if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query size_t value of \"%s\"", pszCfgItem); + else if (i64Val < 0 || (size_t)i64Val != (uint64_t)i64Val) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_OUT_OF_RANGE, "JSON request malformed: Integer \"%s\" is out of range", pszCfgItem); + else + *pcbVal = (size_t)i64Val; + + return rc; +} + + +/** + * Processes and returns the value of the given config item in the JSON request. + * + * @returns IPRT status code. + * @param pcbVal Where to store the config value on success. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param cbDef Default value if the item wasn't found. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessCfgU32Def(uint32_t *pu32Val, const char *pszCfgItem, RTJSONVAL hJsonCfg, uint32_t u32Def, PRTERRINFO pErrInfo) +{ + int64_t i64Val = 0; + int rc = RTJsonValueQueryIntegerByName(hJsonCfg, pszCfgItem, &i64Val); + if (rc == VERR_NOT_FOUND) + { + *pu32Val = u32Def; + rc = VINF_SUCCESS; + } + else if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query uint32_t value of \"%s\"", pszCfgItem); + else if (i64Val < 0 || (uint32_t)i64Val != (uint64_t)i64Val) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_OUT_OF_RANGE, "JSON request malformed: Integer \"%s\" is out of range", pszCfgItem); + else + *pu32Val = (uint32_t)i64Val; + + return rc; +} + + +/** + * Returns the configured input channel for the binary under test. + * + * @returns Selected input channel or RTFUZZOBSINPUTCHAN_INVALID if an error occurred. + * @param pszCfgItem The config item to resolve. + * @param hJsonCfg The JSON object containing the item. + * @param enmChanDef Default value if the item wasn't found. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static RTFUZZOBSINPUTCHAN rtFuzzCmdMasterFuzzRunProcessCfgGetInputChan(const char *pszCfgItem, RTJSONVAL hJsonCfg, RTFUZZOBSINPUTCHAN enmChanDef, PRTERRINFO pErrInfo) +{ + RTFUZZOBSINPUTCHAN enmInputChan = RTFUZZOBSINPUTCHAN_INVALID; + + RTJSONVAL hJsonVal; + int rc = RTJsonValueQueryByName(hJsonCfg, pszCfgItem, &hJsonVal); + if (rc == VERR_NOT_FOUND) + enmInputChan = enmChanDef; + else if (RT_SUCCESS(rc)) + { + const char *pszBinary = RTJsonValueGetString(hJsonVal); + if (pszBinary) + { + if (!RTStrCmp(pszBinary, "File")) + enmInputChan = RTFUZZOBSINPUTCHAN_FILE; + else if (!RTStrCmp(pszBinary, "Stdin")) + enmInputChan = RTFUZZOBSINPUTCHAN_STDIN; + else if (!RTStrCmp(pszBinary, "FuzzingAware")) + enmInputChan = RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT; + else + rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_PARAMETER, "JSON request malformed: \"%s\" for \"%s\" is not known", pszCfgItem, pszBinary); + } + else + rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"%s\" is not a string", pszCfgItem); + + RTJsonValueRelease(hJsonVal); + } + else + rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query \"%s\"", pszCfgItem); + + return enmInputChan; +} + + +/** + * Processes binary related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessBinaryCfg(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonVal; + int rc = RTJsonValueQueryByName(hJsonRoot, "BinaryPath", &hJsonVal); + if (RT_SUCCESS(rc)) + { + const char *pszBinary = RTJsonValueGetString(hJsonVal); + if (RT_LIKELY(pszBinary)) + { + RTFUZZOBSINPUTCHAN enmInputChan = rtFuzzCmdMasterFuzzRunProcessCfgGetInputChan("InputChannel", hJsonRoot, RTFUZZOBSINPUTCHAN_STDIN, pErrInfo); + if (enmInputChan != RTFUZZOBSINPUTCHAN_INVALID) + { + rc = RTFuzzObsSetTestBinary(pFuzzRun->hFuzzObs, pszBinary, enmInputChan); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to add the binary path for the fuzzing run"); + } + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"BinaryPath\" is not a string"); + RTJsonValueRelease(hJsonVal); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query value of \"BinaryPath\""); + + return rc; +} + + +/** + * Processes argument related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessArgCfg(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValArgArray; + int rc = RTJsonValueQueryByName(hJsonRoot, "Arguments", &hJsonValArgArray); + if (RT_SUCCESS(rc)) + { + unsigned cArgs = 0; + rc = RTJsonValueQueryArraySize(hJsonValArgArray, &cArgs); + if (RT_SUCCESS(rc)) + { + if (cArgs > 0) + { + const char **papszArgs = (const char **)RTMemAllocZ(cArgs * sizeof(const char *)); + RTJSONVAL *pahJsonVal = (RTJSONVAL *)RTMemAllocZ(cArgs * sizeof(RTJSONVAL)); + if (RT_LIKELY(papszArgs && pahJsonVal)) + { + unsigned idx = 0; + + for (idx = 0; idx < cArgs && RT_SUCCESS(rc); idx++) + { + rc = RTJsonValueQueryByIndex(hJsonValArgArray, idx, &pahJsonVal[idx]); + if (RT_SUCCESS(rc)) + { + papszArgs[idx] = RTJsonValueGetString(pahJsonVal[idx]); + if (RT_UNLIKELY(!papszArgs[idx])) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Argument %u is not a string", idx); + } + } + + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsSetTestBinaryArgs(pFuzzRun->hFuzzObs, papszArgs, cArgs); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to set arguments for the fuzzing run"); + } + + /* Release queried values. */ + while (idx > 0) + { + RTJsonValueRelease(pahJsonVal[idx - 1]); + idx--; + } + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_MEMORY, "Out of memory allocating memory for the argument vector"); + + if (papszArgs) + RTMemFree(papszArgs); + if (pahJsonVal) + RTMemFree(pahJsonVal); + } + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"Arguments\" is not an array"); + RTJsonValueRelease(hJsonValArgArray); + } + + return rc; +} + + +/** + * Processes process environment related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessEnvironment(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValEnv; + int rc = RTJsonValueQueryByName(hJsonRoot, "Env", &hJsonValEnv); + if (RT_SUCCESS(rc)) + { + bool fReplaceEnv = false; /* false means to append everything to the default block. */ + + rc = RTJsonValueQueryBooleanByName(hJsonRoot, "EnvReplace", &fReplaceEnv); + if ( RT_SUCCESS(rc) + || rc == VERR_NOT_FOUND) + { + RTJSONIT hEnvIt; + RTENV hEnv = NULL; + + if (fReplaceEnv) + rc = RTEnvCreate(&hEnv); + else + rc = RTEnvClone(&hEnv, RTENV_DEFAULT); + + if (RT_SUCCESS(rc)) + { + rc = RTJsonIteratorBeginArray(hJsonValEnv, &hEnvIt); + if (RT_SUCCESS(rc)) + { + do + { + RTJSONVAL hVal; + rc = RTJsonIteratorQueryValue(hEnvIt, &hVal, NULL); + if (RT_SUCCESS(rc)) + { + const char *pszVar = RTJsonValueGetString(hVal); + if (RT_LIKELY(pszVar)) + rc = RTEnvPutEx(hEnv, pszVar); + RTJsonValueRelease(hVal); + } + rc = RTJsonIteratorNext(hEnvIt); + } while (RT_SUCCESS(rc)); + + if ( rc == VERR_JSON_IS_EMPTY + || rc == VERR_JSON_ITERATOR_END) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to parse environment"); + + RTJsonIteratorFree(hEnvIt); + } + else if (rc == VERR_JSON_IS_EMPTY) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"Environment\" is not an array"); + + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsSetTestBinaryEnv(pFuzzRun->hFuzzObs, hEnv); + AssertRC(rc); + } + else if (hEnv) + RTEnvDestroy(hEnv); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to create environment block"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query \"EnvReplace\""); + + RTJsonValueRelease(hJsonValEnv); + } + else if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; /* Just keep using the default environment. */ + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query the \"Environment\""); + + return rc; +} + + +/** + * Processes process environment related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessSanitizers(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValSan; + int rc = RTJsonValueQueryByName(hJsonRoot, "Sanitizers", &hJsonValSan); + if (RT_SUCCESS(rc)) + { + uint32_t fSanitizers = 0; + RTJSONIT hSanIt; + rc = RTJsonIteratorBeginArray(hJsonValSan, &hSanIt); + if (RT_SUCCESS(rc)) + { + do + { + RTJSONVAL hVal; + rc = RTJsonIteratorQueryValue(hSanIt, &hVal, NULL); + if (RT_SUCCESS(rc)) + { + const char *pszSan = RTJsonValueGetString(hVal); + if (!RTStrICmp(pszSan, "Asan")) + fSanitizers |= RTFUZZOBS_SANITIZER_F_ASAN; + else if (!RTStrICmp(pszSan, "SanCov")) + fSanitizers |= RTFUZZOBS_SANITIZER_F_SANCOV; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NOT_FOUND, "JSON request malformed: The sanitizer '%s' is not known", pszSan); + RTJsonValueRelease(hVal); + } + rc = RTJsonIteratorNext(hSanIt); + } while (RT_SUCCESS(rc)); + + if ( rc == VERR_JSON_IS_EMPTY + || rc == VERR_JSON_ITERATOR_END) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to parse sanitizers"); + + RTJsonIteratorFree(hSanIt); + } + else if (rc == VERR_JSON_IS_EMPTY) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"Sanitizers\" is not an array"); + + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsSetTestBinarySanitizers(pFuzzRun->hFuzzObs, fSanitizers); + AssertRC(rc); + } + + RTJsonValueRelease(hJsonValSan); + } + else if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; /* Just keep using the defaults. */ + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query the \"Sanitizers\""); + + return rc; +} + + +/** + * Processes the given seed and adds it to the input corpus. + * + * @returns IPRT status code. + * @param hFuzzCtx The fuzzing context handle. + * @param pszCompression Compression used for the seed. + * @param pszSeed The seed as a base64 encoded string. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessSeed(RTFUZZCTX hFuzzCtx, const char *pszCompression, const char *pszSeed, PRTERRINFO pErrInfo) +{ + int rc = VINF_SUCCESS; + ssize_t cbSeedDecoded = RTBase64DecodedSize(pszSeed, NULL); + if (cbSeedDecoded > 0) + { + uint8_t *pbSeedDecoded = (uint8_t *)RTMemAllocZ(cbSeedDecoded); + if (RT_LIKELY(pbSeedDecoded)) + { + rc = RTBase64Decode(pszSeed, pbSeedDecoded, cbSeedDecoded, NULL, NULL); + if (RT_SUCCESS(rc)) + { + /* Decompress if applicable. */ + if (!RTStrICmp(pszCompression, "None")) + rc = RTFuzzCtxCorpusInputAdd(hFuzzCtx, pbSeedDecoded, cbSeedDecoded); + else + { + RTVFSIOSTREAM hVfsIosSeed; + rc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pbSeedDecoded, cbSeedDecoded, &hVfsIosSeed); + if (RT_SUCCESS(rc)) + { + RTVFSIOSTREAM hVfsDecomp = NIL_RTVFSIOSTREAM; + + if (!RTStrICmp(pszCompression, "Gzip")) + rc = RTZipGzipDecompressIoStream(hVfsIosSeed, RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR, &hVfsDecomp); + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Compression \"%s\" is not known", pszCompression); + + if (RT_SUCCESS(rc)) + { + RTVFSFILE hVfsFile; + rc = RTVfsMemFileCreate(hVfsDecomp, 2 * _1M, &hVfsFile); + if (RT_SUCCESS(rc)) + { + rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + { + /* The VFS file contains the buffer for the seed now. */ + rc = RTFuzzCtxCorpusInputAddFromVfsFile(hFuzzCtx, hVfsFile); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to add input seed"); + RTVfsFileRelease(hVfsFile); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to seek to the beginning of the seed"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to decompress input seed"); + + RTVfsIoStrmRelease(hVfsDecomp); + } + + RTVfsIoStrmRelease(hVfsIosSeed); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create I/O stream from seed buffer"); + } + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to decode the seed string"); + + RTMemFree(pbSeedDecoded); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_MEMORY, "Request error: Failed to allocate %zd bytes of memory for the seed", cbSeedDecoded); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: Couldn't find \"Seed\" doesn't contain a base64 encoded value"); + + return rc; +} + + +/** + * Processes a signle input seed for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonSeed The seed node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessInputSeedSingle(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonSeed, PRTERRINFO pErrInfo) +{ + RTFUZZCTX hFuzzCtx; + int rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonValComp; + rc = RTJsonValueQueryByName(hJsonSeed, "Compression", &hJsonValComp); + if (RT_SUCCESS(rc)) + { + const char *pszCompression = RTJsonValueGetString(hJsonValComp); + if (RT_LIKELY(pszCompression)) + { + RTJSONVAL hJsonValSeed; + rc = RTJsonValueQueryByName(hJsonSeed, "Seed", &hJsonValSeed); + if (RT_SUCCESS(rc)) + { + const char *pszSeed = RTJsonValueGetString(hJsonValSeed); + if (RT_LIKELY(pszSeed)) + rc = rtFuzzCmdMasterFuzzRunProcessSeed(hFuzzCtx, pszCompression, pszSeed, pErrInfo); + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"Seed\" value is not a string"); + + RTJsonValueRelease(hJsonValSeed); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Seed\" value"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"Compression\" value is not a string"); + + RTJsonValueRelease(hJsonValComp); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Compression\" value"); + + RTFuzzCtxRelease(hFuzzCtx); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to query fuzzing context from observer"); + + return rc; +} + + +/** + * Processes the given seed file and adds it to the input corpus. + * + * @returns IPRT status code. + * @param hFuzzCtx The fuzzing context handle. + * @param pszCompression Compression used for the seed. + * @param pszSeed The seed as a base64 encoded string. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessSeedFile(RTFUZZCTX hFuzzCtx, const char *pszCompression, const char *pszFile, PRTERRINFO pErrInfo) +{ + int rc = VINF_SUCCESS; + + /* Decompress if applicable. */ + if (!RTStrICmp(pszCompression, "None")) + rc = RTFuzzCtxCorpusInputAddFromFile(hFuzzCtx, pszFile); + else + { + RTVFSIOSTREAM hVfsIosSeed; + rc = RTVfsIoStrmOpenNormal(pszFile, RTFILE_O_OPEN | RTFILE_O_READ, &hVfsIosSeed); + if (RT_SUCCESS(rc)) + { + RTVFSIOSTREAM hVfsDecomp = NIL_RTVFSIOSTREAM; + + if (!RTStrICmp(pszCompression, "Gzip")) + rc = RTZipGzipDecompressIoStream(hVfsIosSeed, RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR, &hVfsDecomp); + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Compression \"%s\" is not known", pszCompression); + + if (RT_SUCCESS(rc)) + { + RTVFSFILE hVfsFile; + rc = RTVfsMemFileCreate(hVfsDecomp, 2 * _1M, &hVfsFile); + if (RT_SUCCESS(rc)) + { + rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL); + if (RT_SUCCESS(rc)) + { + /* The VFS file contains the buffer for the seed now. */ + rc = RTFuzzCtxCorpusInputAddFromVfsFile(hFuzzCtx, hVfsFile); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to add input seed"); + RTVfsFileRelease(hVfsFile); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to seek to the beginning of the seed"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to decompress input seed"); + + RTVfsIoStrmRelease(hVfsDecomp); + } + + RTVfsIoStrmRelease(hVfsIosSeed); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create I/O stream from seed buffer"); + } + + return rc; +} + + +/** + * Processes a signle input seed given as a file path for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonSeed The seed node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessInputSeedFileSingle(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonSeed, PRTERRINFO pErrInfo) +{ + RTFUZZCTX hFuzzCtx; + int rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonValComp; + rc = RTJsonValueQueryByName(hJsonSeed, "Compression", &hJsonValComp); + if (RT_SUCCESS(rc)) + { + const char *pszCompression = RTJsonValueGetString(hJsonValComp); + if (RT_LIKELY(pszCompression)) + { + RTJSONVAL hJsonValFile; + rc = RTJsonValueQueryByName(hJsonSeed, "File", &hJsonValFile); + if (RT_SUCCESS(rc)) + { + const char *pszFile = RTJsonValueGetString(hJsonValFile); + if (RT_LIKELY(pszFile)) + rc = rtFuzzCmdMasterFuzzRunProcessSeedFile(hFuzzCtx, pszCompression, pszFile, pErrInfo); + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"File\" value is not a string"); + + RTJsonValueRelease(hJsonValFile); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"File\" value"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"Compression\" value is not a string"); + + RTJsonValueRelease(hJsonValComp); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Compression\" value"); + + RTFuzzCtxRelease(hFuzzCtx); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to query fuzzing context from observer"); + + return rc; +} + + +/** + * Processes input seed related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessInputSeeds(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValSeedArray; + int rc = RTJsonValueQueryByName(hJsonRoot, "InputSeeds", &hJsonValSeedArray); + if (RT_SUCCESS(rc)) + { + RTJSONIT hIt; + rc = RTJsonIteratorBegin(hJsonValSeedArray, &hIt); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonInpSeed; + while ( RT_SUCCESS(rc) + && RTJsonIteratorQueryValue(hIt, &hJsonInpSeed, NULL) != VERR_JSON_ITERATOR_END) + { + rc = rtFuzzCmdMasterFuzzRunProcessInputSeedSingle(pFuzzRun, hJsonInpSeed, pErrInfo); + RTJsonValueRelease(hJsonInpSeed); + if (RT_FAILURE(rc)) + break; + rc = RTJsonIteratorNext(hIt); + } + + if (rc == VERR_JSON_ITERATOR_END) + rc = VINF_SUCCESS; + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to create array iterator"); + + RTJsonValueRelease(hJsonValSeedArray); + } + else if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; + + if (RT_SUCCESS(rc)) + { + rc = RTJsonValueQueryByName(hJsonRoot, "InputSeedFiles", &hJsonValSeedArray); + if (RT_SUCCESS(rc)) + { + RTJSONIT hIt; + rc = RTJsonIteratorBegin(hJsonValSeedArray, &hIt); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonInpSeed; + while ( RT_SUCCESS(rc) + && RTJsonIteratorQueryValue(hIt, &hJsonInpSeed, NULL) != VERR_JSON_ITERATOR_END) + { + rc = rtFuzzCmdMasterFuzzRunProcessInputSeedFileSingle(pFuzzRun, hJsonInpSeed, pErrInfo); + RTJsonValueRelease(hJsonInpSeed); + if (RT_FAILURE(rc)) + break; + rc = RTJsonIteratorNext(hIt); + } + + if (rc == VERR_JSON_ITERATOR_END) + rc = VINF_SUCCESS; + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to create array iterator"); + + RTJsonValueRelease(hJsonValSeedArray); + } + else if (rc == VERR_NOT_FOUND) + rc = VINF_SUCCESS; + } + + return rc; +} + + +/** + * Processes miscellaneous config items. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessMiscCfg(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + size_t cbTmp; + int rc = rtFuzzCmdMasterFuzzRunProcessCfgSizeDef(&cbTmp, "InputSeedMax", hJsonRoot, 0, pErrInfo); + if (RT_SUCCESS(rc)) + { + RTFUZZCTX hFuzzCtx; + rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx); + AssertRC(rc); + + rc = RTFuzzCtxCfgSetInputSeedMaximum(hFuzzCtx, cbTmp); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to set maximum input seed size to %zu", cbTmp); + } + + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessCfgU32Def(&pFuzzRun->cProcs, "FuzzingProcs", hJsonRoot, 0, pErrInfo); + if (RT_SUCCESS(rc)) + { + uint32_t msTimeoutMax = 0; + rc = rtFuzzCmdMasterFuzzRunProcessCfgU32Def(&msTimeoutMax, "TimeoutMax", hJsonRoot, 1000, pErrInfo); + if (RT_SUCCESS(rc)) + rc = RTFuzzObsSetTestBinaryTimeout(pFuzzRun->hFuzzObs, msTimeoutMax); + } + + return rc; +} + + +/** + * Processes target recording related configs for the given fuzzing run. + * + * @returns IPRT status code. + * @param pFuzzRun The fuzzing run. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunProcessTgtRecFlags(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValTgt; + int rc = RTJsonValueQueryByName(hJsonRoot, "TgtRec", &hJsonValTgt); + if (RT_SUCCESS(rc)) + { + uint32_t fTgtRecFlags = 0; + RTJSONIT hTgtIt; + rc = RTJsonIteratorBeginArray(hJsonValTgt, &hTgtIt); + if (RT_SUCCESS(rc)) + { + do + { + RTJSONVAL hVal; + rc = RTJsonIteratorQueryValue(hTgtIt, &hVal, NULL); + if (RT_SUCCESS(rc)) + { + const char *pszTgtRec = RTJsonValueGetString(hVal); + if (!RTStrICmp(pszTgtRec, "StdOut")) + fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_STDOUT; + else if (!RTStrICmp(pszTgtRec, "StdErr")) + fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_STDERR; + else if (!RTStrICmp(pszTgtRec, "ProcSts")) + fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_PROCSTATUS; + else if (!RTStrICmp(pszTgtRec, "SanCov")) + fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_SANCOV; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NOT_FOUND, "JSON request malformed: The recording flags '%s' is not known", pszTgtRec); + RTJsonValueRelease(hVal); + } + rc = RTJsonIteratorNext(hTgtIt); + } while (RT_SUCCESS(rc)); + + if ( rc == VERR_JSON_IS_EMPTY + || rc == VERR_JSON_ITERATOR_END) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to parse target recording flags"); + + RTJsonIteratorFree(hTgtIt); + } + else if (rc == VERR_JSON_IS_EMPTY) + rc = VINF_SUCCESS; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"TgtRec\" is not an array"); + + pFuzzRun->fTgtRecFlags = fTgtRecFlags; + + RTJsonValueRelease(hJsonValTgt); + } + else if (rc == VERR_NOT_FOUND) + { + pFuzzRun->fTgtRecFlags = RTFUZZTGT_REC_STATE_F_PROCSTATUS; + rc = VINF_SUCCESS; /* Just keep using the defaults. */ + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query \"TgtRec\""); + + return rc; +} + + +/** + * Sets up the directories for the given fuzzing run. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param pFuzzRun The fuzzing run to setup the directories for. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterFuzzRunSetupDirectories(PRTFUZZCMDMASTER pThis, PRTFUZZRUN pFuzzRun, PRTERRINFO pErrInfo) +{ + /* Create temp directories. */ + char szTmpDir[RTPATH_MAX]; + int rc = RTPathJoin(&szTmpDir[0], sizeof(szTmpDir), pThis->pszTmpDir, pFuzzRun->pszId); + AssertRC(rc); + rc = RTDirCreate(szTmpDir, 0700, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_SET + | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); + if (rc == VERR_ALREADY_EXISTS) + { + /* Clear the directory. */ + rc = RTDirRemoveRecursive(szTmpDir, RTDIRRMREC_F_CONTENT_ONLY); + } + + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsSetTmpDirectory(pFuzzRun->hFuzzObs, szTmpDir); + if (RT_SUCCESS(rc)) + { + rc = RTPathJoin(&szTmpDir[0], sizeof(szTmpDir), pThis->pszResultsDir, pFuzzRun->pszId); + AssertRC(rc); + rc = RTDirCreate(szTmpDir, 0700, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_SET + | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL); + if (RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS) + { + rc = RTFuzzObsSetResultDirectory(pFuzzRun->hFuzzObs, szTmpDir); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to set results directory to %s", szTmpDir); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create results directory %s", szTmpDir); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to set temporary directory to %s", szTmpDir); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create temporary directory %s", szTmpDir); + + return rc; +} + + +/** + * Creates a new fuzzing run with the given ID. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param pszId The ID to use. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterCreateFuzzRunWithId(PRTFUZZCMDMASTER pThis, const char *pszId, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + int rc = VINF_SUCCESS; + PRTFUZZRUN pFuzzRun = (PRTFUZZRUN)RTMemAllocZ(sizeof(*pFuzzRun)); + if (RT_LIKELY(pFuzzRun)) + { + pFuzzRun->pszId = RTStrDup(pszId); + if (RT_LIKELY(pFuzzRun->pszId)) + { + rc = rtFuzzCmdMasterFuzzRunProcessTgtRecFlags(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsCreate(&pFuzzRun->hFuzzObs, RTFUZZCTXTYPE_BLOB, pFuzzRun->fTgtRecFlags); + if (RT_SUCCESS(rc)) + { + rc = rtFuzzCmdMasterFuzzRunProcessBinaryCfg(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessArgCfg(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessEnvironment(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessInputSeeds(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessMiscCfg(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunProcessSanitizers(pFuzzRun, hJsonRoot, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterFuzzRunSetupDirectories(pThis, pFuzzRun, pErrInfo); + + if (RT_SUCCESS(rc)) + { + /* Start fuzzing. */ + RTListAppend(&pThis->LstFuzzed, &pFuzzRun->NdFuzzed); + rc = RTFuzzObsExecStart(pFuzzRun->hFuzzObs, pFuzzRun->cProcs); + if (RT_SUCCESS(rc)) + { + RTTIMESPEC TimeSpec; + RTTimeNow(&TimeSpec); + RTTimeLocalExplode(&pFuzzRun->TimeCreated, &TimeSpec); + pFuzzRun->tsCreatedMs = RTTimeMilliTS(); + pFuzzRun->fStarted = true; + return VINF_SUCCESS; + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to start fuzzing with %Rrc", rc); + } + + int rc2 = RTFuzzObsDestroy(pFuzzRun->hFuzzObs); + AssertRC(rc2); RT_NOREF(rc2); + } + } + + RTStrFree(pFuzzRun->pszId); + pFuzzRun->pszId = NULL; + } + else + rc = VERR_NO_STR_MEMORY; + + RTMemFree(pFuzzRun); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_MEMORY, "Request error: Out of memory allocating the fuzzer state"); + + return rc; +} + + +/** + * Resolves the fuzzing run from the given ID config item and the given JSON request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonRoot The root node of the JSON request. + * @param pszIdItem The JSON item which contains the ID of the fuzzing run. + * @param ppFuzzRun Where to store the pointer to the fuzzing run on success. + */ +static int rtFuzzCmdMasterQueryFuzzRunFromJson(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, const char *pszIdItem, PRTERRINFO pErrInfo, + PRTFUZZRUN *ppFuzzRun) +{ + RTJSONVAL hJsonValId; + int rc = RTJsonValueQueryByName(hJsonRoot, pszIdItem, &hJsonValId); + if (RT_SUCCESS(rc)) + { + const char *pszId = RTJsonValueGetString(hJsonValId); + if (pszId) + { + PRTFUZZRUN pFuzzRun = rtFuzzCmdMasterGetFuzzerById(pThis, pszId); + if (pFuzzRun) + *ppFuzzRun = pFuzzRun; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NOT_FOUND, "Request error: The ID \"%s\" wasn't found", pszId); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Id\" is not a string value"); + + RTJsonValueRelease(hJsonValId); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Id\" value"); + return rc; +} + + +/** + * Processes the "StartFuzzing" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqStart(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValId; + int rc = RTJsonValueQueryByName(hJsonRoot, "Id", &hJsonValId); + if (RT_SUCCESS(rc)) + { + const char *pszId = RTJsonValueGetString(hJsonValId); + if (pszId) + { + PRTFUZZRUN pFuzzRun = rtFuzzCmdMasterGetFuzzerById(pThis, pszId); + if (!pFuzzRun) + rc = rtFuzzCmdMasterCreateFuzzRunWithId(pThis, pszId, hJsonRoot, pErrInfo); + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_ALREADY_EXISTS, "Request error: The ID \"%s\" is already registered", pszId); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Id\" is not a string value"); + + RTJsonValueRelease(hJsonValId); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Id\" value"); + return rc; +} + + +/** + * Processes the "StopFuzzing" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqStop(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + PRTFUZZRUN pFuzzRun; + int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + { + RTListNodeRemove(&pFuzzRun->NdFuzzed); + RTFuzzObsExecStop(pFuzzRun->hFuzzObs); + RTFuzzObsDestroy(pFuzzRun->hFuzzObs); + RTStrFree(pFuzzRun->pszId); + RTMemFree(pFuzzRun); + } + + return rc; +} + + +/** + * Processes the "SuspendFuzzing" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqSuspend(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + PRTFUZZRUN pFuzzRun; + int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + { + if (pFuzzRun->fStarted) + { + rc = RTFuzzObsExecStop(pFuzzRun->hFuzzObs); + if (RT_SUCCESS(rc)) + pFuzzRun->fStarted = false; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Suspending the fuzzing process failed"); + } + } + + return rc; +} + + +/** + * Processes the "ResumeFuzzing" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqResume(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + PRTFUZZRUN pFuzzRun; + int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + { + if (!pFuzzRun->fStarted) + { + rc = rtFuzzCmdMasterFuzzRunProcessCfgU32Def(&pFuzzRun->cProcs, "FuzzingProcs", hJsonRoot, pFuzzRun->cProcs, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzObsExecStart(pFuzzRun->hFuzzObs, pFuzzRun->cProcs); + if (RT_SUCCESS(rc)) + pFuzzRun->fStarted = true; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Resuming the fuzzing process failed"); + } + } + } + + return rc; +} + + +/** + * Processes the "SaveFuzzingState" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqSaveState(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + PRTFUZZRUN pFuzzRun; + int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + { + /* Suspend fuzzing, save and resume if not stopped. */ + if (pFuzzRun->fStarted) + { + rc = RTFuzzObsExecStop(pFuzzRun->hFuzzObs); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Suspending the fuzzing process failed"); + } + + if (RT_SUCCESS(rc)) + { + RTFUZZCTX hFuzzCtx; + rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx); + AssertRC(rc); + + void *pvState = NULL; + size_t cbState = 0; + rc = RTFuzzCtxStateExportToMem(hFuzzCtx, &pvState, &cbState); + if (RT_SUCCESS(rc)) + { + /* Encode to base64. */ + size_t cbStateStr = RTBase64EncodedLength(cbState) + 1; + char *pszState = (char *)RTMemAllocZ(cbStateStr); + if (pszState) + { + rc = RTBase64Encode(pvState, cbState, pszState, cbStateStr, &cbStateStr); + if (RT_SUCCESS(rc)) + { + /* Strip all new lines from the srting. */ + size_t offStr = 0; + while (offStr < cbStateStr) + { +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + char *pszEol = strchr(&pszState[offStr], '\r'); +#else + char *pszEol = strchr(&pszState[offStr], '\n'); +#endif + if (pszEol) + { + offStr += pszEol - &pszState[offStr]; + memmove(pszEol, &pszEol[RTBASE64_EOL_SIZE], cbStateStr - offStr - RTBASE64_EOL_SIZE); + cbStateStr -= RTBASE64_EOL_SIZE; + } + else + break; + } + + const char s_szState[] = "{ \"State\": %s }"; + pThis->pszResponse = RTStrAPrintf2(s_szState, pszState); + if (RT_UNLIKELY(!pThis->pszResponse)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_BUFFER_OVERFLOW, "Request error: Response data buffer overflow", rc); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to encode the state as a base64 string"); + RTMemFree(pszState); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_STR_MEMORY, "Request error: Failed to allocate a state string for the response"); + RTMemFree(pvState); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Exporting the state failed"); + } + + if (pFuzzRun->fStarted) + { + int rc2 = RTFuzzObsExecStart(pFuzzRun->hFuzzObs, pFuzzRun->cProcs); + if (RT_FAILURE(rc2)) + rtFuzzCmdMasterErrorRc(pErrInfo, rc2, "Request error: Resuming the fuzzing process failed"); + } + } + + return rc; +} + + +/** + * Queries the statistics for the given fuzzing run and adds the result to the response. + * + * @returns IPRT static code. + * @param pThis The fuzzing master command state. + * @param pFuzzRun The fuzzing run. + * @param pszIndent Indentation to use. + * @param fLast Flags whether this is the last element in the list. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessQueryRunStats(PRTFUZZCMDMASTER pThis, PRTFUZZRUN pFuzzRun, + const char *pszIndent, bool fLast, PRTERRINFO pErrInfo) +{ + RTFUZZOBSSTATS ObsStats; + RTFUZZCTXSTATS CtxStats; + RTFUZZCTX hFuzzCtx; + RT_ZERO(ObsStats); RT_ZERO(CtxStats); + + int rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx); + if (RT_SUCCESS(rc)) + { + rc = RTFuzzCtxQueryStats(hFuzzCtx, &CtxStats); + RTFuzzCtxRelease(hFuzzCtx); + } + + if (RT_SUCCESS(rc)) + rc = RTFuzzObsQueryStats(pFuzzRun->hFuzzObs, &ObsStats); + if (RT_SUCCESS(rc)) + { + const char s_szStatsFmt[] = "%s{ \n" + "%s \"Id\": \"%s\"\n" + "%s \"TimeCreated\": \"%s\"\n" + "%s \"UptimeSec\": %llu\n" + "%s \"FuzzedInputsPerSec\": %u\n" + "%s \"FuzzedInputs\": %u\n" + "%s \"FuzzedInputsHang\": %u\n" + "%s \"FuzzedInputsCrash\": %u\n" + "%s \"MemoryUsage\": %zu\n" + "%s \"CorpusSize\": %llu\n" + "%s}%s\n"; + char aszTime[_1K]; RT_ZERO(aszTime); + char aszStats[_4K]; RT_ZERO(aszStats); + + if (RTTimeToString(&pFuzzRun->TimeCreated, aszTime, sizeof(aszTime))) + { + ssize_t cchStats = RTStrPrintf2(&aszStats[0], sizeof(aszStats), + s_szStatsFmt, pszIndent, + pszIndent, pFuzzRun->pszId, + pszIndent, aszTime, + pszIndent, (RTTimeMilliTS() - pFuzzRun->tsCreatedMs) / RT_MS_1SEC_64, + pszIndent, ObsStats.cFuzzedInputsPerSec, + pszIndent, ObsStats.cFuzzedInputs, + pszIndent, ObsStats.cFuzzedInputsHang, + pszIndent, ObsStats.cFuzzedInputsCrash, + pszIndent, CtxStats.cbMemory, + pszIndent, CtxStats.cMutations, + pszIndent, fLast ? "" : ","); + if (RT_LIKELY(cchStats > 0)) + { + rc = RTStrAAppend(&pThis->pszResponse, &aszStats[0]); + if (RT_FAILURE(rc)) + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to build statistics response", rc); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_BUFFER_OVERFLOW, "Request error: Response data buffer overflow"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_BUFFER_OVERFLOW, "Request error: Buffer overflow conerting time to string"); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to query fuzzing statistics with %Rrc", rc); + + return rc; +} + + +/** + * Processes the "QueryStats" request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReqQueryStats(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValId; + int rc = RTJsonValueQueryByName(hJsonRoot, "Id", &hJsonValId); + if (RT_SUCCESS(rc)) + { + RTJsonValueRelease(hJsonValId); + PRTFUZZRUN pFuzzRun; + rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun); + if (RT_SUCCESS(rc)) + rc = rtFuzzCmdMasterProcessQueryRunStats(pThis, pFuzzRun, " ", + true /*fLast*/, pErrInfo); + } + else if (rc == VERR_NOT_FOUND) + { + /* Id is not there, so collect statistics of all running jobs. */ + rc = RTStrAAppend(&pThis->pszResponse, " [\n"); + if (RT_SUCCESS(rc)) + { + PRTFUZZRUN pRun = NULL; + RTListForEach(&pThis->LstFuzzed, pRun, RTFUZZRUN, NdFuzzed) + { + bool fLast = RTListNodeIsLast(&pThis->LstFuzzed, &pRun->NdFuzzed); + rc = rtFuzzCmdMasterProcessQueryRunStats(pThis, pRun, " ", fLast, pErrInfo); + if (RT_FAILURE(rc)) + break; + } + if (RT_SUCCESS(rc)) + rc = RTStrAAppend(&pThis->pszResponse, " ]\n"); + } + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't get \"Id\" value"); + + return rc; +} + + +/** + * Processes a JSON request. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param hJsonValRoot The root node of the JSON request. + * @param pErrInfo Where to store the error information on failure, optional. + */ +static int rtFuzzCmdMasterProcessJsonReq(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo) +{ + RTJSONVAL hJsonValReq; + int rc = RTJsonValueQueryByName(hJsonRoot, "Request", &hJsonValReq); + if (RT_SUCCESS(rc)) + { + const char *pszReq = RTJsonValueGetString(hJsonValReq); + if (pszReq) + { + if (!RTStrCmp(pszReq, "StartFuzzing")) + rc = rtFuzzCmdMasterProcessJsonReqStart(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "StopFuzzing")) + rc = rtFuzzCmdMasterProcessJsonReqStop(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "SuspendFuzzing")) + rc = rtFuzzCmdMasterProcessJsonReqSuspend(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "ResumeFuzzing")) + rc = rtFuzzCmdMasterProcessJsonReqResume(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "SaveFuzzingState")) + rc = rtFuzzCmdMasterProcessJsonReqSaveState(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "QueryStats")) + rc = rtFuzzCmdMasterProcessJsonReqQueryStats(pThis, hJsonRoot, pErrInfo); + else if (!RTStrCmp(pszReq, "Shutdown")) + pThis->fShutdown = true; + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Request\" contains unknown value \"%s\"", pszReq); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Request\" is not a string value"); + + RTJsonValueRelease(hJsonValReq); + } + else + rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Request\" value"); + + return rc; +} + + +/** + * Loads a fuzzing configuration for immediate startup from the given file. + * + * @returns IPRT status code. + * @param pThis The fuzzing master command state. + * @param pszFuzzCfg The fuzzing config to load. + */ +static int rtFuzzCmdMasterFuzzCfgLoadFromFile(PRTFUZZCMDMASTER pThis, const char *pszFuzzCfg) +{ + RTJSONVAL hJsonRoot; + int rc = RTJsonParseFromFile(&hJsonRoot, pszFuzzCfg, NULL); + if (RT_SUCCESS(rc)) + { + rc = rtFuzzCmdMasterProcessJsonReqStart(pThis, hJsonRoot, NULL); + RTJsonValueRelease(hJsonRoot); + } + else + rc = rtFuzzCmdMasterErrorRc(NULL, rc, "JSON request malformed: Couldn't load file \"%s\"", pszFuzzCfg); + + return rc; +} + + +/** + * Destroys all running fuzzers for the given master state. + * + * @param pThis The fuzzing master command state. + */ +static void rtFuzzCmdMasterDestroy(PRTFUZZCMDMASTER pThis) +{ + RT_NOREF(pThis); +} + + +/** + * Sends an ACK response to the client. + * + * @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. + * + * @param hSocket The socket handle to send the ACK to. + * @param pErrInfo Optional error information to send along. + */ +static void rtFuzzCmdMasterTcpSendNAck(RTSOCKET hSocket, PRTERRINFO pErrInfo) +{ + const char s_szFail[] = "{ \"Status\": \"NACK\" }\n"; + const char s_szFailInfo[] = "{ \"Status\": \"NACK\"\n \"Information\": \"%s\" }\n"; + + if (pErrInfo) + { + char szTmp[_1K]; + ssize_t cchResp = RTStrPrintf2(szTmp, sizeof(szTmp), s_szFailInfo, pErrInfo->pszMsg); + if (cchResp > 0) + RTTcpWrite(hSocket, szTmp, cchResp); + else + RTTcpWrite(hSocket, s_szFail, strlen(s_szFail)); + } + else + RTTcpWrite(hSocket, s_szFail, strlen(s_szFail)); +} + + +/** + * TCP server serving callback for a single connection. + * + * @returns IPRT status code. + * @param hSocket The socket handle of the connection. + * @param pvUser Opaque user data. + */ +static DECLCALLBACK(int) rtFuzzCmdMasterTcpServe(RTSOCKET hSocket, void *pvUser) +{ + PRTFUZZCMDMASTER pThis = (PRTFUZZCMDMASTER)pvUser; + size_t cbReqMax = _32K; + size_t cbReq = 0; + uint8_t *pbReq = (uint8_t *)RTMemAllocZ(cbReqMax); + + if (RT_LIKELY(pbReq)) + { + uint8_t *pbCur = pbReq; + + for (;;) + { + size_t cbThisRead = cbReqMax - cbReq; + int rc = RTTcpRead(hSocket, pbCur, cbThisRead, &cbThisRead); + if ( RT_SUCCESS(rc) + && cbThisRead) + { + cbReq += cbThisRead; + + /* Check for a zero terminator marking the end of the request. */ + uint8_t *pbEnd = (uint8_t *)memchr(pbCur, 0, cbThisRead); + if (pbEnd) + { + /* Adjust request size, data coming after the zero terminiator is ignored right now. */ + cbReq -= cbThisRead - (pbEnd - pbCur) + 1; + + RTJSONVAL hJsonReq; + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + + rc = RTJsonParseFromBuf(&hJsonReq, pbReq, cbReq, &ErrInfo.Core); + if (RT_SUCCESS(rc)) + { + rc = rtFuzzCmdMasterProcessJsonReq(pThis, hJsonReq, &ErrInfo.Core); + if (RT_SUCCESS(rc)) + rtFuzzCmdMasterTcpSendAck(hSocket, pThis->pszResponse); + else + rtFuzzCmdMasterTcpSendNAck(hSocket, &ErrInfo.Core); + RTJsonValueRelease(hJsonReq); + } + else + rtFuzzCmdMasterTcpSendNAck(hSocket, &ErrInfo.Core); + + if (pThis->pszResponse) + { + RTStrFree(pThis->pszResponse); + pThis->pszResponse = NULL; + } + break; + } + else if (cbReq == cbReqMax) + { + /* Try to increase the buffer. */ + uint8_t *pbReqNew = (uint8_t *)RTMemRealloc(pbReq, cbReqMax + _32K); + if (RT_LIKELY(pbReqNew)) + { + cbReqMax += _32K; + pbReq = pbReqNew; + pbCur = pbReq + cbReq; + } + else + rtFuzzCmdMasterTcpSendNAck(hSocket, NULL); + } + else + pbCur += cbThisRead; + } + else + break; + } + } + else + rtFuzzCmdMasterTcpSendNAck(hSocket, NULL); + + if (pbReq) + RTMemFree(pbReq); + + return pThis->fShutdown ? VERR_TCP_SERVER_STOP : VINF_SUCCESS; +} + + +/** + * Mainloop for the fuzzing master. + * + * @returns Process exit code. + * @param pThis The fuzzing master command state. + * @param pszLoadCfg Initial config to load. + */ +static RTEXITCODE rtFuzzCmdMasterRun(PRTFUZZCMDMASTER pThis, const char *pszLoadCfg) +{ + if (pszLoadCfg) + { + int rc = rtFuzzCmdMasterFuzzCfgLoadFromFile(pThis, pszLoadCfg); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + } + + /* Start up the control server. */ + int rc = RTTcpServerCreateEx(NULL, pThis->uPort, &pThis->hTcpSrv); + if (RT_SUCCESS(rc)) + { + do + { + rc = RTTcpServerListen(pThis->hTcpSrv, rtFuzzCmdMasterTcpServe, pThis); + } while (rc != VERR_TCP_SERVER_STOP); + } + + RTTcpServerDestroy(pThis->hTcpSrv); + rtFuzzCmdMasterDestroy(pThis); + return RTEXITCODE_SUCCESS; +} + + +RTR3DECL(RTEXITCODE) RTFuzzCmdMaster(unsigned cArgs, char **papszArgs) +{ + /* + * Parse the command line. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--fuzz-config", 'c', RTGETOPT_REQ_STRING }, + { "--temp-dir", 't', RTGETOPT_REQ_STRING }, + { "--results-dir", 'r', RTGETOPT_REQ_STRING }, + { "--listen-port", 'p', RTGETOPT_REQ_UINT16 }, + { "--daemonize", 'd', RTGETOPT_REQ_NOTHING }, + { "--daemonized", 'Z', RTGETOPT_REQ_NOTHING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING }, + { "--version", 'V', RTGETOPT_REQ_NOTHING }, + }; + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, + RTGETOPTINIT_FLAGS_OPTS_FIRST); + if (RT_SUCCESS(rc)) + { + /* Option variables: */ + bool fDaemonize = false; + bool fDaemonized = false; + const char *pszLoadCfg = NULL; + RTFUZZCMDMASTER This; + + RTListInit(&This.LstFuzzed); + This.hTcpSrv = NIL_RTTCPSERVER; + This.uPort = 4242; + This.pszTmpDir = NULL; + This.pszResultsDir = NULL; + This.fShutdown = false; + This.pszResponse = NULL; + + /* Argument parsing loop. */ + bool fContinue = true; + do + { + RTGETOPTUNION ValueUnion; + int chOpt = RTGetOpt(&GetState, &ValueUnion); + switch (chOpt) + { + case 0: + fContinue = false; + break; + + case 'c': + pszLoadCfg = ValueUnion.psz; + break; + + case 'p': + This.uPort = ValueUnion.u16; + break; + + case 't': + This.pszTmpDir = ValueUnion.psz; + break; + + case 'r': + This.pszResultsDir = ValueUnion.psz; + break; + + case 'd': + fDaemonize = true; + break; + + case 'Z': + fDaemonized = true; + fDaemonize = false; + break; + + case 'h': + RTPrintf("Usage: to be written\nOption dump:\n"); + for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++) + RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong); + fContinue = false; + break; + + case 'V': + RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision()); + fContinue = false; + break; + + default: + rcExit = RTGetOptPrintError(chOpt, &ValueUnion); + fContinue = false; + break; + } + } while (fContinue); + + if (rcExit == RTEXITCODE_SUCCESS) + { + /* + * Daemonize ourselves if asked to. + */ + if (fDaemonize) + { + rc = RTProcDaemonize(papszArgs, "--daemonized"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize: %Rrc\n", rc); + } + else + rcExit = rtFuzzCmdMasterRun(&This, pszLoadCfg); + } + } + else + rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc); + return rcExit; +} + |