Hash-flooding risk in HTTP header, query, trailer, and chunk-extension maps
Seastar's HTTP server inserts client-controlled field names into hash maps without adding a secret hash key or enforcing field-count limits. On builds with predictable standard-library string hashing, distinct names chosen for the same bucket can drive cumulative insertion toward quadratic CPU work. Request headers and query parameters are processed before route handlers; with the default non-streaming configuration, chunk extensions and trailers are also parsed before the handler runs.
Affected code
| Input | Container and insertion path |
|---|---|
| Request header names | _headers (include/seastar/http/request.hh:68); Ragel assign_field calls try_emplace (src/http/request_parser.rl:70–78). Case-equivalent duplicate names append to the existing value. |
| Decoded query names | Both _query_params and deprecated query_parameters (request.hh:70,95–97) are populated by request::add_query_param (src/http/request.cc:91–106). Every accepted parameter updates both maps. |
| Trailer names | http_chunk_trailer_parser::_headers (src/http/chunk_parsers.rl:225) is populated at 176–185, then moved into request::trailing_headers (include/seastar/http/internal/content_source.hh:219). |
| Chunk-extension names | The parser first inserts into its per-chunk _extensions map (chunk_parsers.rl:55–56,94). The result is merged into request-wide chunk_extensions, with duplicate-name values appended (content_source.hh:146–149). Distinct extension names can accumulate across chunks. |
std::hash<sstring> delegates to std::hash<std::string_view> (include/seastar/core/sstring.hh:803–807). string_view_hash uses these same hashers; the header map's case_insensitive_hash lowercases a copy before hashing it (include/seastar/util/string_utils.hh:44–68). These wrappers add no secret seed.
Collision inputs must target the actual stored keys: case-folded header names, URL-decoded query names, and the parsed trailer/extension names. They must also account for the target map's bucket growth. The two query maps perform separate hash-table updates; the current one additionally preserves repeated values in a vector.
Execution order and limits
- Headers and query:
connection::read_one()parses headers atsrc/http/httpd.cc:196–204.generate_reply()parses query parameters at366before calling_routes.handleat371. The request grammar, query loop, and Ragel parser base contain no explicit request-target/header byte cap or field-count cap (request_parser.rl:105–123;request.cc:112–127;include/seastar/core/ragel.hh). - Default body handling:
_content_streamingdefaults tofalse(include/seastar/http/httpd.hh:137).set_request_content()reads the entire body beforegenerate_reply()(httpd.cc:159–170,260–264), so chunked metadata is parsed before route handling. When streaming is enabled, parsing follows body reads; the server also performs a post-handler read to check for unread content (272–276). Streaming alone does not establish that these parsers are unreachable. - The content-length setting does not bound these fields:
_content_length_limitdefaults tosize_t::max()(httpd.hh:136). Its check compares the suppliedContent-Lengthafter header parsing (httpd.cc:228–235). It does not cap request-target/header bytes or accumulated chunk metadata. The chunk decoder receives no such limit and imposes no trailer/extension count cap (content_source.hh:149–160,208–225).
All maps belong to the current request or parser, so the collision workload can be contained in one request. The hash-table operations execute synchronously in parsing callbacks; excessive work can delay other tasks on the same reactor shard. Deployment-level request limits and access controls can restrict exposure.
Suggested mitigations
- Use a hash-flooding-resistant keyed hasher while preserving each map's existing equality semantics.
- Enforce request-target/header byte limits and explicit counts for headers, query fields, trailers, and chunk extensions, including cumulative extensions across chunks.
- Apply chunk metadata limits independently of decoded body length. Consider retiring the deprecated query map to avoid duplicate insertion work.
Source: scylladb/seastar