Add sse_parser_fuzzer for detail::SSEParser (#2665)

Feed the event stream to the parser in chunks and abort if the reported events, ids and retries differ from parsing it in one piece. Also run with a length limit to cover the early-stop path.
This commit is contained in:
yhirose authored and GitHub committed 2026-11-12 21:11:48 -05:00
1 parent 8c5fd6aa08
commit 53a7f4964e
2 files changed
+53 -1

No files matched your search

+4 -1
View File
@@ -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
+49
View File
@@ -0,0 +1,49 @@
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <httplib.h>
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<size_t>(data[0]) + 1;
std::string stream(reinterpret_cast<const char *>(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;
}