diff --git a/test/fuzzing/Makefile b/test/fuzzing/Makefile index 82773755..f352385d 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 client_fuzzer multipart_parser_fuzzer websocket_fuzzer +FUZZERS = server_fuzzer url_parser_fuzzer header_parser_fuzzer client_fuzzer multipart_parser_fuzzer websocket_fuzzer sse_parser_fuzzer # Runs all the tests and also fuzz tests against seed corpus. all : $(FUZZERS) @@ -41,5 +41,8 @@ multipart_parser_fuzzer : multipart_parser_fuzzer.cc ../../httplib.h websocket_fuzzer : websocket_fuzzer.cc ../../httplib.h $(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl +sse_parser_fuzzer : sse_parser_fuzzer.cc ../../httplib.h + $(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl + clean: rm -f server_fuzzer pem *.0 *.o *.1 *.srl *.zip diff --git a/test/fuzzing/sse_parser_fuzzer.cc b/test/fuzzing/sse_parser_fuzzer.cc new file mode 100644 index 00000000..85e8849f --- /dev/null +++ b/test/fuzzing/sse_parser_fuzzer.cc @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +#include + +namespace { + +// Feeds the stream in chunks and records every handler call in order +std::string parse_in_chunks(const std::string &stream, size_t chunk, + size_t max_event_length) { + std::string trace; + httplib::detail::SSEParser parser( + [&](const httplib::sse::SSEMessage &msg) { + trace += "event[" + msg.event + "|" + msg.data + "|" + msg.id + "]"; + }, + [&](const std::string &id) { trace += "id[" + id + "]"; }, + [&](int retry_ms) { trace += "retry[" + std::to_string(retry_ms) + "]"; }, + max_event_length); + + for (size_t off = 0; off < stream.size(); off += chunk) { + if (!parser.parse(stream.data() + off, + std::min(chunk, stream.size() - off))) { + break; + } + } + return trace; +} + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size < 2 || size > 65536) { return 0; } + + // First byte selects the chunk size, the rest is the event stream + auto chunk = static_cast(data[0]) + 1; + std::string stream(reinterpret_cast(data + 1), size - 1); + + // Without a length limit, chunking must not change what is reported + auto whole = parse_in_chunks(stream, stream.size(), 0); + auto chunked = parse_in_chunks(stream, chunk, 0); + if (whole != chunked) { std::abort(); } + + // With a limit, only check that the parser stops cleanly + parse_in_chunks(stream, chunk, 64); + + return 0; +}