In January 2021, a major buffer overflow vulnerability, impacting a large chunk of the Linux ecosystem, was discovered in the sudo application by security auditing firm, Qualys. The vulnerability, known widely as “Baron Samedit,” received a CVE identifier of CVE-2021-3156 and was patched with the release of Sudo v1.9.5p2.
CVE-2021-3156 was entered into the CVE database on Jan 15th with a high CVSS severity score of 7.8. The official CVE description in the database reads:
Sudo before 1.9.5p2 has a Heap-based Buffer Overflow, allowing privilege escalation to root via "sudoedit -s" and a command-line argument that ends with a single backslash character.
The CWE associated with this CVE is CWE-787: Out-of-bounds Write—where the software writes data past the end, or beginning, of the intended buffer. This CWE is a ChildOf CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer. The CWE system, which groups different types of CVEs into category, is hierarchical. Many CWEs are related to each other, but with different levels of abstraction. “ParentOf” and “ChildOf” are the most common descriptions of higher and lower levels of abstraction respectively. CWE-119 is more general and thus a higher level of abstraction than CWE-787, which is more specific to the vulnerability that was Baron Samedit.
Before I can begin to explain how the Baron Samedit buffer overflow was so damaging, it’s important to first understand exactly what the sudo application is, what it does and how it is used.
Sudo allows administrators to delegate limited root access to other users. The Unix command su stands for “super user,” and allows you to log in as root (a synonym for super user) and do whatever you want with the system. Sudo stands for "super user do" and it allows you to elevate your current user account to have root privileges temporarily—think of this as meaning “do something as a super user and then return to normal.”
Sudo is driven by a default policy plugin called sudoers where the user executing sudo has their privileges checked to make sure they are allowed to do what they are asking. The sudoers security policy requires that users authenticate themselves before they can use sudo—a critical observation to understand the Baron Samedit bug.
A bit about programs & command-line flags in Linux
Utilities like sudo have a large number of command-line flags to make the utility as flexible and utilitarian as possible. Since the bug is tied very tightly to how command-line flags work, I will introduce that concept to those who do not use Linux every day.
A Linux user is presented with a command-line prompt like this:
There are hundreds of commands you can run from a prompt such as this. In fact the /user/bin directory on my Mac holds 1,102 commands (programs) I can run. How do I know that? I ran two commands connected together, ls | wc -1.
The first command is ls which stands for “long listing” and shows you all the files in a directory. I ran the output of that into the second command, wc, which is for counting words but with the -l command-line flag, it counts lines instead. So ls listed out all commands and wc counted the number of lines, resulting in the answer of 1,102 (sudo was one of them).
As a command, sudo has one of the more complex selections of command-line flags you will encounter (see the -help output below). Way too much to get into here, but two of its flags, -i and -s, are very relevant to the Baron Samedit bug.
These flags direct sudo to run another command and to do so as a super user. When sudo runs a command in this way—which is called shell-mode—it protects special characters in the flags to that command so they don’t get misinterpreted. There are two special characters in a Linux shell, the backslash and the back tick. The backslash allows for the application to take the next character literally and not interpret it as a “special character” to the shell. Meanwhile, all characters written in between back ticks are executed before the main command.
For example, in the shell $x is a variable so in order to pass $ through to the shell it must be escaped. The following command line calling the echo command and result of that command elucidate this point:
$ echo "A dollar is \$, backslash is \\, backtick is \`."
A dollar is $, backslash is \, backtick is `.
It is important to note—because this is where the buffer overflow vulnerability resides—the shell mode options involve careful handling of escaped characters.
In order to determine if the user making the request has the permission to do so, sudo processes all command-line arguments. If the command is being run in shell-mode, the sudoers policy plugin will then remove the escape characters from the arguments before evaluating the sudoers policy (which doesn’t expect the escape characters).
Why is the Baron Samedit bug so important?
There are several reasons why this bug in the sudo application is so important and potentially damaging.
First, it impacts most Unix-like operating systems, including Linux, MacOS, and AIX. And even though Linux only runs on 2% of desktop computers, it runs on 498 out of 500 of the most powerful supercomputers in the world, 96.5% of the top one million internet domains, and 92% of the public cloud infrastructure.
You can bet that only a fraction of the above have been patched. The unfortunate reality of security patches is that they are rarely downloaded by impacted users as soon as the patch is available. Running security patches can often be a difficult, costly task, and one that organizations tend to avoid. In fact, most cyberattacks are not as a result of exploiting zero-day vulnerabilities, but rather exploiting bugs that have had patches available for a full year or more.
Second, the vulnerability took nearly ten years to be discovered, even though it was hiding in plain sight. The world at large first found out about the Baron Samedit bug in January 2021, however it is believed to have been first introduced into the sudo application in July 2011. Part of the reason it was able to go undiscovered for so long is an attacker would leave no trace unless the command they executed actually resulted in damage.
There have been two other sudo bugs reported in the past two years, however, Baron Samedit is unique because it can be successfully weaponized in the real world. If exploited, an attacker with access to a low-privileged account can easily obtain super user privileges, even if the account isn't listed in /etc/sudoers—a config file that controls which users can execute sudo commands. Since a lot of commands in Linux run as super user, an attacker could easily avoid detection.
A code walk-through: isolating the Baron Samedit bug
Now, I’m going to show some actual C programming language code. The reason I need to do this is to demonstrate that not all buffer overflow vulnerabilities are obvious. This is a subtle bug and hence was able to sit undetected in heavily scrutinized open source code for close to a decade.
We start with sudoers concatenating the command-line from sudo into a heap-based buffer called user_args; this is the buffer that will be overflowed by a successful attack.
First step is to cover all the command-line arguments adding up their sizes to get a total size for the set.
852 for (size = 0, av = NewArgv + 1; *av; av++)
853 size += strlen(*av) + 1;
852 a for loop has 3 parts: initialization, loop-while condition-is-true, and action-if-still-looping.
Initialization sets the size—where we’ll accumulate the overall size of all arguments—to 0 and sets our argument vector (av) pointer to point to the first argument past the name of the program, which is always the first argument.
The for loop will continue until av points to a null indicating the end of the argument list (in C, the convention is that a 0-value byte delineates the end of a string of characters).
If looping continues we increment the av arg vector to point to the next argument. Note that av is a pointer to a string and in C to get to the string itself you use *av which is called dereferencing that pointer.
853 size += A means size = size + A. Here, we’re computing the size of string pointed to by av and adding 1 (to leave room for the null termination) to the accumulating value in size.
Now, the code can create the user_args buffer to be just the right size to hold the above list of arguments. Here you see the use of malloc() so be on the lookout for a later storing of data into user_args as potential for buffer overflow.
854 user_args = malloc(size);
854 malloc() takes one parameter: the size of the buffer being requested. Here, that’s the total size of all arguments that we computed on line 853 above. malloc() returns the pointer to the beginning of the buffer which is saved in user_args.
This next part is a bit of code for you to parse—this is where the buffer overflow happens. In case your knowledge of C syntax is rusty, == means “is identical to”, ++ as a suffix on a pointer object moves that pointer to the next thing in the array it’s referencing, and * as a prefix “dereferences” the pointer. Dereferencing a pointer to a character string evaluates to a single character.
864 for (to=user_args, av=NewArgv + 1;(from = *av); av++) {
865 while (*from) {
866 if (from[0] == '\\' && !isspace(from[1]))
867 from++;
868 *to++ = *from++;
869 }
870 *to++ = ' ';
871 }
Line by line
864 sets up a loop to run down the list of arguments in NewArgv
865 In C, the convention is that a 0 byte delineates the end of a string of characters. Also in C, “true” is indicated by a non-0 value. Thus 865-869 loops until it encounters a 0-value byte in the current argument string (from).
Note that from points to a string argument in the argument vector (av) but *from are the actual characters in the string.
866-867
If the first character is a ‘\’ and the next character is not a space we increment from to look at the next character.
In other words, if we encounter an escaped character “\x”, we want to treat it as just ‘x’ (skip past the backslash). This is the Baron Samedit bug! The programmer did not anticipate how this code would behave if a solitary backslash ended the command. Here’s where things went wrong:
from[0] is the backslash character, and from[1] is the argument’s null terminator (i.e., not a space character) so when from is incremented, it points to the null terminator.
868 Copy a character from from to to, while incrementing both the from and to pointers. Remember to is pointing to user_args which was the buffer created by the call to malloc(). So this is literally the line of code where the overflow will actually occur:
The null terminator is copied to the user_args buffer, and from is incremented again and points to the first character after the null terminator (i.e., out of the buffer’s bounds)
Then the rest of the while loop at lines 865-869 reads and copies out-of-bounds characters to the user_args buffer for as long as the attacker wants until there is a null terminator--the attacker’s null terminator!
870 We get here when the loop condition of *from being null is reached; we concatenate a space onto the end of the string.
What this all means is this: if an argument ends in a single backslash (‘\’), the pointer increment on line 867 followed by the pointer increment on line 868 causes the from pointer to skip over the null byte that the while (*from) loop is looking for. As a programmer you might not expect the null terminator to be skipped right over—this is why this bug is so subtle. Few if any static analysis tools would find this buffer overflow bug. But CoreGuard absolutely would every time.
Here is the shell command where it all goes wrong and the buffer overflow will happen. And it happens if a command-line argument ends in a solitary ‘\’ character like this:
sudo -s '\' `perl -e 'print "X" x 65536’`
Following the sudo command that ends so strangely, is syntax that allows you to direct a shell command’s output in place as if it is another command-line argument. One does this with the “back-tick” character ( ` ) inside of which is the perl command with the perl script itself on the command line, which is to print the character "X" 65,536 times. That huge array of X characters is added to the command line to sudo, but it stopped calculating the buffer size after the backslash character ( \ ) so the X’s flood the buffer.
Instead of this perl script, if this was a real attack, the attacker would use the commands to escalate privilege level. (There was one more trick here involving using a slightly different form of sudo called sudoedit but the details of that do not change this scenario, just were necessary to fully exploit the bug.)
To turn this exploit into the privilege escalation, the attacker could instead fill the overflowed buffer with a script that turns them into super user using setuid(0) and then runs a shell using execve(“/bin/sh”). They would now have full control over that Linux or MacOS computer.
That is, unless Dover’s CoreGuard solution was protecting your system.
Preventing the exploitation of buffer overflows, like Baron Samedit, with CoreGuard
Dover’s CoreGuard technology is an oversight system that monitors every instruction, at every layer of the software stack, and detects violations of integrity and confidentiality. CoreGuard protects against entire classes of network-based attacks, including buffer overflows, like the Sudo attack.
CoreGuard is a hybrid hardware/software solution. CoreGuard silicon IP sits next to the host processor on the same SoC, making it unassailable over the network. The hardware component is also how enforcement is achieved. CoreGuard enforces a set of rules, called micropolicies, which are informed by metadata, or information about relevant entities in memory.
The CoreGuard Heap micropolicy (included in our base set) prevents buffer overflows and protects heap blocks in memory. If it had been in place on the impacted systems, it would have stopped any attempts at exploiting the Baron Samedit bug. It does this by assigning a color to each buffer, as well as to the corresponding pointers to each buffer. The micropolicy dictates that an instruction cannot write data to a buffer with a color that doesn’t match the color of the pointer to the buffer. If such an action is attempted, CoreGuard will issue a violation to the host and stop the instruction from executing, before any damage can be done.
It is important to note that CoreGuard does not explicitly “fix” the bugs in the code, they would still be there, but attackers would no longer be able to exploit them. However, CoreGuard does tell you where to find the bug so you can fix it—the micropolicy violation includes the exact line in the source code where the violation happened. Thus, a security patch will eventually need to be deployed and installed, but any delay in patching wouldn’t leave a system vulnerable to attack.
To learn more about CoreGuard and to see the Heap micropolicy in action, request a demo today.