diff --git a/src/core/dynamic-user.c b/src/core/dynamic-user.c index 8035bee231d..185d0e5f00a 100644 --- a/src/core/dynamic-user.c +++ b/src/core/dynamic-user.c @@ -150,6 +150,42 @@ int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret) { return 1; } +static int make_uid_symlinks(uid_t uid, const char *name, bool b) { + + char path1[strlen("/run/systemd/dynamic-uid/direct:") + DECIMAL_STR_MAX(uid_t) + 1]; + const char *path2; + int r = 0; + + /* Add direct additional symlinks for direct lookups of dynamic UIDs and their names by userspace code. The + * only reason we have this is because dbus-daemon cannot use D-Bus for resolving users and groups (since it + * would be its own client then). We hence keep these world-readable symlinks in place, so that the + * unprivileged dbus user can read the mappings when it needs them via these symlinks instead of having to go + * via the bus. Ideally, we'd use the lock files we keep for this anyway, but we can't since we use BSD locks + * on them and as those may be taken by any user with read access we can't make them world-readable. */ + + xsprintf(path1, "/run/systemd/dynamic-uid/direct:" UID_FMT, uid); + if (unlink(path1) < 0) { + if (errno != ENOENT) + r = -errno; + } + if (b) { + if (symlink(name, path1) < 0) + r = -errno; + } + + path2 = strjoina("/run/systemd/dynamic-uid/direct:", name); + if (unlink(path2) < 0) { + if (errno != ENOENT) + r = -errno; + } + if (b) { + if (symlink(path1 + strlen("/run/systemd/dynamic-uid/direct:"), path2) < 0) + r = -errno; + } + + return r; +} + static int pick_uid(const char *name, uid_t *ret_uid) { static const uint8_t hash_key[] = { @@ -223,6 +259,7 @@ static int pick_uid(const char *name, uid_t *ret_uid) { } (void) ftruncate(lock_fd, l); + (void) make_uid_symlinks(candidate, name, true); /* also add direct lookup symlinks */ *ret_uid = candidate; r = lock_fd; @@ -324,14 +361,16 @@ static int dynamic_user_push(DynamicUser *d, uid_t uid, int lock_fd) { return 0; } -static void unlink_uid_lock(int lock_fd, uid_t uid) { +static void unlink_uid_lock(int lock_fd, uid_t uid, const char *name) { char lock_path[strlen("/run/systemd/dynamic-uid/") + DECIMAL_STR_MAX(uid_t) + 1]; if (lock_fd < 0) return; xsprintf(lock_path, "/run/systemd/dynamic-uid/" UID_FMT, uid); - (void) unlink_noerrno(lock_path); + (void) unlink(lock_path); + + (void) make_uid_symlinks(uid, name, false); /* remove direct lookup symlinks */ } int dynamic_user_realize(DynamicUser *d, uid_t *ret) { @@ -399,7 +438,7 @@ int dynamic_user_realize(DynamicUser *d, uid_t *ret) { /* So, we found a working UID/lock combination. Let's see if we actually still need it. */ if (lockf(d->storage_socket[0], F_LOCK, 0) < 0) { - unlink_uid_lock(uid_lock_fd, uid); + unlink_uid_lock(uid_lock_fd, uid, d->name); return -errno; } @@ -407,7 +446,7 @@ int dynamic_user_realize(DynamicUser *d, uid_t *ret) { if (r < 0) { if (r != -EAGAIN) { /* OK, something bad happened, let's get rid of the bits we acquired. */ - unlink_uid_lock(uid_lock_fd, uid); + unlink_uid_lock(uid_lock_fd, uid, d->name); goto finish; } @@ -416,7 +455,7 @@ int dynamic_user_realize(DynamicUser *d, uid_t *ret) { /* Hmm, so as it appears there's now something stored in the storage socket. Throw away what we * acquired, and use what's stored now. */ - unlink_uid_lock(uid_lock_fd, uid); + unlink_uid_lock(uid_lock_fd, uid, d->name); safe_close(uid_lock_fd); uid = new_uid; @@ -513,7 +552,7 @@ static int dynamic_user_close(DynamicUser *d) { goto finish; /* This dynamic user was realized and dynamically allocated. In this case, let's remove the lock file. */ - unlink_uid_lock(lock_fd, uid); + unlink_uid_lock(lock_fd, uid, d->name); r = 1; finish: diff --git a/src/core/execute.c b/src/core/execute.c index 4c786a2e335..0af8eb5a02f 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -91,6 +91,7 @@ #include "selinux-util.h" #include "signal-util.h" #include "smack-util.h" +#include "special.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -1384,6 +1385,7 @@ static void do_idle_pipe_dance(int idle_pipe[4]) { } static int build_environment( + Unit *u, const ExecContext *c, const ExecParameters *p, unsigned n_fds, @@ -1401,7 +1403,7 @@ static int build_environment( assert(c); assert(ret); - our_env = new0(char*, 12); + our_env = new0(char*, 13); if (!our_env) return -ENOMEM; @@ -1436,6 +1438,16 @@ static int build_environment( our_env[n_env++] = x; } + /* If this is D-Bus, tell the nss-systemd module, since it relies on being able to use D-Bus look up dynamic + * users via PID 1, possibly dead-locking the dbus daemon. This way it will not use D-Bus to resolve names, but + * check the database directly. */ + if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) { + x = strdup("SYSTEMD_NSS_BYPASS_BUS=1"); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + if (home) { x = strappend("HOME=", home); if (!x) @@ -2100,6 +2112,7 @@ static int exec_child( } r = build_environment( + unit, context, params, n_fds, diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c index 7078c0c50cc..17d04e958d5 100644 --- a/src/nss-systemd/nss-systemd.c +++ b/src/nss-systemd/nss-systemd.c @@ -21,11 +21,14 @@ #include "sd-bus.h" +#include "alloc-util.h" #include "bus-common-errors.h" #include "env-util.h" +#include "fs-util.h" #include "macro.h" #include "nss-util.h" #include "signal-util.h" +#include "stdio-util.h" #include "string-util.h" #include "user-util.h" #include "util.h" @@ -75,15 +78,50 @@ static const struct group nobody_group = { NSS_GETPW_PROTOTYPES(systemd); NSS_GETGR_PROTOTYPES(systemd); +static int direct_lookup_name(const char *name, uid_t *ret) { + _cleanup_free_ char *s = NULL; + const char *path; + int r; + + assert(name); + + /* Normally, we go via the bus to resolve names. That has the benefit that it is available from any mount + * namespace and subject to proper authentication. However, there's one problem: if our module is called from + * dbus-daemon itself we really can't use D-Bus to communicate. In this case, resort to a client-side hack, + * and look for the dynamic names directly. This is pretty ugly, but breaks the cyclic dependency. */ + + path = strjoina("/run/systemd/dynamic-uid/direct:", name); + r = readlink_malloc(path, &s); + if (r < 0) + return r; + + return parse_uid(s, ret); +} + +static int direct_lookup_uid(uid_t uid, char **ret) { + char path[strlen("/run/systemd/dynamic-uid/direct:") + DECIMAL_STR_MAX(uid_t) + 1], *s; + int r; + + xsprintf(path, "/run/systemd/dynamic-uid/direct:" UID_FMT, uid); + + r = readlink_malloc(path, &s); + if (r < 0) + return r; + if (!valid_user_group_name(s)) { /* extra safety check */ + free(s); + return -EINVAL; + } + + *ret = s; + return 0; +} + enum nss_status _nss_systemd_getpwnam_r( const char *name, struct passwd *pwd, char *buffer, size_t buflen, int *errnop) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; uint32_t translated; size_t l; int r; @@ -114,30 +152,45 @@ enum nss_status _nss_systemd_getpwnam_r( if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) goto not_found; - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) { - r = sd_bus_call_method(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LookupDynamicUserByName", - &error, - &reply, - "s", - name); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + /* Access the dynamic UID allocation directly if we are called from dbus-daemon, see above. */ + r = direct_lookup_name(name, (uid_t*) &translated); + if (r == -ENOENT) goto not_found; + if (r < 0) + goto fail; - goto fail; + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByName", + &error, + &reply, + "s", + name); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &translated); + if (r < 0) + goto fail; } - r = sd_bus_message_read(reply, "u", &translated); - if (r < 0) - goto fail; - l = strlen(name); if (buflen < l+1) { *errnop = ENOMEM; @@ -175,6 +228,7 @@ enum nss_status _nss_systemd_getpwuid_r( _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *direct = NULL; const char *translated; size_t l; int r; @@ -204,30 +258,42 @@ enum nss_status _nss_systemd_getpwuid_r( if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) goto not_found; - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) { - r = sd_bus_call_method(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LookupDynamicUserByUID", - &error, - &reply, - "u", - (uint32_t) uid); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + r = direct_lookup_uid(uid, &direct); + if (r == -ENOENT) goto not_found; + if (r < 0) + goto fail; - goto fail; + translated = direct; + + } else { + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByUID", + &error, + &reply, + "u", + (uint32_t) uid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "s", &translated); + if (r < 0) + goto fail; } - r = sd_bus_message_read(reply, "s", &translated); - if (r < 0) - goto fail; - l = strlen(translated) + 1; if (buflen < l) { *errnop = ENOMEM; @@ -262,9 +328,6 @@ enum nss_status _nss_systemd_getgrnam_r( char *buffer, size_t buflen, int *errnop) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; uint32_t translated; size_t l; int r; @@ -294,30 +357,45 @@ enum nss_status _nss_systemd_getgrnam_r( if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) goto not_found; - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) { - r = sd_bus_call_method(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LookupDynamicUserByName", - &error, - &reply, - "s", - name); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + /* Access the dynamic GID allocation directly if we are called from dbus-daemon, see above. */ + r = direct_lookup_name(name, (uid_t*) &translated); + if (r == -ENOENT) goto not_found; + if (r < 0) + goto fail; + } else { - goto fail; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByName", + &error, + &reply, + "s", + name); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &translated); + if (r < 0) + goto fail; } - r = sd_bus_message_read(reply, "u", &translated); - if (r < 0) - goto fail; - l = sizeof(char*) + strlen(name) + 1; if (buflen < l) { *errnop = ENOMEM; @@ -353,6 +431,7 @@ enum nss_status _nss_systemd_getgrgid_r( _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *direct = NULL; const char *translated; size_t l; int r; @@ -382,30 +461,41 @@ enum nss_status _nss_systemd_getgrgid_r( if (getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) goto not_found; - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; + if (getenv_bool("SYSTEMD_NSS_BYPASS_BUS") > 0) { - r = sd_bus_call_method(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LookupDynamicUserByUID", - &error, - &reply, - "u", - (uint32_t) gid); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + r = direct_lookup_uid(gid, &direct); + if (r == -ENOENT) goto not_found; + if (r < 0) + goto fail; - goto fail; + translated = direct; + } else { + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LookupDynamicUserByUID", + &error, + &reply, + "u", + (uint32_t) gid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_DYNAMIC_USER)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "s", &translated); + if (r < 0) + goto fail; } - r = sd_bus_message_read(reply, "s", &translated); - if (r < 0) - goto fail; - l = sizeof(char*) + strlen(translated) + 1; if (buflen < l) { *errnop = ENOMEM;