From 0cbeafe6a4026e0ebf890d8e779dca65d8e0a3b9 Mon Sep 17 00:00:00 2001 From: DavidKorczynski Date: Wed, 29 Apr 2026 03:05:29 +0100 Subject: [PATCH] Add client fuzzing harness (#2437) Cover client request processing logic. The goal is to enable this running on OSS-Fuzz. Signed-off-by: David Korczynski --- test/fuzzing/Makefile | 6 ++- test/fuzzing/client_fuzzer.cc | 88 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 test/fuzzing/client_fuzzer.cc diff --git a/test/fuzzing/Makefile b/test/fuzzing/Makefile index 7f0b4da..da5d696 100644 --- a/test/fuzzing/Makefile +++ b/test/fuzzing/Makefile @@ -13,7 +13,7 @@ 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 -FUZZERS = server_fuzzer url_parser_fuzzer header_parser_fuzzer +FUZZERS = server_fuzzer url_parser_fuzzer header_parser_fuzzer client_fuzzer # Runs all the tests and also fuzz tests against seed corpus. all : $(FUZZERS) @@ -25,6 +25,10 @@ server_fuzzer : server_fuzzer.cc ../../httplib.h $(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl zip -q -r server_fuzzer_seed_corpus.zip corpus +client_fuzzer : client_fuzzer.cc ../../httplib.h + $(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl + zip -q -r client_fuzzer_seed_corpus.zip corpus + header_parser_fuzzer : header_parser_fuzzer.cc ../../httplib.h $(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl diff --git a/test/fuzzing/client_fuzzer.cc b/test/fuzzing/client_fuzzer.cc new file mode 100644 index 0000000..bffa051 --- /dev/null +++ b/test/fuzzing/client_fuzzer.cc @@ -0,0 +1,88 @@ +#include +#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 static_cast(size); + } + + ssize_t write(const char *ptr, size_t size) override { + request_.append(ptr, size); + return static_cast(size); + } + + ssize_t write(const char *ptr) { return write(ptr, strlen(ptr)); } + + ssize_t write(const std::string &s) { return write(s.data(), s.size()); } + + bool is_readable() const override { return true; } + + bool wait_readable() const override { return true; } + + bool wait_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; + } + + void get_local_ip_and_port(std::string &ip, int &port) const override { + ip = "127.0.0.1"; + port = 8080; + } + + socket_t socket() const override { return 0; } + + time_t duration() const override { return 0; }; + +private: + const uint8_t *data_; + size_t size_; + size_t read_pos_; + std::string request_; +}; + +class FuzzableClient : public httplib::ClientImpl { +public: + FuzzableClient() : httplib::ClientImpl("localhost", 8080) {} + + void ProcessFuzzedResponse(FuzzedStream &stream, const std::string &method) { + httplib::Request req; + req.method = method; + req.path = "/"; + httplib::Response res; + bool close_connection = false; + httplib::Error error = httplib::Error::Success; + + process_request(stream, req, res, close_connection, error); + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 1) return 0; + + FuzzedStream stream{data + 1, size - 1}; + FuzzableClient client; + + // Use the first byte to select method + std::string method; + switch (data[0] % 6) { + case 0: method = "GET"; break; + case 1: method = "POST"; break; + case 2: method = "PUT"; break; + case 3: method = "PATCH"; break; + case 4: method = "DELETE"; break; + case 5: method = "OPTIONS"; break; + } + + client.ProcessFuzzedResponse(stream, method); + return 0; +}