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!