CVE-2023-52266: Use After Free (CWE-416)
value: ehttp 1.0.6 before 17405b9 has an epoll_socket.cpp read_func use-after-free. An attacker can make many connections over a short time to trigger this.
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
CVSS v3 base score: 7.5
Published: 2023-12-31T00:15Z
Repository: https://github.com/hongliuliao/ehttp
Language: C++
Files changed: Makefile, src/epoll_socket.cpp, src/sim_parser.cpp, test/issue38/poc.py
Commit date: 2023-10-08 06:54:01 +0000
Commit message: fix log overflow & epoll_context use after free bug

Diff: diff --git a/Makefile b/Makefile
index 9a312f5..79116e0 100644
--- a/Makefile
+++ b/Makefile
@@ -3,12 +3,12 @@
 CXX=g++
 CXXFLAGS += -g -Wall
 LDFLAGS += -pthread
 
 ifdef ACOV
-	CXXFLAGS += -fprofile-arcs -ftest-coverage
-	LDFLAGS += -lgcov --coverage
+	CXXFLAGS += -fprofile-arcs -ftest-coverage -fsanitize=address
+	LDFLAGS += -lgcov --coverage -fsanitize=address
 endif
 
 MULTIPART_PARSER_INC_PATH=-I deps/multipart-parser-c/
 MULTIPART_PARSER_LIB_PATH=deps/multipart-parser-c/multipart_parser.o
 
diff --git a/src/epoll_socket.cpp b/src/epoll_socket.cpp
index 635067c..0c75897 100644
--- a/src/epoll_socket.cpp
+++ b/src/epoll_socket.cpp
@@ -226,15 +226,10 @@ int EpollSocket::handle_accept_event(int &epollfd, epoll_event &event, EpollSock
 }
 
 void read_func(void *data) {
     TaskData *td = (TaskData *) data;
     td->es->handle_readable_event(td->event);
-
-    EpollContext *hc = (EpollContext *) td->event.data.ptr;
-    if (hc != NULL) {
-        hc->_ctx_status = CONTEXT_READ_OVER;
-    }
     delete td;
 }
 
 int EpollSocket::handle_readable_event(epoll_event &event) {
     EpollContext *epoll_context = (EpollContext *) event.data.ptr;
@@ -246,10 +241,13 @@ int EpollSocket::handle_readable_event(epoll_event &event) {
 
     int ret = _watcher->on_readable(_epollfd, event);
     if (ret == READ_CLOSE) {
         return close_and_release(event);
     }
+    if (epoll_context != NULL) {
+        epoll_context->_ctx_status = CONTEXT_READ_OVER;
+    }
 
     if (ret == READ_CONTINUE) {
         event.events = EPOLLIN | EPOLLONESHOT;
         ret = epoll_ctl(_epollfd, EPOLL_CTL_MOD, fd, &event);
     } else if (ret == READ_OVER) { // READ_OVER
diff --git a/src/sim_parser.cpp b/src/sim_parser.cpp
index 105db3a..faeba33 100644
--- a/src/sim_parser.cpp
+++ b/src/sim_parser.cpp
@@ -575,11 +575,11 @@ int Request::parse_request(const char *read_buffer, int read_size) {
     _total_req_size += read_size;
     if (_total_req_size > MAX_REQ_SIZE) {
         LOG_INFO("TOO BIG REQUEST WE WILL REFUSE IT! MAX_REQ_SIZE:%d", MAX_REQ_SIZE);
         return -1;
     }
-    LOG_DEBUG("read from client: size:%d, content:%s", read_size, read_buffer);
+    LOG_DEBUG("read from client: size:%d, content:%.4096s", read_size, read_buffer);
     ssize_t nparsed = http_parser_execute(&_parser, &_settings, read_buffer, read_size);
     if (nparsed != read_size) {
         std::string err_msg = "unkonw";
         if (_parser.http_errno) {
             err_msg = http_errno_description(HTTP_PARSER_ERRNO(&_parser));
diff --git a/test/issue38/poc.py b/test/issue38/poc.py
new file mode 100644
index 0000000..b299268
--- /dev/null
+++ b/test/issue38/poc.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+
+import socket
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+sock.connect(("localhost", 8080))
+sock.send(b"GET"*5000+b" /hello"*5000+b" HTTP/1.1\r\nHost:localhost:8080\r\n\r\n")
+response = sock.recv(4096)
+sock.close()

Vulnerable code: 	CXXFLAGS += -fprofile-arcs -ftest-coverage
	LDFLAGS += -lgcov --coverage

    EpollContext *hc = (EpollContext *) td->event.data.ptr;
    if (hc != NULL) {
        hc->_ctx_status = CONTEXT_READ_OVER;
    }
    LOG_DEBUG("read from client: size:%d, content:%s", read_size, read_buffer);
Fixed code: 	CXXFLAGS += -fprofile-arcs -ftest-coverage -fsanitize=address
	LDFLAGS += -lgcov --coverage -fsanitize=address
    if (epoll_context != NULL) {
        epoll_context->_ctx_status = CONTEXT_READ_OVER;
    }
    LOG_DEBUG("read from client: size:%d, content:%.4096s", read_size, read_buffer);
#!/usr/bin/env python3

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("localhost", 8080))
sock.send(b"GET"*5000+b" /hello"*5000+b" HTTP/1.1\r\nHost:localhost:8080\r\n\r\n")
response = sock.recv(4096)
sock.close()