Maybe a good time to plug doas, a simpler alternative to sudo from OpenBSD folks[1], developed partly due to security fears about sudo. It’s also been ported to Linux and is available in e.g. Alpine and Debian.
https://github.com/sudo-project/sudo
On the contributors tab:
https://github.com/sudo-project/sudo/graphs/contributors
We can see that sudo has been maintained by one person for ~29 years who has single-handedly committed ~2,936,000 changes over that time resulting in a net increase of ~480,000. It is also being actively developed with ~270,000 changes resulting in a net increase of ~40,000 lines in the last twelve months.
In contrast doas is, just eyeballing it, maybe ~500 lines and at most 1000 lines all together.
It doesn’t remove a bit of how impressive the dedication of Todd C. Miller is of course.
These are two solutions with a common core goal but greatly different concerns.
I don't think it makes sense to add all added and removed lines together and call it "changes": for example, this counts even a single character typo fix as 2 changes.
A lot of people do want to do more, especially in some corporate contexts people run very complex sudo setups.
Consider something like Linode; maybe you want some of the more experienced support people to issue some commands on some machines, without having full control. And you've got a gazillion machines so you don't want to setup each one individually (as well as revoke access once they leave). Things can get fairly complex pretty quickly once you move out of simple settings.
For my desktop machine, doas is perfect. For our servers it's fine too because we have just a few people with access. But I'm not everyone of course.
doas was explicitly written to NOT cover all use cases, and that makes it better for the (simpler) use cases it was intended to solve, at the price of not covering other use cases of course.
Some other folks use LDAP to get the sudoers files or even allowed ssh keys itself
There is also "just run thing as user" but also "set up same way shell would and pretend user logged as different user", first one is simple, second is a bunch of setup, copying env variables etc.
There is a good argument for splitting "just run app as different user" and "everything else that has to do with interactive shell and admins doing things on server" but now you have 2 configs to manage...
That being said I don't think sudo's code is complicated? It's big because it has all of these plugins supporting various mechanisms, but each file is pretty straightforward, and the control flow is really not that hard (after you get that it's plugins).
And have an audit log? And clean the environment? In particular the path? Dynamic library path?
Still, I suspect "ensure autz" is the problem (implies Pam, 2fa, kerberos, etc etc).
function sudo { su -c "$@" root }
though I guess half the struggle is enabling sudo without a passwordLast time I checked there were several different ports floating around. Now I see at least Debian has chosen opendoas, I guess people have mostly converged to that one. Also fun stuff like https://xn--1xa.duncano.de/slicer69-doas.html
The point being that while openbsd doas is undoubtedly very nice, the linux ports are separate projects
But for simple systems that aren't running complex user privilege management, doas is very much a great replacement.
https://wiki.archlinux.org/title/Doas#doas_persist_feature
https://wiki.gentoo.org/wiki/Doas
With the persist keyword doas can remember an authenticated user and will not require confirmation by password for five minutes.
The biggest difference we found is that sudo is a little more convenient to manage with a config management solution, since each piece of config management code can just drop a file in /etc/sudoers.d. With doas, we have to coordinate a bunch of parts of the config management manipulating one file, /etc/doas.conf. This always turns out a bit messy. But maybe we'll just merge everything into one big and somewhat messy monster doas-conf-template with a ton of feature toggles and loops and go from there.
But beyond that, almost all of our sudo rules are 2-3 patterns: Admin can sudo as everyone, zabbix/telegraf can run data gathering commands, and maybe some other automation triggers like pgbackrest wiping a postgres data dir during a restore, that's about it. All of those are pretty trivial doas rules all in all.
Apparently whether it's used is configured at build time, because I get different results trying it on Arch and Alpine, but doas absolutely has a /etc/doas.d
EDIT: Oops, apparently Alpine added that - https://git.alpinelinux.org/aports/tree/main/doas/configurat...
This seems like a really weird thing for a distro to add themselves rather than upstream.
If you don't need those features, doas is probably a good replacement. But if you do, it probably isn't an option.
Sometimes less is more. My immediate response to that is that it sounds like needless complexity which can be a source of errors, bugs and security-vulnerabilities.
What typical use-cases do these particular features have?
For the io logs: auditing. And in some cases such logs are necessary for compliance reasons.
For wildcards: allowing to run a command, but only if they supply a specific option that makes it less dangerous. Now, you do have to be very careful when doing that, but there are cases where it is useful and explicitly listing every variation of options that is allowed isn't practical.
I also forgot to mention sudoedit. Although, making something similar for doas would be pretty simple, so I'm kind of surprised there isn't something.
Allowing a user to use systemctl with specific daemons, so ```systemctl * unit-name```, without a password. But anything outside of that I need a password.
That you've heard about. There's two big reasons: (1) people often don't try to spend a lot of time finding exploits for userspace programs that don't open network ports or perform privilege escalation. And (2) when they do find exploits in those programs, they don't make it to HN.
https://slackbuilds.org/repository/15.0/system/opendoas/?sea...
It also contains some information on how to setup doas on Linux
libxml? zlib? PHP?
The déjà vs are many…
In practice, this means that if your password is only one char, then the actual buffer is two bytes long, and the seventh byte past the buffer is then zeroed/set to the null terminator. I wonder if and how this is exploitable.
When I see things like this, the first question I have is why? A password isn't going to be long enough to require dynamic allocation, so just use a fixed-size buffer. 255 is already generous and a good round number. The best solutions are often also the simplest.
I guess things are different in the Linux/PC software world.
Perhaps because they were still fighting the last war, where they were hurt by a greater-than-255-character buffer overflow vulnerability.
My default assumption is three letter agencies surreptitiously adding back doors.
But there are plenty of ways for such agencies to gain similar access, including any kind of closed code in BIOSes, drivers, firmware etc. Or by taking control of select infra, and injecting MITM features there (that would remain stealthy, and only activate for very select targets.)
https://googleprojectzero.blogspot.com/2014/08/the-poisoned-...
There have been other examples where only a 1 could be written.
Well, looking at the upstream code, the original value is saved away before zeroing it out and then unconditionally restored after running crypt.
If sudo is single threaded and crypt() doesn't malloc() anything, I don't think that can be exploited. Worst case would be a segfault if the password was somehow close enough to a page boundary.
glibc malloc() should be aligned to 2*sizeof(size_t), so strup("")[x] on 64-bit systems (with 16-byte alignment) can never crash or overlap another object where x<16
On 32-bit systems and with other mallocs you could potentially be reaching another page (like I think you are imagining) or trashing some bookkeeping bits which might crash free() but I cannot yet see how you would induce that, nor convince myself it cannot be done without spending more time thinking about it (something I'm reluctant to do with my afternoon)
Would be at worst a memory read past the inputted password, which wouldn't be super useful outside of a leak for another vulnerability but even that seems unlikely.
So it's not just reading past the end of the buffer, but it's overwriting a single byte potentially belonging to another object. It may still cause a crash but it's relatively unlikely that it could cause something more severe.
Ah that makes a lot more sense.
> So it's not just reading past the end of the buffer, but it's overwriting a single byte potentially belonging to another object. It may still cause a crash but it's relatively unlikely that it could cause something more severe.
Yeah I agree that nothing severe should come from it. The allocations are probably larger then the size of the buffer being used so it may not even right off the end of its own allocation.
if (pw_len == DESLEN || HAS_AGEINFO(pw_epasswd, pw_len)) {
strlcpy(des_pass, pass, sizeof(des_pass));
pass = des_pass;
}
Doesn’t that truncate your password if it happens to have DESLEN characters?No wonder that these kind of tooling keeps being ignored until we get liability in software products.
The first question that we all want to ask
Could it be mitigated by safer, modern tech?
One does not even need to reach for Rust. Literally any other language (in common use) other than very badly used C++ would not have had this problem.
C delenda est.
Is it that GNAT only became available relatively late in the lifetime of GNU/Linux? Or is there another technical reason for it?
Anything else required buying the product and justify why the compilers in the box wouldn't do it .
When UNIX SDKs became commercial, it was even worse, you would naturally one buy the main one, not pay twice for programming languages.
Then the UNIX clones also followed the same culture focusing on C for the clones. Early versions of the GNU manifesto explicitly refer that C should be the preferred language.
That is how we landed here.
Dynamically typed languages as a whole would be a bad idea.
Java's startup time for such a small executable would be a problem.
I'm just saying this problem is unique to C, and in my opinion, sufficiently endemic to security software to disqualify it entirely.
Mind you, you might well end up at Rust in the end anyhow. Perhaps D. It isn't necessarily a long list for a sudo replacement. But...
C delenda est.
PL/I does bounds checking by default.
RH does have an interest in Rust, used in projects such as Stratis. It's just that the Linux dev ecosystem has been very C-reliant for a long time, and a massive amount of binding and other ecosystem work is still happening to make this possible.
*EDIT:* and the reason I mention Rust specifically is that, in these types of lower-level projects, a lot of things can start to get hairy very quickly in higher level languages. Things like some namespace APIs very much wanting to be run on a single thread, or trying to maintain performance when you're intercepting and examining every D-Bus message, or even just when you want your functionality to be in a reusable core library.
Languages like D even allow you to disable the garbage collector for specific methods, giving you the benefit of GC-less performance and characteristics in critical code paths and the YOLO memory management of GC languages in the wrappers around them.
I guess the answer is "because all the people over at Redhat know C"
> /*
> * Truncate to 8 chars if standard DES since not all crypt()'s do this.
> * If this turns out not to be safe we will have to use OS #ifdef's (sigh).
> */
> sav = pass[8];
This bug was introduced by the latter.
If you seriously think a buffer for a password needs to be dynamically allocated, the "safest" and most "modern" shit won't help. It'll just contribute to the ongoing decline.
But it would
Rust/whatever is not a magic bullet
This was already a solved problem in NEWP, JOVIAL and PL/I, no need for modern tech, only not to insist in using broken by design one.
Like... Coverity Scan?
In my experience, this bug looks like a classic example for something that Coverity should find.
And it looks like sudo is already on there: https://scan.coverity.com/projects/sudo
The last analysis was 2 weeks ago. I wonder if this CVE is among the outstanding memory corruption and illegal access defects (5 each).
People use fucking java in HFT
dont worry, our basic tools are fine with Rust, go or even c#
Bounds checking has been standard in every language other than C since 1970, but for some reason C programmers refuse to use it, normally using arguments like "just make sure the indexes are correct", which is basically "just don't write bugs".
Are you going to rewrite all of that in $other_language_than_c? How many hours of work do you think this will take to rewrite?
This is the real issue.
I'd argue that's (at least part of) the problem. More code = more surface area for bugs, and sudo has a lot of code.
(also, 150k lines of code is a little bit misleading, since not all code is for all platforms, sudo has a plugin architecture I believe, etc.)
Surely that can‘t be that much in the name of security, no?
sudo make install, ok, great, some of the many operations you need to do requires privileges? Better give elevated privileges to all operations!
Even worse with GUI: enter your password to install. Now I have absolutely no clue what the scope of sudo is.
Of course I don't want to enter my password for all individual cp and mv operations, but if sudo had a better/smaller scope that'd be great.
On the other hand, having to always explicitly specify all the fine grained capabilities a process might need is a pain, too.
Files within /etc do, for security reasons, but there's no reason why you couldn't use user groups or other ACLs to secure those folders.
chown /etc to nobody:wheel and chmod it to g+rwx; users in group wheel will now be able to manage /etc. You've got to make sure you set your umask right if you do use sudo for /etc again, but that's also just part of your system configuration.
I did some extensive testing of this some years ago (on Debian/Ubuntu) and many system services and tools expect/require these directories to have specific ownership and permissions.
In the context I was experimenting with it was pretty simple too - renaming the UID 0 'root' account to some other name. That revealed that many tools actually test for "root" (the string) not uid == 0.
As I dug into the code of those tools I found many would also check and insist on particular ownership and modes on the directories and files.
I forget which one really annoyed me, but 'all' I wanted to do was allow members of group 'adm' to read/write into a particular sub-directory of /etc/ but the service would bail out if the directory wasn't owned by "root":"root" (or 0:0) and had 0700 permissions which is a pain when wanting to run services unprivileged and using 'setcap' to enable capabilities without starting as UID 0 and dropping privileges.
net.ipv4.ip_unprivileged_port_start=80
Or whatever port you want unprivileged to start at. If you set it to 0 it means any user can bind to any port < 1024.Ref: https://www.kernel.org/doc/htmldd/latest/networking/ip-sysct...
And remember to do it again every time the binary is updated :/
> chown /etc to nobody:wheel and
Bad idea! nobody is supposed to own no files at all. You run untrusted services (or untrusted users without account; something like anonymous FTP access) as nobody. This would potentially allow the least trusted entity to change your configs.
Apart from that. Since root can read any file anyways there is no reason to change the owner. And some programs may complain if the configuration is not owned by root.
Doing fine-grained access is really hard; even without "root" you still have things like, say, "archive_command" in postgresql.conf which will allow running people to run arbitrary commands as the postgres user, and is that really what you want? There's lots of little things like that ranging from application configurations to crontabs to your init system.
But these days many computers are only used by one user.
Everything I care about on my computer is readable by my user and a program running as my user could put fake binaries in my path.
Flatpack et al. have improved this situation somewhat, but come with their own drawbacks. Linux needs a central application-level permission system like Android, where I can grant/revoke e.g. internet access to applications. Frankly, I should never have to use sudo to install anything in my daily life, that is unfortunately not the case with the common ubuntu install, and will probably stay this way for a long time.
Now on a server, sudo for a single user probably doesn't make sense, just use root and keep it simple.
B - Not sure how practical most of this is yet, but there's cool stuff around isolating individual programs even on single-user machines.
C - My desktop has a couple things that listen on the network, and it's nice that they only have access to specific things.
For example, sound demon like pulseaudio runs as your user (...for some reason, fucking Lennart) but it really should not have write access to anything aside from its own config and for 99,99% users also not have access to read anything your user owns aside from its own config.
Even browsers should probably be limited, or user should at least get prompt, there is little reason to allow browser to dig around your system willy nilly, let alone in locations like ~/.ssh
For installing things, you generally need write permissions to /usr/bin and likes. So you could create an user with such privileges and sudo to that.
The real issue, I think, is Linux not being capability based, so there's no programmatic way for scripts to communicate which sort of permissions are needed.
OpenBSD has something like this https://man.openbsd.org/pledge.2
Pledge protects the system from buggy well-intentioned, cooperative software that could have bugs. What's needed is something that protects the system from ill-intentioned, uncooperative software.
I see this attitude in pentesting too on embedded systems. A developer encounters a problem they don't quite understand but the problem disappears when they run their app as root, so away we go.
That's a part of a reason for its complexity it does allow you to do anything between "make user be another user with all priviledges" to "just allow to run this particular command and nothing more".
Having one that had option for more limits would be interesting (say use cgroups to change running user but disallow command from modifying anything aside from this one single directory you specified) but, well, that's way more code that also needs to be secure...
Even ignoring security issues, all of make getting elevated privileges can cause other issues as well.
All it takes is the incremental build system being a little finicky, and "sudo make install" could rebuild an object as root, and now one or more of your ".o" files are owned by root and your build directory is broken.
If you are in sudoers and you are compromised, then there are like a million ways of getting root for a malicious program. They could override your sudo, override your terminal, override your shell, override your de, even override say "cat" so that instead of exiting when it is done, it starts a shell that mitms all your commands and waits for a sudo one.
<https://access.redhat.com/security/cve/CVE-2022-43995>
Description:
... Sudo 1.8.0 through 1.9.12 ...
Statement:
The sudo package as distributed with Red Hat Enterprise Linux 7, 8 and 9 is not affected by this issue as it currently doesn't ship the affected code.
<https://access.redhat.com/downloads/content/sudo/x86_64/pack...> 1.9.5p2-7.el9
1.8.29-8.el8Got it. Linux distributions (ex. RHEL) have --with-pam in configure, so not vulnerable (code not compiled). (If you have --with-passwd in configure, then passwd.c is compiled, and you are vulnerable, but Linux distributions do not do this.)
<https://ubuntu.com/security/CVE-2022-43995>
sudo packages in Ubuntu are compiled with PAM support, so the vulnerable code isn't part of the binaries.
Not vulnerable (code not compiled)Zig, Nim, Rust, D, V, whatever -- can't we just move on from C/C++ already? It's obvious they are not up for the job.
And even if modern C++ is amazing -- I have no doubt it's improving all the time -- that doesn't change the fact that there are metric tons of C++ code out there that nobody will ever modernize. So C++ getting improved with time is sadly an almost moot point.
I'd personally advocate for Rust but I've heard people say there are a few other languages that allow you to achieve the same memory safety so, by all means, let's please start rewriting and make our everyday tools something different than a Swiss cheese of potential and actual security threats. It really is time.
The language doesn't matter, the outcomes do. And in terms of outcomes I maintain that C and C++ have not stood the test of time in terms of security and amount of foot guns. Too much sentimental value is attached to them as well and that doesn't help matters either.
We work with tech. We don't make love with it. We should all start acting like this is a job and not [only] a hobby.
Complexity High isn't about what an attacker gets, it's about whether or not any specific configuration must exist for the attack to happen. For instance, if an app that talks to several services over different file transfer protocols has a vulnerability in only the FTP component, and these are not under attacker control, that's Complexity High.
Why can't we just have a minimal version of sudo that does just that and only that so the majority of smaller servers and home users can run sudo without fear of a security bug ever other month? Preferably using the same executable path so that everything else doesn't break.
It just seems like most of sudo's security bugs come from weird obscure features almost no one uses. Like that time sudoedit had a security issue. I didn't even know that command existed until it broke things, and it still seems pointless when you can just run "sudo nano" or "sudo vi".
Lessons:
* Serious bugs doesn't care in which language the error happens
* C++ implementation was safe
* Java implementation was unsafe
* Test-Coverage would help...
PS: I don't say Rust is good/bad. C++ is good/bad. Or is good/bad. Neither about Java.This just isn't true.
Buffer overflows are not possible with bounds checking.
Using a language that provides containers with bounds checked access methods would have prevented this. This isn't a point of debate or something, it's a fact.
C is virtually the only language that doesn't provide a safe way to access elements.
C++ provides bounds checking with std::array, std::vector and std::string using the "at()" methods. All Rust containers are checked by default. Pretty much every other language also is checked by default as well. All of these language's could have prevented this error and the other buffer overflow errors which there are tons of.
If your point is that C++ lets you do unsafe things then yes, of course it does. But so does Rust.
Yes, given enough care and effort you might write code that will not have those bugs, but not having a possibility (aside from unsafe{}) to have them in the first place is usually better approach.
Like, yeah, it is a dumb meme but in this case not without merit.