Add support for fuzzing cppdap
Add build rules, scripts, basic corpus, and dictionary.
Currently requires recent clang toolchain.
diff --git a/.gitignore b/.gitignore
index 7590faf..af8d40a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
build/
+fuzz/corpus
+fuzz/logs
.vs/
.vscode/settings.json
CMakeSettings.json
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 9a85195..29baf54 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -18,6 +18,14 @@
"args": []
},
{
+ "type": "cppdbg",
+ "request": "launch",
+ "name": "fuzzer (lldb)",
+ "program": "${workspaceFolder}/fuzz/build/cppdap-fuzzer",
+ "cwd": "${workspaceRoot}",
+ "args": []
+ },
+ {
"name": "unittests (gdb)",
"type": "cppdbg",
"request": "launch",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3231185..2e2f28f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,6 +31,7 @@
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_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_MSAN "Build dap with memory sanitizer" OFF)
option_if_not_defined(CPPDAP_TSAN "Build dap with thread sanitizer" OFF)
@@ -223,6 +224,26 @@
target_link_libraries(cppdap-unittests cppdap "${CPPDAP_OS_LIBS}")
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
if(CPPDAP_BUILD_EXAMPLES)
function(build_example target)
diff --git a/fuzz/dictionary.txt b/fuzz/dictionary.txt
new file mode 100644
index 0000000..bb30ef9
--- /dev/null
+++ b/fuzz/dictionary.txt
@@ -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"
\ No newline at end of file
diff --git a/fuzz/fuzz.cpp b/fuzz/fuzz.cpp
new file mode 100644
index 0000000..6f459be
--- /dev/null
+++ b/fuzz/fuzz.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/fuzz/run.sh b/fuzz/run.sh
new file mode 100755
index 0000000..2d54039
--- /dev/null
+++ b/fuzz/run.sh
@@ -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
diff --git a/fuzz/seed/empty_json b/fuzz/seed/empty_json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/fuzz/seed/empty_json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/fuzz/seed/request b/fuzz/seed/request
new file mode 100644
index 0000000..7a183ad
--- /dev/null
+++ b/fuzz/seed/request
@@ -0,0 +1,8 @@
+{
+ "seq": 153,
+ "type": "request",
+ "command": "next",
+ "arguments": {
+ "threadId": 3
+ }
+}
\ No newline at end of file
diff --git a/include/dap/io.h b/include/dap/io.h
index e1f378d..22a638f 100644
--- a/include/dap/io.h
+++ b/include/dap/io.h
@@ -52,6 +52,7 @@
// ReaderWriter is an interface that combines the Reader and Writer interfaces.
class ReaderWriter : public Reader, public Writer {
+ public:
// create() returns a ReaderWriter that delegates the interface methods on to
// the provided Reader and Writer.
// isOpen() returns true if the Reader and Writer both return true for