Add support for fuzzing cppdap

Add build rules, scripts, basic corpus, and dictionary.
Currently requires recent clang toolchain.
This commit is contained in:
Ben Clayton 2020-05-26 13:30:57 +01:00
parent cc93ba9747
commit 773f0dff68
9 changed files with 458 additions and 0 deletions

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
build/ build/
fuzz/corpus
fuzz/logs
.vs/ .vs/
.vscode/settings.json .vscode/settings.json
CMakeSettings.json CMakeSettings.json

8
.vscode/launch.json vendored
View File

@ -17,6 +17,14 @@
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"args": [] "args": []
}, },
{
"type": "cppdbg",
"request": "launch",
"name": "fuzzer (lldb)",
"program": "${workspaceFolder}/fuzz/build/cppdap-fuzzer",
"cwd": "${workspaceRoot}",
"args": []
},
{ {
"name": "unittests (gdb)", "name": "unittests (gdb)",
"type": "cppdbg", "type": "cppdbg",

View File

@ -31,6 +31,7 @@ endfunction()
option_if_not_defined(CPPDAP_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF) option_if_not_defined(CPPDAP_WARNINGS_AS_ERRORS "Treat warnings as errors" OFF)
option_if_not_defined(CPPDAP_BUILD_EXAMPLES "Build example applications" OFF) option_if_not_defined(CPPDAP_BUILD_EXAMPLES "Build example applications" OFF)
option_if_not_defined(CPPDAP_BUILD_TESTS "Build tests" OFF) option_if_not_defined(CPPDAP_BUILD_TESTS "Build tests" OFF)
option_if_not_defined(CPPDAP_BUILD_FUZZER "Build fuzzer" OFF)
option_if_not_defined(CPPDAP_ASAN "Build dap with address sanitizer" OFF) option_if_not_defined(CPPDAP_ASAN "Build dap with address sanitizer" OFF)
option_if_not_defined(CPPDAP_MSAN "Build dap with memory sanitizer" OFF) option_if_not_defined(CPPDAP_MSAN "Build dap with memory sanitizer" OFF)
option_if_not_defined(CPPDAP_TSAN "Build dap with thread sanitizer" OFF) option_if_not_defined(CPPDAP_TSAN "Build dap with thread sanitizer" OFF)
@ -223,6 +224,26 @@ if(CPPDAP_BUILD_TESTS)
target_link_libraries(cppdap-unittests cppdap "${CPPDAP_OS_LIBS}") target_link_libraries(cppdap-unittests cppdap "${CPPDAP_OS_LIBS}")
endif(CPPDAP_BUILD_TESTS) endif(CPPDAP_BUILD_TESTS)
# fuzzer
if(CPPDAP_BUILD_FUZZER)
if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(FATAL_ERROR "CPPDAP_BUILD_FUZZER can currently only be used with the clang toolchain")
endif()
set(DAP_FUZZER_LIST
${CPPDAP_LIST}
${CMAKE_CURRENT_SOURCE_DIR}/fuzz/fuzz.cpp
)
add_executable(cppdap-fuzzer ${DAP_FUZZER_LIST})
target_compile_options(cppdap-fuzzer PUBLIC "-fsanitize=fuzzer,address")
target_link_libraries(cppdap-fuzzer "-fsanitize=fuzzer,address")
target_include_directories(cppdap-fuzzer PUBLIC
${CPPDAP_INCLUDE_DIR}
${CPPDAP_SRC_DIR}
${CPPDAP_JSON_DIR}/include/
)
target_link_libraries(cppdap-fuzzer cppdap "${CPPDAP_OS_LIBS}")
endif(CPPDAP_BUILD_FUZZER)
# examples # examples
if(CPPDAP_BUILD_EXAMPLES) if(CPPDAP_BUILD_EXAMPLES)
function(build_example target) function(build_example target)

225
fuzz/dictionary.txt Normal file
View File

@ -0,0 +1,225 @@
"accessType"
"adapterData"
"adapterID"
"additionalModuleColumns"
"address"
"addressRange"
"algorithm"
"allThreadsContinued"
"allThreadsStopped"
"args"
"arguments"
"attach"
"attributeName"
"attributes"
"breakMode"
"breakpoint"
"breakpointLocations"
"breakpoints"
"cancel"
"cancellable"
"capabilities"
"category"
"checksum"
"checksums"
"clientID"
"clientName"
"column"
"columnsStartAt1"
"command"
"completions"
"completionTriggerCharacters"
"condition"
"configurationDone"
"context"
"continue"
"continued"
"count"
"cwd"
"data"
"dataBreakpointInfo"
"dataId"
"dateTimeStamp"
"default"
"description"
"disassemble"
"disconnect"
"endColumn"
"endLine"
"env"
"evaluate"
"evaluateName"
"exceptionBreakpointFilters"
"exceptionInfo"
"exceptionOptions"
"exitCode"
"exited"
"expensive"
"expression"
"expression"
"filter"
"filter"
"filters"
"format"
"frameId"
"fullTypeName"
"goto"
"gotoTargets"
"group"
"hex"
"hitCondition"
"id"
"includeAll"
"indexedVariables"
"indexedVariables"
"initialized"
"innerException"
"instruction"
"instructionBytes"
"instructionCount"
"instructionOffset"
"instructionPointerReference"
"isLocalProcess"
"isOptimized"
"isUserCode"
"kind"
"label"
"launch"
"length"
"levels"
"line"
"lines"
"linesStartAt1"
"loadedSource"
"loadedSources"
"locale"
"location"
"logMessage"
"memoryReference"
"message"
"module"
"moduleCount"
"moduleId"
"modules"
"name"
"namedVariables"
"names"
"negate"
"next"
"noDebug"
"offset"
"origin"
"output"
"parameterNames"
"parameters"
"parameterTypes"
"parameterValues"
"path"
"pathFormat"
"pause"
"percentage"
"pointerSize"
"presentationHint"
"preserveFocusHint"
"process"
"progressEnd"
"progressId"
"progressStart"
"progressUpdate"
"readMemory"
"reason"
"requestId"
"resolveSymbols"
"restart"
"restartFrame"
"reverseContinue"
"runInTerminal"
"scopes"
"selectionLength"
"selectionStart"
"sendTelemetry"
"seq"
"setBreakpoints"
"setDataBreakpoints"
"setExceptionBreakpoints"
"setExpression"
"setFunctionBreakpoints"
"setVariable"
"showUser"
"sortText"
"source"
"sourceModified"
"sourceReference"
"sources"
"stackTrace"
"start"
"startFrame"
"startMethod"
"startModule"
"stepBack"
"stepIn"
"stepInTargets"
"stepOut"
"stopped"
"supportedChecksumAlgorithms"
"supportsBreakpointLocationsRequest"
"supportsCancelRequest"
"supportsClipboardContext"
"supportsCompletionsRequest"
"supportsConditionalBreakpoints"
"supportsConfigurationDoneRequest"
"supportsDataBreakpoints"
"supportsDelayedStackTraceLoading"
"supportsDisassembleRequest"
"supportsEvaluateForHovers"
"supportsExceptionInfoRequest"
"supportsExceptionOptions"
"supportsFunctionBreakpoints"
"supportsGotoTargetsRequest"
"supportsHitConditionalBreakpoints"
"supportsLoadedSourcesRequest"
"supportsLogPoints"
"supportsMemoryReferences"
"supportsModulesRequest"
"supportsProgressReporting"
"supportsReadMemoryRequest"
"supportsRestartFrame"
"supportsRestartRequest"
"supportsRunInTerminalRequest"
"supportsSetExpression"
"supportsSetVariable"
"supportsStepBack"
"supportsStepInTargetsRequest"
"supportsTerminateRequest"
"supportsTerminateThreadsRequest"
"supportsValueFormattingOptions"
"supportsVariablePaging"
"supportsVariableType"
"supportTerminateDebuggee"
"symbol"
"symbolFilePath"
"symbolStatus"
"systemProcessId"
"targetId"
"terminate"
"terminated"
"terminateDebuggee"
"terminateThreads"
"text"
"thread"
"threadId"
"threadIds"
"threads"
"title"
"title"
"type"
"typeName"
"url"
"urlLabel"
"value"
"variables"
"variablesReference"
"verified"
"version"
"visibility"
"width"

173
fuzz/fuzz.cpp Normal file
View File

@ -0,0 +1,173 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// cppdap fuzzer program.
// Run with: ${CPPDAP_PATH}/fuzz/run.sh
// Requires modern clang toolchain.
#include "content_stream.h"
#include "string_buffer.h"
#include "dap/protocol.h"
#include "dap/session.h"
#include <condition_variable>
#include <mutex>
namespace {
// Event provides a basic wait and signal synchronization primitive.
class Event {
public:
// wait() blocks until the event is fired or the given timeout is reached.
template <typename DURATION>
inline void wait(const DURATION& duration) {
std::unique_lock<std::mutex> lock(mutex);
cv.wait_for(lock, duration, [&] { return fired; });
}
// fire() sets signals the event, and unblocks any calls to wait().
inline void fire() {
std::unique_lock<std::mutex> lock(mutex);
fired = true;
cv.notify_all();
}
private:
std::mutex mutex;
std::condition_variable cv;
bool fired = false;
};
} // namespace
// List of requests that we handle for fuzzing.
#define DAP_REQUEST_LIST() \
DAP_REQUEST(dap::AttachRequest, dap::AttachResponse) \
DAP_REQUEST(dap::BreakpointLocationsRequest, \
dap::BreakpointLocationsResponse) \
DAP_REQUEST(dap::CancelRequest, dap::CancelResponse) \
DAP_REQUEST(dap::CompletionsRequest, dap::CompletionsResponse) \
DAP_REQUEST(dap::ConfigurationDoneRequest, dap::ConfigurationDoneResponse) \
DAP_REQUEST(dap::ContinueRequest, dap::ContinueResponse) \
DAP_REQUEST(dap::DataBreakpointInfoRequest, dap::DataBreakpointInfoResponse) \
DAP_REQUEST(dap::DisassembleRequest, dap::DisassembleResponse) \
DAP_REQUEST(dap::DisconnectRequest, dap::DisconnectResponse) \
DAP_REQUEST(dap::EvaluateRequest, dap::EvaluateResponse) \
DAP_REQUEST(dap::ExceptionInfoRequest, dap::ExceptionInfoResponse) \
DAP_REQUEST(dap::GotoRequest, dap::GotoResponse) \
DAP_REQUEST(dap::GotoTargetsRequest, dap::GotoTargetsResponse) \
DAP_REQUEST(dap::InitializeRequest, dap::InitializeResponse) \
DAP_REQUEST(dap::LaunchRequest, dap::LaunchResponse) \
DAP_REQUEST(dap::LoadedSourcesRequest, dap::LoadedSourcesResponse) \
DAP_REQUEST(dap::ModulesRequest, dap::ModulesResponse) \
DAP_REQUEST(dap::NextRequest, dap::NextResponse) \
DAP_REQUEST(dap::PauseRequest, dap::PauseResponse) \
DAP_REQUEST(dap::ReadMemoryRequest, dap::ReadMemoryResponse) \
DAP_REQUEST(dap::RestartFrameRequest, dap::RestartFrameResponse) \
DAP_REQUEST(dap::RestartRequest, dap::RestartResponse) \
DAP_REQUEST(dap::ReverseContinueRequest, dap::ReverseContinueResponse) \
DAP_REQUEST(dap::RunInTerminalRequest, dap::RunInTerminalResponse) \
DAP_REQUEST(dap::ScopesRequest, dap::ScopesResponse) \
DAP_REQUEST(dap::SetBreakpointsRequest, dap::SetBreakpointsResponse) \
DAP_REQUEST(dap::SetDataBreakpointsRequest, dap::SetDataBreakpointsResponse) \
DAP_REQUEST(dap::SetExceptionBreakpointsRequest, \
dap::SetExceptionBreakpointsResponse) \
DAP_REQUEST(dap::SetExpressionRequest, dap::SetExpressionResponse) \
DAP_REQUEST(dap::SetFunctionBreakpointsRequest, \
dap::SetFunctionBreakpointsResponse) \
DAP_REQUEST(dap::SetVariableRequest, dap::SetVariableResponse) \
DAP_REQUEST(dap::SourceRequest, dap::SourceResponse) \
DAP_REQUEST(dap::StackTraceRequest, dap::StackTraceResponse) \
DAP_REQUEST(dap::StepBackRequest, dap::StepBackResponse) \
DAP_REQUEST(dap::StepInRequest, dap::StepInResponse) \
DAP_REQUEST(dap::StepInTargetsRequest, dap::StepInTargetsResponse) \
DAP_REQUEST(dap::StepOutRequest, dap::StepOutResponse) \
DAP_REQUEST(dap::TerminateRequest, dap::TerminateResponse) \
DAP_REQUEST(dap::TerminateThreadsRequest, dap::TerminateThreadsResponse) \
DAP_REQUEST(dap::ThreadsRequest, dap::ThreadsResponse) \
DAP_REQUEST(dap::VariablesRequest, dap::VariablesResponse)
// Fuzzing main function.
// See http://llvm.org/docs/LibFuzzer.html for details.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// The first byte can optionally control fuzzing mode.
enum class ControlMode {
// Don't wrap the input data with a stream writer. Allows testing for stream
// writing.
TestStreamWriter,
// Don't append a 'done' request. This may cause the test to take longer to
// complete (it may have to block on a timeout), but exercises the
// unrecognised-message cases.
DontAppendDoneRequest,
// Number of control modes in this enum.
Count,
};
// Scan first byte for control mode.
bool useContentStreamWriter = true;
bool appendDoneRequest = true;
if (size > 0 && data[0] < static_cast<uint8_t>(ControlMode::Count)) {
useContentStreamWriter =
data[0] != static_cast<uint8_t>(ControlMode::TestStreamWriter);
appendDoneRequest =
data[0] != static_cast<uint8_t>(ControlMode::DontAppendDoneRequest);
data++;
size--;
}
// in contains the input data
auto in = std::make_shared<dap::StringBuffer>();
dap::ContentWriter writer(in);
if (useContentStreamWriter) {
writer.write(std::string(reinterpret_cast<const char*>(data), size));
} else {
in->write(data, size);
}
if (appendDoneRequest) {
writer.write(R"(
{
"seq": 10,
"type": "request",
"command": "done",
}
)");
}
// Each test is done if we receive a request, or report an error.
Event requestOrError;
#define DAP_REQUEST(REQUEST, RESPONSE) \
session->registerHandler([&](const REQUEST&) { \
requestOrError.fire(); \
return RESPONSE{}; \
});
auto session = dap::Session::create();
DAP_REQUEST_LIST();
session->onError([&](const char*) { requestOrError.fire(); });
auto out = std::make_shared<dap::StringBuffer>();
session->bind(dap::ReaderWriter::create(in, out));
// Give up after a second if we don't get a request or error reported.
requestOrError.wait(std::chrono::seconds(1));
return 0;
}

19
fuzz/run.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
set -e # Fail on any error.
FUZZ_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
cd ${FUZZ_DIR}
# Ensure we're testing with latest build
[ ! -d "build" ] && mkdir "build"
cd "build"
cmake ../.. -GNinja -DCPPDAP_BUILD_FUZZER=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo
ninja
cd ${FUZZ_DIR}
[ ! -d "corpus" ] && mkdir "corpus"
[ ! -d "logs" ] && mkdir "logs"
cd "logs"
rm crash-* fuzz-* || true
${FUZZ_DIR}/build/cppdap-fuzzer ${FUZZ_DIR}/corpus ${FUZZ_DIR}/seed -dict=${FUZZ_DIR}/dictionary.txt -jobs=128

1
fuzz/seed/empty_json Normal file
View File

@ -0,0 +1 @@
{}

8
fuzz/seed/request Normal file
View File

@ -0,0 +1,8 @@
{
"seq": 153,
"type": "request",
"command": "next",
"arguments": {
"threadId": 3
}
}

View File

@ -52,6 +52,7 @@ class Writer : virtual public Closable {
// ReaderWriter is an interface that combines the Reader and Writer interfaces. // ReaderWriter is an interface that combines the Reader and Writer interfaces.
class ReaderWriter : public Reader, public Writer { class ReaderWriter : public Reader, public Writer {
public:
// create() returns a ReaderWriter that delegates the interface methods on to // create() returns a ReaderWriter that delegates the interface methods on to
// the provided Reader and Writer. // the provided Reader and Writer.
// isOpen() returns true if the Reader and Writer both return true for // isOpen() returns true if the Reader and Writer both return true for