From 92aecf85d84998e4da071e01c1358a9b42a2f488 Mon Sep 17 00:00:00 2001 From: yhirose Date: Fri, 1 May 2026 21:39:46 +0900 Subject: [PATCH] Fix OSS-Fuzz #508087118: avoid stack overflow in str2tag str2tag_core is recursive (one frame per character), so a long runtime input such as a fuzzer-supplied Content-Type would overflow the stack. Rewrite the runtime entry point str2tag() iteratively while keeping the recursive constexpr str2tag_core for compile-time UDL evaluation. The hash output is unchanged for all inputs. --- httplib.h | 11 ++++++++++- ...imized-header_parser_fuzzer-4645558454386688 | Bin 0 -> 58160 bytes test/test.cc | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 test/fuzzing/corpus/clusterfuzz-testcase-minimized-header_parser_fuzzer-4645558454386688 diff --git a/httplib.h b/httplib.h index fe3b716..a2de258 100644 --- a/httplib.h +++ b/httplib.h @@ -6374,7 +6374,16 @@ inline constexpr unsigned int str2tag_core(const char *s, size_t l, } inline unsigned int str2tag(const std::string &s) { - return str2tag_core(s.data(), s.size(), 0); + // Iterative form of str2tag_core: the recursive constexpr version is kept + // for compile-time UDL evaluation of short string literals, but at runtime + // we may receive arbitrarily long inputs (e.g. fuzzed Content-Type) that + // would blow the stack with one frame per character. + unsigned int h = 0; + for (auto c : s) { + h = (((std::numeric_limits::max)() >> 6) & h * 33) ^ + static_cast(c); + } + return h; } namespace udl { diff --git a/test/fuzzing/corpus/clusterfuzz-testcase-minimized-header_parser_fuzzer-4645558454386688 b/test/fuzzing/corpus/clusterfuzz-testcase-minimized-header_parser_fuzzer-4645558454386688 new file mode 100644 index 0000000000000000000000000000000000000000..7a970af30c3434bd4fe219683541f31f114cced1 GIT binary patch literal 58160 zcmeI(zfQtH901@WPP)}csA6JrF+PAA9s2~%>T2TV3phHuF!~M-?!JL9z(8D`Tuo}B z%0Qr|1hkazmy|-U_wTz)8SZ)*^G+H=?8fvcZig}k)iJue$S};5cH?G@-EoFt0mt)2 zS@(N+s{R~TH|X_x>B&K!=X=F55pgGmvn(r$I?u8!UI+pN2oNApDe(GSL{j}1R5!IV z&X6R@Lvf6vpWo(rk__t`$5Z+331PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009Ec7qA%F{4vr(0t5&UAV7cs0RjXF5FkK+009C72oNB!L;*_$OT=w;(PmEL zhu0f#jUOTnBtU=w0RjXF5FkK+009C72oNAZfB*pk1PBnADqt~kDvOH*2-Fm4&$FaG zJ*hc_ZYMy1009C72oNAZfB*pk1PBlyK!5-N0tCJVtQMFG^9h(a^SS+DF0TUrA-1Xr z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t6N)U@>xmn2I8>#sZ!tYdrYBbp5~Q#us%E B-Pr&D literal 0 HcmV?d00001 diff --git a/test/test.cc b/test/test.cc index f90a024..84123de 100644 --- a/test/test.cc +++ b/test/test.cc @@ -767,6 +767,13 @@ TEST(ParseAcceptHeaderTest, InvalidCases) { EXPECT_EQ(result[0], "text/*"); } +// Regression test for OSS-Fuzz #508087118: a long Content-Type ran str2tag +// recursively (one stack frame per character) and overflowed the stack. +TEST(Str2tagTest, LongInputDoesNotOverflowStack) { + std::string long_content_type(60000, 'x'); + EXPECT_NO_THROW(detail::can_compress_content_type(long_content_type)); +} + TEST(ParseAcceptHeaderTest, ContentTypesPopulatedAndInvalidHeaderHandling) { Server svr;