Today I fell into a rabbit hole around user and process permissions and had to learn how sudo actually worked. For the program I was working on I set out to figure out how to perform something akin to a “user swap” when launching subprocesses. On its face it seems simple enough, my program runs with user id 1000 and I wish to shunt child processes over to run as another user id. sudo can do it, so why can’t I? “For reasons” is the answer.

The alternative for this post could be “unix permissions are insane”.

Regardless of whether spawning a child or doing something within the process for modifying “who” is running a program is effectively the same. The program runs with the user id (uid) of the caller, so in the case of your shell which is running as you (often uid of 1000) and spawned process will inherit the same uid .

In a C program, I can however reassign my process’ uid with the setuid(2) system call:

setuid() sets the effective user ID of the calling process. If the calling process is privileged (more precisely: if the process has the CAP_SETUID capability in its user namespace), the real UID and saved set-user-ID are also set.

(In Rust this function is helpfully exposed via the nix crate.)

My normal shell user is not the root user and therefore in just about every program I execute, they cannot use setuid(2) to change to any other user id.

If processes spawned by my user cannot assume other user ids, how the heck does sudo do it?

Based on the man page excerpt above, it’s easy to assume that perhaps the process has the CAP_SETUID capability? Per-file capabilities are a Linux-ism which exposed to user-land via libcap:

Libcap implements the user-space interfaces to the POSIX 1003.1e capabilities available in Linux kernels. These capabilities are a partitioning of the all powerful root privilege into a set of distinct privileges.

These capabilities can provide the desired setuid functionality, but they can also be dangerous and lead to vulnerabilities. This blog post has a simple demonstration of using CAP_SETUID to gain root on a machine. We can check whether the sudo binary has this capability with the getcap program, which is not always in the distribution’s default packages:

❯ sudo getcap `which sudo`

❯

Unfortunately it looks like sudo has no special file capabilities, fortunately there is one other way to for a program to run with root privileges, but if you don’t know where to look, good luck finding it!

The file system contains a little bit more information about an executable referred to as the “setuid bit”. Using chmod you take a binary that you own, set the “setuid bit” and then whenever any user runs the binary, it will run as your user. Naturally if a binary is owned by root with the setuid bit set, any caller of that binary will run the binary as root.

This is exactly what sudo does, which you can check just by exampling the file mode:

❯ ls -lah `which sudo`
-rwsr-xr-x 1 root root 181K May 27 07:49 /usr/bin/sudo
❯

Note the rws at the beginning of the mode string, this means read (r), write (w), and setuid (s) are all set at the user level on the file. Additionally the executable (x) bits allow all users within the root group, and everybody else to execute the file.

Sudo basically starts out as root and then will read its configuration file and validate/drop permissions as necessary. Since the program is running with the root level privileges, it can easily call setuid(2) type functions and change to any user on the system.

Should you wish to explore exactly how sudo works, the source code is on GitHub.


There is effectively no user hierarchy in Unix-inspired systems, there is root and then there is everybody else. Unfortunately for my hacking this means I cannot have a user (1000) launch a process with a “lateral” user permission such as uid 1001. I would first need to escalate privileges in some manner from 1000 to root and then drop back down again to 1001.

If you ever see a program that “drops privileges” such as Docker, Nginx, or Systemd, just remember that in there is no dropping privileges without first escalating to root in a Unix-inspired system!