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
1000) and spawned process will inherit the
In a C program, I can however reassign my process’
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
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
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
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
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 ❯
rws at the beginning of the mode string, this means read (
w), and setuid (
s) are all set at the user level on the file.
Additionally the executable (
x) bits allow all users within the
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
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
root and then drop back down again to
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!