I’ve been trying to understand how to safely change users when a daemon executes a task under Linux (and other Unixes, for that matter). I spent quite some time researching and, in most cases, discarding several ways to do this.
The traditional way - setuid()
Traditionally, changing users happens via the setuid() system call,
sufficiently old that it is part of the lizard brain of UNIX, going all the way
back to version one. For executables that need to run as another user, the
setuid bit is traditionally set on the file (chmod u+s <file>), such that a
single binary can be elevated to a superuser for some particular task. For
instance, the ping(8) utility has traditionally needed the setuid bit set to
create raw sockets to send ICMP packets.
setuid makes admins nervous
The problem with setuid is that programs using it have to be written extremely carefully, because a single mistake can lead to a malicious user leveraging the setuid binary to elevate themselves to an all-powerful root user. This isn’t purely academic - this is a real attack vector that gets exploited all the time, in places you wouldn’t think to consider:
The capability way
One way that Linux maintainers have been starting to clamp down on dangerous
setuid() binaries is to instead use the capabilities (caps) subsystem to
grant granular access to privileged operations. For instance, just recursively
looking through my /usr directory I find a few items with capabilities
assigned:
root@kosh:/usr# getcap -r /usr/*
/usr/bin/kwin_wayland cap_sys_nice=ep
/usr/lib/i386-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper cap_net_bind_service,cap_net_admin,cap_sys_nice=ep
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper cap_net_bind_service,cap_net_admin,cap_sys_nice=ep
/usr/lib/x86_64-linux-gnu/libexec/ksysguard/ksgrd_network_helper cap_net_raw=ep
/usr/lib/x86_64-linux-gnu/libexec/org_kde_powerdevil cap_wake_alarm=ep
Should the KDE power consumption daemon (powerdevil) be able to wake up the
system from sleep? Yes! Should it need to be fucking root to do it? Of course
not! Hence, CAP_WAKE_ALARM.
Back to the setuid problem - can I add a cap to my binary to make it possible
to change users, ideally precluding the ability to change to the root user?
Ehh… not really. While I certainly can add CAP_SETUID to my binary, it’s
effectively exactly the same as giving my binary the setuid bit. Back to the
drawing board!
old school cool - the sudo way
I would be remiss to not write about sudo here. However, I really feel that
sudo is more of an administration tool rather than something that should be in
the critical path of a service. While setuid and capabilities are assigned to
binaries, sudo applies to users or groups. Presumably your daemon user would be
using NOPASSWD, and essentially you end up in the same boat as setuid.
The polkit way
More granular and application-focused than sudo, Policy Kit (polkit) is an expressive toolkit used for allowing privileged operations for unprivileged processes. It’s often used for desktop environments to escalate particular daemons or processes without giving them full root access.
In my research, it seems that polkit is widely distributed but not widely used among server installs, as many of these installations lack a desktop environment where polkit is typically used. polkit has also had its own share of CVEs, many of which come from the non-memory-safe nature of C, but it’s generally considered robust and reliable today. I’m still trying to put my finger on the pulse of how administrators feel about polkit, and whether it’s worth pursuing.
Polkit isn’t exclusive to Linux, and has been ported to FreeBSD, OpenBSD and others chiefly for desktop environment integration. That’s a plus, but the specific functionality I’m after is combined with systemd, which can use polkit as an authorization mechanism for making decisions about privileged operations. More on that in a later article.