1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2024-12-24 21:34:08 +03:00

homed: take BSD file lock on LUKS file while activated

Fixes: #19758
This commit is contained in:
Lennart Poettering 2021-10-05 10:26:25 +02:00
parent 23cff6d4fe
commit 2aaf565a2d
7 changed files with 163 additions and 58 deletions

View 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;
}

View 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);

View 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,13 +1038,6 @@ 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);
@ -1047,37 +1052,40 @@ static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) {
if (m < 0)
return m;
cmsg_close_all(&mh);
/* Ensure the size matches what we determined before */
if (m != n)
if (m != n) {
cmsg_close_all(&mh);
return -EMSGSIZE;
}
CMSG_FOREACH(cmsg, &mh)
CMSG_FOREACH(cmsg, &mh) {
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_CREDENTIALS &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
memcpy(ret_sender, CMSG_DATA(cmsg), sizeof(struct ucred));
found_ucred = true;
assert(!sender);
sender = (struct ucred*) CMSG_DATA(cmsg);
}
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;
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_RIGHTS) {
/* Ensure the size matches what we determined before */
if (m != n)
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
cmsg_close_all(&mh);
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;
}

View 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");

View 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"))

View 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
};

View File

@ -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);