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 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.