A major vulnerability in Sudo
A longstanding hole in the Sudo privilege-delegation tool that was discovered in late January is a potent local vulnerability. Exploiting it allows local users to run code of their choosing as root by way of a bog-standard heap-buffer overflow. It seems like the kind of bug that might have been found earlier via code inspection or fuzzing, but it has remained in this security-sensitive utility since it was introduced in 2011.
Qualys reported the bug on January 26; it has been in Sudo from version 1.8.2, released in August 2011, up through 1.9.5p1, which was released on January 11. At the same time as the announcement, Sudo released version 1.9.5p2 to fix the problems. The bug has been assigned CVE-2021-3156, which Qualys has dubbed "Baron Samedit". That name combines Baron Samedi, the name of the vodou loa of the dead, with sudoedit, which is integral to the exploit.
Unlike last year's Sudo vulnerability, which exploited an uncommon, non-default configuration, this time around the problem is more widespread. Any systems with untrusted users will want to upgrade Sudo to avoid the problem. The major Linux distributions have already issued updates at this point.
One fairly straightforward test to see if a system is vulnerable is shown in the report:
$ sudoedit -s '\' `perl -e 'print "A" x 65536'` malloc(): corrupted top size Aborted (core dumped)
Systems that have the problem will see the "corrupted top size" warning message. Another, perhaps less worrisome test, sans scary messages, is shown in the Sudo project advisory:
$ sudoedit -s / sudoedit: /: not a regular file # or it might prompt for a password
In both cases, systems that are not vulnerable will simply give a usage string. As we will see, attackers can control what gets written beyond the end of a heap buffer, which effectively allows them to make the program do their bidding. That means attackers can subvert sudoedit, which is simply a symbolic link to the setuid-root sudo binary. Game over, as they say.
The "-s" option is important to the flaw, but it is also not really meant for sudoedit at all; it specifies that the user's shell should be used, but sudoedit invokes an editor. The "-s" (or "--shell") option is valid and does make sense for the underlying sudo command, however. Part of the fix for CVE-2021-3156 is to restrict the valid options for sudoedit, which is why the patched versions give a usage string instead of a crash or other error.
Defeating the escape code
The buffer overflow itself also needs to be addressed, even though the option fix leaves no known path to get to that code. The basic problem is that a command-line argument that ends with a backslash can break the code that escapes meta-characters during argument parsing, leading to writing past the end of a heap-based buffer.
When sudo is executing in shell mode (due to -s or the related -i), it collects up all of the command-line arguments into a single buffer and escapes any meta-characters found with a backslash. It creates a new version of the argv array that consists of the shell to be executed, "-c" as the command argument to the shell, followed by this new buffer. That new argv will then be used when the shell is executed.
Later in the processing, Sudo processes the new argv and, if necessary, un-escapes the meta-characters into another buffer in order to match them in the sudoers file as well as for logging purposes. But that un-escaping process can go awry if any command-line argument ends with a single backslash. Under that condition, the un-escaping code copies the character after the backslash, even if it is the NUL at the end of the string. It merrily keeps on copying until it hits an un-escaped NUL, so it copies into a heap buffer beyond its end, the size of which was calculated based on the first NUL.
In theory, every buffer that gets un-escaped has already been escaped internally so every backslash has been escaped with another. If true, that would mean this condition could not occur, but, of course, it turns out not to be true in (at least) one case. The test used to decide whether to force the escaping is subtly different than the test to decide whether to un-escape. By using sudoedit with the -s option, the "proper" path could be tickled, so that the options were not escaped, but were later un-escaped, which led to the buffer overflow. That was fixed with a separate patch addressing both the tests and the broken logic when the buffer ends with a single backslash.
As described by the report, this buffer overflow is ideal from the attacker's perspective because they can control everything about the contents and size of the overflow. The last command-line argument to sudo is conveniently followed by the environment variables, so the attacker can precisely arrange the contents and even include NUL bytes since ending arguments or environment variables with a single backslash results in a NUL. The report gives an example of how to write attacker-controlled values to the data structure used by malloc():
env -i 'AA=a\' 'B=b\' 'C=c\' 'D=d\' 'E=e\' 'F=f' sudoedit -s '1234567890123456789012\' ------------------------------------------------------------------------ --|--------+--------+--------+--------|--------+--------+--------+--------+-- | | |12345678|90123456|789012.A|A=a.B=b.|C=c.D=d.|E=e.F=f.| --|--------+--------+--------+--------|--------+--------+--------+--------+-- size <---- user_args buffer ----> size fd bk
There is even more information about exploiting the flaw in the "Exploitation" section of the report. It makes for some fascinating reading for those who are curious about how these kinds of exploits can work.
In the end, though, this is caused by a typical C programming error leading to a complete compromise of a setuid-root program—but only for local users. It once again highlights the dangers of using C for these kinds of tools, but it also points to a certain amount of complacency within our community. For a bug of this sort to persist for this long in a tool of this nature would seem to indicate that we are not really scrutinizing our code as well as we should be. Or testing and fuzzing enough either.
As Hanno Böck noted, though, sudo likely has far more complexity than a tool of this sort should have; there are alternatives, but those have potential downsides as well. Wholesale replacement in a safer language (and perhaps with many fewer features) has its attractions, but someone has to do that work too; then there is a new code base, in a possibly less-familiar language, that needs a lot of scrutiny as well. As always, there are no silver bullets.
Index entries for this article | |
---|---|
Security | Vulnerabilities/Privilege escalation |
Posted Feb 3, 2021 1:16 UTC (Wed)
by jreiser (subscriber, #11027)
[Link] (1 responses)
Posted Feb 3, 2021 1:50 UTC (Wed)
by re:fi.64 (subscriber, #132628)
[Link]
Posted Feb 3, 2021 1:53 UTC (Wed)
by NYKevin (subscriber, #129325)
[Link] (26 responses)
(Normally, I'd be perfectly content with a response of the form "Well, it made sense at the time...", but that's not an answer in this case, because the linked patch just changes the logic that decides whether to un-escape the string, as well as adding a check for end-of-string. IMHO the un-escape code path should not exist at all.)
Posted Feb 3, 2021 2:42 UTC (Wed)
by Rudd-O (guest, #61155)
[Link] (11 responses)
Posted Feb 3, 2021 7:01 UTC (Wed)
by wahern (subscriber, #37304)
[Link] (10 responses)
But it's also easy to do the wrong thing, especially when slowly accreting features, and especially when you're risk averse (for good reason!) to refactoring components in response to growing complexity.
And just to head off the typical arguments about C lacking good buffer primitives: 1) I suspect sudo lacks a good solution in its util library because of its age (see risk averse, above); and 2) one obvious way to refactor the buffer management would be to make use of fmemopen and open_memstream, though unfortunately a project like sudo might not be able to depend on them being available.
I'm not saying C is the best language for implementing something like sudo (heck, it's already grown Python extension capabilities!), just that there are other dynamics at play (e.g. complexity management) which are arguably more proximate causes of the failures.
Posted Feb 3, 2021 12:24 UTC (Wed)
by roc (subscriber, #30627)
[Link] (5 responses)
C lacks solid string and buffer primitives regardless of how you lampshade it. fmemopen and open_memstream are massive overkill for string manipulation ... they both implement locking by default! C just doesn't have an ergonomic way to track bounded string/array slices or growable buffers. All the library solutions are excessively verbose, so developers are constantly tempted to write hand-rolled code with raw pointers and manual buffer size calculations, and they regularly get it wrong with disastrous consequences.
Posted Feb 3, 2021 14:39 UTC (Wed)
by Paf (subscriber, #91811)
[Link] (1 responses)
This, a thousand times this. It’s really not a good strings language. It’s just not.
Posted Feb 4, 2021 6:51 UTC (Thu)
by marcH (subscriber, #57642)
[Link]
> It’s really not a good strings language. It’s just not.
The language of manual memory management and corruption.
Posted Feb 17, 2021 12:11 UTC (Wed)
by mina86 (guest, #68442)
[Link] (2 responses)
Perl. ;)
Posted Feb 23, 2021 10:26 UTC (Tue)
by anselm (subscriber, #2796)
[Link] (1 responses)
What's wrong with
Posted Feb 23, 2021 10:45 UTC (Tue)
by mina86 (guest, #68442)
[Link]
Posted Feb 3, 2021 21:01 UTC (Wed)
by NYKevin (subscriber, #129325)
[Link] (3 responses)
Posted Feb 3, 2021 21:24 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (2 responses)
Posted Feb 4, 2021 8:32 UTC (Thu)
by NYKevin (subscriber, #129325)
[Link] (1 responses)
Posted Feb 4, 2021 9:25 UTC (Thu)
by Cyberax (✭ supporter ✭, #52523)
[Link]
Posted Feb 3, 2021 10:44 UTC (Wed)
by epa (subscriber, #39769)
[Link] (13 responses)
Posted Feb 3, 2021 11:08 UTC (Wed)
by Jonno (subscriber, #49613)
[Link] (12 responses)
That is indeed what sudo does by default. It is only when you pass both "-s" / "--shell" (run a shell) or "-i" / "--login" (run a login shell) as well as specifying a command to execute that sudo starts going haywire, by passing the command to the shell using the "-c" argument of the shell in question.
Having sudo execute the specified command through a shell running as the target user is useful for running shell builtins, doing shell redirections, etc; so the alternative to sudo doing the escaping is the caller doing them and then calling `sudo -- /bin/sh -c "..."`. It is _usually_ better to keep tricky stuff like this in one place rather than a hundred, but that presupposes that someone actually checks that it does so correctly...
Posted Feb 3, 2021 12:17 UTC (Wed)
by ibukanov (subscriber, #3942)
[Link] (6 responses)
Posted Feb 3, 2021 13:52 UTC (Wed)
by tzafrir (subscriber, #11501)
[Link] (1 responses)
Posted Feb 3, 2021 16:00 UTC (Wed)
by ibukanov (subscriber, #3942)
[Link]
Posted Feb 3, 2021 14:25 UTC (Wed)
by Jonno (subscriber, #49613)
[Link] (1 responses)
Because sudo gets the command to run and each of its arguments as separate entries in argv[], but sh -c expects the command and all its arguments as a single entry in its argv[], which it will interpret...
Posted Feb 3, 2021 15:55 UTC (Wed)
by ibukanov (subscriber, #3942)
[Link]
What sudo does is way too smart given the danger of bugs in it.
Posted Feb 3, 2021 18:58 UTC (Wed)
by smoogen (subscriber, #97)
[Link] (1 responses)
Posted Feb 4, 2021 6:39 UTC (Thu)
by epa (subscriber, #39769)
[Link]
Posted Feb 3, 2021 16:10 UTC (Wed)
by epa (subscriber, #39769)
[Link] (4 responses)
Posted Feb 4, 2021 11:11 UTC (Thu)
by smoogen (subscriber, #97)
[Link] (3 responses)
When I was younger, I was naive to believe that most of the core programs could be easily replaced with simpler programs.. and you could just keep to doing that one thing. Then you find that most of the people who espouse the Unix philosophy also think that if you made ooone little change to /bin/rm it would be much better for their usecase.. and 8 billion users later, rm now is setuid, sends email and plays mp3s.
Posted Feb 5, 2021 18:50 UTC (Fri)
by nix (subscriber, #2304)
[Link] (2 responses)
Posted Feb 11, 2021 4:47 UTC (Thu)
by linuxrocks123 (subscriber, #34648)
[Link] (1 responses)
Posted Feb 11, 2021 17:43 UTC (Thu)
by nix (subscriber, #2304)
[Link]
Posted Feb 3, 2021 2:40 UTC (Wed)
by Rudd-O (guest, #61155)
[Link] (7 responses)
Posted Feb 3, 2021 4:06 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (5 responses)
Posted Feb 3, 2021 5:23 UTC (Wed)
by eery (guest, #142325)
[Link] (4 responses)
Less that qubes would prevent the abuse of sudo, more that qubes just doesn't rely on the root superuser for access control of applications and data.
Posted Feb 3, 2021 5:38 UTC (Wed)
by Cyberax (✭ supporter ✭, #52523)
[Link] (3 responses)
The fundamental problem is that you need to have some way to elevate privileges for many practical tasks. And that privileged elevator module is the obvious place for bugs.
Posted Feb 3, 2021 16:37 UTC (Wed)
by dd9jn (✭ supporter ✭, #4459)
[Link] (2 responses)
Posted Feb 7, 2021 23:22 UTC (Sun)
by rcampos (subscriber, #59737)
[Link] (1 responses)
Posted Feb 8, 2021 9:23 UTC (Mon)
by ber (subscriber, #2142)
[Link]
Posted Feb 17, 2021 14:58 UTC (Wed)
by immibis (subscriber, #105511)
[Link]
Posted Feb 3, 2021 5:26 UTC (Wed)
by alison (subscriber, #63752)
[Link] (2 responses)
How about
Posted Feb 3, 2021 5:54 UTC (Wed)
by cyphar (subscriber, #110703)
[Link] (1 responses)
Posted Feb 3, 2021 7:20 UTC (Wed)
by wahern (subscriber, #37304)
[Link]
I'm normally quite cynical when it comes to Linux security--my background assumption is that if someone has shell access on a Linux host, they have root access. But holding that opinion is no excuse for neglecting a diligent and accurate assessment of relative threat potential.
Posted Feb 3, 2021 8:47 UTC (Wed)
by marcH (subscriber, #57642)
[Link] (9 responses)
"We" generally don't find this fun and "we" rarely get paid for any of that either. At least not on the defence side.
People who actually care about memory safety are just moving to safe languages - why would they bother about all this tedious and error-prone review and fuzzing work when that part comes "for free" in newer languages? Every minute of work spent on memory safety is a minute not spent on something more productive - including of course other, higher level security issues.
Posted Feb 3, 2021 19:02 UTC (Wed)
by smoogen (subscriber, #97)
[Link] (4 responses)
Posted Feb 3, 2021 20:12 UTC (Wed)
by mathstuf (subscriber, #69389)
[Link] (1 responses)
Posted Feb 3, 2021 22:04 UTC (Wed)
by marcH (subscriber, #57642)
[Link]
Exactly, "git grep unsafe" and win.
This is once again not a yes/no question, it's all about *how much* time is spent in code review and other quality assessments = what every manager say they want but never allocate proper resources for.
And to be clear, when I wrote "it comes for free" I meant _memory safety_ comes for (almost) free. Of course switching to a different programming language is everything but free and even practically impossible for some projects.
Posted Feb 5, 2021 22:35 UTC (Fri)
by ssmith32 (subscriber, #72404)
[Link] (1 responses)
But it's all about risk management anyways, so might as well try to skew the odds in your favor.
Posted Feb 6, 2021 0:45 UTC (Sat)
by marcH (subscriber, #57642)
[Link]
Posted Feb 4, 2021 2:34 UTC (Thu)
by plugwash (subscriber, #29694)
[Link] (3 responses)
Posted Feb 4, 2021 6:47 UTC (Thu)
by Cyberax (✭ supporter ✭, #52523)
[Link] (1 responses)
Posted Feb 4, 2021 17:58 UTC (Thu)
by mathstuf (subscriber, #69389)
[Link]
Posted Feb 4, 2021 9:44 UTC (Thu)
by Jonno (subscriber, #49613)
[Link]
Rust does allow you to use the full rust ABI in shared libraries, but the Rust ABI is still unstable between compiler releases. If you want ABI stability you are restricted to the C ABI, which is inherently unsafe. Creating a stable Rust ABI is on the roadmap, but not for anytime soon....
Posted Feb 3, 2021 12:10 UTC (Wed)
by tschoerbi (subscriber, #88015)
[Link]
On my systems, I have had permission to sudo restricted for years. I only every need group sudo to be able to execute sudo:
dpkg-statoverride --force-statoverride-add --update --add root sudo 4750 /usr/bin/sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
foreach (split //, "abcde") {
…
}
?
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
Yes, if you are going to run sh -c "command", where command hasn't been passed as a single argument but somehow as separate args, then you need to do fancy escaping so that the command line is built correctly. The answer is not to offer this functionality at all, it's too error-prone. As others noted, this is only the less used --shell and --login modes. Better to require the user to escalate using a mechanism that's as simple as possible, and then if the user wants to run sh -c that's their decision, once the authentication has been safely done. sudo's own manual page gives an example of this use:A major vulnerability in Sudo
$ sudo sh -c "cd /home ; du -s * | sort -rn > USAGE"
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
Via a fifo named /dev/mp3, and this exciting daemon:
A major vulnerability in Sudo
#!/usr/bin/zsh
FIFO=/dev/mp3
[[ ! -p $FIFO ]] && { echo "Error: $FIFO does not exist or is not a FIFO." >&2; exit 1; }
while true; do mpg123 -b 1024 - < $FIFO >/dev/null 2>/dev/null; done
(This is decades old. If I was doing it these days, I'd probably not pick zsh at random and I'd probably use gst123 so it could play anything, not just mp3s. Obviously this needs a systemwide PulseAudio or something similar: again, this script predates PulseAudio entirely :) )
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
userv as alternative
https://packages.debian.org/buster/userv
http://www.chiark.greenend.org.uk/~ian/userv/
is, what Werner refers to
A major vulnerability in Sudo
A major vulnerability in Sudo
> $ sudoedit -s '\' `perl -e 'print "A" x 65536'`
$ sudo --version
?
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
It's like a static C analyser but built in, not optional and with barely any false positive ever.
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo
A major vulnerability in Sudo