Heap-based buffer overflow in Sudo(qualys.com) |
Heap-based buffer overflow in Sudo(qualys.com) |
As far as I can say, never ever use slicer69/doas, I've found 3 critical security vulnerabilities in it, the author does not understand C or how it should work in general.
Here are 3 examples if issues I found and the author used misleading commit titles to hide the issues and made excuses saying a clear buffer overflow very similar to the one found in sudo is just "potential":
- https://github.com/slicer69/doas/commit/261c2164496dbebe6e3e...
- https://github.com/slicer69/doas/commit/2f83222829448e5bc4c9...
I even had to do a PR myself to fix an issue the author was not able to understand and more and more people started to use it:
sudo unattended-upgrade --dry-run
Looks like I get a new sudo!anything below version 1.9.5p2 is affected
EDIT: see above post provided by comfydragon
a few years ago I found a flaw in sshd. because it was impacting a Linux PAM login/auth module I was writing in C. my module should have worked. but it wasnt. because of sshd. it blew me away, given how important that server is. luckily, others must have complained too, and it ended up being fixed in a newer sshd release. but the fact that it made it into a release in the first place, impacting PAM, was scary
on a not-totally-unrelated note, that was also the last C project I wrote, and since then I fell in love with Go and Rust. for systems code, for me, theres no going back. C is scary given the modern threat ecosystem and whats at stake
Fair enough but what do you recommend?
Me, I try to keep people out of my systems that I don't trust. This particular snag needs local access but I will grant you that my web server or other exposed service might provide a local interface.
Instead of throwing your hands up and screaming "crap" you do your risk assessment and attempt to mitigate as best you can. I read a lot of blogs and have a fair amount of logging and analytics lying around the place (and that's just at home).
Fairly recently I found that my wife's car had loose nuts on the front nearside wheel. That was a change to fix a worn tyre for obvious safety reasons but for whatever reason the fixings were not done up properly. I think they were done up finger tight but a distraction caused the mechanic to forget to use a spanner (wrench) to finish the job to spec. The wheel seemed to work fine but you would get a low rumble sound on corners. It was not a trivial to diagnose fault because you had to notice it before failure - I'm a (non chartered) Civ Eng and IT bod but not a mechanic. There is a minimally screwed on plastic cover that stopped the bolts from flying out - not much.
A car wheel is a thing we can all look at and see that the four bolts are not working properly, once you remove the plastic cover and see them wobble.
Now that is what you can do to protect yourself (risk assess, mitigate etc.) However there should also be something that protects "civilians" and I think that is what is missing. I'm not too sure how we do that.
Longer: One of two things -
1. Choose the most boring software possible, trust that the process will work as expected and that you're no worse off than anyone else. Update your software regularly
2. Choose the simplest, most robust software possible (Alpine, OpenBSD, etc). In this case, doas instead of sudo. Pray that works better than everyone else or that you get some benefits through obscurity. Still get surprised every so often. Update regularly
Either way, modern software has gotten complex enough that there's few options for the average person
why do you need to have sudo? I'm perfectly fine without it. sudo is maybe useful in the case where others on the system don't need to know the a password to run as someone else (including root) but need to be able to do that anyway. sudo seems to have gotten a big installation bases through ubuntu and everybody thinks it's normal now, but really for me it's not.
MirageOS unikernels like were mentioned yesterday? Get away from running network services on Linux entirely?
"The PR does not include tests" is not the same as "nobody performed any tests" is not the same as "nobody actually noticed a bug".
And of course, it's perfectly reasonable to form beliefs about code from reading it.
Broadly yes, but it would be hubris to claim you can tell the correctness of all code from merely looking at it.
It just seems SO EASY to add a test for this problem, literally the relevant test input is one slash by itself, or any string ending in a slash! So simple! If I sent a change like this at work, no matter how trivial, that said it fixed this bug but I didn't send any tests, the reviewer would reject it out of hand or, probably, just silently ignore my change. But that's the real problem here. This program is a monograph. There are no reviewers and there are, consequently, no standards.
https://github.com/sudo-project/sudo/graphs/contributors
That’s one maintainer, not even full time according to his résumé. What you just described is multiple specialists and some supporting tools, so another way of looking at this is to ask how much value the IT world has gotten from sudo but not contributed back in support.
When something is this widely used, it’s easy to forget that the answer to who should do more isn’t the one person who actually steps up to keep it alive.
Everyone has decided to stay home this year, though.
That is not fair. This software is maintained by Todd Miller, the well known OpenBSD developer, and has both tests and certainly discusses code proposals. Not everyone has to use github or the language de jour to be useful.
Ironic that OpenBSD dropped sudo in favor of a much simpler utility.
"C Is Not a Low-level Language, Your computer is not a fast PDP-11."
In some point someone will rewrite all GNU/UNIX user land in modern Rust or similar and save the day. Until this happens these kind of incidents will happen yearly.
Not that I have any faith in modern "best practices". I'm imagining `grep` written in modern a modern language like JS and I shutter when I think of the hundreds of micro-dependencies like "left-pad" that would need to be downloaded to make it work.
Maybe it's fair to say that systems programming languages are in a better state in terms of modern best practices than scripting languages.
Or everyone just switches to musl + busybox.
As a bonus, the person who wrote that turned out to have published C code containing multiple exploitable buffer overflows.
Despite this, the wisdom of the crowd is that you should never su to root, for ... reasons? Fat fingering is a thing, but if you can accidentally be in a root terminal without realizing it you have done something horribly wrong.
Heck, from a certain point of view if you have someone in the habit of repeatedly typing sudo over and over again then all sudo has really done is open up every single terminal to be a gateway to the nether realm of super user privs. And in this case, more attack surface.
I was under the impression that different Linux userspaces sometimes implement these common commands differently. Like "ls" sometimes actually being aliased to a bash script, or maybe BSD having one implementation and Ubuntu another. Is that not the case? Is "sudo" not maintained by an entity like gnu, bsd, etc?
edit - in other words, I always assumed "sudo" was a highly-dependent system-level tool, not just some useful helper binary that is maintained by one independent person.
OpenBSD dropped sudo from the base OS several years ago. sudo just became too complex, tailored to the feature creep demanded and required (PAM, ugh) by Linux users.
It looks like this is pretty far reaching. All of my boxes were vulnerable to this before updating today.
> exploitable by any local user [...] without authentication
> introduced in July 2011 [...] in their default configuration
> full root privileges
You'd need shell access to the host to execute `sudo` and attempt to exploit it.
It looks like this is pretty far reaching. All of my boxes were vulnerable to this before updating today.
https://oss-security.openwall.org/wiki/mailing-lists/distros...
Never knew it had a mascot, if you could call it so... I will never unsee it. Nightmare fuel at it's finest.
This made my day. God it is scary.
Just want to echo other praise here for doas. It's fantastic, most likely does everything you need it to do, and is secure. Install it and see for yourself!
NM:
- we can defeat ASLR by partially overwriting the function pointer getenv_fn (which points to the function sudoers_hook_getenv() in the shared library sudoers.so); and luckily, the beginning of sudoers.so contains a call to execve() (or execv()):
Also, the sarcasm in your comment really doesn't help your message.
Secondly, the Sudo code in question seems to be a result of a poor or nonexistent design process (which also seems consistent with Sudo's horrific bloat of features), so this isn't a good example for preaching against C.
Slightly off-topic, but FTR I view C as somewhat crippled and C++ as an easy to misuse Lovecraftian monstrosity (but much more powerful and useful than C), and I suspect Rust's way of forbidding the user from making memory errors is more trouble than it's worth.
[1]: https://www.sudo.ws/
* https://lists.debian.org/debian-security-announce/
The message for this issue went out 18:05 UTC:
* https://lists.debian.org/debian-security-announce/2021/msg00...
See also the RSS feed for:
And... macOS looks vulnerable to me
% cd ~ && ln -s /usr/bin/sudo sudoedit && ./sudoedit -s /
Password:
sudoedit: /: not a regular file
As per the advisory it looks vulnerable (sudoedit: and not usage:)sudo looks quite complicated, just by looking at what it has to parse and validate (sudoers file). I'd rather not have it on the server, and just use custom purpose made static built suid binaries for necessary minimal purposes of privilege granting.
It's not even installed by default on some popular Linux distros. Of course I realize that it's very ubiquitous, regardless of this. And it's probably fine to use on your workstation. But leaving it unattended on some server feels a bit dreadful.
`su` takes the password of the user you're becoming, while `sudo` takes the password (or not) of the user you already are. So using `su` to become root implies that there's a root password that multiple people (well, assuming there's multiple admins on the box) know.
Having multiple admins that need to be able to administrate a system might seem like another case where sudo simplifies things, but isn't that what the wheel group is for? Yes there is an M:N issue between administrators and root passwords and we all know that reusing root passwords across boxes is just asking to get pwnd, but if admins can already ssh into a user that has wheel on a system then that implies that there is another existing authentication system that surely could be used to provide the password in a centralized manner. There is complexity in such a service, but that is only required for the M:N case and if it has issues then it can be fixed once. Busted sudo? You have to push emergency updates to (checks notes) ... literally every EC2 image on amazon. If admin:server is 1:N then password manager, copy, paste, and cut sudo out of the loop entirely.
My favorite command.
Anyone checked what buggy horrors await in all those modules? And it's not a static set of old trusty modules either. The fresh new complicated stuff is being added, like systemd-homed, and so on.
pam_systemd and pam_systemd_home have by itself the size of all other 46 PAM modules combined (on my Arch system).
$ man doas | wc -l
58
$ man doas.conf | wc -l
101
$ man sudo | wc -l
741
$ man sudoers | wc -l
3254
And a bonus: $ man sudoers | grep -C1 despair
The sudoers file grammar will be described below in Extended Backus-Naur
Form (EBNF). Don't despair if you are unfamiliar with EBNF; it is fairly
simple, and the definitions below are annotated.
That only accounts for a small subset of sudo's complexity. It's easily 100x more complex than it needs to be to solve this problem. Now compare the two CVE lists:http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/security/doa...
http://ftp.netbsd.org/pub/pkgsrc/current/pkgsrc/security/sud...
My reaction to this vulnerability was mild amusement, then later wondering if I should go discredit the inevitable Rust brigade. We don't have to rewrite everything in Rust to get better security. We just have to use simpler tools.
Yes, it would still be vulnerable to logic errors, like the last famous sudo bug where you pass -1 as the UID. But it wouldn't be vulnerable to this. (And this isn't the first memory safety bug to be found in sudo.)
Yes, sudo's complexity is useless for 99.99% of its users. But wouldn't it be nice if the result were merely a gross feeling rather than a security hole?
People don't add these features for the fun of it; they're present because they solve a specific use case.
I use doas as well; it's a neat little program that covers many of the common use cases, but it doesn't cover all of them. Usually you should use the simplest tool that solves your problem, but sometimes your problem is complex and thus your tool will be complex.
That is not to say that sudo can't perhaps be simpler; the first version was released in 1985 and there are probably things that can be improved, but sudo really isn't written by idiots who just add features because they have nothing better to do.
But the two are pieces of the same puzzle (defence in depth). Ideally, SUID binaries should be formally specified/verified (ada+spark?), though you could still have bugs in the specification.
And I'd argue that if you need more specific features than just `sudo`'s core functionality, you should probably just make your own setuid binary. That still exposes you to making the same mistakes, so better keep the complexity low, and still rely on a memory-safe language. Using proved, lightweight libraries helps getting an implementation correct.
As much as I like C (which is the language I'm most proficient with), it just gives you too many ways to shoot yourself in the foot, and IMO isn't really the best suited for something where:
- performance doesn't really matter
- memory safety, typechecking are critical.
You could always get away with a transpiler for a DSL, or a compiler that injects more checks, but better suited tools are available anyway.
I'm curious whether you run OpenBSD, since you mentioned you use doas. Do you have any thoughts on OpenBSD?
Or said another way, is the lack of CVEs for doas an indication it is more secure or just less (ab?)used?
It is true that BSD and linux sometimes have different implementations of posix commands.
The vast majority of linux distros are using the same gnu coreutils though. There are alternate implementations (like busybox, among others), but they're not often used in desktop distros.
I'm curious if you have any example of a linux distro that does treat ls so weirdly; that uses anything other gnu coreutils or busybox for it.
$ (. /etc/os-release; echo "$NAME:$VERSION_ID")
openSUSE Tumbleweed:20210121
$ command -v ls
alias ls='_ls'
$ grep -A6 -B1 '_ls ()' /etc/profile.d/ls.bash
bash|dash|ash)
_ls ()
{
local IFS=' '
command ls $LS_OPTIONS ${1+"$@"}
}
alias ls=_ls
;;But every tool has to be maintained by someone. Its not like GNU is a faceless corporation.
You don't have to imagine: `ripgrep` exists and has all of 10 direct dependencies, 4 of which by the same author because they extracted features / systems from ripgrep (or developed them separately from the start) as they were useful on their own e.g.
* regex, for obvious reasons
* ignore to match and apply ignorefiles (gitignore and friends which ripgrep takes in account by default)
* bstr for conventional utf8 strings rather than guaranteed
* grep intends to be essentially "ripgrep as a library"
Quote from C.A.R Hoare at his Turing Award speech in 1981:
"Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to--they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980, language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
This is what is missing to force C to finally stop being the JavaScript/PHP of systems programming, liability for exploits with hefty sums.
Second, I keep being told that it is possible to write perfectly safe code in C, it is just a matter of using the tools.
From your assertion, it appears the sudo project has some learning to put into place then, because code reviews weren't enough to prevent this exploit.
Rust is all around better of an experience.
I mean last time I had sshd hanging and randomly losing access to remote machines due to systemd's newest PAM improvements (homed), when I put the process under gdb, it shown dbus stuff, so it's certainly more than 1-2kloc.
An individual leaves the company and the security / compliance people say all their access to be revoked, which is kind of a headache with shared logins.
The security / compliance people want audit logs of who does what, which is harder to do with shared logins.
I think both of these things are encouraged (maybe required?) by various certifications that companies doing certain things might need.
This means the webdev has the least amount of access necessary for their work without giving them straight up root or using setuid on a script, which can lead to easy bugs (did you check PATH?)
https://access.redhat.com/security/cve/CVE-2021-3156
Still nothing on Centos. Perhaps just another reason to not trust Redhat.
The first systems programming language that would prevented this kind of exploit was written in 1961, 10 years before C was invented.
[Ed.: Actually, the tone is probably a factor in the audience not being interested.]
Coding is not brute forcing. I feel like this is taking an extreme position at the complete opposite end from not testing anything.
EDIT: Misread the comment I replied to. I agree that it is not likely that anyone can tell the correctness of all code from merely looking at it.
I wonder why OpenBSD wrote their own version. Could it be that, knowing how the sausage is made, they thought it was better to have a salad...?
Why should that be of concern to casual home use? Why do parts of a factory have to trickle down into my home? Wouldn't that be like the need to have a cow to drink milk, or a farm to have something to eat instead of a more apt product to buy for a reasonable price and in good quality?
It's far more useful to explain why or how PAM is bad, because no one (sane) will agree that the idea of PAM is bad.
If doas doesn't cover your edge case, then the responsible thing is to make a new tool which covers just that, and not to shove your complexity into a critical security component that the other 99% of the userbase doesn't need. Remember Heartbleed? The entire internet shat its pants because of a vulnerability in a feature that no one uses.
Failing to uphold that principle over and over again leads to broken, unreliable, insecure programs. This is why everything is on fire. Not C.
The last major sudo bug was in the PAM code (which lead to the creation of doas), which is something many people don't need, but also something that many others do need.
And writing separate tools would be the equal (or more!) lines of code and an equal amount of bugs in total (or probably more, since people will be reinventing stuff and there will be fewer reviewers per line of code). This isn't reducing complexity, it's just spreading it out.
>writing separate tools would be the equal (or more!) lines of code and an equal amount of bugs in total
In total, yes, but crucially, not all on your system at the same time.
For home users there is doas, also written by a OpenBSD developer. It's really simple, but I never found anything to be missing for my use case. All the logging and auditing and whatnot can (and imo should) be performed somewhere else.
There’s a number of reasons openbsd dropped it, and all of them are fundamentally rooted in size and complexity: https://flak.tedunangst.com/post/doas
For one, the config file is actually easy enough to read properly.
Having worked with the IC for decades, I'm well aware of what's going on.
If you only want to allow specific units the authorizer is passed the unit under `action.lookup("unit")`.
You can get started here: https://google.github.io/oss-fuzz/
Folk love to bring up "responsibility" and all of that, but you can't really expect people to bear the responsibility of the world on their shoulders for their spare time projects. It's neither realistic nor fair.
OpenBSD replaced sudo with doas (http://man.openbsd.org/doas) in 2016 (https://www.infoworld.com/article/3099038/openbsd-60-tighten...). It’s a safe bet it’s more secure than sudo.
Then do it! On your own time, rather than complaining that someone else didn't do it on theirs!
Personally, while I doubt these are the case, having never heard of MirageOS before, I'm willing to be proven wrong. However, if the answer to any of these is "no", I have a hard time seeing how "just abandon Linux wholesale for NewShinyThing" is a viable option for more than a tiny subset of users. (Even if they're all "yes", it's still a wildly unrealistic expectation...)
Code review can work, but often it doesn't. There are countless examples of projects with "strict" review requirements that have similar issues (whereas qmail only had one).
Writing tests is the important thing, it keeps you honest.
No, of course not; the only thing that supports everything that Linux supports is Linux. But most use cases don't use every part of Linux.
> Is there a robust ecosystem of MirageOS users online able to help troubleshoot issues from the simple to the arcane?
I doubt it, but how do we get to there from here except by more users starting to use it?
> Is it supported by VPS providers the world over?
Yes, since what you build is just a VM image.
> However, if the answer to any of these is "no", I have a hard time seeing how "just abandon Linux wholesale for NewShinyThing" is a viable option for more than a tiny subset of users. (Even if they're all "yes", it's still a wildly unrealistic expectation...)
I don't disagree as such, but I do think that at this point building anything on these insecure foundations is throwing good money after bad / building castles on sand. Probably 95% of the time you build something that serves your present business purposes, accept a certain amount of insecurity, and get on with your life. But it's worth putting a bit of effort into looking for better ways to do things.
The problem is feature parity. Most rewrites cannot guarantee that off-the-bat, so they end up struggling to persuade people to switch - why break stuff that works just fine and lose features, in the name of some engineering purity?
Implementing a subset of sudo is a weekend project, for some values of useful.
openbsd dropped sudo for "weekend project" `doas`, in no small part because it does not support most of that stuff.
Here's the configuration file documentation: https://man.openbsd.org/doas.conf.5
Wonder no more: https://flak.tedunangst.com/post/doas
> I started working on doas quite some time ago after some personal issues with the default sudo config. The “safe environment” was under constant revision and I regularly found myself unable to run pkg_add or build a flavored port or whatever because the expected variables were being excised from the environment. If I had been paying attention, keeping sudoers up to date probably would not have been such an ordeal, but I don’t like change.
> The core of the problem was really that some people like to use sudo to build elaborate sysadmin infrastructures with highly refined sets of permissions and checks and balances. Some people (me) like to use sudo to get a root shell without remembering two passwords.
> […]
> Talking with deraadt and millert, however, I wasn’t quite alone. There were some concerns that sudo was too big, running too much code in a privileged process. And there was also pressure to enable even more options, because the feature set shipped in base wasn’t big enough. (As shipped in OpenBSD, the compiled sudo was already five times larger than just about any other setuid program.) Hurray, tension. It wasn’t the problem I was trying to solve, but it was an opening from which to launch my diabolical plan.
With most other projects, I would smell a major case of Not-Invented-Here, but the OpenBSD developers seem to have an impressive track record of actually learning from mistakes, both from their own and those made by others.
> knowing how the sausage is made, they thought it was better to have a salad
I love that phrase! (Coincidentally, an engineer working in food processing once explained to me how chicken nuggets are made (while we were eating!), I have mostly avoided them ever since...)
How so? The OCaml compiler is written in OCaml and bootstraps itself.
But I don’t see a point in using systems languages for the usual UNIX tools, they could be rewritten in a more secure language. In the rare case performance is important, there is FFI, but they are usually IO-bound so there is not much point.
https://gitlab.freedesktop.org/polkit/polkit/-/blob/master/s...
Which also seems to not have tests. In fact, the only tests I'm seeing are from 2 years ago.
https://gitlab.freedesktop.org/polkit/polkit/-/tree/master/t...
> has much better security
It's using D-bus. My faith in D-bus security is close to my faith in seeing a fresh Linux install with zero D-bus error messages from apps. Which is to say, nonexistent.
> integrates with your DE instead of expecting passwords on the terminal.
There's nothing inherently insecure about passwords on the terminal, and certainly nothing a DE can do better. I have yet to see a display manager or lock screen app that knows what the hell PAM is doing. Try doing even the simplest things with PAM, such as getting a fingerprint reader or Yubikey working, and every single display manager simply chokes.
I'm not sure which is more of a Byzantine mess: Linux authentication and authorization, or Linux audio.
With all the .so modules loading into some process, etc. Some questionable design in sshd makes it lock up completely for all incoming connections when used with PAM and when pam module ends up in infinite loop.
Nevermind that systemd pam modules pull in a shitton of stuff, including dbus, into any process that tries to use PAM for auth, these days.
I guess it all runs as root, too. sshd at least tries to fork a child for all this and waits for it and kills it, so that the parent process can't be polluted. It just has no timeout when waiting for result, and doesn't accept further connections when waiting.
Sometimes it's better not to look too deep under the covers.
sshd needs to run as root (obviously) because it grants login shells to people, so it needs to run in a privileged context. And the PAM modules it executes also need to be run as root, because PAM modules need to do things like read /etc/shadow.
(In the past, OpenBSD had a login-locally-or-via-Kerberos binary there, which does show the downside of that approach over PAM's more flexible approach.)
However, I do want to see programming as a culture adopt a higher standard when it comes to checking their work, and I think the continued prevalence of bugs like this are an indicator that we actually need to do so. I'm not asking for NSA-proof because that's not reasonable. But memory safety is a solved problem, and we need to be putting in the legwork to make more of our stack memory safe.
Not wearing a seatbelt when at work or in your spare time is irresponsible.
Writing code, without tests, that others use (and for security at that) is irresponsible.
But this is frakkin sudo we're talking about.
It's a wonder that anything works, ever.
Reading the code of important open source projects is not for the faint hearted!
> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
If you don't like this license, you're free to use other software.
Nothing, as far as I can see. If someone writes some crappy security software and hides behind a licence that only means those relying on it have joined them in being irresponsible. Responsibility multiplies, it’s not zero-sum.
You can choose to run this code, or you can choose not to run this code. It's really up to you.
This is very different from a sealbelt, as I can't choose to not have an accident with you, potentially causing a needless fatality.
You can choose to wear this seatbelt, or you can choose not to wear this seatbelt. It's really up to you.
There is a large disconnect here.
I don't want to come across as pedantic - the point I mean to make is that I think a lot of people use sudo without thinking about it much. Sudo's just "the way to use linux" for a lot of people I know.
I don't think the sudo contributors should be labelled as irresponsible, because everything they've added to the project is available for the public to see and scrutinise. I don't think they've ever mislead people; rather that people have assumed things.
Maybe people who care about security will notice now that sudo doesn't have comprehensive testing, and will make their own alternative.
I know this is not exactly what you're trying to say, but it is what it comes down to.
So, what it actually comes down to is that they didn’t bother to write tests. There was no time pressure, there was no urgency or requirement, they just couldn’t be bothered to do that prior to release. If there’s a note somewhere saying “I know it’s not quite done...” then I’ll let it slide.
Have you seen something along those lines?
Kindly go to the source repository.
> If there’s a note somewhere saying “I know it’s not quite done...” then I’ll let it slide. Have you seen something along those lines?
That's what a TODO file is for. There is one you can browse to here: https://www.sudo.ws/repos/sudo/file/tip/TODO
There is also literally a dozen lines in the LICENSE file saying that the software is not quite production ready (THE SOFTWARE IS PROVIDED "AS IS") and should not be relied upon (THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS).
It's all spelled black on white, not sure what you would want more from the author?
I'm puzzled that we don't have a memory-safe ABI (e.g. amd64-safe) and runtime for C so we could just compile things with
clang -safe sudo.c
to avoid memory errors. I'm fine with sudo (or whatever) taking a 60% performance hit to be more reliable - processors are thousands of times faster now than they were in 1980 when sudo was written. If we had a memory-safe ABI for C/C++ in common use its performance overhead could probably be reduced significantly over time due to implementation improvements, and we might see hardware acceleration for it as well.There are a number of proof-of-concept memory-safe compilers for C using fat pointers, etc., but memory safety hasn't made it into gcc or clang. 64-bit CPUs can help because you can repurpose address bits. Even Pascal (which is largely isometric to C) supported a degree of memory safety via array bounds checking. I believe Ada compilers also support memory safety. PL/I was actually memory safe and is why Multics never had any buffer overflows. Obviously Rust is memory safe, but for a lot of legacy C code it is impractical to rewrite everything in Rust but eminently practical to recompile it with memory safety turned on.
Also to ship this in a Linux distro you'd need two builds of many packages. Tons of tools would need to be updated to work with the new ABI. It would be a nightmare.
Furthermore, a new fat-pointer ABI would not address lifetime errors like use-after-free, so what would your plan be there? Boehm GC? More complexity, more overhead, more compatibility issues.
So in practice this is not that appealing, which is why we don't do it.
Any C code that needs changing to deal with fat-pointers is probably already UB in C (or at best, has some implementation-defined behaviour).
That's because the representation of pointers themselves is undefined (so you can't get a valid result by looking at those). Pointer/integer casts (either direction) are implementation defined. And accesses via pointers to anything beyond their bounds is already UB.
There's some good and interesting discussion of what's involved in all of this on: https://www.ralfj.de/blog/2020/12/14/provenance.html
And there's already bodies of work within the Rust (and C/C++) communities around the concepts/technologies that would need to be developed to achieve something like a memory-safe UNIX TCB.
Maybe it shouldn't be doing that? Isn't that the whole point of something like a -safe flag? Increase security as you go along. Yes it is going to take time. The best time to plant a tree is 20 years ago, next best is today.
The kernel won't allow you to setuid scripts, there is a reason for this: it's very easy to leave glaring security holes while doing so.
My main point was that you could rewrite sudo in all sorts of languages, but saying "just rewrite it in Perl" (assuming it worked) isn't a enough justification to make it happen. Nobody is going to re-create their own project in Perl, Rust, etc just to eliminate buffer overflows. If somebody wants sudo in Rust, they'll have to do it themselves, and it still might never replace the original.
This is not true. Complexity breeds bugs, including security bugs, and memory safety doesn't change that. Your example is a good one - here's another: doas once failed to limit the environment variables which are passed to the child process, which could be used to nefariously influence the program running (e.g. with LD_PRELOAD). How would Rust prevent that oversight? It wouldn't.
A simpler program will generally be more secure than a complicated one, no matter what language either is written in. Furthermore, rewriting an established program from one language to another will always introduce more bugs than it fixes, and more severely the more complex the program is. The single best way to improve security is to reduce the attack surface, and the single best way to do that is to reduce the complexity of your system.
"Yes, it would still be vulnerable to logic errors... But it wouldn't be vulnerable to this. "
I think you'll find in disagreeing with the comment on logic errors you just said the same thing the comment did about logic errors.
Also I think the generalization that rewriting an established bit of code in a new language in a secure language is a bit too general. clearly Firefox not only set out to make Rust for this purpose but it's not had an explosion in vulnerabilities with the modules it has replaced. Quite the opposite actually. Nor has every tool or app rewritten become a security failure compared to the original. I do think it's something that can easily be screwed up though, especially if someone rushes through by focusing on functionality duplication instead of building a more secure version of something.
Regardless, both "using a memory safe language results in a more safe program" and "having a minimum attack sufrace results in a more safe program" can be true. There is no need to make it a choice of A or B.
I think you'll find that my comment explicitly acknowledges this and expands on it with another example. Are we done telling each other to read the things we're writing?
>Firefox not only set out to make Rust for this purpose but it's not had an explosion in vulnerabilities with the modules it has replaced.
You're setting the bar pretty high with an "explosion" of vulnerabilities here. Rust programs have vulnerabilities, including rewrites. They also have other kinds of bugs, often ones which were not present in the code that they're replacing. You need only browse your nearest convenient RiiR bug tracker to find evidence of this.
Let me restate my thesis in mathematical terms. If we presume that 1 in 100 lines of production code has a bug in it, regardless of language (generous, I know), and that 1 in 10 bugs in C programs are memory corruption related, then saving 10% of those bugs by rewriting it in Rust would take a 10,000 line codebase from 100 bugs to 90 bugs. A 1,000 line codebase, still written in C and without the advantage of memory safety, would have only 10.
In today's example, sudo is a caricature of runaway complexity. Rust is often touted as a panacea, but C has very little to do with why sudo is insecure. Sudo is comically overengineered and that level of overengineering has no place in a security context. This is the larger issue that needs to be addressed, not Rust.
Here[1] is a link to a slideshow of a talk on the F# language, with a case study from EON PowerGen company rewriting the core of an application evaluating revenue due from their balancing services contracts nationwide in the UK. It was originally 350,000 lines of C# developed by 8 people in 5 years and incomplete. It was redeveloped by 3 people (2 had never used F# before) in 30,000 lines, complete in 1 year.
They claim zero bugs in the F# redeveloped system (page 29). This example also gets a mention in a Don Syme (F# language designer) talk in 2018[2] with the PowerGen employee in the audience.
The PDF cites a testimonial from Kaggle saying they're moving more and more of their application into F# which is "shorter, easier to read, easier to refactor, and because of strong typing, contains far fewer bugs".
[1] https://www.microsoft.com/en-us/research/wp-content/uploads/...
The first bug lets any user cause doas to read out of bounds of an array, though not in a way that's exploitable.
Well, it's arguably a bug in libc. If you run doas with a completely empty argv (argc = 0, so not even an executable name; the two systems I tried, Linux and macOS, both let you do this), getopt will exit with optind = 1. Then when doas does;
argv += optind;
argc -= optind;
`argc` will become negative, and `argv` will advance past the null terminator. On most OSes, the `argv` array is immediately followed in memory by `environ`, so argv will now point to the list of environment variables.doas will then dereference argv, and generally act as if you tried to execute a command consisting of the environment variables. However, the environment variables are not secret, and doas doesn't behave any differently than if you just passed the environment variables as normal command-line arguments, so this is not exploitable.
On an OS where argv is not followed by environ or a similar array of character pointers, doas might crash instead, although since it only reads from those pointers rather than writing to them, this still probably wouldn't be exploitable.
The second bug would compromise memory safety if things were slightly different. The bug is in configuration file parsing. Even if it did compromise memory safety, it would not actually be exploitable, because doas normally only parses the trusted systemwide configuration file. It can be asked to parse a configuration file passed on the command line, but it drops privileges before doing so. This is a good example of layered defense, so kudos to doas for that! Still, I thought the bug was worth mentioning.
The bug is a traditional sort of integer overflow. parse.y grows the array of rules with
maxrules *= 2;
but maxrules is an int, so this will eventually overflow if the configuration file is large enough.However, because maxrules happens to be signed, before doubling produces a smaller-than-expected positive value, it will first produce a negative value. This will then get sign-extended when converting to size_t (assuming 32-bit int and 64-bit size_t), and reallocarray's overflow check will trigger, causing reallocarray to return NULL. doas interprets that as out-of-memory and handles it cleanly.
(On a system where sizeof(int) == sizeof(size_t), things are a bit different, but it will just run out of memory before maxrules gets that high.)
Moral of the story? Well, as I see it:
Simplicity and layered defense, both featured in doas, are both effective ways to avoid vulnerabilities. But guaranteed memory safety, which would require a different implementation language, is also an effective way to avoid vulnerabilities. You aren't forced to pick and choose. Why not demand all three?
As for your second find. It already got fixed: https://marc.info/?l=openbsd-cvs&m=161176698927944&w=2
As for the integer overflow case, it's also highly unlikely to be exploitable, even if it were unsigned - the system would have to, as I'm sure you can infer, have tens of millions of rules before this was an issue. It's quite within the realm of reason, in my opinion, to declare this an acceptable trade-off. The rest of your explanation shows that even if this weren't the case, the bug wouldn't be exploitable.
Anyway, I like your comment, but I'd recommend a different moral to this story: in the space of 47 minutes you were able to conduct a reasonably thorough audit on the doas codebase. Wanna give that a shot for sudo now?
> Complexity breeds bugs, including security bugs, and memory safety doesn't change that.
Yes, memory safety changes that radically.
> A simpler program will generally be more secure than a complicated one, no matter what language either is written in.
Disagree, but the statement is really weak anyways, especially since 'complexity' is an ill-defined term. More features? Cyclomatic?
> urthermore, rewriting an established program from one language to another will always introduce more bugs than it fixes, and more severely the more complex the program is.
Should be obvious to anyone that this isn't true.
> The single best way to improve security is to reduce the attack surface,
Not true, but it's a great way to start.
I'm not sure of any definition of complexity you could appeal to which makes my argument weak.
>>rewriting an established program from one language to another will always introduce more bugs than it fixes, and more severely the more complex the program is.
>Should be obvious to anyone that this isn't true.
The opposite is painfully obvious: (1) Writing code causes bugs. (2) Rewriting an established project involves writing more code than leaving it would. (3) Writing all of that new code will introduce new bugs which were not present in the original.
> people can do in their spare time whatever they want, including writing code without tests
I did not refer to the Sudo project at all, so you might want to redirect your post to someone who did.
If you have to make all kinds of changes to the code you might as well just translate it into a different language.
If someone would get serious about security, auditing every setuid binary would certainly be something on their list (if they use any). If they really want the functionality, rewriting it to cover just enough of the required functionality wouldn't be unheard of.
I'm not saying that a rewrite is never justified, but rather that the argument that we should rewrite in Rust simply to avoid bugs has little weight.
But it comes with costs. Someone has to learn Rust and then convert all of these programs. And it also has the issue that Rust programs are only memory safe if the unsafe keyword is not used anywhere in the program (correct me if wrong?). So it looks like the effort to do such thing, while noble, and valiant, is essentially an experiment with an uncertain pay-off that could turn out to be small or large.
Much more interesting (to my mind, anyway) is something like Miri. The rust interpreter, which uses fat-pointers to make things (more? completely? someone more informed can correct me..) memory-safe by inserting some relatively lightweight run-time checks.
And, then again, if such a thing could be compiled rather than interpreted (some things similar to this already exist, like C with fat-pointers). And if the source language was C (or something like it) or C++ (or some future C++) then the human aspect of re-training a generation of programmers goes from being a very big hurdle, to a much lower one.
At that point the benefits go up quite a bit, and the costs come down quite a bit. And I think that might be a promising path to overcoming the sort of human/political hurdles/inertia involved in rewriting the world :)
You will use existing libraries that contain unsafe code, but you should be able to stick to popular well-tested libraries, which means it will be very difficult for an attacker to find a new exploitable bug in those libraries to attack your tools.
Someone has to learn compiler engineering and then design and implement a 'safe' ABI. Unlike learning Rust, this is probably worthy of a research paper.
> Rust programs are only memory safe if the unsafe keyword is not used anywhere in the program
If you use unsafe, then you take some of the responsibility for maintaining memory safety. However, you can audit the unsafe parts of the code, and it will compose with the compiler-provided guarantees for the rest of the code. Besides, one can easily avoid unsafe code for safety-critical tools like these.
> Much more interesting (to my mind, anyway) is something like Miri. The rust interpreter, which uses fat-pointers to make things (more? completely? someone more informed can correct me..) memory-safe by inserting some relatively lightweight run-time checks.
Miri does not support most interaction with the outside world [1]. It is focused more on detecting UB in unsafe code when it is exercised by tests, than on having your code running in production through Miri. Moreover, I wouldn't call a thousand-fold slowdown [2] "relatively lightweight".
[1]: https://github.com/rust-lang/miri#miri [2]: https://www.reddit.com/r/rust/comments/hosvqu/will_the_miri_...
This is true, but for a language where dynamically sized arrays are a standard data type, the most natural thing to do is to start by collecting the arguments into an array (maybe copying the strings at this point, maybe not). All further argument parsing is done with the array and is thus bounds-checked. I checked Rust's standard library and sure enough, it follows this pattern. Though, I could imagine some hypothetical startup code messing up the argc=0 case if it tried to separate argv[0] from the rest of the arguments while constructing the array.
> Anyway, I like your comment, but I'd recommend a different moral to this story: in the space of 47 minutes you were able to conduct a reasonably thorough audit on the doas codebase. Wanna give that a shot for sudo now?
Fair point. (And I didn't downvote you.) But in my opinion, that just confirms my view: ideally you want both simplicity and memory safety.
Both Chrome & Microsoft found about 70% of bugs to be memory safety related. I've heard similar numbers out of FB as well. The math looks a little different with that data.
https://www.chromium.org/Home/chromium-security/memory-safet...
https://www.zdnet.com/article/microsoft-70-percent-of-all-se...
There's another argument I could make, too. Look at the bug tracker for the program you want to rewrite in Rust, examining the historical bugs. You'll find that there are often hundreds or thousands of mistakes that they made and already fixed in the original codebase. If you're rewriting it from scratch, can you be sure you won't make just as many? A stable, maintained codebase with a low throughput of changes tends to have fewer bugs over time, as the lack of churn avoids introducing new bugs and the application of time susses out all of the existing bugs. Rewriting the whole thing from scratch has a very high rate of churn, introducing a whole new slew of bugs on its own.
Now, a small codebase, focused on delivering its key value-adds without distractions, kept stable and at a low-churn rate over a long period of time: no matter what language you use, this is the best recipe for reliability and security.
Rewrites do bring the chance to Royally Screw it Up™ so it's certainly not simply a product of "it is now written in <Language X> therefore safe" but as it said not only have projects shown the security didn't fall apart but they have shown the opposite.
I agree you don't get there by a bunch of yolo rewrites to whatever is hip though, it has to be a planned effort that isn't rushed. Much in the same way quickly writing a small replacement utility does not inherently make it more secure or reliable than an existing significantly more complex utility. Even just trying to shave some functionality off the existing code is rife with "but how does removing this piece affect the app remaining logic" and takes time and effort to do right.
Both methods do have to be done right and both do greatly help security but there is nothing about picking a memory safe language or making a significantly narrower focused utility that preclude each other.
it is literally impossible to write "a small codebase focused on its key value-adds without distractions" in a language that doesn't have strings and requires you to build a dictionary from scratch
I agree that rewrites have the serious potential to introduce new bugs and the cost is rarely worth it if the codebase is actually that stable and low througput, but the reality is that most aren't. A one time high cost in exchange for introducing 70% less bugs over a period of N years starts to look like a good trade off.
Yes, complexity is the root of all evil. I can get onboard with the whole statement except the "no matter what language you use". If you have the ability to use any language that enforces memory safety, we should use it.
Maybe but not necessarily; it's reasonable to assume that Microsoft and FaceBook put non-zero effort into designing around, programming around, testing, looking for and fixing memory safety related issues in their C code. It could be the case that not having to care so much about those frees up some non-trivial amount of attention and time which could be spent on the other classes of problems.
Yes, all good points. What I'm getting at is that it seems like nobody has yet re-written sudo in this safe way. And it's not just a matter of re-writing it. If (when) someone comes along with this re-write there's "if this person goes away, will we find someone else to maintain it?" and all those other very conservative social forces at play.
I think any new programming language community has these sorts of adoption hurdles to face. And I'm sure the rust community is working hard to build up that pool of developers and I think that's all really positive so I don't want to sound like I'm subtracting from it at all. I'm just also an interested spectator of PL and systems programming research/new directions :)
> If you use unsafe, then you take some of the responsibility for maintaining memory safety. However, you can audit the unsafe parts of the code, and it will compose with the compiler-provided guarantees for the rest of the code. Besides, one can easily avoid unsafe code for safety-critical tools like these.
Thanks, yep. That's why I think that generally Rust is a good idea, and rewriting the TCB in it is a worthwhile project. In regards to safety it looks like a step in the right direction. We're just quibbling about the cost/benefit analysis of how big of a step it is compared to above-mentioned issues that all new programming languages face. Personally, I've no doubt that even with all that factored in, it's still a net positive.
> Miri does not support most interaction with the outside world [1]. It is focused more on detecting UB in unsafe code when it is exercised by tests, than on having your code running in production through Miri. Moreover, I wouldn't call a thousand-fold slowdown [2] "relatively lightweight"
Thanks for the clarifications, you're definitely more up to speed on that project than I am! But yeah, what I meant there was not that the implementation of miri was something to use as-is, more that it's an interesting direction in PL/systems programming research (imo). And some of the ideas there, especially where runtime cost _in principle_ can be made to be relatively lightweight are interesting. I've seen some other research where C implementations with bounds-checking have been implemented part-statically and where the remaining checks are done at run-time with fat-pointers.
OK, bounds checking isn't memory safety, but the paper was a while ago. Maybe it was this one https://www.comp.nus.edu.sg/~ryap/Projects/LowFat/ ?
So I mean, it sounds like you might be able to get to a place where you can use some bits of unsafe in rust, but maybe the program overall could still be safe because the compiler can have a mode where run-time checks (which can be statically eliminated in a lot of cases) are included.
But hey, I'm just a relatively amateur outside-observer of all this, maybe that's a totally impossible pipe dream? :)
I have trouble imagining how aborting leads to a security vulnerability? That's literally running no code, the opposite of running arbitrary code.
Aborting is fine in any language. Criticisms of C here would come about because C doesn't abort when it should (null pointer deref, array out of bounds, etc), not the inverse.
What? Rust has so few libraries of significance that it still depends on C for security-critical areas like SSL.
>it is literally impossible to write "a small codebase focused on its key value-adds without distractions" in a language that doesn't have strings and requires you to build a dictionary from scratch
Strings are misunderstood, I'm not going to get into it here. My dictionaries in C usually clock in at about two dozen lines of code. The complexity doesn't go away because your language does it for you.
https://paste.sr.ht/~sircmpwn/3122d4a27a8e5312462e2329bf7ed6...
Actually managed to get it to exactly 2 dozen lines of code, not including the header, which isn't bad for an off-the-cuff remark.
You'd naturally expand or shrink this with whatever subset of map functions you require, like key/value enumeration, object deletion, resizing, whatever. It depends on your use-case. I don't believe in generic code.