From 5292142046779fa1aea1035196872558f4557b0d Mon Sep 17 00:00:00 2001 From: Omkar Jadhav Date: Thu, 15 Oct 2020 17:41:40 +0530 Subject: [PATCH] Add cpp-httplib to oss-fuzz (#684) * *Add server fuzzer target and seed corpus * Add fuzz_test option to Makefile * Fix #685 * Try to fix Github actions on Ubuntu * Added ReadTimeoutSSL test * Comment out `-fsanitize=address` * Rebase upstream changes * remove address sanitizer temporarily * Add separate Makefile for fuzzing * 1. Remove special char from dictionary 2. Clean fuzzing/Makefile * Use specific path to avoid accidently linking openssl version brought in by oss-fuzz * remove addition of flags * Refactor Makefile * Add missing newline * Add fuzztest to github workflow * Fix Co-authored-by: yhirose --- .github/workflows/test.yaml | 3 + test/Makefile | 1 - test/Makefile.fuzz_test | 36 +++ test/fuzzing/Makefile | 26 ++ test/fuzzing/corpus/1 | 1 + test/fuzzing/corpus/2 | 5 + test/fuzzing/server_fuzzer.cc | 88 +++++++ test/fuzzing/server_fuzzer.dict | 224 ++++++++++++++++++ .../fuzzing/standalone_fuzz_target_runner.cpp | 35 +++ 9 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 test/Makefile.fuzz_test create mode 100644 test/fuzzing/Makefile create mode 100644 test/fuzzing/corpus/1 create mode 100644 test/fuzzing/corpus/2 create mode 100644 test/fuzzing/server_fuzzer.cc create mode 100644 test/fuzzing/server_fuzzer.dict create mode 100644 test/fuzzing/standalone_fuzz_target_runner.cpp diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 627df1e..fde94d9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -27,6 +27,9 @@ jobs: - name: make if: matrix.os != 'windows-latest' run: cd test && make + - name: check fuzz test target + if: matrix.os == 'ubuntu-latest' + run: cd test && make -f Makefile.fuzz_test - name: setup msbuild on windows if: matrix.os == 'windows-latest' uses: warrenbuckley/Setup-MSBuild@v1 diff --git a/test/Makefile b/test/Makefile index abcb331..8d09773 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,4 +1,3 @@ - #CXX = clang++ CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address diff --git a/test/Makefile.fuzz_test b/test/Makefile.fuzz_test new file mode 100644 index 0000000..bc582ed --- /dev/null +++ b/test/Makefile.fuzz_test @@ -0,0 +1,36 @@ + +#CXX = clang++ +CXXFLAGS += -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion + +OPENSSL_DIR = /usr/local/opt/openssl@1.1 +OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto + +ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz + +BROTLI_DIR = /usr/local/opt/brotli +BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec + +# By default, use standalone_fuzz_target_runner. +# This runner does no fuzzing, but simply executes the inputs +# provided via parameters. +# Run e.g. "make all LIB_FUZZING_ENGINE=/path/to/libFuzzer.a" +# to link the fuzzer(s) against a real fuzzing engine. +# OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE. +LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o + +# Runs server_fuzzer.cc based on value of $(LIB_FUZZING_ENGINE). +# Usage: make fuzz_test LIB_FUZZING_ENGINE=/path/to/libFuzzer +all fuzz_test: server_fuzzer + ./server_fuzzer fuzzing/corpus/* + +# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use. +server_fuzzer : fuzzing/server_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o + $(CXX) $(CXXFLAGS) -o $@ $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread + +# Standalone fuzz runner, which just reads inputs from fuzzing/corpus/ dir and +# feeds it to server_fuzzer. +standalone_fuzz_target_runner.o : fuzzing/standalone_fuzz_target_runner.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +clean: + rm -f server_fuzzer pem *.0 *.o *.1 *.srl *.zip diff --git a/test/fuzzing/Makefile b/test/fuzzing/Makefile new file mode 100644 index 0000000..b39a8b4 --- /dev/null +++ b/test/fuzzing/Makefile @@ -0,0 +1,26 @@ + +#CXX = clang++ +# Do not add default sanitizer flags here as OSS-fuzz adds its own sanitizer flags. +CXXFLAGS += -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I../.. -I. -Wall -Wextra -Wtype-limits -Wconversion + +OPENSSL_DIR = /usr/local/opt/openssl@1.1 + +# Using full path to libssl and libcrypto to avoid accidentally picking openssl libs brought in by msan. +OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -I$(OPENSSL_DIR)/lib /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a + +ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz + +BROTLI_DIR = /usr/local/opt/brotli +# BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec + +# Runs all the tests and also fuzz tests against seed corpus. +all : server_fuzzer + ./server_fuzzer corpus/* + +# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use. +server_fuzzer : server_fuzzer.cc ../../httplib.h + $(CXX) $(CXXFLAGS) -o $@ $< -Wl,-Bstatic $(OPENSSL_SUPPORT) -Wl,-Bdynamic -ldl $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread + zip -q -r server_fuzzer_seed_corpus.zip corpus + +clean: + rm -f server_fuzzer pem *.0 *.o *.1 *.srl *.zip diff --git a/test/fuzzing/corpus/1 b/test/fuzzing/corpus/1 new file mode 100644 index 0000000..2b9fcc4 --- /dev/null +++ b/test/fuzzing/corpus/1 @@ -0,0 +1 @@ +PUT /search/sample?a=12 HTTP/1.1 \ No newline at end of file diff --git a/test/fuzzing/corpus/2 b/test/fuzzing/corpus/2 new file mode 100644 index 0000000..bdb9bcc --- /dev/null +++ b/test/fuzzing/corpus/2 @@ -0,0 +1,5 @@ +GET /hello.htm HTTP/1.1 +User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT) +Accept-Language: en-us +Accept-Encoding: gzip, deflate +Connection: Keep-Alive \ No newline at end of file diff --git a/test/fuzzing/server_fuzzer.cc b/test/fuzzing/server_fuzzer.cc new file mode 100644 index 0000000..420ae69 --- /dev/null +++ b/test/fuzzing/server_fuzzer.cc @@ -0,0 +1,88 @@ +#include +#include + +class FuzzedStream : public httplib::Stream { + public: + FuzzedStream(const uint8_t* data, size_t size) + : data_(data), size_(size), read_pos_(0) {} + + ssize_t read(char* ptr, size_t size) override { + if (size + read_pos_ > size_) { + size = size_ - read_pos_; + } + memcpy(ptr, data_ + read_pos_, size); + read_pos_ += size; + return size; + } + + ssize_t write(const char* ptr, size_t size) override { + response_.append(ptr, size); + return static_cast(size); + } + + int write(const char* ptr) { return write(ptr, strlen(ptr)); } + + int write(const std::string& s) { return write(s.data(), s.size()); } + + std::string get_remote_addr() const { return ""; } + + bool is_readable() const override { return true; } + + bool is_writable() const override { return true; } + + void get_remote_ip_and_port(std::string &ip, int &port) const override { + ip = "127.0.0.1"; + port = 8080; + } + + private: + const uint8_t* data_; + size_t size_; + size_t read_pos_; + std::string response_; +}; + +class FuzzableServer : public httplib::Server { + public: + void ProcessFuzzedRequest(FuzzedStream& stream) { + bool connection_close = false; + process_request(stream, /*last_connection=*/false, connection_close, + nullptr); + } +}; + +static FuzzableServer g_server; + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + g_server.Get(R"(.*)", + [&](const httplib::Request& req, httplib::Response& res) { + res.set_content("response content", "text/plain"); + }); + g_server.Post(R"(.*)", + [&](const httplib::Request& req, httplib::Response& res) { + res.set_content("response content", "text/plain"); + }); + g_server.Put(R"(.*)", + [&](const httplib::Request& req, httplib::Response& res) { + res.set_content("response content", "text/plain"); + }); + g_server.Patch(R"(.*)", + [&](const httplib::Request& req, httplib::Response& res) { + res.set_content("response content", "text/plain"); + }); + g_server.Delete(R"(.*)", + [&](const httplib::Request& req, httplib::Response& res) { + res.set_content("response content", "text/plain"); + }); + g_server.Options(R"(.*)", + [&](const httplib::Request& req, httplib::Response& res) { + res.set_content("response content", "text/plain"); + }); + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedStream stream{data, size}; + g_server.ProcessFuzzedRequest(stream); + return 0; +} \ No newline at end of file diff --git a/test/fuzzing/server_fuzzer.dict b/test/fuzzing/server_fuzzer.dict new file mode 100644 index 0000000..47283dc --- /dev/null +++ b/test/fuzzing/server_fuzzer.dict @@ -0,0 +1,224 @@ +# Sources: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields + +# misc +"HTTP/1.1" + +# verbs +"CONNECT" +"DELETE" +"GET" +"HEAD" +"OPTIONS" +"PATCH" +"POST" +"PUT" +"TRACE" + + +# Webdav/caldav verbs +"ACL" +"BASELINE-CONTROL" +"BIND" +"CHECKIN" +"CHECKOUT" +"COPY" +"LABEL" +"LINK" +"LOCK" +"MERGE" +"MKACTIVITY" +"MKCALENDAR" +"MKCOL" +"MKREDIRECTREF" +"MKWORKSPACE" +"MOVE" +"ORDERPATCH" +"PRI" +"PROPFIND" +"PROPPATCH" +"REBIND" +"REPORT" +"SEARCH" +"UNBIND" +"UNCHECKOUT" +"UNLINK" +"UNLOCK" +"UPDATE" +"UPDATEREDIRECTREF" +"VERSION-CONTROL" + + +# Fields +"A-IM" +"Accept" +"Accept-Charset" +"Accept-Datetime" +"Accept-Encoding" +"Accept-Language" +"Accept-Patch" +"Accept-Ranges" +"Access-Control-Allow-Credentials" +"Access-Control-Allow-Headers" +"Access-Control-Allow-Methods" +"Access-Control-Allow-Origin" +"Access-Control-Expose-Headers" +"Access-Control-Max-Age" +"Access-Control-Request-Headers" +"Access-Control-Request-Method" +"Age" +"Allow" +"Alt-Svc" +"Authorization" +"Cache-Control" +"Connection" +"Connection:" +"Content-Disposition" +"Content-Encoding" +"Content-Language" +"Content-Length" +"Content-Location" +"Content-MD5" +"Content-Range" +"Content-Security-Policy" +"Content-Type" +"Cookie" +"DNT" +"Date" +"Delta-Base" +"ETag" +"Expect" +"Expires" +"Forwarded" +"From" +"Front-End-Https" +"HTTP2-Settings" +"Host" +"IM" +"If-Match" +"If-Modified-Since" +"If-None-Match" +"If-Range" +"If-Unmodified-Since" +"Last-Modified" +"Link" +"Location" +"Max-Forwards" +"Origin" +"P3P" +"Pragma" +"Proxy-Authenticate" +"Proxy-Authorization" +"Proxy-Connection" +"Public-Key-Pins" +"Range" +"Referer" +"Refresh" +"Retry-After" +"Save-Data" +"Server" +"Set-Cookie" +"Status" +"Strict-Transport-Security" +"TE" +"Timing-Allow-Origin" +"Tk" +"Trailer" +"Transfer-Encoding" +"Upgrade" +"Upgrade-Insecure-Requests" +"User-Agent" +"Vary" +"Via" +"WWW-Authenticate" +"Warning" +"X-ATT-DeviceId" +"X-Content-Duration" +"X-Content-Security-Policy" +"X-Content-Type-Options" +"X-Correlation-ID" +"X-Csrf-Token" +"X-Forwarded-For" +"X-Forwarded-Host" +"X-Forwarded-Proto" +"X-Frame-Options" +"X-Http-Method-Override" +"X-Powered-By" +"X-Request-ID" +"X-Requested-With" +"X-UA-Compatible" +"X-UIDH" +"X-Wap-Profile" +"X-WebKit-CSP" +"X-XSS-Protection" + +# Source: string and character literals in httplib.h +" " +"&" +", " +"-" +"--" +"." +".." +":" +"=" +" = = " +"0123456789abcdef" +"%02X" +"%0A" +"\\x0a\\x0d" +"%0D" +"%20" +"%27" +"%2B" +"%2C" +"%3A" +"%3B" +"application/javascript" +"application/json" +"application/pdf" +"application/xhtml+xml" +"application/xml" +"application/x-www-form-urlencoded" +"Bad Request" +"boundary=" +"bytes=" +"chunked" +"close" +"CONNECT" +"css" +"Forbidden" +"Found" +"gif" +"gzip" +"html" +"ico" +"image/gif" +"image/jpg" +"image/png" +"image/svg+xml" +"image/x-icon" +"index.html" +"Internal Server Error" +"jpeg" +"js" +"json" +"Location" +"Moved Permanently" +"multipart/form-data" +"Not Found" +"Not Modified" +"OK" +"pdf" +"png" +"Range" +"REMOTE_ADDR" +"See Other" +"svg" +"text/" +"text/css" +"text/html" +"text/plain" +"txt" +"Unsupported Media Type" +"xhtml" +"xml" \ No newline at end of file diff --git a/test/fuzzing/standalone_fuzz_target_runner.cpp b/test/fuzzing/standalone_fuzz_target_runner.cpp new file mode 100644 index 0000000..8e34792 --- /dev/null +++ b/test/fuzzing/standalone_fuzz_target_runner.cpp @@ -0,0 +1,35 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); + +// This runner does not do any fuzzing, but allows us to run the fuzz target +// on the test corpus or on a single file, +// e.g. the one that comes from a bug report. + +#include +#include +#include +#include + +// Forward declare the "fuzz target" interface. +// We deliberately keep this inteface simple and header-free. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); + +// It reads all files passed as parameters and feeds their contents +// one by one into the fuzz target (LLVMFuzzerTestOneInput). +int main(int argc, char **argv) { + for (int i = 1; i < argc; i++) { + std::ifstream in(argv[i]); + in.seekg(0, in.end); + size_t length = in.tellg(); + in.seekg (0, in.beg); + std::cout << "Reading " << length << " bytes from " << argv[i] << std::endl; + // Allocate exactly length bytes so that we reliably catch buffer overflows. + std::vector bytes(length); + in.read(bytes.data(), bytes.size()); + LLVMFuzzerTestOneInput(reinterpret_cast(bytes.data()), + bytes.size()); + std::cout << "Execution successful" << std::endl; + } + std::cout << "Execution finished" << std::endl; + return 0; +} \ No newline at end of file