Jah Rule
Security is a multi-faceted subject in computing, and even amongst those who use Unix in one form or another there are disagreements about how best to handle various aspects of system administration. In today's blog post I'm going to be talking about the subject of controlled priviledge escalation, and in particular the presence of suid binaries on your system.
A (brief) overview of Unix permissions
Unix permissions work due to a number which is stored along with each file representing what actions may be taken on that file and by whom. These permissions are encoded by looking at the actual bits which represent that number, which can best be visualized by looking at a few numbers in binary form.
- 0b0001 - 1
- 0b0010 - 2
- 0b0011 - 3
- 0b0100 - 4
- 0b0101 - 5
- 0b0111 - 6
- 0b1000 - 7
In looking at just these numbers, and just considering the bits that happen to be flipped on, we can see that the number three contains the bits of both 1 and 2, while the bumber 5 contains the bits of 1 and 4. Unix permissions use a 32 bit integer, allowing for 32 distinct boolean values to be stored in one integer. For the sake of this article, the interesting bit is the one representing the suid permission. When an executable has this bit flipped on, then that executable is run with the effective user id of the owner of the file. If that owner happens to be root, then a normal user can run that program with root level privilege.
It should go without saying that a suid binary is a powerful tool which is rife with the possibility for abuse. If that program can be used to spawn other programs, such as a shell, then the security implications can be absolute. Therefore it is a good idea from a security standpoint to keep the number of suid binaries on your system to a minimum and to make sure that the ones which do exist are well designed and difficult to misuse.
As often happens to systems as they develop, the number of suid
binaries on an average Linux system has steadily increased over the
years. You might expect to have only one or two of them,
su and sudo coming immediately to mind, but
you would likely be wrong. I just did a check on my laptop running Arch
and found 25 suid binaries in /usr/bin, which is kind of scary to be
honest. Some of them are suid for reasons which are not immediately
apparent. A CD burning program might be installed suid in order to be
able to write to the device file /dev/cd0, although arguably the same
effect could be had by setting the ownership of those device files to a
certain group and ensuring that every user needing access to those
programs is a member of that group. That and, you know, optical media
is not very common anymore. But I digress, it's absolutely overkill to
have a suid root owned binary for this purpose.
The big two
The big two of suid programs that you will find on almost any
Unix machine are su and sudo. The source of
the su program might vary depending on if you are running
Linux, FreeBSD, or Solaris. It might also differ based on your actual
Linux distribution - case in point Alpine uses Busybox. But in all
cases su is a nice simple program. The suckless
implementation in Ubase is 124 lines.
Now sudo on the other hand is absolutely not a simple program. In fact, checking the latest release with tokei I get 108255 lines of C and 18747 lines of C headers. There's also some 57860 lines of autoconf and a smattering of perl, among others. Sudo allows fine grained access control and is highly configurable. It has had a few security vulnerabilities over the years, but to me those aren't the big issue.
The issue that I have with sudo is that it's so overkill for what it does. Leading from that, it has a relatively complex configuration file and is often, if not regularly, configured in an insecure manner. Linux distributions started pushing the use of sudo rather then su some years back because of the idea that you didn't need a long running root shell to just run a couple of commands. That makes sense, but frankly they were not thinking clearly when they put such a powerful and easy to screw up program on every system, including systems being run by non technical users who will never need even a fraction of it's features.
All you really need to do 99.999% of what sudo gets used for is a
program that can grant root privilege to a specific subset of users
using a proper authentication method. There is no need for a
configuration file whatsoever. There is no reason to support any means
of authentication besides that used by the login command
on your system (passwords hashed via libcrypt). There is definitely no
excuse for such a program to be comprised of thousands of lines written
in an unsafe language like C.
What's even more frustrating is that the su command
already was able to run arbitrary commands via the -c
command line flag, so a simple shell alias could arguably have fit the
bill, too.
Alternatives
The OpenBSD folks released a sudo replacement a few years back called
doas. OpenBSD is a great system, and while you can run
doas on other systems, some of the things that make it
especially secure (relatively speaking) on OpenBSD just don't translate
to other operating systems. For instance, the OpenBSD kernel has a
system call which allows one to stay authenticated for a period of
time, after which privileges are automatically dropped. This function
has to be implemented using temporary files and timestamps on other
systems.
The pkexec program (part of the polkit package) is a part
of certain modern stacks (cough cough Gnome) and functions differently.
It's a daemon which runs in the background and you connect to it with a
client to authenticate. My issue is that computing cycles are not free,
and why should we have a background process running the entire time
that you're logged in to facilitate the once in a while need for
increased privileges? That and, once again, it's an overly complex
system that one can just imagine having serious bugs lurking about. In
fact there was just such a vulnerability discovered in pkexec about a
year ago aptly named
pwnkit.
There was a somewhat tongue in cheak blog post by a fairly well known
Rust dev not long ago that, unfortunately in my opinion, got taken a
bit too seriously. This blog post described the "🥺" program, designed
to address the issue of sudo's complexity by creating a brutally simple
replacement. 🥺's security model is that in order to run the program,
one must be able to type an emoji, thus making it unlikely that a bot
will ever discover how to run the program. Thus it has no
authentication mechanism whatsoever. It also has no facility to run a
program as any user or group other than root:root. I will
admit to loving simplicity, but this security model is naive at best.
However, it was an entertaining blog post and it happened to be the
inspiration for this one. The original post can be read here.
Jah Rule
Naming things is hard, right? Well, a program like sudo or doas allows
a normal user to act as if they were the superuser, who can do anything
on your system. Kind of like being God of your little Unix world. The
Rastafarian name for God is Jah, and being a Bob Marley
fan I figured it might be nice to think of Bob when I run my updates
each day. So my sudo replacement is named jah.
Jah is written in Rust, for whatever that is worth. It's nowhere near
as minimal as "🥺" because I want it to be more than just a toy, but
it's still an extremely minimalist command line tool. The
Cargo.toml file pulls in a total of four direct
dependencies. I'm using Clap because it's simply the best argument
parser in existence, but I have the feature set somewhat reduced. It
also uses libc because due to the nature of the beast the program has
to make use of some interfaces not provided by Rust's std.
The rpasswd crate is used to hide passwords as they are
being typed, and finally the Rust bindings to libcrypt.
Run COMMAND as another user Usage: jah [OPTIONS] <COMMAND>... Arguments: <COMMAND>... Options: -u, --user <user> The user that the command will run as [default: root] -g, --group <group> The group that the command will run as [default: wheel] -h, --help Print help (see more with '--help') -V, --version Print version
Jah has no configuration file and the only options it accepts are a
user or group, if one wants to deviate from the default of
root:root. I haven't bothered to alter the environment
used to run the command like sudo does with the exception of changing
the HOME variable to that of the new user. If that is a
concern, then one could always invoke it as jah env -i
<command>. But I did take care, unlike the above mentioned
emoji named command, to make sure that the program's stack has had
temporary values and variables dropped before it execs
into the desired command. Running tokei tells me that as
of this writing jah is made up of 420 lines of Rust, excluding external
crates. The binary weighs in at 616k on x86_64.
I could have made this much simpler, but I want it to be actually
useful and not annoying. Sudo will save the time of your last
authentication and allow you to run it again without authenticating for
another five minutes. Jah does the same. Instead of determining who can
use the program via configuration file, any user who is a member of the
group wheel can run any program using jah after
authenticating with their own password. If even typing a password is
too much for you, then you can make a system group named 'jah' and add
your user to it, and you will then be able to run any command as any
user and group without a password. If that makes you uncomfortable (and
it probably should) then simply do not have that group on your machine.
NOTE: The 'wheel' group is historically for sysadmins and most
suimplementations require a user to be a member of 'wheel' in order to be allowed to use it. Thus the design has an obvious historical precedent.
Code for jah is at the codeberg repository. It is
intended to become a part of HitchHiker Linux in the near future.
Tags for this post: Security Programming Rust Unix Permissions