Look at /etc/login.defs for the system_max_[ug]id values

It makes little sense to make the boundary between systemd and user guids
configurable. Nevertheless, a completely fixed compile-time define is not
enough in two scenarios:
- the systemd_uid_max boundary has moved over time. The default used to be
  500 for a long time. Systems which are upgraded over time might have users
  in the wrong range, but changing existing systems is complicated and
  expensive (offline disks, backups, remote systems, read-only media, etc.)
- systems are used in a heterogenous enviornment, where some vendors pick
  one value and others another.
So let's make this boundary overridable using /etc/login.defs.

Fixes #3855, #10184.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2020-09-25 16:31:42 +02:00
parent 28add648a8
commit 53393c894d
7 changed files with 175 additions and 14 deletions

View File

@ -171,10 +171,11 @@ pick — given that 64K UIDs are assigned to each container according to this
allocation logic, the maximum UID used for this range is hence
1878982656+65535=1879048191.)
Note that systemd does not make any of these values runtime-configurable. All
these boundaries are chosen during build time. That said, the system UID/GID
boundary is traditionally configured in /etc/login.defs, though systemd won't
look there during runtime.
Systemd has compile-time default for these boundaries. Using those defaults is
recommended. It will nevertheless query `/etc/login.defs` at runtime, when
compiled with `-Dcompat-mutable-uid-boundaries=true` and that file is present.
Support for this is considered only a compatibility feature and should not be
used except when upgrading systems which were creating with different defaults.
## Considerations for container managers

View File

