On January 26, 2021, Qualys published a blog describing their findings on the heap overflow vulnerability in sudo, CVE-2021-3156, which they named “Baron Samedit”.
Sudo is a central tool in many different Linux/Unix distributions that allows users to run programs with elevated security privileges. It’s a component that’s commonly included and installed with Linux, BSD, macOS, AIX, Solaris, and others.
I’m going to demonstrate Parasoft Insure++ memory over-read and overwrite detection using this vulnerability. Insure++ is a memory debugging tool that uses patented instrumentation techniques to quickly identify leaks and other memory issues.
Insure++ makes discovering, understanding, and remediating such bugs much easier than using traditional debugging tools and techniques.
When vulnerable versions of sudo (v1.8.2 – v1.9.5p1) are run with a command in shell mode and the command line argument ends in a single backslash, a buffer overflow of the user_args string occurs due to a flaw in the way un-escaping of command line arguments is performed.
When un-escaping the last ‘\’ character, the function will overwrite the null terminator of the string resulting in a buffer overflow. For a thorough breakdown of the vulnerability, check out the original Qualys blog. I’ll cover similar details when we analyze the results from Insure++.
To replicate the vulnerability in Insure++, we need to download a vulnerable version of sudo and compile it using Insure++. The source for sudo v1.9.5p1, the latest vulnerable version, is available here on the sudo website. After unpacking the source code, be sure Insure++ is on your PATH and set your CC to "insure gcc" before running configure.
You should consider setting a different prefix as well in order to not override the system sudo if applicable. From here run make and make install as usual.
The Qualys article provided a simple proof of concept command that demonstrates the buffer overflow vulnerability. It is as follows:
sudoedit -s '\' 'perl -e 'print "A" x 65536''
Replace sudoedit with the path to the one you compiled and run the command.
We can tell from the malloc message that the proof of concept executed properly.
Let’s see what Insure++ found by examining results in the Insra window.
We can see the first memory error is a read overflow that happens in sudoers.c at line 971. Insra tells us that the from pointer read one byte outside of the two-byte memory block it was pointing to.
Note the memory block is two bytes because the first is the ‘\’ character and the second is the null terminator. A stack trace shows us that this occurs in the set_cmnd() function.
The next memory error is a read from a bad address in sudoers.c on line 972. Since the from pointer is already out of bounds, as we saw in the first memory error, any indexed access using from will be a bad index.
The next memory error is a read overflow that occurred on line 974. Here we’re dereferencing the out of bounds pointer from and writing it to the buffer pointed to by the to pointer.
Note that Insure++ is tracking the origin of these memory blocks in all of the preceding memory errors. Insure++ is aware that the block of memory we’re reading is outside of the bounds of what was originally defined as the argv parameter in the main() method in sudo.c.
The next memory error is a write overflow in the same statement as before. Now, Insure++ is triggering on writing to the buffer the to pointer points at. Note that the size of the buffer to points to is 65539 bytes. The overflow command string we provided in the proof of concept was a ‘\’ character, a space, 65536 ‘A’ characters, and an implicit null terminator totaling 65539 bytes.
The next memory error occurs outside of if check for ‘\’ characters. Why is this causing a write overflow now? Since previous memory errors have corrupted the to pointer to point outside of its memory block, all subsequent write operations with this pointer will result in a write overflow.
Here we see the final memory corruption where the program null terminates the string. This is absolutely the correct behavior! However, the write overflow occurs because the to pointer is corrupted and already pointing outside of the block of memory it should be.
We can see that Parasoft Insure++ has made the analysis of this memory vulnerability much easier. We were able to quickly identify the location of the read and subsequent write overflows, know which variables were involved, and see how the invalid pointer affected subsequent memory operations. When combined with good testing coverage and fuzzing, Insure++ can make many complex memory bugs much simpler to understand, diagnose, and fix.
Anthony is a senior software engineer at Parasoft, focusing on runtime error detection, static analysis, reverse engineering, vulnerability analysis, and exploitation.