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 exec
s 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
su
implementations 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.