sysusers: optionally create fully locked accounts (#34876)

Let's ramp up security for system user accounts, at least where
possible, by creating them fully locked (instead of just with an invalid
password). This matters when taking non-password (i.e. SSH) logins into
account.

Fixes: #13522
This commit is contained in:
Luca Boccassi 2024-10-29 18:46:14 +00:00 committed by GitHub
commit d140d478e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 55 additions and 16 deletions

View File

@ -93,9 +93,9 @@ r - lowest-highest</programlisting>
field description, home directory, and login shell:</para>
<programlisting>#Type Name ID GECOS Home directory Shell
u httpd 404 "HTTP User"
u _authd /usr/bin/authd "Authorization user"
u postgres - "Postgresql Database" /var/lib/pgsql /usr/libexec/postgresdb
u! httpd 404 "HTTP User"
u! _authd /usr/bin/authd "Authorization user"
u! postgres - "Postgresql Database" /var/lib/pgsql /usr/libexec/postgresdb
g input - -
m _authd input
u root 0 "Superuser" /root /bin/zsh
@ -119,6 +119,12 @@ r - 500-900
bearing the same name unless the ID field specifies it. The account will be
created disabled, so that logins are not allowed.</para>
<para>Type <varname>u</varname> may be suffixed with an exclamation mark (<literal>u!</literal>) to
create a fully locked account. This is recommended, since logins should typically not be allowed
for system users. With or without the exclamation mark an invalid password is set. For
<literal>u!</literal>, the account is also locked, which makes a difference for non-password forms
of authentication, such as SSH or similar.</para>
<xi:include href="version-info.xml" xpointer="v215"/></listitem>
</varlistentry>

View File

@ -8,7 +8,7 @@ Distribution=|fedora
Environment=
GIT_URL=https://src.fedoraproject.org/rpms/systemd.git
GIT_BRANCH=rawhide
GIT_COMMIT=a67221c3f0d0b81b9b5b3230a71d09044342f1a4
GIT_COMMIT=e42eed4afd6267cd954d393d8eec79e0e7573de0
PKG_SUBDIR=fedora
[Content]

View File

@ -86,6 +86,8 @@ typedef struct Item {
bool uid_set;
bool locked;
bool todo_user;
bool todo_group;
} Item;
@ -654,7 +656,7 @@ static int write_temporary_shadow(
.sp_max = -1,
.sp_warn = -1,
.sp_inact = -1,
.sp_expire = -1,
.sp_expire = i->locked ? 1 : -1, /* Negative expiration means "unset". Expiration 0 or 1 means "locked" */
.sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
};
@ -1707,10 +1709,17 @@ static int parse_line(
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Trailing garbage.");
/* Verify action */
if (strlen(action) != 1)
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Unknown modifier '%s'.", action);
if (isempty(action))
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
"Empty command specification.");
bool locked = false;
for (int pos = 1; action[pos]; pos++)
if (action[pos] == '!' && !locked)
locked = true;
else
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
"Unknown modifiers in command '%s'.", action);
if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE))
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
@ -1793,6 +1802,10 @@ static int parse_line(
switch (action[0]) {
case ADD_RANGE:
if (locked)
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Flag '!' not permitted on lines of type 'r'.");
if (resolved_name)
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Lines of type 'r' don't take a name field.");
@ -1820,6 +1833,10 @@ static int parse_line(
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Lines of type 'm' require a user name in the second field.");
if (locked)
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Flag '!' not permitted on lines of type 'm'.");
if (!resolved_id)
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Lines of type 'm' require a group name in the third field.");
@ -1886,6 +1903,7 @@ static int parse_line(
i->description = TAKE_PTR(resolved_description);
i->home = TAKE_PTR(resolved_home);
i->shell = TAKE_PTR(resolved_shell);
i->locked = locked;
h = c->users;
break;
@ -1895,6 +1913,10 @@ static int parse_line(
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Lines of type 'g' require a user name in the second field.");
if (locked)
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Flag '!' not permitted on lines of type 'g'.");
if (description || home || shell)
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
"Lines of type '%c' don't take a %s field.",

View File

@ -11,7 +11,7 @@ u root 0:0 "Super User" /root
# The nobody user/group for NFS file systems
g {{NOBODY_GROUP_NAME}} 65534 - -
u {{NOBODY_USER_NAME }} 65534:65534 "Kernel Overflow User" -
u! {{NOBODY_USER_NAME }} 65534:65534 "Kernel Overflow User" -
# Administrator group: can *see* more than normal users
g adm {{ADM_GID }} - -

View File

@ -5,4 +5,4 @@
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
u systemd-coredump - "systemd Core Dumper"
u! systemd-coredump - "systemd Core Dumper"

View File

@ -5,4 +5,4 @@
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
u systemd-network {{SYSTEMD_NETWORK_UID}} "systemd Network Management"
u! systemd-network {{SYSTEMD_NETWORK_UID}} "systemd Network Management"

View File

@ -5,4 +5,4 @@
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
u systemd-oom - "systemd Userspace OOM Killer"
u! systemd-oom - "systemd Userspace OOM Killer"

View File

@ -5,4 +5,4 @@
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
u systemd-journal-remote - "systemd Journal Remote"
u! systemd-journal-remote - "systemd Journal Remote"

View File

@ -5,4 +5,4 @@
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
u systemd-resolve {{SYSTEMD_RESOLVE_UID}} "systemd Resolver"
u! systemd-resolve {{SYSTEMD_RESOLVE_UID}} "systemd Resolver"

View File

@ -5,4 +5,4 @@
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
u systemd-timesync {{SYSTEMD_TIMESYNC_UID}} "systemd Time Synchronization"
u! systemd-timesync {{SYSTEMD_TIMESYNC_UID}} "systemd Time Synchronization"

View File

@ -6,6 +6,17 @@ set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
systemd-sysusers - <<EOF
u unlockedtestuser - "An unlocked system user" / /bin/bash
u! lockedtestuser - "A locked system user" / /bin/bash
EOF
userdbctl -j user unlockedtestuser
userdbctl -j user lockedtestuser
assert_eq "$(userdbctl -j user unlockedtestuser | jq .locked)" "null"
assert_eq "$(userdbctl -j user lockedtestuser | jq .locked)" "true"
at_exit() {
set +e
userdel -r foobarbaz