Allow child processes to setgroups() on SmartOS LX
I'm trying to run Dovecot 2.3.19 (RPM from repo.dovecot.org) in CentOS Stream 8, running in a SmartOS LX zone (an Illumos kernel with a Linux syscall emulation layer).
When I launch Dovecot, it emits this message:
log(…): Fatal: setgroups() failed: Operation not permitted
If I understand correctly, the following sequence of events leads to that error:
-
master/main.ccallsdrop_capabilities(), which updates themasterprocess to have only certain Permitted and Effective capabilities, and no Inheritable capabilities -
master/main.cforks and execs thelogprocess -
log/main.ccallsrestrict_access_by_env(), which callsrestrict_access(), which callsfix_groups_list(), which callssetgroups() -
setgroups()fails because thelogprocess does not have theCAP_SETGIDcapability (or any capabilities, for that matter), because in step 1 themasterprocess specified no Inheritable capabilities
Therefore I think the solution is to have drop_capabilities() also specify Inheritable capabilities.
Test
❌ dovecot-2.3.19-2.x86_64.rpm from repo.dovecot.org
# uname -a
Linux localhost 3.13.0 BrandZ virtual linux x86_64 x86_64 x86_64 GNU/Linux
# cat /etc/system-release
CentOS Stream release 8
# /usr/sbin/dovecot -F
May 12 19:42:03 master: Info: Dovecot v2.3.19 (b3ad6004dc) starting up for imap, pop3, lmtp (core dumps disabled)
May 12 19:42:03 log(96325): Fatal: setgroups() failed: Operation not permitted
May 12 19:42:03 master: Error: service(log): child 96325 returned error 89 (Fatal failure)
May 12 19:42:03 master: Error: service(log): command startup failed, throttling for 2.000 secs
May 12 19:42:03 master: Error: service(config): command startup failed, throttling for 2.000 secs
May 12 19:42:03 master: Error: service(anvil): command startup failed, throttling for 2.000 secs
May 12 19:42:08 master: Error: service(config): command startup failed, throttling for 4.000 secs
May 12 19:42:08 master: Error: service(stats): command startup failed, throttling for 2.000 secs
May 12 19:42:12 master: Error: service(config): command startup failed, throttling for 8.000 secs
May 12 19:42:12 master: Error: service(imap-login): command startup failed, throttling for 2.000 secs
# # from another terminal:
# nc 10.0.42.6 143 < /dev/null
# # (no output)
✅ Local build with this MR
# /opt/dovecot/sbin/dovecot -F -c /etc/dovecot/dovecot.conf
May 12 19:43:08 master: Info: Dovecot v0.0.0-30709+7e36988a4f-dirty (7e36988a4f) starting up for imap, pop3, lmtp (core dumps disabled)
May 12 19:43:16 imap-login: Info: Disconnected: Connection closed (no auth attempts in 0 secs): user=<>, rip=10.0.43.2, lip=10.0.42.6, session=<qsAvH9neG9MKACsC>
# # from another terminal:
# nc 10.0.42.6 143 < /dev/null
* OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ STARTTLS LOGINDISABLED] Dovecot ready.
Hmm. I don't completely understand Linux capabilities, and the current capabilities code in Dovecot was written by other people. But my understanding is that Dovecot uses them to drop privileges that root generally has. I don't think Dovecot supports starting itself as non-root but with the necessary capabilities, which is what capabilities are also used for.
So I'm a bit worried about changing security sensitive code that I don't fully understand. Since it works in normal Linux, isn't this an incompatibility bug in SmartOS LX instead? It seems like if I add extra capabilities it's possible that there might now be too many capabilities. Like would it allow setgid() calls even after dropping root privileges?
This changes the capabilities to be inheritable, which in SetCap would look like CapName+ip instead of CapName+p. It does not actually change what capabilities are provided.
Hi, @sirainen and @cmouse, and thanks for the feedback.
[sirainen] Since it works in normal Linux, isn't this an incompatibility bug in SmartOS LX instead?
Hmm, from my understanding of how the capabilities system works, the SmartOS LX behavior seems POSIX-compliant(*) to me.
(*) Er, "compliant with the withdrawn POSIX 1003.1e spec", I mean.)
I'm not sure why the existing Dovecot code works on Linux. The Linux Kernel's setgroups() implementation checks whether the current namespace has CAP_SETGID — so the child process needs to have that capability. But AFAIK the child process can only receive it by inheriting from a process that has that capability in both its Permitted and Inheritable sets. Since the existing Dovecot code specifies an empty Inheritable set, I would expect the child process to not have CAP_SETGID, and thus I would expect its setgroups() call to fail.
[cmouse] This changes the capabilities to be inheritable, which in SetCap would look like CapName+ip instead of CapName+p. It does not actually change what capabilities are provided.
Yes, that's my intent — this PR grants the master process's existing capabilities to its child processes, so the child processes have permission to drop root privileges.
would it allow setgid() calls even after dropping root privileges?
Ah, good point. I wrote a quick test — https://github.com/smokris/drop-priv-test — and on both a real Linux kernel and SmartOS LX, setgid() and setuid() are blocked after dropping root privileges, even if the capabilities are inheritable:
# uname -a
Linux localhost 4.18.0-383.el8.x86_64 #1 SMP Wed Apr 20 15:38:08 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
# make && ./test-parent
cc -lcap -o test-parent test-parent.c
cc -lcap -o test-child test-child.c
test-parent: ========= no cap_set_proc
test-child: uid=0 euid=0 gid=0 egid=0
test-child: dropping root privileges...
test-child: uid=89 euid=89 gid=89 egid=89
test-child: attempting to regain root privileges...
test-child: setgid failed: Operation not permitted
test-child: setuid failed: Operation not permitted
test-child: uid=89 euid=89 gid=89 egid=89
test-parent: ========= cap_set_proc with Permitted and Effective
test-child: uid=0 euid=0 gid=0 egid=0
test-child: dropping root privileges...
test-child: uid=89 euid=89 gid=89 egid=89
test-child: attempting to regain root privileges...
test-child: setgid failed: Operation not permitted
test-child: setuid failed: Operation not permitted
test-child: uid=89 euid=89 gid=89 egid=89
test-parent: ========= cap_set_proc with Permitted and Effective and Inheritable
test-child: uid=0 euid=0 gid=0 egid=0
test-child: dropping root privileges...
test-child: uid=89 euid=89 gid=89 egid=89
test-child: attempting to regain root privileges...
test-child: setgid failed: Operation not permitted
test-child: setuid failed: Operation not permitted
test-child: uid=89 euid=89 gid=89 egid=89
I need root too
merged as 88a558776a10c7b3bef9fcb60126418cda3681e2