Browse Source

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 <[email protected]>
Omkar Jadhav 5 years ago
parent
commit
5292142046

+ 3 - 0
.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

+ 0 - 1
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
 

+ 36 - 0
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/[email protected]
+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

+ 26 - 0
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/[email protected]
+
+# 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

+ 1 - 0
test/fuzzing/corpus/1

@@ -0,0 +1 @@
+PUT /search/sample?a=12 HTTP/1.1

+ 5 - 0
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

+ 88 - 0
test/fuzzing/server_fuzzer.cc

@@ -0,0 +1,88 @@
+#include <memory>
+#include <httplib.h>
+
+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<int>(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;
+}

+ 224 - 0
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"

+ 35 - 0
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 <cassert>
+#include <iostream>
+#include <fstream>
+#include <vector>
+
+// 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<char> bytes(length);
+    in.read(bytes.data(), bytes.size());
+    LLVMFuzzerTestOneInput(reinterpret_cast<const uint8_t *>(bytes.data()),
+                           bytes.size());
+    std::cout << "Execution successful" << std::endl;
+  }
+  std::cout << "Execution finished" << std::endl;
+  return 0;
+}