mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-27 12:31:28 +00:00
Guard regex routes against stack overflow from long paths
RegexMatcher::match() called std::regex_match() directly on the attacker-controlled request path. For quantified patterns such as "(.*)", std::regex_match's recursive backtracking implementation (most acute on libstdc++) recurses roughly once per matched character, so a long enough path can exhaust the calling thread's stack and crash the process. Verified against real GNU libstdc++: under the default thread stack size, a path of a couple thousand characters against a simple quantified route pattern reliably crashed the process, well within the existing 8192-byte request URI limit. Add CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH (default 256) and reject paths longer than it before ever calling std::regex_match, treating them as a non-match instead. Confirmed the fix eliminates the crash under the same libstdc++ build and default stack size that reproduced it.
This commit is contained in:
1 parent
89e0c5c238
commit
88240172ea
2 files changed
+66
No files matched your search
@@ -15412,6 +15412,55 @@ TEST(PathParamsTest, SemicolonInTheMiddleIsNotAParam) {
|
||||
EXPECT_EQ(request.path_params, expected_params);
|
||||
}
|
||||
|
||||
TEST(RegexMatcherTest, OverlongPathIsRejectedBeforeRegexMatch) {
|
||||
// std::regex_match's backtracking (most acute on libstdc++) can exhaust the
|
||||
// calling thread's stack on a long enough path for a quantified pattern;
|
||||
// RegexMatcher must refuse to run regex matching on paths beyond
|
||||
// CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH instead of invoking it.
|
||||
detail::RegexMatcher matcher("/files/(.*)");
|
||||
|
||||
Request within_limit;
|
||||
within_limit.path = "/files/" + std::string(10, 'A');
|
||||
EXPECT_TRUE(matcher.match(within_limit));
|
||||
|
||||
Request over_limit;
|
||||
over_limit.path =
|
||||
"/files/" + std::string(CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH, 'A');
|
||||
EXPECT_FALSE(matcher.match(over_limit));
|
||||
}
|
||||
|
||||
TEST(RegexMatcherTest, ServerSurvivesOverlongPathOnRegexRoute) {
|
||||
Server svr;
|
||||
svr.Get(R"(/files/(.*))", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
auto port = svr.bind_to_any_port(HOST);
|
||||
auto thread = std::thread([&]() { svr.listen_after_bind(); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
svr.wait_until_ready();
|
||||
|
||||
// Comfortably past CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH, well within the
|
||||
// request URI limit; this used to be able to crash the process.
|
||||
std::string path =
|
||||
"/files/" + std::string(CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH * 4, 'A');
|
||||
std::string req = "GET " + path +
|
||||
" HTTP/1.1\r\n"
|
||||
"Host: " +
|
||||
std::string(HOST) + ":" + std::to_string(port) +
|
||||
"\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
std::string response;
|
||||
ASSERT_TRUE(send_request(5, req, &response, port));
|
||||
EXPECT_NE(std::string::npos, response.find("404"));
|
||||
}
|
||||
|
||||
TEST(ParseUrlTest, VariousPatterns) {
|
||||
{
|
||||
detail::UrlComponents uc;
|
||||
|
||||
Reference in new issue
Block a user