From: Lennart Poettering Date: Tue, 5 Oct 2021 08:26:25 +0000 (+0200) Subject: homed: take BSD file lock on LUKS file while activated X-Git-Tag: v250-rc1~531^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2aaf565a2da8eb0ea9adf84028c9d0ab7a90e53e;p=thirdparty%2Fsystemd.git homed: take BSD file lock on LUKS file while activated Fixes: #19758 --- diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 0260b8041ca..d8f192650ef 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -134,6 +134,7 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) { .sysfs = TAKE_PTR(ns), .signed_locally = -1, .pin_fd = -1, + .luks_lock_fd = -1, }; r = hashmap_put(m->homes_by_name, home->user_name, home); @@ -208,6 +209,7 @@ Home *home_free(Home *h) { h->current_operation = operation_unref(h->current_operation); safe_close(h->pin_fd); + safe_close(h->luks_lock_fd); h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source); @@ -367,6 +369,23 @@ static void home_update_pin_fd(Home *h, HomeState state) { return HOME_STATE_SHALL_PIN(state) ? home_pin(h) : home_unpin(h); } +static void home_maybe_close_luks_lock_fd(Home *h, HomeState state) { + assert(h); + + if (h->luks_lock_fd < 0) + return; + + if (state < 0) + state = home_get_state(h); + + /* Keep the lock as long as the home dir is active or has some operation going */ + if (HOME_STATE_IS_EXECUTING_OPERATION(state) || HOME_STATE_IS_ACTIVE(state) || state == HOME_LOCKED) + return; + + h->luks_lock_fd = safe_close(h->luks_lock_fd); + log_debug("Successfully closed LUKS backing file lock for %s.", h->user_name); +} + static void home_maybe_stop_retry_deactivate(Home *h, HomeState state) { assert(h); @@ -458,6 +477,7 @@ static void home_set_state(Home *h, HomeState state) { home_state_to_string(new_state)); home_update_pin_fd(h, new_state); + home_maybe_close_luks_lock_fd(h, new_state); home_maybe_stop_retry_deactivate(h, new_state); if (HOME_STATE_IS_EXECUTING_OPERATION(old_state) && !HOME_STATE_IS_EXECUTING_OPERATION(new_state)) { @@ -612,6 +632,8 @@ static int convert_worker_errno(Home *h, int e, sd_bus_error *error) { return sd_bus_error_setf(error, BUS_ERROR_NO_DISK_SPACE, "Not enough disk space for home %s", h->user_name); case -EKEYREVOKED: return sd_bus_error_setf(error, BUS_ERROR_HOME_CANT_AUTHENTICATE, "Home %s has no password or other authentication mechanism defined.", h->user_name); + case -EADDRINUSE: + return sd_bus_error_setf(error, BUS_ERROR_HOME_IN_USE, "Home %s is currently being used elsewhere.", h->user_name); } return 0; @@ -1158,6 +1180,12 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord _exit(EXIT_FAILURE); } + /* If we haven't locked the device yet, ask for a lock to be taken and be passed back to us via sd_notify(). */ + if (setenv("SYSTEMD_LUKS_LOCK", one_zero(h->luks_lock_fd < 0), 1) < 0) { + log_error_errno(errno, "Failed to set $SYSTEMD_LUKS_LOCK: %m"); + _exit(EXIT_FAILURE); + } + if (h->manager->default_storage >= 0) if (setenv("SYSTEMD_HOME_DEFAULT_STORAGE", user_storage_to_string(h->manager->default_storage), 1) < 0) { log_error_errno(errno, "Failed to set $SYSTEMD_HOME_DEFAULT_STORAGE: %m"); @@ -1957,28 +1985,49 @@ HomeState home_get_state(Home *h) { return HOME_INACTIVE; } -void home_process_notify(Home *h, char **l) { +void home_process_notify(Home *h, char **l, int fd) { + _cleanup_close_ int taken_fd = TAKE_FD(fd); const char *e; int error; int r; assert(h); - e = strv_env_get(l, "ERRNO"); - if (!e) { - log_debug("Got notify message lacking ERRNO= field, ignoring."); + e = strv_env_get(l, "SYSTEMD_LUKS_LOCK_FD"); + if (e) { + r = parse_boolean(e); + if (r < 0) + return (void) log_debug_errno(r, "Failed to parse SYSTEMD_LUKS_LOCK_FD value: %m"); + if (r > 0) { + if (taken_fd < 0) + return (void) log_debug("Got notify message with SYSTEMD_LUKS_LOCK_FD=1 but no fd passed, ignoring: %m"); + + safe_close(h->luks_lock_fd); + h->luks_lock_fd = TAKE_FD(taken_fd); + + log_debug("Successfully acquired LUKS lock fd from worker."); + + /* Immediately check if we actually want to keep it */ + home_maybe_close_luks_lock_fd(h, _HOME_STATE_INVALID); + } else { + if (taken_fd >= 0) + return (void) log_debug("Got notify message with SYSTEMD_LUKS_LOCK_FD=0 but fd passed, ignoring: %m"); + + h->luks_lock_fd = safe_close(h->luks_lock_fd); + } + return; } + e = strv_env_get(l, "ERRNO"); + if (!e) + return (void) log_debug("Got notify message lacking both ERRNO= and SYSTEMD_LUKS_LOCK_FD= field, ignoring."); + r = safe_atoi(e, &error); - if (r < 0) { - log_debug_errno(r, "Failed to parse received error number, ignoring: %s", e); - return; - } - if (error <= 0) { - log_debug("Error number is out of range: %i", error); - return; - } + if (r < 0) + return (void) log_debug_errno(r, "Failed to parse received error number, ignoring: %s", e); + if (error <= 0) + return (void) log_debug("Error number is out of range: %i", error); h->worker_error_code = error; } diff --git a/src/home/homed-home.h b/src/home/homed-home.h index e5a70e40923..7cd8a9edb40 100644 --- a/src/home/homed-home.h +++ b/src/home/homed-home.h @@ -161,6 +161,9 @@ struct Home { /* A time event used to repeatedly try to unmount home dir after use if it didn't work on first try */ sd_event_source *retry_deactivate_event_source; + + /* An fd that locks the backing file of LUKS home dirs with a BSD lock. */ + int luks_lock_fd; }; int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret); @@ -187,7 +190,7 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error); HomeState home_get_state(Home *h); -void home_process_notify(Home *h, char **l); +void home_process_notify(Home *h, char **l, int fd); int home_killall(Home *h); diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index 070fd97d69b..39782304f64 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -1010,13 +1010,25 @@ static int manager_bind_varlink(Manager *m) { return 0; } -static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) { +static ssize_t read_datagram( + int fd, + struct ucred *ret_sender, + void **ret, + int *ret_passed_fd) { + + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int))) control; _cleanup_free_ void *buffer = NULL; + _cleanup_close_ int passed_fd = -1; + struct ucred *sender = NULL; + struct cmsghdr *cmsg; + struct msghdr mh; + struct iovec iov; ssize_t n, m; assert(fd >= 0); assert(ret_sender); assert(ret); + assert(ret_passed_fd); n = next_datagram_size_fd(fd); if (n < 0) @@ -1026,58 +1038,54 @@ static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) { if (!buffer) return -ENOMEM; - if (ret_sender) { - CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control; - bool found_ucred = false; - struct cmsghdr *cmsg; - struct msghdr mh; - struct iovec iov; - - /* Pass one extra byte, as a size check */ - iov = IOVEC_MAKE(buffer, n + 1); + /* Pass one extra byte, as a size check */ + iov = IOVEC_MAKE(buffer, n + 1); - mh = (struct msghdr) { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; + mh = (struct msghdr) { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; - m = recvmsg_safe(fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - if (m < 0) - return m; + m = recvmsg_safe(fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (m < 0) + return m; + /* Ensure the size matches what we determined before */ + if (m != n) { cmsg_close_all(&mh); + return -EMSGSIZE; + } - /* Ensure the size matches what we determined before */ - if (m != n) - return -EMSGSIZE; + CMSG_FOREACH(cmsg, &mh) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { + assert(!sender); + sender = (struct ucred*) CMSG_DATA(cmsg); + } - CMSG_FOREACH(cmsg, &mh) - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_CREDENTIALS && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { - memcpy(ret_sender, CMSG_DATA(cmsg), sizeof(struct ucred)); - found_ucred = true; + if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) { + cmsg_close_all(&mh); + return -EMSGSIZE; } - if (!found_ucred) - *ret_sender = (struct ucred) { - .pid = 0, - .uid = UID_INVALID, - .gid = GID_INVALID, - }; - } else { - m = recv(fd, buffer, n + 1, MSG_DONTWAIT); - if (m < 0) - return -errno; - - /* Ensure the size matches what we determined before */ - if (m != n) - return -EMSGSIZE; + assert(passed_fd < 0); + passed_fd = *(int*) CMSG_DATA(cmsg); + } } + if (sender) + *ret_sender = *sender; + else + *ret_sender = (struct ucred) UCRED_INVALID; + + *ret_passed_fd = TAKE_FD(passed_fd); + /* For safety reasons: let's always NUL terminate. */ ((char*) buffer)[n] = 0; *ret = TAKE_PTR(buffer); @@ -1088,7 +1096,8 @@ static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) { static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_strv_free_ char **l = NULL; _cleanup_free_ void *datagram = NULL; - struct ucred sender; + _cleanup_close_ int passed_fd = -1; + struct ucred sender = UCRED_INVALID; Manager *m = userdata; ssize_t n; Home *h; @@ -1096,7 +1105,7 @@ static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void * assert(s); assert(m); - n = read_datagram(fd, &sender, &datagram); + n = read_datagram(fd, &sender, &datagram, &passed_fd); if (IN_SET(n, -EAGAIN, -EINTR)) return 0; if (n < 0) @@ -1117,7 +1126,7 @@ static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void * if (!l) return log_oom(); - home_process_notify(h, l); + home_process_notify(h, l, TAKE_FD(passed_fd)); return 0; } diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index c9c2476ed6e..30b63e34810 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -8,11 +8,14 @@ #include #include +#include "sd-daemon.h" + #include "blkid-util.h" #include "blockdev-util.h" #include "btrfs-util.h" #include "chattr-util.h" #include "dm-util.h" +#include "env-util.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" @@ -1042,6 +1045,40 @@ int run_fallocate_by_path(const char *backing_path) { return run_fallocate(backing_fd, NULL); } +static int lock_image_fd(int image_fd, const char *ip) { + int r; + + /* If the $SYSTEMD_LUKS_LOCK environment variable is set we'll take an exclusive BSD lock on the + * image file, and send it to our parent. homed will keep it open to ensure no other instance of + * homed (across the network or such) will also mount the file. */ + + r = getenv_bool("SYSTEMD_LUKS_LOCK"); + if (r == -ENXIO) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to parse $SYSTEMD_LUKS_LOCK environment variable: %m"); + if (r > 0) { + if (flock(image_fd, LOCK_EX|LOCK_NB) < 0) { + + if (errno == EWOULDBLOCK) + log_error_errno(errno, "Image file '%s' already locked, can't use.", ip); + else + log_error_errno(errno, "Failed to lock image file '%s': %m", ip); + + return errno != EWOULDBLOCK ? -errno : -EADDRINUSE; /* Make error recognizable */ + } + + log_info("Successfully locked image file '%s'.", ip); + + /* Now send it to our parent to keep safe while the home dir is active */ + r = sd_pid_notify_with_fds(0, false, "SYSTEMD_LUKS_LOCK_FD=1", &image_fd, 1); + if (r < 0) + log_warning_errno(r, "Failed to send LUKS lock fd to parent, ignoring: %m"); + } + + return 0; +} + int home_prepare_luks( UserRecord *h, bool already_activated, @@ -1176,6 +1213,10 @@ int home_prepare_luks( S_ISDIR(st.st_mode) ? SYNTHETIC_ERRNO(EISDIR) : SYNTHETIC_ERRNO(EBADFD), "Image file %s is not a regular file or block device: %m", ip); + r = lock_image_fd(image_fd, ip); + if (r < 0) + return r; + r = luks_validate(image_fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size); if (r < 0) return log_error_errno(r, "Failed to validate disk label: %m"); diff --git a/src/home/homework.c b/src/home/homework.c index ee1d4068bad..d35712c3514 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -1706,6 +1706,7 @@ static int run(int argc, char *argv[]) { * ENOEXEC → file system is currently not active * ENOSPC → not enough disk space for operation * EKEYREVOKED → user record has not suitable hashed password or pkcs#11 entry, we cannot authenticate + * EADDRINUSE → home image is already used elsewhere (lock taken) */ if (streq(argv[1], "activate")) diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 43f9a7bdaad..61c5509e1c8 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -142,6 +142,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_TOO_MANY_OPERATIONS, ENOBUFS), SD_BUS_ERROR_MAP(BUS_ERROR_AUTHENTICATION_LIMIT_HIT, ETOOMANYREFS), SD_BUS_ERROR_MAP(BUS_ERROR_HOME_CANT_AUTHENTICATE, EKEYREVOKED), + SD_BUS_ERROR_MAP(BUS_ERROR_HOME_IN_USE, EADDRINUSE), SD_BUS_ERROR_MAP_END }; diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index fd8f0c240e9..348cd5094b3 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -126,5 +126,6 @@ #define BUS_ERROR_TOO_MANY_OPERATIONS "org.freedesktop.home1.TooManyOperations" #define BUS_ERROR_AUTHENTICATION_LIMIT_HIT "org.freedesktop.home1.AuthenticationLimitHit" #define BUS_ERROR_HOME_CANT_AUTHENTICATE "org.freedesktop.home1.HomeCantAuthenticate" +#define BUS_ERROR_HOME_IN_USE "org.freedesktop.home1.HomeInUse" BUS_ERROR_MAP_ELF_USE(bus_common_errors);