
نشرة أمنية: تسريب ذاكرة بدون مصادقة يؤدي إلى استنفاد الذاكرة (TinyWeb)
معرّف CVE المعيّن: CVE-2026-67183
يُخصّص TinyWeb عدة كائنات أثناء تحليل كل طلب ولا يحرّرها أبداً. لا تحتوي بنيتا الطلب والترويسات على مُدمِّرات (destructors)، ولا شيء يحذفهما بعد إرسال الاستجابة. تزداد ذاكرة عملية العامل (worker) مع كل طلب ولا تنخفض أبداً، لذا فإن تياراً ثابتاً من الطلبات العادية يستنزف ذاكرة العامل حتى نفادها.
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). لاحظ معرّف العملية (PID) لإحدى عمليات العامل.
سجّل الذاكرة المقيمة لعملية العامل:
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
النمو رتيب (monotonic)، وتبلغ زيادته 20 إلى 28 كيلوبايت تقريباً لكل طلب، دون أي هضبة. تكرار الدفعة يواصل الارتفاع حتى تُقتل عملية العامل بسبب نفاد الذاكرة.
يمكن لمهاجم غير مصادَق استنفاد ذاكرة العامل بتيار طلبات مستمر وإجبار حالة نفاد الذاكرة، مما يمنع الخدمة. كما تتسرّب الذاكرة بمرور الوقت مع الحركة العادية، لذا يمكن أن تحدث الحالة دون وجود مهاجم.
امنح HttpRequest و HttpHeaders مُدمِّرات تحرّر url وheaders وbody وكل HttpHeader في generals، أو احتفظ بها في مؤشرات ذكية بحيث تُحرَّر تلقائياً. وأيضاً حرّر مخازن مجمع الذاكرة لكل طلب بعد إرسال الاستجابة.