.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);
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);
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);
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)) {
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;
_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");
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;
}
/* 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);
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);
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)
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);
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;
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)
if (!l)
return log_oom();
- home_process_notify(h, l);
+ home_process_notify(h, l, TAKE_FD(passed_fd));
return 0;
}
#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"
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,
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");
* 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"))
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
};
#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);