Merge pull request #17172 from keszybz/read-login-defs

Read /etc/login.defs
This commit is contained in:
Lennart Poettering 2020-10-02 11:01:30 +02:00 committed by GitHub
commit c14ebe07a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 621 additions and 235 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

@ -299,6 +299,7 @@ substs.set('CERTIFICATEROOT', get_option('certif
substs.set('RANDOM_SEED', join_paths(randomseeddir, 'random-seed'))
substs.set('SYSTEM_SYSVINIT_PATH', sysvinit_path)
substs.set('SYSTEM_SYSVRCND_PATH', sysvrcnd_path)
substs.set('SYSTEMD_TEST_DATA', join_paths(testsdir, 'testdata'))
substs.set('RC_LOCAL_PATH', get_option('rc-local'))
substs.set('MEMORY_ACCOUNTING_DEFAULT', memory_accounting_default ? 'yes' : 'no')
substs.set('STATUS_UNIT_FORMAT_DEFAULT', status_unit_format_default)
@ -694,35 +695,31 @@ if time_epoch == -1
endif
conf.set('TIME_EPOCH', time_epoch)
system_uid_max = get_option('system-uid-max')
if system_uid_max == -1
system_uid_max = run_command(
awk,
'/^\s*SYS_UID_MAX\s+/ { uid=$2 } END { print uid }',
'/etc/login.defs').stdout().strip()
if system_uid_max == ''
system_uid_max = 999
else
system_uid_max = system_uid_max.to_int()
foreach tuple : [['system-alloc-uid-min', 'SYS_UID_MIN', 1], # Also see login.defs(5).
['system-uid-max', 'SYS_UID_MAX', 999],
['system-alloc-gid-min', 'SYS_GID_MIN', 1],
['system-gid-max', 'SYS_GID_MAX', 999]]
v = get_option(tuple[0])
if v == -1
v = run_command(
awk,
'/^\s*@0@\s+/ { uid=$2 } END { print uid }'.format(tuple[1]),
'/etc/login.defs').stdout().strip()
if v == ''
v = tuple[2]
else
v = v.to_int()
endif
endif
conf.set(tuple[0].underscorify().to_upper(), v)
substs.set(tuple[0].underscorify().to_upper(), v)
endforeach
if conf.get('SYSTEM_ALLOC_UID_MIN') >= conf.get('SYSTEM_UID_MAX')
error('Invalid uid allocation range')
endif
conf.set('SYSTEM_UID_MAX', system_uid_max)
substs.set('systemuidmax', system_uid_max)
system_gid_max = get_option('system-gid-max')
if system_gid_max == -1
system_gid_max = run_command(
awk,
'/^\s*SYS_GID_MAX\s+/ { gid=$2 } END { print gid }',
'/etc/login.defs').stdout().strip()
if system_gid_max == ''
system_gid_max = 999
else
system_gid_max = system_gid_max.to_int()
endif
if conf.get('SYSTEM_ALLOC_GID_MIN') >= conf.get('SYSTEM_GID_MAX')
error('Invalid gid allocation range')
endif
conf.set('SYSTEM_GID_MAX', system_gid_max)
substs.set('systemgidmax', system_gid_max)
dynamic_uid_min = get_option('dynamic-uid-min')
dynamic_uid_max = get_option('dynamic-uid-max')
@ -1444,6 +1441,7 @@ foreach term : ['analyze',
'idn',
'ima',
'initrd',
'compat-mutable-uid-boundaries',
'ldconfig',
'localed',
'logind',
@ -1470,8 +1468,11 @@ foreach term : ['analyze',
have = get_option(term)
name = 'ENABLE_' + term.underscorify().to_upper()
conf.set10(name, have)
substs.set10(name, have)
endforeach
enable_sysusers = conf.get('ENABLE_SYSUSERS') == 1
foreach tuple : [['nss-mymachines', 'machined'],
['nss-resolve', 'resolve']]
want = get_option(tuple[0])
@ -2966,8 +2967,8 @@ public_programs += executable(
install_rpath : rootlibexecdir,
install : true)
if conf.get('ENABLE_SYSUSERS') == 1
public_programs += executable(
if enable_sysusers
exe = executable(
'systemd-sysusers',
'src/sysusers/sysusers.c',
include_directories : includes,
@ -2975,9 +2976,17 @@ if conf.get('ENABLE_SYSUSERS') == 1
install_rpath : rootlibexecdir,
install : true,
install_dir : rootbindir)
public_programs += exe
if want_tests != 'false'
test('test-sysusers',
test_sysusers_sh,
# https://github.com/mesonbuild/meson/issues/2681
args : exe.full_path())
endif
if have_standalone_binaries
public_programs += executable(
exe = executable(
'systemd-sysusers.standalone',
'src/sysusers/sysusers.c',
include_directories : includes,
@ -2988,6 +2997,14 @@ if conf.get('ENABLE_SYSUSERS') == 1
libjournal_client],
install : true,
install_dir : rootbindir)
public_programs += exe
if want_tests != 'false'
test('test-sysusers.standalone',
test_sysusers_sh,
# https://github.com/mesonbuild/meson/issues/2681
args : exe.full_path())
endif
endif
endif
@ -3539,12 +3556,12 @@ status = [
get_option('debug-tty')),
'TTY GID: @0@'.format(tty_gid),
'users GID: @0@'.format(substs.get('USERS_GID')),
'maximum system UID: @0@'.format(system_uid_max),
'maximum system GID: @0@'.format(system_gid_max),
'minimum dynamic UID: @0@'.format(dynamic_uid_min),
'maximum dynamic UID: @0@'.format(dynamic_uid_max),
'minimum container UID base: @0@'.format(container_uid_base_min),
'maximum container UID base: @0@'.format(container_uid_base_max),
'system UIDs: <=@0@ (alloc >=@1@)'.format(conf.get('SYSTEM_UID_MAX'),
conf.get('SYSTEM_ALLOC_UID_MIN')),
'system GIDs: <=@0@ (alloc >=@1@)'.format(conf.get('SYSTEM_GID_MAX'),
conf.get('SYSTEM_ALLOC_GID_MIN')),
'dynamic UIDs: @0@@1@'.format(dynamic_uid_min, dynamic_uid_max),
'container UID bases: @0@@1@'.format(container_uid_base_min, container_uid_base_max),
'/dev/kvm access mode: @0@'.format(get_option('dev-kvm-mode')),
'render group access mode: @0@'.format(get_option('group-render-mode')),
'certificate root directory: @0@'.format(get_option('certificate-root')),
@ -3628,6 +3645,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')
@ -192,6 +194,10 @@ option('status-unit-format-default', type : 'combo',
description : 'use unit name or description in messages by default')
option('time-epoch', type : 'integer', value : '-1',
description : 'time epoch for time clients')
option('system-alloc-uid-min', type : 'integer', value : '-1',
description : 'minimum system UID used when allocating')
option('system-alloc-gid-min', type : 'integer', value : '-1',
description : 'minimum system GID used when allocating')
option('system-uid-max', type : 'integer', value : '-1',
description : 'maximum system UID')
option('system-gid-max', type : 'integer', value : '-1',

View File

@ -940,6 +940,42 @@ int search_and_fopen_nulstr(const char *path, const char *mode, const char *root
return search_and_fopen_internal(path, mode, root, s, _f);
}
int chase_symlinks_and_fopen_unlocked(
const char *path,
const char *root,
unsigned chase_flags,
const char *open_flags,
FILE **ret_file,
char **ret_path) {
_cleanup_close_ int fd = -1;
_cleanup_free_ char *final_path = NULL;
int mode_flags, r;
FILE *f;
assert(path);
assert(open_flags);
assert(ret_file);
mode_flags = mode_to_flags(open_flags);
if (mode_flags < 0)
return mode_flags;
fd = chase_symlinks_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
if (fd < 0)
return fd;
r = fdopen_unlocked(fd, open_flags, &f);
if (r < 0)
return r;
TAKE_FD(fd);
*ret_file = f;
if (ret_path)
*ret_path = TAKE_PTR(final_path);
return 0;
}
int fflush_and_check(FILE *f) {
assert(f);

View File

@ -81,6 +81,14 @@ int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **r
int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f);
int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f);
int chase_symlinks_and_fopen_unlocked(
const char *path,
const char *root,
unsigned chase_flags,
const char *open_flags,
FILE **ret_file,
char **ret_path);
int fflush_and_check(FILE *f);
int fflush_sync_and_check(FILE *f);

View File

@ -81,7 +81,7 @@ enum {
CHASE_PREFIX_ROOT = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */
CHASE_NONEXISTENT = 1 << 1, /* It's OK if the path doesn't actually exist. */
CHASE_NO_AUTOFS = 1 << 2, /* Return -EREMOTE if autofs mount point found */
CHASE_SAFE = 1 << 3, /* Return EPERM if we ever traverse from unprivileged to privileged files or directories */
CHASE_SAFE = 1 << 3, /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */
CHASE_TRAIL_SLASH = 1 << 4, /* Any trailing slash will be preserved */
CHASE_STEP = 1 << 5, /* Just execute a single step of the normalization */
CHASE_NOFOLLOW = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's

View File

@ -3,7 +3,7 @@
#include <grp.h>
#if ENABLE_GSHADOW
#include <gshadow.h>
# include <gshadow.h>
#endif
#include <pwd.h>
#include <shadow.h>
@ -61,30 +61,6 @@ int take_etc_passwd_lock(const char *root);
#define ETC_PASSWD_LOCK_PATH "/etc/.pwd.lock"
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;
}
static inline bool uid_is_dynamic(uid_t uid) {
return DYNAMIC_UID_MIN <= uid && uid <= DYNAMIC_UID_MAX;
}
static inline bool gid_is_dynamic(gid_t gid) {
return uid_is_dynamic((uid_t) gid);
}
static inline bool uid_is_container(uid_t uid) {
return CONTAINER_UID_BASE_MIN <= uid && uid <= CONTAINER_UID_BASE_MAX;
}
static inline bool gid_is_container(gid_t gid) {
return uid_is_container((uid_t) gid);
}
/* The following macros add 1 when converting things, since UID 0 is a valid UID, while the pointer
* NULL is special */
#define PTR_TO_UID(p) ((uid_t) (((uintptr_t) (p))-1))

View File

@ -19,6 +19,7 @@
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
#include "user-record.h"
#include "user-util.h"
/* Takes a value generated randomly or by hashing and turns it into a UID in the right range */

View File

@ -80,9 +80,9 @@ modulesloaddir=${modules_load_dir}
catalog_dir=/usr/lib/systemd/catalog
catalogdir=${catalog_dir}
system_uid_max=@systemuidmax@
system_uid_max=@SYSTEM_UID_MAX@
systemuidmax=${system_uid_max}
system_gid_max=@systemgidmax@
system_gid_max=@SYSTEM_GID_MAX@
systemgidmax=${system_gid_max}
dynamic_uid_min=@dynamicuidmin@

View File

@ -46,6 +46,7 @@
#include "string-util.h"
#include "strv.h"
#include "tmpfile-util.h"
#include "user-record.h"
#include "user-util.h"
/* The maximum size up to which we process coredumps */
@ -682,7 +683,7 @@ static int change_uid_gid(const Context *context) {
if (r < 0)
return r;
if (uid <= SYSTEM_UID_MAX) {
if (uid_is_system(uid)) {
const char *user = "systemd-coredump";
r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0);

View File

@ -55,6 +55,7 @@
#include "string-table.h"
#include "string-util.h"
#include "syslog-util.h"
#include "user-record.h"
#include "user-util.h"
#define USER_JOURNALS_MAX 1024

View File

@ -6,7 +6,7 @@ set -ex
repart=$1
test -x $repart
D=$(mktemp --directory)
D=$(mktemp --tmpdir --directory "test-repart.XXXXXXXXXX")
trap "rm -rf '$D'" EXIT INT QUIT PIPE
mkdir -p $D/definitions

View File

@ -43,6 +43,7 @@
#include "string-table.h"
#include "string-util.h"
#include "tomoyo-util.h"
#include "user-record.h"
#include "user-util.h"
#include "util.h"
#include "virt.h"

View File

@ -18,13 +18,11 @@ static bool uid_range_intersect(UidRange *range, uid_t start, uid_t nr) {
}
static void uid_range_coalesce(UidRange **p, unsigned *n) {
unsigned i, j;
assert(p);
assert(n);
for (i = 0; i < *n; i++) {
for (j = i + 1; j < *n; j++) {
for (unsigned i = 0; i < *n; i++) {
for (unsigned j = i + 1; j < *n; j++) {
UidRange *x = (*p)+i, *y = (*p)+j;
if (uid_range_intersect(x, y->start, y->nr)) {
@ -59,7 +57,6 @@ static int uid_range_compare(const UidRange *a, const UidRange *b) {
int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr) {
bool found = false;
UidRange *x;
unsigned i;
assert(p);
assert(n);
@ -67,7 +64,7 @@ int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr) {
if (nr <= 0)
return 0;
for (i = 0; i < *n; i++) {
for (unsigned i = 0; i < *n; i++) {
x = (*p) + i;
if (uid_range_intersect(x, start, nr)) {
found = true;
@ -143,14 +140,13 @@ int uid_range_add_str(UidRange **p, unsigned *n, const char *s) {
int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid) {
uid_t closest = UID_INVALID, candidate;
unsigned i;
assert(p);
assert(uid);
candidate = *uid - 1;
for (i = 0; i < n; i++) {
for (unsigned i = 0; i < n; i++) {
uid_t begin, end;
begin = p[i].start;
@ -173,12 +169,10 @@ int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid) {
}
bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid) {
unsigned i;
assert(p);
assert(uid);
for (i = 0; i < n; i++)
for (unsigned i = 0; i < n; i++)
if (uid >= p[i].start && uid < p[i].start + p[i].nr)
return true;

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,122 @@
#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;
}
#endif
int read_login_defs(UGIDAllocationRange *ret_defs, const char *path, const char *root) {
UGIDAllocationRange defs = {
.system_alloc_uid_min = SYSTEM_ALLOC_UID_MIN,
.system_uid_max = SYSTEM_UID_MAX,
.system_alloc_gid_min = SYSTEM_ALLOC_GID_MIN,
.system_gid_max = SYSTEM_GID_MAX,
};
#if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES
_cleanup_fclose_ FILE *f = NULL;
int r;
if (!path)
path = "/etc/login.defs";
r = chase_symlinks_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT, "re", &f, NULL);
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_MIN")))
(void) parse_alloc_uid(path, "SYS_UID_MIN", t, &defs.system_alloc_uid_min);
else 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_MIN")))
(void) parse_alloc_uid(path, "SYS_GID_MIN", t, &defs.system_alloc_gid_min);
else if ((t = first_word(line, "SYS_GID_MAX")))
(void) parse_alloc_uid(path, "SYS_GID_MAX", t, &defs.system_gid_max);
}
assign:
if (defs.system_alloc_uid_min > defs.system_uid_max) {
log_debug("%s: SYS_UID_MIN > SYS_UID_MAX, resetting.", path);
defs.system_alloc_uid_min = MIN(defs.system_uid_max - 1, (uid_t) SYSTEM_ALLOC_UID_MIN);
/* Look at sys_uid_max to make sure sys_uid_min..sys_uid_max remains a valid range. */
}
if (defs.system_alloc_gid_min > defs.system_gid_max) {
log_debug("%s: SYS_GID_MIN > SYS_GID_MAX, resetting.", path);
defs.system_alloc_gid_min = MIN(defs.system_gid_max - 1, (gid_t) SYSTEM_ALLOC_GID_MIN);
/* Look at sys_gid_max to make sure sys_gid_min..sys_gid_max remains a valid range. */
}
#endif
*ret_defs = defs;
return 0;
}
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_alloc_uid_min = SYSTEM_ALLOC_UID_MIN,
.system_uid_max = SYSTEM_UID_MAX,
.system_alloc_gid_min = SYSTEM_ALLOC_GID_MIN,
.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, 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,6 +17,35 @@
/* The default disk size to use when nothing else is specified, relative to free disk space */
#define USER_DISK_SIZE_DEFAULT_PERCENT 85
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;
}
static inline bool gid_is_dynamic(gid_t gid) {
return uid_is_dynamic((uid_t) gid);
}
static inline bool uid_is_container(uid_t uid) {
return CONTAINER_UID_BASE_MIN <= uid && uid <= CONTAINER_UID_BASE_MAX;
}
static inline bool gid_is_container(gid_t gid) {
return uid_is_container((uid_t) gid);
}
typedef struct UGIDAllocationRange {
uid_t system_alloc_uid_min;
uid_t system_uid_max;
gid_t system_alloc_gid_min;
gid_t system_gid_max;
} UGIDAllocationRange;
int read_login_defs(UGIDAllocationRange *ret_defs, const char *path, const char *root);
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

@ -26,6 +26,7 @@
#include "strv.h"
#include "tmpfile-util-label.h"
#include "uid-range.h"
#include "user-record.h"
#include "user-util.h"
#include "utf8.h"
#include "util.h"
@ -83,6 +84,9 @@ static uid_t search_uid = UID_INVALID;
static UidRange *uid_range = NULL;
static unsigned n_uid_range = 0;
static UGIDAllocationRange login_defs = {};
static bool login_defs_need_warning = false;
STATIC_DESTRUCTOR_REGISTER(groups, ordered_hashmap_freep);
STATIC_DESTRUCTOR_REGISTER(users, ordered_hashmap_freep);
STATIC_DESTRUCTOR_REGISTER(members, ordered_hashmap_freep);
@ -104,6 +108,26 @@ static int errno_is_not_exists(int code) {
return IN_SET(code, 0, ENOENT, ESRCH, EBADF, EPERM);
}
static void maybe_emit_login_defs_warning(void) {
if (!login_defs_need_warning)
return;
if (login_defs.system_alloc_uid_min != SYSTEM_ALLOC_UID_MIN ||
login_defs.system_uid_max != SYSTEM_UID_MAX)
log_warning("login.defs specifies UID allocation range "UID_FMT""UID_FMT
" that is different than the built-in defaults ("UID_FMT""UID_FMT")",
login_defs.system_alloc_uid_min, login_defs.system_uid_max,
SYSTEM_ALLOC_UID_MIN, SYSTEM_UID_MAX);
if (login_defs.system_alloc_gid_min != SYSTEM_ALLOC_GID_MIN ||
login_defs.system_gid_max != SYSTEM_GID_MAX)
log_warning("login.defs specifies GID allocation range "GID_FMT""GID_FMT
" that is different than the built-in defaults ("GID_FMT""GID_FMT")",
login_defs.system_alloc_gid_min, login_defs.system_gid_max,
SYSTEM_ALLOC_GID_MIN, SYSTEM_GID_MAX);
login_defs_need_warning = false;
}
static int load_user_database(void) {
_cleanup_fclose_ FILE *f = NULL;
const char *passwd_path;
@ -1001,6 +1025,8 @@ static int add_user(Item *i) {
/* And if that didn't work either, let's try to find a free one */
if (!i->uid_set) {
maybe_emit_login_defs_warning();
for (;;) {
r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
if (r < 0)
@ -1167,6 +1193,8 @@ static int add_group(Item *i) {
/* And if that didn't work either, let's try to find a free one */
if (!i->gid_set) {
maybe_emit_login_defs_warning();
for (;;) {
/* We look for new GIDs in the UID pool! */
r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
@ -1949,10 +1977,25 @@ static int run(int argc, char *argv[]) {
return log_error_errno(errno, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
if (!uid_range) {
/* Default to default range of 1..SYSTEM_UID_MAX */
r = uid_range_add(&uid_range, &n_uid_range, 1, SYSTEM_UID_MAX);
/* Default to default range of SYSTEMD_UID_MIN..SYSTEM_UID_MAX. */
r = read_login_defs(&login_defs, NULL, arg_root);
if (r < 0)
return log_oom();
return log_error_errno(r, "Failed to read %s%s: %m",
strempty(arg_root), "/etc/login.defs");
login_defs_need_warning = true;
/* We pick a range that very conservative: we look at compiled-in maximum and the value in
* /etc/login.defs. That way the uids/gids which we allocate will be interpreted correctly,
* even if /etc/login.defs is removed later. (The bottom bound doesn't matter much, since
* it's only used during allocation, so we use the configured value directly). */
uid_t begin = login_defs.system_alloc_uid_min,
end = MIN3((uid_t) SYSTEM_UID_MAX, login_defs.system_uid_max, login_defs.system_gid_max);
if (begin < end) {
r = uid_range_add(&uid_range, &n_uid_range, begin, end - begin + 1);
if (r < 0)
return log_oom();
}
}
r = add_implicit();

View File

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

View File

@ -31,6 +31,7 @@
#include "strv.h"
#include "tests.h"
#include "tomoyo-util.h"
#include "user-record.h"
#include "user-util.h"
#include "virt.h"

104
src/test/test-user-record.c Normal file
View File

@ -0,0 +1,104 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <unistd.h>
#include <sys/types.h>
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
#include "tmpfile-util.h"
#include "tests.h"
#include "user-record.h"
static void test_read_login_defs(const char *path) {
log_info("/* %s(\"%s\") */", __func__, path ?: "<custom>");
_cleanup_(unlink_tempfilep) char name[] = "/tmp/test-user-record.XXXXXX";
_cleanup_fclose_ FILE *f = NULL;
if (!path) {
assert_se(fmkostemp_safe(name, "r+", &f) == 0);
fprintf(f,
"SYS_UID_MIN "UID_FMT"\n"
"SYS_UID_MAX "UID_FMT"\n"
"SYS_GID_MIN "GID_FMT"\n"
"SYS_GID_MAX "GID_FMT"\n",
SYSTEM_ALLOC_UID_MIN + 5,
SYSTEM_UID_MAX + 5,
SYSTEM_ALLOC_GID_MIN + 5,
SYSTEM_GID_MAX + 5);
assert_se(fflush_and_check(f) >= 0);
}
UGIDAllocationRange defs;
assert_se(read_login_defs(&defs, path ?: name, NULL) >= 0);
log_info("system_alloc_uid_min="UID_FMT, defs.system_alloc_uid_min);
log_info("system_uid_max="UID_FMT, defs.system_uid_max);
log_info("system_alloc_gid_min="GID_FMT, defs.system_alloc_gid_min);
log_info("system_gid_max="GID_FMT, defs.system_gid_max);
if (!path) {
uid_t offset = ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES ? 5 : 0;
assert_se(defs.system_alloc_uid_min == SYSTEM_ALLOC_UID_MIN + offset);
assert_se(defs.system_uid_max == SYSTEM_UID_MAX + offset);
assert_se(defs.system_alloc_gid_min == SYSTEM_ALLOC_GID_MIN + offset);
assert_se(defs.system_gid_max == SYSTEM_GID_MAX + offset);
} else if (streq(path, "/dev/null")) {
assert_se(defs.system_alloc_uid_min == SYSTEM_ALLOC_UID_MIN);
assert_se(defs.system_uid_max == SYSTEM_UID_MAX);
assert_se(defs.system_alloc_gid_min == SYSTEM_ALLOC_GID_MIN);
assert_se(defs.system_gid_max == SYSTEM_GID_MAX);
}
}
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_alloc_uid_min="UID_FMT, defs->system_alloc_uid_min);
log_info("system_uid_max="UID_FMT, defs->system_uid_max);
log_info("system_alloc_gid_min="GID_FMT, defs->system_alloc_gid_min);
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_read_login_defs("/dev/null");
test_read_login_defs("/etc/login.defs");
test_read_login_defs(NULL);
test_acquire_ugid_allocation_range();
test_uid_is_system();
test_gid_is_system();
return 0;
}

View File

@ -2,8 +2,6 @@
in_files = ['basic.conf']
enable_sysusers = conf.get('ENABLE_SYSUSERS') == 1
foreach file : in_files
gen = configure_file(
input : file + '.in',

View File

@ -1 +0,0 @@
../TEST-01-BASIC/Makefile

View File

@ -1,2 +0,0 @@
u1:x:300:u2
u2:x:SYSTEM_UID_MAX:

View File

@ -1,2 +0,0 @@
u1:x:300:300::/:NOLOGIN
u2:x:SYSTEM_UID_MAX:SYSTEM_UID_MAX::/:NOLOGIN

View File

@ -1 +0,0 @@
aaa:x:SYSTEM_UID_MAX:987::/:NOLOGIN

View File

@ -1,2 +0,0 @@
g1:x:111:
u1:x:SYSTEM_UID_MAX:

View File

@ -1 +0,0 @@
u1:x:SYSTEM_UID_MAX:SYSTEM_UID_MAX::/:NOLOGIN

View File

@ -1 +0,0 @@
username:x:SYSTEM_UID_MAX:300::/:NOLOGIN

View File

@ -1,2 +0,0 @@
user1:x:300:300::/:NOLOGIN
user2:x:SYSTEM_UID_MAX:300::/:NOLOGIN

View File

@ -1,128 +0,0 @@
#!/usr/bin/env bash
set -e
TEST_DESCRIPTION="Sysuser-related tests"
IMAGE_NAME="sysusers"
. $TEST_BASE_DIR/test-functions
test_setup() {
mkdir -p $TESTDIR/etc/sysusers.d $TESTDIR/usr/lib/sysusers.d $TESTDIR/tmp
}
prepare_testdir() {
rm -f $TESTDIR/etc/*{passwd,group,shadow}
for i in $1.initial-{passwd,group,shadow}; do
test -f $i && cp $i $TESTDIR/etc/${i#*.initial-}
done
return 0
}
preprocess() {
in="$1"
# see meson.build how to extract this. gcc -E was used before to
# get this value from config.h, however the autopkgtest fails with
# it
[[ -e /etc/login.defs ]] && login_defs_file="/etc/login.defs" || login_defs_file="/usr/etc/login.defs"
SYSTEM_UID_MAX=$(awk 'BEGIN { uid=999 } /^\s*SYS_UID_MAX\s+/ { uid=$2 } END { print uid }' $login_defs_file)
SYSTEM_GID_MAX=$(awk 'BEGIN { gid=999 } /^\s*SYS_GID_MAX\s+/ { gid=$2 } END { print gid }' $login_defs_file)
# we can't rely on config.h to get the nologin path, as autopkgtest
# uses pre-compiled binaries, so extract it from the systemd-sysusers
# binary which we are about to execute
NOLOGIN=$(strings $(type -p systemd-sysusers) | grep nologin)
sed -e "s/SYSTEM_UID_MAX/${SYSTEM_UID_MAX}/g" \
-e "s/SYSTEM_GID_MAX/${SYSTEM_GID_MAX}/g" \
-e "s#NOLOGIN#${NOLOGIN}#g" "$in"
}
compare() {
if ! diff -u $TESTDIR/etc/passwd <(preprocess ${1%.*}.expected-passwd); then
echo "**** Unexpected output for $f"
exit 1
fi
if ! diff -u $TESTDIR/etc/group <(preprocess ${1%.*}.expected-group); then
echo "**** Unexpected output for $f $2"
exit 1
fi
}
test_run() {
# ensure our build of systemd-sysusers is run
PATH=${BUILD_DIR}:$PATH
rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
# happy tests
for f in test-*.input; do
echo "*** Running $f"
prepare_testdir ${f%.input}
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
systemd-sysusers --root=$TESTDIR
compare $f ""
done
for f in test-*.input; do
echo "*** Running $f on stdin"
prepare_testdir ${f%.input}
touch $TESTDIR/etc/sysusers.d/test.conf
cat $f | systemd-sysusers --root=$TESTDIR -
compare $f "on stdin"
done
for f in test-*.input; do
echo "*** Running $f on stdin with --replace"
prepare_testdir ${f%.input}
touch $TESTDIR/etc/sysusers.d/test.conf
# this overrides test.conf which is masked on disk
cat $f | systemd-sysusers --root=$TESTDIR --replace=/etc/sysusers.d/test.conf -
# this should be ignored
cat test-1.input | systemd-sysusers --root=$TESTDIR --replace=/usr/lib/sysusers.d/test.conf -
compare $f "on stdin with --replace"
done
# test --inline
echo "*** Testing --inline"
prepare_testdir
# copy a random file to make sure it is ignored
cp $f $TESTDIR/etc/sysusers.d/confuse.conf
systemd-sysusers --root=$TESTDIR --inline \
"u u1 222 - - /bin/zsh" \
"g g1 111"
compare inline "(--inline)"
# test --replace
echo "*** Testing --inline with --replace"
prepare_testdir
# copy a random file to make sure it is ignored
cp $f $TESTDIR/etc/sysusers.d/confuse.conf
systemd-sysusers --root=$TESTDIR \
--inline \
--replace=/etc/sysusers.d/confuse.conf \
"u u1 222 - - /bin/zsh" \
"g g1 111"
compare inline "(--inline --replace=…)"
rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
# tests for error conditions
for f in unhappy-*.input; do
echo "*** Running test $f"
prepare_testdir ${f%.input}
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
systemd-sysusers --root=$TESTDIR 2>&1 | tail -n1 > $TESTDIR/tmp/err
if ! diff -u $TESTDIR/tmp/err ${f%.*}.expected-err; then
echo "**** Unexpected error output for $f"
cat $TESTDIR/tmp/err
exit 1
fi
done
}
do_test "$@"

View File

@ -18,7 +18,7 @@ if [ ! -x "$SYSTEMD_HWDB" ]; then
exit 1
fi
D=$(mktemp --directory)
D=$(mktemp --tmpdir --directory "hwdb-test.XXXXXXXXXX")
trap "rm -rf '$D'" EXIT INT QUIT PIPE
mkdir -p "$D/etc/udev"
ln -s "$ROOTDIR/hwdb.d" "$D/etc/udev/hwdb.d"

View File

@ -58,6 +58,21 @@ test_network_generator_conversion_sh = find_program('test-network-generator-conv
############################################################
test_sysusers_dir = join_paths(meson.current_source_dir(), 'test-sysusers')
test_sysusers_sh = configure_file(
input : 'test-sysusers.sh.in',
output : 'test-sysusers.sh',
configuration : substs)
if install_tests and conf.get('ENABLE_SYSUSERS') == 1
install_data(test_sysusers_sh,
install_dir : testsdir)
install_subdir('test-sysusers',
install_dir : testdata_dir)
endif
############################################################
rule_syntax_check_py = find_program('rule-syntax-check.py')
if want_tests != 'false'
test('rule-syntax-check',

View File

@ -17,7 +17,7 @@ for f in "$src"/test-*.input; do
echo "*** Running $f"
(
out=$(mktemp --directory)
out=$(mktemp --tmpdir --directory "test-network-generator-conversion.XXXXXXXXXX")
trap "rm -rf '$out'" EXIT INT QUIT PIPE
$generator --root "$out" -- $(cat $f)

160
test/test-sysusers.sh.in Executable file
View File

@ -0,0 +1,160 @@
#!/usr/bin/env bash
set -e
SYSUSERS="${1:-systemd-sysusers}"
[ -e "$(dirname $0)/../systemd-runtest.env" ] && . "$(dirname $0)/../systemd-runtest.env"
SYSTEMD_TEST_DATA=${SYSTEMD_TEST_DATA:-@SYSTEMD_TEST_DATA@}
SOURCE=$SYSTEMD_TEST_DATA/test-sysusers
TESTDIR=$(mktemp --tmpdir --directory "test-sysusers.XXXXXXXXXX")
trap "rm -rf '$TESTDIR'" EXIT INT QUIT PIPE
prepare_testdir() {
mkdir -p $TESTDIR/etc/sysusers.d/
mkdir -p $TESTDIR/usr/lib/sysusers.d/
rm -f $TESTDIR/etc/*{passwd,group,shadow}
for i in $1.initial-{passwd,group,shadow}; do
test -f $i && cp $i $TESTDIR/etc/${i#*.initial-}
done
return 0
}
[ @SYSTEM_UID_MAX@ -lt @SYSTEM_GID_MAX@ ] && system_guid_max=@SYSTEM_UID_MAX@ || system_guid_max=@SYSTEM_GID_MAX@
preprocess() {
m=${2:-$system_guid_max}
sed -e "s/SYSTEM_UGID_MAX/$m/g;
s#NOLOGIN#@NOLOGIN@#g" "$1"
}
compare() {
if ! diff -u $TESTDIR/etc/passwd <(preprocess $1.expected-passwd $3); then
echo "**** Unexpected output for $f $2"
exit 1
fi
if ! diff -u $TESTDIR/etc/group <(preprocess $1.expected-group $3); then
echo "**** Unexpected output for $f $2"
exit 1
fi
}
rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
# happy tests
for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
echo "*** Running $f"
prepare_testdir ${f%.input}
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
$SYSUSERS --root=$TESTDIR
compare ${f%.*} ""
done
for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
echo "*** Running $f on stdin"
prepare_testdir ${f%.input}
touch $TESTDIR/etc/sysusers.d/test.conf
cat $f | $SYSUSERS --root=$TESTDIR -
compare ${f%.*} "on stdin"
done
for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
echo "*** Running $f on stdin with --replace"
prepare_testdir ${f%.input}
touch $TESTDIR/etc/sysusers.d/test.conf
# this overrides test.conf which is masked on disk
cat $f | $SYSUSERS --root=$TESTDIR --replace=/etc/sysusers.d/test.conf -
# this should be ignored
cat $SOURCE/test-1.input | $SYSUSERS --root=$TESTDIR --replace=/usr/lib/sysusers.d/test.conf -
compare ${f%.*} "on stdin with --replace"
done
# test --inline
echo "*** Testing --inline"
prepare_testdir $SOURCE/inline
# copy a random file to make sure it is ignored
cp $f $TESTDIR/etc/sysusers.d/confuse.conf
$SYSUSERS --root=$TESTDIR --inline \
"u u1 222 - - /bin/zsh" \
"g g1 111"
compare $SOURCE/inline "(--inline)"
# test --replace
echo "*** Testing --inline with --replace"
prepare_testdir $SOURCE/inline
# copy a random file to make sure it is ignored
cp $f $TESTDIR/etc/sysusers.d/confuse.conf
$SYSUSERS --root=$TESTDIR \
--inline \
--replace=/etc/sysusers.d/confuse.conf \
"u u1 222 - - /bin/zsh" \
"g g1 111"
compare $SOURCE/inline "(--inline --replace=…)"
rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
cat >$TESTDIR/etc/login.defs <<EOF
SYS_UID_MIN abcd
SYS_UID_MAX abcd
SYS_GID_MIN abcd
SYS_GID_MAX abcd
SYS_UID_MIN 401
SYS_UID_MAX 555
SYS_GID_MIN 405
SYS_GID_MAX 666
SYS_UID_MIN abcd
SYS_UID_MAX abcd
SYS_GID_MIN abcd
SYS_GID_MAX abcd
SYS_UID_MIN999
SYS_UID_MAX999
SYS_GID_MIN999
SYS_GID_MAX999
EOF
for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
echo "*** Running $f (with login.defs)"
prepare_testdir ${f%.input}
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
$SYSUSERS --root=$TESTDIR
[ @ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES@ = 1 ] && bound=555 || bound=$system_guid_max
compare ${f%.*} "(with login.defs)" $bound
done
rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
mv $TESTDIR/etc/login.defs $TESTDIR/etc/login.defs.moved
ln -s ../../../../../etc/login.defs.moved $TESTDIR/etc/login.defs
for f in $(ls -1 $SOURCE/test-*.input | sort -V); do
echo "*** Running $f (with login.defs symlinked)"
prepare_testdir ${f%.input}
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
$SYSUSERS --root=$TESTDIR
[ @ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES@ = 1 ] && bound=555 || bound=$system_guid_max
compare ${f%.*} "(with login.defs symlinked)" $bound
done
rm -f $TESTDIR/etc/sysusers.d/* $TESTDIR/usr/lib/sysusers.d/*
# tests for error conditions
for f in $(ls -1 $SOURCE/unhappy-*.input | sort -V); do
echo "*** Running test $f"
prepare_testdir ${f%.input}
cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
$SYSUSERS --root=$TESTDIR 2>&1 | tail -n1 > $TESTDIR/err
if ! diff -u $TESTDIR/err ${f%.*}.expected-err; then
echo "**** Unexpected error output for $f"
cat $TESTDIR/err
exit 1
fi
done

View File

@ -0,0 +1,2 @@
u1:x:300:u2
u2:x:SYSTEM_UGID_MAX:

View File

@ -0,0 +1,2 @@
u1:x:300:300::/:NOLOGIN
u2:x:SYSTEM_UGID_MAX:SYSTEM_UGID_MAX::/:NOLOGIN

View File

@ -1,5 +1,5 @@
hoge:x:300:
baz:x:302:
yyy:x:SYSTEM_GID_MAX:
yyy:x:SYSTEM_UGID_MAX:
foo:x:301:
ccc:x:305:

View File

@ -2,4 +2,4 @@ foo:x:301:301::/:NOLOGIN
aaa:x:303:302::/:NOLOGIN
bbb:x:304:302::/:NOLOGIN
ccc:x:305:305::/:NOLOGIN
zzz:x:306:SYSTEM_GID_MAX::/:NOLOGIN
zzz:x:306:SYSTEM_UGID_MAX::/:NOLOGIN

View File

@ -0,0 +1 @@
aaa:x:SYSTEM_UGID_MAX:987::/:NOLOGIN

View File

@ -1,4 +1,4 @@
u1:x:SYSTEM_UID_MAX:
u1:x:SYSTEM_UGID_MAX:
u2:x:777:
u3:x:778:
u4:x:779:

View File

@ -1,4 +1,4 @@
u1:x:SYSTEM_UID_MAX:SYSTEM_UID_MAX:some gecos:/random/dir:NOLOGIN
u1:x:SYSTEM_UGID_MAX:SYSTEM_UGID_MAX:some gecos:/random/dir:NOLOGIN
u2:x:777:777:some gecos:/random/dir:/bin/zsh
u3:x:778:778::/random/dir2:/bin/bash
u4:x:779:779::/:/bin/csh

View File

@ -1,4 +1,4 @@
# Test generation of ID dynamically based on SYSTEM_UID_MAX and
# Test generation of ID dynamically based on SYSTEM_UGID_MAX and
# replacement of all fields up to the login shell.
#
#Type Name ID GECOS homedir shell

View File

@ -0,0 +1,2 @@
g1:x:111:
u1:x:SYSTEM_UGID_MAX:

View File

@ -0,0 +1 @@
u1:x:SYSTEM_UGID_MAX:SYSTEM_UGID_MAX::/:NOLOGIN

View File

@ -0,0 +1 @@
username:x:SYSTEM_UGID_MAX:300::/:NOLOGIN

View File

@ -0,0 +1,2 @@
user1:x:300:300::/:NOLOGIN
user2:x:SYSTEM_UGID_MAX:300::/:NOLOGIN