
Security Advisory: Unauthenticated Memory Leak Leads To Memory Exhaustion (TinyWeb)
Assigned CVE ID: CVE-2026-67183
TinyWeb выделяет несколько объектов при разборе каждого запроса и никогда не освобождает их. Структуры запроса и заголовков не имеют деструкторов, и ничто не удаляет их после отправки ответа. Память рабочего процесса растёт с каждым запросом и никогда не снижается, поэтому постоянный поток обычных запросов приводит к исчерпанию памяти рабочего процесса.
TnyWeb/0.0.8e48f15d (2018-11-20), где были введены эти структуры и парсер, вплоть до a381da2 (2023-11-22, последний в 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:NНеаутентифицированный удалённый злоумышленник, который может подключиться к прослушивающему TCP-порту сервера (9090 в поставляемой конфигурации) и отправлять запросы с умеренной постоянной скоростью. Не требуются ни учётные данные, ни взаимодействие с пользователем. Запросы могут быть корректно сформированными; специальная нагрузка не нужна.
Для каждого запроса HttpParser::execute() выделяет Url, HttpHeaders и один HttpHeader на каждую строку заголовка:
// src/tiny_http/http_parser.cc:1692
request->url = new Url;
// src/tiny_http/http_parser.cc:1709
request->headers = new HttpHeaders;
// src/tiny_http/http_parser.cc:1033-1040
HttpHeader* header = new HttpHeader;
return_val = parseHeader(stream, offset, len, header);
...
result->generals.push_back(header); // stored in a raw-pointer list
HttpRequest и HttpHeaders — это простые структуры без деструктора, поэтому уничтожение HttpRequest не освобождает ни один из этих объектов:
// src/tiny_http/http_parser.h:442
typedef struct HttpRequest {
...
Url* url;
HttpHeaders* headers;
HttpBody* body;
} HttpRequest;
// src/tiny_http/http_parser.h:279-304
typedef struct HttpHeaders {
...
std::list<HttpHeader*> generals; // raw pointers, never deleted
...
} HttpHeaders;
WebProtocol хранит запрос в std::shared_ptr<HttpRequest> (src/tiny_http/http_protocol.h:49). Когда следующий запрос заменяет его, выполняется стандартный ~HttpRequest, который приводит к утечке url, headers, body и каждого HttpHeader в generals. Единственный delete request в коде находится в getDataFromProxy() (src/tiny_http/http_protocol.cc:194) — функции, которая никогда не вызывается в пути обработки запроса. Буферы ответов на каждый запрос из пула памяти также увеличивают рост.
Запустите сервер с поставляемой конфигурацией (прослушивание порта 9090). Запишите идентификатор процесса рабочего процесса.
Зафиксируйте резидентную память рабочего процесса:
grep VmRSS /proc/<worker_pid>/status
for i in $(seq 1 2000); do
curl -s -o /dev/null http://TARGET:9090/
done
VmRSS. Он вырос и впоследствии не снижается. Пример выполнения:VmRSS before: 3704 kB
after 500 requests: 4788 kB
after 1000 requests: 22068 kB
after 1500 requests: 41652 kB
after 2000 requests: 59956 kB
Рост монотонен, примерно 20–28 кБ на запрос, без выхода на плато. Повторение пакета продолжает рост, пока рабочий процесс не будет убит из-за нехватки памяти.
Неаутентифицированный злоумышленник может исчерпать память рабочего процесса постоянным потоком запросов и вызвать состояние нехватки памяти, отказывая в обслуживании. Обычный трафик также со временем приводит к утечкам, поэтому данное состояние может возникнуть и без злоумышленника.
Добавьте HttpRequest и HttpHeaders деструкторы, которые освобождают url, headers, body и каждый HttpHeader в generals, либо храните их в умных указателях, чтобы они освобождались автоматически. Также освобождайте буферы пула памяти на каждый запрос после отправки ответа.