sysusers: handle soft-static allocations in a way that is not influenced by line order
systemd version the issue has been seen with
257.5
Used distribution
Fedora 43
Linux kernel version used
No response
CPU architectures issue was seen on
None
Component
No response
Expected behaviour you didn't see
~]# systemd-sysusers ./cyrus-sasl.conf
Creating group 'saslauth' with GID 76.
Creating user 'saslauth' (Saslauthd user) with UID 989 and GID 76.
~]#
~]# systemd-sysusers ./cyrus-imapd.conf
Creating user 'cyrus' (Cyrus IMAP Server) with UID 76 and GID 12.
~]#
Unexpected behaviour you saw
~]# systemd-sysusers ./cyrus-sasl.conf
Creating group 'saslauth' with GID 76.
Creating user 'saslauth' (Saslauthd user) with UID 76 and GID 76.
~]#
~]# systemd-sysusers ./cyrus-imapd.conf
Suggested user ID 76 for cyrus already used.
Creating user 'cyrus' (Cyrus IMAP Server) with UID 989 and GID 12.
~]#
Steps to reproduce the problem
cyrus-sasl.conf:
#Type Name ID GECOS Home directory Shell
g saslauth 76
u saslauth - "Saslauthd user" /run/saslauthd /sbin/nologin
m saslauth saslauth
cyrus-imapd.conf:
#Type Name ID GECOS Home directory Shell
g saslauth 76
u cyrus 76:mail "Cyrus IMAP Server" /var/lib/imap /sbin/nologin
m cyrus saslauth
run: ~]# systemd-sysusers ./cyrus-sasl.conf ~]# systemd-sysusers ./cyrus-imapd.conf
Additional program output to the terminal or log subsystem illustrating the issue
It shouldn't execute the following code in add_user():
1245 /* Otherwise, try to reuse the group ID */
1246 if (!i->uid_set && i->gid_set) {
1247 r = uid_is_ok(c, (uid_t) i->gid, i->name, true);
1248 if (r < 0)
1249 return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
1250 if (r > 0) {
1251 i->uid = (uid_t) i->gid;
1252 i->uid_set = true;
1253 }
1254 }
but try to find the free one instead:
1256 /* And if that didn't work either, let's try to find a free one */
1257 if (!i->uid_set) {
1258 maybe_emit_login_defs_warning(c);
1259
1260 for (;;) {
1261 r = uid_range_next_lower(c->uid_range, &c->search_uid);
...
The above breaks the situation where 2 distinct daemons (2 distinct packages) share the same GID, but only one of them has UID equal GID and this UID is reserved for it.
For example cyrus-sasl and cyrus-imapd.
Cyrus-sasl has the following in its sysusers config (only GID=76 defined):
#Type Name ID GECOS Home directory Shell
g saslauth 76
u saslauth - "Saslauthd user" /run/saslauthd /sbin/nologin
m saslauth saslauth
Cyrus-imapd has this (UID=GID=76 set):
#Type Name ID GECOS Home directory Shell
g saslauth 76
u cyrus 76:mail "Cyrus IMAP Server" /var/lib/imap /sbin/nologin
m cyrus saslauth
Cyrus-imapd has a reserved UID (76) for it, but cyrus-sasl doesn't:
~]# grep -w 76 /usr/share/doc/setup/uidgid
cyrus 76 (12) /var/imap /bin/bash cyrus-imapd
saslauth - 76 - - cyrus-sasl, cyrus-imap
~]#
If curus-sasl is installed before cyrus-imapd, saslauth takes 76 as its UID making the cyrus user dynamically allocated instead of being static across all systems:
~]# systemd-sysusers ./cyrus-sasl.conf
Creating group 'saslauth' with GID 76.
Creating user 'saslauth' (Saslauthd user) with UID 76 and GID 76.
~]#
~]# systemd-sysusers ./cyrus-imapd.conf
Suggested user ID 76 for cyrus already used.
Creating user 'cyrus' (Cyrus IMAP Server) with UID 989 and GID 12.
~]#
Hmm, maybe better to introduce a knob to acquire a uid in dynamic range even if there exists a group with the same name. E.g.
u saslauth (dynamic) "Saslauthd user" /run/saslauthd /sbin/nologin
We do not support schemes where two records share the same id. it's a broken concept, because reverse lookups won't work.
The UID you specify is mostly a hint, we'll refuse to allocate it if it is already used, for robustness, to avoid creating ownership conflicts.
I guess we can document this better, but avoiding conflicts is definitely the better approach than accepting them, and opening up the system to vulnerabilities because distinct groups suddenly open up access to each other's resources.
@poettering This shouldn't be a hint. The current behaviour leads to breaking the concept of soft static allocation on Fedora.:
https://docs.fedoraproject.org/en-US/packaging-guidelines/UsersAndGroups/#_soft_static_allocation
If there are pre-allocated UIDs/GIDs systemd-sysusers shouldn't decide by itself which one to allocate to which service and leave the other service to get a random UID.
'-' should allocate a higher number and not allocate from the soft static allocation pool.
cc: @keszybz
sorry, but sharing uids/gids between multiple records is not something we are going to support. that's just broken. I don't think that the fedora policy condones that, why you seem to imply with that link?
sorry, but sharing uids/gids between multiple records is not something we are going to support. that's just broken. I don't think that the fedora policy condones that, why you seem to imply with that link?
Nah, this is a misunderstanding. No uid/gid sharing is happening. Instead, the request is to have a setup where the uid and gid used for the user and group with the same name are different.
~]# systemd-sysusers ./cyrus-sasl.conf Creating group 'saslauth' with GID 76. Creating user 'saslauth' (Saslauthd user) with UID 76 and GID 76.
~]# systemd-sysusers ./cyrus-imapd.conf Suggested user ID 76 for cyrus already used. Creating user 'cyrus' (Cyrus IMAP Server) with UID 989 and GID 12.
The issue is that when the only the first file is available, systemd-sysusers has no way to know that UID 76 should not be used for saslauth user. systemd-sysusers has the general approach that the uid and gid should be the same, so when instructed to create a group with a fixed gid and a user with no gid specified, it'll use the same number for both.
The easiest way to solve this is to make make sure the full config is available to sysusers in all installation scenarios. This can be solved downstream by rearranging the config.
Unfortunately, systemd-sysusers seems to grab the uid/gid numbers immediately when reading the config, instead of reading the config first, figuring out all the fixed uids/gids, and then doing the assignments later. This makes the effect sensitive to declaration order:
$ build/systemd-sysusers --root /tmp/empty/ --dry-run --inline 'g groupa -' 'g groupb 999'
Creating group 'groupa' with GID 999.
Suggested group ID 999 for groupb already used.
Creating group 'groupb' with GID 998.
$ build/systemd-sysusers --root /tmp/empty/ --dry-run --inline 'g groupb 999' 'g groupa -'
Creating group 'groupb' with GID 999.
Creating group 'groupa' with GID 998.
In the more complicated case of saslauth/cyrus above, this works:
$ build/systemd-sysusers --root /var/tmp/inst8/ --dry-run --inline 'g saslauth 76' 'g mail 12' 'u cyrus 76:mail' 'u saslauth -'
Creating group 'saslauth' with GID 76.
Creating group 'mail' with GID 12.
Creating user 'cyrus' (n/a) with UID 76 and GID 12.
Creating user 'saslauth' (n/a) with UID 999 and GID 76.
but this doesn't:
$ build/systemd-sysusers --root /var/tmp/inst8/ --dry-run --inline 'g saslauth 76' 'g mail 12' 'u saslauth -' 'u cyrus 76:mail'
Creating group 'saslauth' with GID 76.
Creating group 'mail' with GID 12.
Creating user 'saslauth' (n/a) with UID 76 and GID 76.
Suggested user ID 76 for cyrus already used.
Creating user 'cyrus' (n/a) with UID 999 and GID 12.
Things are easier to configure and more robust if line order doesn't matter. I think we should change the code to handle this better.