From a3451c2c4ce7d3c02451f6ace4ee9f873880f78f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 5 May 2020 22:45:54 +0200 Subject: [PATCH] tmpfiles: optionally, read /etc/passwd + /etc/group without NSS There are two libc APIs for accessing the user database: NSS/getpwuid(), and fgetpwent(). if we run in --root= mode (i.e. "offline" mode), let's use the latter. Otherwise the former. This means tmpfiles can use the database included in the root environment for chowning, which is a lot more appropriate. Fixes: #14806 --- meson.build | 2 + src/tmpfiles/offline-passwd.c | 122 ++++++++++++++++++++++++++++++++++ src/tmpfiles/offline-passwd.h | 9 +++ src/tmpfiles/tmpfiles.c | 70 ++++++++++++++++--- 4 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 src/tmpfiles/offline-passwd.c create mode 100644 src/tmpfiles/offline-passwd.h diff --git a/meson.build b/meson.build index a922f9a2f1..0600bdeff5 100644 --- a/meson.build +++ b/meson.build @@ -2884,6 +2884,8 @@ if conf.get('ENABLE_TMPFILES') == 1 exe = executable( 'systemd-tmpfiles', 'src/tmpfiles/tmpfiles.c', + 'src/tmpfiles/offline-passwd.c', + 'src/tmpfiles/offline-passwd.h', include_directories : includes, link_with : [libshared], dependencies : [libacl], diff --git a/src/tmpfiles/offline-passwd.c b/src/tmpfiles/offline-passwd.c new file mode 100644 index 0000000000..8ac5431c66 --- /dev/null +++ b/src/tmpfiles/offline-passwd.c @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "fd-util.h" +#include "offline-passwd.h" +#include "path-util.h" +#include "user-util.h" + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(uid_gid_hash_ops, char, string_hash_func, string_compare_func, free); + +int name_to_uid_offline( + const char *root, + const char *user, + uid_t *ret_uid, + Hashmap **cache) { + + void *found; + int r; + + assert(user); + assert(ret_uid); + assert(cache); + + if (!*cache) { + _cleanup_(hashmap_freep) Hashmap *uid_by_name = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct passwd *pw; + const char *passwd_path; + + passwd_path = prefix_roota(root, "/etc/passwd"); + f = fopen(passwd_path, "re"); + if (!f) + return errno == ENOENT ? -ESRCH : -errno; + + uid_by_name = hashmap_new(&uid_gid_hash_ops); + if (!uid_by_name) + return -ENOMEM; + + while ((r = fgetpwent_sane(f, &pw)) > 0) { + _cleanup_free_ char *n = NULL; + + n = strdup(pw->pw_name); + if (!n) + return -ENOMEM; + + r = hashmap_put(uid_by_name, n, UID_TO_PTR(pw->pw_uid)); + if (r == -EEXIST) { + log_warning_errno(r, "Duplicate entry in %s for %s: %m", passwd_path, pw->pw_name); + continue; + } + if (r < 0) + return r; + + TAKE_PTR(n); + } + + *cache = TAKE_PTR(uid_by_name); + } + + found = hashmap_get(*cache, user); + if (!found) + return -ESRCH; + + *ret_uid = PTR_TO_UID(found); + return 0; +} + +int name_to_gid_offline( + const char *root, + const char *group, + gid_t *ret_gid, + Hashmap **cache) { + + void *found; + int r; + + assert(group); + assert(ret_gid); + assert(cache); + + if (!*cache) { + _cleanup_(hashmap_freep) Hashmap *gid_by_name = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct group *gr; + const char *group_path; + + group_path = prefix_roota(root, "/etc/group"); + f = fopen(group_path, "re"); + if (!f) + return errno == ENOENT ? -ESRCH : -errno; + + gid_by_name = hashmap_new(&uid_gid_hash_ops); + if (!gid_by_name) + return -ENOMEM; + + while ((r = fgetgrent_sane(f, &gr)) > 0) { + _cleanup_free_ char *n = NULL; + + n = strdup(gr->gr_name); + if (!n) + return -ENOMEM; + + r = hashmap_put(gid_by_name, n, GID_TO_PTR(gr->gr_gid)); + if (r == -EEXIST) { + log_warning_errno(r, "Duplicate entry in %s for %s: %m", group_path, gr->gr_name); + continue; + } + if (r < 0) + return r; + + TAKE_PTR(n); + } + + *cache = TAKE_PTR(gid_by_name); + } + + found = hashmap_get(*cache, group); + if (!found) + return -ESRCH; + + *ret_gid = PTR_TO_GID(found); + return 0; +} diff --git a/src/tmpfiles/offline-passwd.h b/src/tmpfiles/offline-passwd.h new file mode 100644 index 0000000000..90bdfc79be --- /dev/null +++ b/src/tmpfiles/offline-passwd.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include + +#include "hashmap.h" + +int name_to_uid_offline(const char *root, const char *user, uid_t *ret_uid, Hashmap **cache); +int name_to_gid_offline(const char *root, const char *group, gid_t *ret_gid, Hashmap **cache); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 7137e9fbd7..37dde99ef0 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -39,6 +39,7 @@ #include "main-func.h" #include "mkdir.h" #include "mountpoint-util.h" +#include "offline-passwd.h" #include "pager.h" #include "parse-util.h" #include "path-lookup.h" @@ -2487,7 +2488,63 @@ static int patch_var_run(const char *fname, unsigned line, char **path) { return 0; } -static int parse_line(const char *fname, unsigned line, const char *buffer, bool *invalid_config) { +static int find_uid(const char *user, uid_t *ret_uid, Hashmap **cache) { + int r; + + assert(user); + assert(ret_uid); + + /* First: parse as numeric UID string */ + r = parse_uid(user, ret_uid); + if (r >= 0) + return r; + + /* Second: pass to NSS if we are running "online" */ + if (!arg_root) + return get_user_creds(&user, ret_uid, NULL, NULL, NULL, 0); + + /* Third, synthesize "root" unconditionally */ + if (streq(user, "root")) { + *ret_uid = 0; + return 0; + } + + /* Fourth: use fgetpwent() to read /etc/passwd directly, if we are "offline" */ + return name_to_uid_offline(arg_root, user, ret_uid, cache); +} + +static int find_gid(const char *group, gid_t *ret_gid, Hashmap **cache) { + int r; + + assert(group); + assert(ret_gid); + + /* First: parse as numeric GID string */ + r = parse_gid(group, ret_gid); + if (r >= 0) + return r; + + /* Second: pass to NSS if we are running "online" */ + if (!arg_root) + return get_group_creds(&group, ret_gid, 0); + + /* Third, synthesize "root" unconditionally */ + if (streq(group, "root")) { + *ret_gid = 0; + return 0; + } + + /* Fourth: use fgetgrent() to read /etc/group directly, if we are "offline" */ + return name_to_gid_offline(arg_root, group, ret_gid, cache); +} + +static int parse_line( + const char *fname, + unsigned line, + const char *buffer, + bool *invalid_config, + Hashmap **uid_cache, + Hashmap **gid_cache) { _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL; _cleanup_(item_free_contents) Item i = {}; @@ -2718,9 +2775,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool } if (!empty_or_dash(user)) { - const char *u = user; - - r = get_user_creds(&u, &i.uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); + r = find_uid(user, &i.uid, uid_cache); if (r < 0) { *invalid_config = true; return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve user '%s': %m", user); @@ -2730,9 +2785,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer, bool } if (!empty_or_dash(group)) { - const char *g = group; - - r = get_group_creds(&g, &i.gid, USER_CREDS_ALLOW_MISSING); + r = find_gid(group, &i.gid, gid_cache); if (r < 0) { *invalid_config = true; return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve group '%s'.", group); @@ -2981,6 +3034,7 @@ static int parse_argv(int argc, char *argv[]) { } static int read_config_file(char **config_dirs, const char *fn, bool ignore_enoent, bool *invalid_config) { + _cleanup_(hashmap_freep) Hashmap *uid_cache = NULL, *gid_cache = NULL; _cleanup_fclose_ FILE *_f = NULL; Iterator iterator; unsigned v = 0; @@ -3026,7 +3080,7 @@ static int read_config_file(char **config_dirs, const char *fn, bool ignore_enoe if (IN_SET(*l, 0, '#')) continue; - k = parse_line(fn, v, l, &invalid_line); + k = parse_line(fn, v, l, &invalid_line, &uid_cache, &gid_cache); if (k < 0) { if (invalid_line) /* Allow reporting with a special code if the caller requested this */