One of my earlier posts outlined how I had discovered six security vulnerabilities in the Privoxy software using the technique of fuzzing to cause the software to crash. This post outlines how I discovered three more vulnerabilities in Privoxy related to a non-crashing vulnerability: memory leaks.
In many ways, fuzzing is much more than just finding crashes in a program. Fuzzing offers a way to generate inputs which hit corner-cases of the state machine; which may, or may not, cause certain problems to occur within the program. By using fuzzing to discover all of these corner-cases, we can test for various conditions which we wish to explore further.
One of the corner-cases which I decided to look for in Privoxy was memory leaks. If certain HTTP requests or responses could cause memory to leak in the Privoxy process, an attacker could take advantage and continuously build up the amount of memory Privoxy is using until it simply runs out of memory. In some cases, this could make the host machine completely inoperable, resulting in denial-of-service.
In order to fuzz for memory leaks, I opted to use LeakSanitizer (LSAN), which is a run-time memory leak checker similar to Valgrind/memcheck. LSAN usually works by recording all of the memory allocations from a process’ start, and upon the process’ exit, compares them to those that were freed; any non-freed memory is then recorded as being leaked. However, this default behaviour was incompatible with AFL++’s persistent fuzzing mode – LSAN would only report leaks at the exiting of a program, which does not occur for every single fuzzing input. Likewise, LSAN does not raise and abort() upon detecting leaks, but rather exits with the return-code 23.
At first, I implemented LSAN into AFL++ to be used with the __AFL_LEAK_CHECK() macro. This macro would activate the leak checker when it is called, and if a leak is detected, signal to the fuzzer that an issue was detected. This diagram outlines that process:
In the case of Privoxy, there was some memory which was always detected as being leaked by LSAN, in relation to the initial configuration parsing of the program. This was not a “real” memory leak, and could be ignored. Therefore, in order to effectively “ignore” memory being allocated, I implemented the macros __AFL_LSAN_OFF() and __AFL_LSAN_ON(), which instruct LSAN to not track the memory allocated in-between these calls. In the end, my implementation of LSAN into AFL++ looked like this:
In my Privoxy build, I added __AFL_LEAK_CHECK() to the end of my __AFL_LOOP(), added __AFL_LSAN_OFF() to the beginning of main(), and __AFL_LSAN_ON() after all of the memory for configuration/startup had been handled. A simple diff describes the simple process of fuzzing for memory leaks in Privoxy:
Through fuzzing, three memory leaks were detected, which could be triggered by a client sending unexpected HTTP requests.
|Memory Leak in URI Rewriting Parsing||CVE-2021-44540|
|Memory Leak in HTTPS Request Header Parsing||CVE-2021-44541|
|Memory Leak in HTTP Request Body Sending||CVE-2021-44542|
Thank you to Fabian Keil for his ongoing support of Privoxy.