Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/cifuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: CIFuzz
on:
push:
branches:
- master
pull_request:
permissions: {}
jobs:
Fuzzing:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'crow'
language: c++
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'crow'
language: c++
fuzz-seconds: 800
output-sarif: true
- name: Upload Crash
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Sarif
if: always() && steps.build.outcome == 'success'
uses: github/codeql-action/upload-sarif@v2
with:
# Path to SARIF file relative to the root of the repository
sarif_file: cifuzz-sarif/results.sarif
checkout_path: cifuzz-sarif
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ find_package(Python3)
#####################################
option(CROW_BUILD_EXAMPLES "Build the examples in the project" ${CROW_IS_MAIN_PROJECT})
option(CROW_BUILD_TESTS "Build the tests in the project" ${CROW_IS_MAIN_PROJECT})
option(CROW_BUILD_FUZZER "Instrument and build Crow fuzzer" OFF)
option(CROW_AMALGAMATE "Combine all headers into one" OFF)
option(CROW_INSTALL "Add install step for Crow" ON )
option(CROW_USE_BOOST "Use Boost.Asio for Crow" OFF)
Expand Down Expand Up @@ -134,6 +135,11 @@ if(CROW_BUILD_TESTS)
endif()
endif()

# Fuzzers
if (CROW_BUILD_FUZZER)
add_subdirectory(tests/fuzz)
endif()

#####################################
# Install Files
#####################################
Expand Down
29 changes: 29 additions & 0 deletions tests/fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Utilized by OSSFuzz to build the harness(es) for continuous fuzz-testing
# OSSFuzz defines the following environment variables, that this target relies upon:
# CXX, CFLAGS, LIB_FUZZING_ENGINE, OUT
cmake_minimum_required(VERSION 3.14)

function(define_fuzzer executable_name)
add_executable(${executable_name} ${executable_name}.cpp)
target_link_libraries(${executable_name} PRIVATE Crow $ENV{LIB_FUZZING_ENGINE})
target_compile_features(${executable_name} PRIVATE cxx_std_17)

if (DEFINED ENV{OUT})
install(TARGETS ${executable_name} DESTINATION $ENV{OUT})
else ()
message(WARNING "Cannot install if $OUT is not defined!")
endif ()
endfunction()

add_definitions(-DNDEBUG) # Do not want assertions

if (DEFINED ENV{CFLAGS})
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}")
endif ()
if (DEFINED ENV{CXXFLAGS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{CXXFLAGS}")
endif ()

define_fuzzer(template_fuzzer)
define_fuzzer(request_fuzzer)
define_fuzzer(b64_fuzzer)
27 changes: 27 additions & 0 deletions tests/fuzz/b64_fuzzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <cstdint>
#include <fuzzer/FuzzedDataProvider.h>

#include "crow.h"

class FuzzException : public std::exception
{
virtual const char* what() const throw()
{
return "Base64 decoding error!";
}
};

extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, const std::size_t size)
{
FuzzedDataProvider fdp{data, size};

std::string plaintext = fdp.ConsumeRandomLengthString();
std::string encoded = crow::utility::base64encode(plaintext, plaintext.size());
std::string decoded = crow::utility::base64decode(encoded, encoded.size());

if (plaintext != decoded)
{
throw FuzzException();
}
return 0;
}
8 changes: 8 additions & 0 deletions tests/fuzz/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
cd $SRC/crow
mkdir -p build
cmake -S . -B build -DCROW_BUILD_FUZZER=ON -DCROW_BUILD_EXAMPLES=OFF -DCROW_BUILD_TESTS=OFF && cmake --build build --target install

# Build the corpora
cd tests/fuzz
zip -q $OUT/template_fuzzer_seed_corpus.zip template_corpus/*
zip -q $OUT/request_fuzzer_seed_corpus.zip html_corpus/*
4 changes: 4 additions & 0 deletions tests/fuzz/html_corpus/get.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
GET /test/?string_param=exampleString&integer_param=12345 HTTP/1.1
Host: example.com
User-Agent: Fuzzer/1.0
Accept: */*
82 changes: 82 additions & 0 deletions tests/fuzz/request_fuzzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include <cstdint>
#include <fuzzer/FuzzedDataProvider.h>

#include <sys/socket.h>

#include "crow.h"

constexpr const int SERVER_PORT = 18080;

/**
* To be run in a separate thread,
*
* Starts up the web-server, configures a dummy route, and serves incoming requests
*/
static void start_web_server()
{
crow::SimpleApp app{};

CROW_ROUTE(app, "/test/<string>/<int>")
([](const crow::request& req, std::string a, int b)
{
std::string resp{};
for (const auto & param : req.get_body_params().keys())
{
resp += param;
}
return resp;
});

crow::logger::setLogLevel(crow::LogLevel::CRITICAL);
app.bindaddr("127.0.0.1")
.port(SERVER_PORT)
.multithreaded()
.run();
}

/**
* Called once at fuzzer start-up, initializes the web-server
* @return True,
*/
static bool initialize_web_server()
{
static std::thread ws_th{start_web_server};
return true;
}

static int send_request_to_web_server(FuzzedDataProvider &fdp)
{
int rc = -1;

int sock = socket(AF_INET, SOCK_STREAM, 0);
auto http_msg = fdp.ConsumeRemainingBytesAsString();
sockaddr_in ws_addr{.sin_family=AF_INET, .sin_port= htons(SERVER_PORT)};
ws_addr.sin_addr.s_addr = INADDR_ANY;

if (-1 == sock)
{
goto done;
}

if (-1 == connect(sock, (struct sockaddr*) &ws_addr, sizeof(ws_addr)))
{
close(sock);
goto done;
}
http_msg.insert(0, "GET / HTTP/1.1\r\n");

send(sock, http_msg.c_str(), http_msg.length(), 0);
close(sock);
rc = 0;
done:
return rc;
}

extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, const std::size_t size)
{
static bool initialized = initialize_web_server();
FuzzedDataProvider fdp{data, size};

send_request_to_web_server(fdp);
return 0;
}
12 changes: 12 additions & 0 deletions tests/fuzz/template_corpus/template.seed
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>User Profile</title>
</head>
<body>
<h1>{{name}}'s Profile</h1>
<p>{{bio}}</p>
<p>Favorite Programming Language: {{favoriteLanguage}}</p>
</body>
</html>
33 changes: 33 additions & 0 deletions tests/fuzz/template_fuzzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <cstdint>
#include <fuzzer/FuzzedDataProvider.h>

#include "crow.h"

static crow::mustache::context build_context_object(FuzzedDataProvider &fdp)
{
crow::mustache::context ctx{};

for (auto i = 0; i < fdp.ConsumeIntegralInRange(0, 10); ++i)
{
ctx[fdp.ConsumeRandomLengthString()] = fdp.ConsumeRandomLengthString();
}

return ctx;
}

extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, const std::size_t size)
{
FuzzedDataProvider fdp{data, size};
try
{
auto page = crow::mustache::compile(fdp.ConsumeRandomLengthString());
auto ctx = build_context_object(fdp);
page.render_string(ctx);
}
catch (const crow::mustache::invalid_template_exception& e)
{
return -1;
}

return 0;
}