@ -1443,6 +1443,7 @@ foreach term : ['analyze',
'idn',
'ima',
'initrd',
'compat-mutable-uid-boundaries',
'ldconfig',
'localed',
'logind',
@ -3627,6 +3628,7 @@ foreach tuple : [
['libcurl'],
['idn'],
['initrd'],
['compat-mutable-uid-boundaries'],
['libidn2'],
['libidn'],
['libiptc'],

View File

@ -28,9 +28,9 @@ option('static-libsystemd', type : 'combo',
description : '''install a static library for libsystemd''')
option('static-libudev', type : 'combo',
choices : ['false', 'true', 'pic', 'no-pic'],
description : '''install a static library for libudev''')
description : 'install a static library for libudev')
option('standalone-binaries', type : 'boolean', value : 'false',
description : '''also build standalone versions of supported binaries''')
description : 'also build standalone versions of supported binaries')
option('sysvinit-path', type : 'string', value : '/etc/init.d',
description : 'the directory where the SysV init scripts are located')
@ -40,8 +40,10 @@ option('telinit-path', type : 'string', value : '/lib/sysvinit/telinit',
description : 'path to telinit')
option('rc-local', type : 'string',
value : '/etc/rc.local')
option('initrd', type: 'boolean',
option('initrd', type : 'boolean',
description : 'install services for use when running systemd in initrd')
option('compat-mutable-uid-boundaries', type : 'boolean', value : 'false',
description : 'look at uid boundaries in /etc/login.defs for compatibility')
option('quotaon-path', type : 'string', description : 'path to quotaon')
option('quotacheck-path', type : 'string', description : 'path to quotacheck')

View File

@ -5,6 +5,8 @@
#include "cgroup-util.h"
#include "dns-domain.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "hostname-util.h"
@ -21,6 +23,100 @@
#define DEFAULT_RATELIMIT_BURST 30
#define DEFAULT_RATELIMIT_INTERVAL_USEC (1*USEC_PER_MINUTE)
#if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES
static int parse_alloc_uid(const char *path, const char *name, const char *t, uid_t *ret_uid) {
uid_t uid;
int r;
r = parse_uid(t, &uid);
if (r < 0)
return log_debug_errno(r, "%s: failed to parse %s %s, ignoring: %m", path, name, t);
if (uid == 0)
uid = 1;
*ret_uid = uid;
return 0;
}
static int read_login_defs(UGIDAllocationRange *ret_defs, const char *path) {
_cleanup_fclose_ FILE *f = NULL;
UGIDAllocationRange defs = {
.system_uid_max = SYSTEM_UID_MAX,
.system_gid_max = SYSTEM_GID_MAX,
};
int r;
if (!path)
path = "/etc/login.defs";
r = fopen_unlocked(path, "re", &f);
if (r == -ENOENT)
goto assign;
if (r < 0)
return log_debug_errno(r, "Failed to open %s: %m", path);
for (;;) {
_cleanup_free_ char *line = NULL;
char *t;
r = read_line(f, LINE_MAX, &line);
if (r < 0)
return log_debug_errno(r, "Failed to read %s: %m", path);
if (r == 0)
break;
if ((t = first_word(line, "SYS_UID_MAX")))
(void) parse_alloc_uid(path, "SYS_UID_MAX", t, &defs.system_uid_max);
else if ((t = first_word(line, "SYS_GID_MAX")))
(void) parse_alloc_uid(path, "SYS_GID_MAX", t, &defs.system_gid_max);
}
assign:
*ret_defs = defs;
return 0;
}
#endif
const UGIDAllocationRange *acquire_ugid_allocation_range(void) {
#if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES
static thread_local UGIDAllocationRange defs = {
#else
static const UGIDAllocationRange defs = {
#endif
.system_uid_max = SYSTEM_UID_MAX,
.system_gid_max = SYSTEM_GID_MAX,
};
#if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES
/* This function will ignore failure to read the file, so it should only be called from places where
* we don't crucially depend on the answer. In other words, it's appropriate for journald, but
* probably not for sysusers. */
static thread_local bool initialized = false;
if (!initialized) {
(void) read_login_defs(&defs, NULL);
initialized = true;
}
#endif
return &defs;
}
bool uid_is_system(uid_t uid) {
const UGIDAllocationRange *defs;
assert_se(defs = acquire_ugid_allocation_range());
return uid <= defs->system_uid_max;
}
bool gid_is_system(gid_t gid) {
const UGIDAllocationRange *defs;
assert_se(defs = acquire_ugid_allocation_range());
return gid <= defs->system_gid_max;
}
UserRecord* user_record_new(void) {
UserRecord *h;

View File

@ -17,13 +17,8 @@
/* The default disk size to use when nothing else is specified, relative to free disk space */
#define USER_DISK_SIZE_DEFAULT_PERCENT 85
static inline bool uid_is_system(uid_t uid) {
return uid <= SYSTEM_UID_MAX;
}
static inline bool gid_is_system(gid_t gid) {
return gid <= SYSTEM_GID_MAX;
}
bool uid_is_system(uid_t uid);
bool gid_is_system(gid_t gid);
static inline bool uid_is_dynamic(uid_t uid) {
return DYNAMIC_UID_MIN <= uid && uid <= DYNAMIC_UID_MAX;
@ -41,6 +36,13 @@ static inline bool gid_is_container(gid_t gid) {
return uid_is_container((uid_t) gid);
}
typedef struct UGIDAllocationRange {
uid_t system_uid_max;
gid_t system_gid_max;
} UGIDAllocationRange;
const UGIDAllocationRange *acquire_ugid_allocation_range(void);
typedef enum UserDisposition {
USER_INTRINSIC, /* root and nobody */
USER_SYSTEM, /* statically allocated users for system services */

View File

@ -339,6 +339,10 @@ tests += [
[],
[]],
[['src/test/test-user-record.c'],
[],
[]],
[['src/test/test-user-util.c'],
[],
[]],

View File

@ -0,0 +1,54 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <unistd.h>
#include <sys/types.h>
#include "format-util.h"
#include "tests.h"
#include "user-record.h"
static void test_acquire_ugid_allocation_range(void) {
log_info("/* %s */", __func__);
const UGIDAllocationRange *defs;
assert_se(defs = acquire_ugid_allocation_range());
log_info("system_uid_max="UID_FMT, defs->system_uid_max);
log_info("system_gid_max="GID_FMT, defs->system_gid_max);
}
static void test_uid_is_system(void) {
log_info("/* %s */", __func__);
uid_t uid = 0;
log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid)));
uid = 999;
log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid)));
uid = getuid();
log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid)));
}
static void test_gid_is_system(void) {
log_info("/* %s */", __func__);
gid_t gid = 0;
log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid)));
gid = 999;
log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid)));
gid = getgid();
log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid)));
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
test_acquire_ugid_allocation_range();
test_uid_is_system();
test_gid_is_system();
return 0;
}