
Security Advisory: Unauthenticated NULL Pointer Dereference Crashes the Server (TinyWeb)
Assigned CVE ID: CVE-2026-67184
When TinyWeb fails to parse a request line, it returns an error but leaves the request's URL pointer set to NULL. The caller ignores the error and passes the request to the response builder, which dereferences that NULL pointer and crashes the worker. A few malformed requests take the whole server down, and it does not recover.
TnyWeb/0.0.8e48f15d (2018-11-20), where this parser and response path were introduced, through a381da2 (2023-11-22, latest on master).CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:NAn unauthenticated remote attacker who can reach the server's listening TCP port (9090 in the shipped configuration). No credentials or user interaction are required.
HttpParser::execute() allocates the URL object only after the request line has parsed successfully. A malformed HTTP version makes the version check fail and jump to the error label, which returns -1 before the allocation runs:
// src/tiny_http/http_parser.cc:1401 (the "H" of "HTTP" is expected here)
checkOrGoError((ch == 'H')); // false -> goto error
// src/tiny_http/http_parser.cc:1692 (skipped by the goto above)
request->url = new Url;
// src/tiny_http/http_parser.cc:1750-1753
error:
LOG(Debug) << "http request content is invalid\n";
return -1;
The HttpRequest is created with std::make_shared<HttpRequest>(), which value-initializes it, so url stays NULL when the allocation is skipped.
WebProtocol::dataReceived() records the return value but calls buildResponse() regardless:
// src/tiny_http/http_protocol.cc:57-61
valid = m_nParser.execute(data.c_str(), begin, data.size(), m_pRequest.get());
valid_requ = (valid == -1) ? false : true;
// src/tiny_http/http_protocol.cc:75
m_nResponser.buildResponse(m_pRequest.get(), valid_requ, m_pResponse.get());
buildResponse() receives valid_requ == false but never checks it, and dereferences the NULL URL pointer:
// src/tiny_http/http_responser.cc:66
Url* url = req->url; // NULL
// src/tiny_http/http_responser.cc:83
if (url->field_set & (1 << HTTP_UF_PATH)) { // SIGSEGV
The worker process crashes on this access. It is not restarted into a serving state, so once the workers are gone the server stops answering.
Start the server with the shipped configuration (listening on port 9090).
Confirm it answers a normal request:
curl http://TARGET:9090/
XTTP/1.1 fails the version check after the request line has already committed to request mode:printf 'GET / XTTP/1.1\r\n\r\n' | nc TARGET 9090
A debugger attached to a worker shows the crash at the NULL dereference:
#0 HttpBuilder::buildResponse (valid_requ=false) at src/tiny_http/http_responser.cc:83
#1 WebProtocol::dataReceived (data="GET / XTTP/1.1\r\n...") at src/tiny_http/http_protocol.cc:75
An unauthenticated attacker can crash the server workers with a few short requests and keep the service offline by resending them. This is a denial of service.
In dataReceived(), when valid_requ is false, build an error response and return instead of calling buildResponse(). In buildResponse(), check req->url for NULL before using it. Allocating the URL object before the parse can fail would also close the gap.