]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
homed: take BSD file lock on LUKS file while activated
authorLennart Poettering <lennart@poettering.net>
Tue, 5 Oct 2021 08:26:25 +0000 (10:26 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 11 Oct 2021 14:00:34 +0000 (16:00 +0200)
Fixes: #19758
src/home/homed-home.c
src/home/homed-home.h
src/home/homed-manager.c
src/home/homework-luks.c
src/home/homework.c
src/libsystemd/sd-bus/bus-common-errors.c
src/libsystemd/sd-bus/bus-common-errors.h

index 0260b8041ca710043759c5c36e6d5a760bd40b02..d8f192650efac3ecf28801f16638d97933f779e5 100644 (file)
@@ -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;
 }
index e5a70e409236965b97cc42e2258f3da4d20e08d0..7cd8a9edb40e0499800e4afd44eea53076ab31aa 100644 (file)
@@ -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);
 
index 070fd97d69bfbfa1d093ed61b4e88531598057c3..39782304f642c69dcab5d175239ba5faa7a543b3 100644 (file)
@@ -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;
 }
 
index c9c2476ed6e4741609ce281d35566d2a0ac50160..30b63e348109bb944f5597e248dc6f020670f878 100644 (file)
@@ -8,11 +8,14 @@
 #include <sys/mount.h>
 #include <sys/xattr.h>
 
+#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");
index ee1d4068bad92779eadd5ee3ade372b66da5eb96..d35712c3514d8d4a99820ca1ffad5d9939738976 100644 (file)
@@ -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"))
index 43f9a7bdaadadd3c6aa489bf9f9aa1ae35024403..61c5509e1c82c2911928d2486f7a864417ac9d12 100644 (file)
@@ -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
 };
index fd8f0c240e96a5c8bfb410eb0bef6ef1f1960d18..348cd5094b3861d9790a8775e378ea44e71f8bb5 100644 (file)
 #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);