割り当て済み 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、およびヘッダー行ごとに 1 つの 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、および generals 内のすべての HttpHeader がリークします。コード内で唯一の delete request は getDataFromProxy() (src/tiny_http/http_protocol.cc:194) にあり、この関数はリクエスト処理経路から呼び出されることはありません。メモリプールからのリクエスト単位のレスポンスバッファも増加に拍車をかけます。
同梱構成でサーバーを起動します (ポート 9090 で待ち受け)。ワーカーのプロセス ID をメモします。
ワーカーの常駐メモリを記録します:
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 kB であり、頭打ちになりません。バッチを繰り返すと増加が続き、最終的にワーカーはメモリ不足で強制終了されます。
認証されていない攻撃者は、持続的なリクエストストリームによってワーカーのメモリを枯渇させ、メモリ不足状態を引き起こしてサービスを拒否できます。通常のトラフィックでも時間の経過とともにリークするため、攻撃者がいなくてもこの状態が発生し得ます。
HttpRequest と HttpHeaders に、url、headers、body、および generals 内のすべての HttpHeader を解放するデストラクタを追加するか、スマートポインタで保持して自動的に解放されるようにしてください。また、レスポンス送信後にはリクエスト単位のメモリプールバッファも解放してください。