mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-16 10:49:36 +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
@@ -138,6 +138,18 @@
|
||||
#define CPPHTTPLIB_RANGE_MAX_COUNT 1024
|
||||
#endif
|
||||
|
||||
// std::regex_match's backtracking implementation (most acutely on libstdc++)
|
||||
// recurses roughly once per matched character for quantified patterns such
|
||||
// as "(.*)", so a long enough path can exhaust the calling thread's stack; on
|
||||
// a default ~8MB thread stack that has been observed to take on the order of
|
||||
// a couple thousand characters for a simple pattern. 256 leaves a wide safety
|
||||
// margin below that (well under the 8192-byte request URI limit) while still
|
||||
// fitting any realistic route segment; raise it if a route legitimately needs
|
||||
// longer paths. Regex routes are never applied to paths longer than this.
|
||||
#ifndef CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH
|
||||
#define CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH 256
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_TCP_NODELAY
|
||||
#define CPPHTTPLIB_TCP_NODELAY false
|
||||
#endif
|
||||
@@ -11633,6 +11645,11 @@ inline bool PathParamsMatcher::match(Request &request) const {
|
||||
|
||||
inline bool RegexMatcher::match(Request &request) const {
|
||||
request.path_params.clear();
|
||||
// See CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH: an overlong path is treated as
|
||||
// a non-match rather than risking a stack overflow in std::regex_match.
|
||||
if (request.path.length() > CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
return std::regex_match(request.path, request.matches, regex_);
|
||||
}
|
||||
|
||||
|
||||
@@ -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