diff options
author | Lennart Poettering <lennart@poettering.net> | 2021-10-11 23:11:03 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-11 23:11:03 +0200 |
commit | de3ef2524e9f4ebcd2cee2c70daa78189dcfd339 (patch) | |
tree | 0ada892739627d41b53c60628f2d3381e490e4e4 | |
parent | Merge pull request #20776 from medhefgo/boot-timeout (diff) | |
parent | update TODO (diff) | |
download | systemd-de3ef2524e9f4ebcd2cee2c70daa78189dcfd339.tar.xz systemd-de3ef2524e9f4ebcd2cee2c70daa78189dcfd339.zip |
Merge pull request #20968 from poettering/homed-pin
homed: pin+lock homes while logged in + keep trying to unmount on logging out + optionally drop caches on logging out
-rw-r--r-- | TODO | 4 | ||||
-rw-r--r-- | man/homectl.xml | 11 | ||||
-rw-r--r-- | src/basic/socket-util.c | 2 | ||||
-rw-r--r-- | src/basic/socket-util.h | 3 | ||||
-rw-r--r-- | src/basic/user-util.c | 11 | ||||
-rw-r--r-- | src/basic/user-util.h | 2 | ||||
-rw-r--r-- | src/home/homectl.c | 23 | ||||
-rw-r--r-- | src/home/homed-home.c | 251 | ||||
-rw-r--r-- | src/home/homed-home.h | 40 | ||||
-rw-r--r-- | src/home/homed-manager.c | 152 | ||||
-rw-r--r-- | src/home/homework-luks.c | 41 | ||||
-rw-r--r-- | src/home/homework.c | 72 | ||||
-rw-r--r-- | src/home/homework.h | 1 | ||||
-rw-r--r-- | src/home/user-record-util.c | 2 | ||||
-rw-r--r-- | src/journal/journald-stream.c | 8 | ||||
-rw-r--r-- | src/libsystemd/sd-bus/bus-common-errors.c | 1 | ||||
-rw-r--r-- | src/libsystemd/sd-bus/bus-common-errors.h | 1 | ||||
-rw-r--r-- | src/libsystemd/sd-bus/sd-bus.c | 1 | ||||
-rw-r--r-- | src/libsystemd/sd-login/sd-login.c | 2 | ||||
-rw-r--r-- | src/shared/rm-rf.c | 3 | ||||
-rw-r--r-- | src/shared/rm-rf.h | 1 | ||||
-rw-r--r-- | src/shared/user-record-show.c | 3 | ||||
-rw-r--r-- | src/shared/user-record.c | 21 | ||||
-rw-r--r-- | src/shared/user-record.h | 2 | ||||
-rw-r--r-- | src/shared/varlink.c | 7 |
25 files changed, 547 insertions, 118 deletions
@@ -1248,10 +1248,6 @@ Features: fallback logic to get a regular user created on uninitialized systems. - store PKCS#11 + FIDO2 token info in LUKS2 header, compatible with systemd-cryptsetup, so that it can unlock homed volumes - - try to unmount in regular intervals when home dir was busy when we - tried because idle. - - keep an fd to the homedir open at all times, to keep the fs pinned - (autofs and such) while user is logged in. * add a new switch --auto-definitions=yes/no or so to systemd-repart. If specified, synthesize a definition automatically if we can: enlarge last diff --git a/man/homectl.xml b/man/homectl.xml index 245ebcee00..c2b1ec6c9b 100644 --- a/man/homectl.xml +++ b/man/homectl.xml @@ -611,6 +611,17 @@ </varlistentry> <varlistentry> + <term><option>--drop-caches=</option><replaceable>BOOL</replaceable></term> + + <listitem><para>Automatically flush OS file system caches on logout. This is useful in combination + with the fscrypt storage backend to ensure the OS does not keep decrypted versions of the files and + directories in memory (and accessible) after logout. This option is also supported on other backends, + but should not bring any benefit there. Defaults to off, except if the selected storage backend is + fscrypt, where it defaults to on. Note that flushing OS caches will negatively influence performance + of the OS shortly after logout.</para></listitem> + </varlistentry> + + <varlistentry> <term><option>--fs-type=</option><replaceable>TYPE</replaceable></term> <listitem><para>When LUKS2 storage is used configures the file system type to use inside the home diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 1e66f8700b..94ae90929a 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -551,7 +551,7 @@ int getpeername_pretty(int fd, bool include_port, char **ret) { return -errno; if (sa.sa.sa_family == AF_UNIX) { - struct ucred ucred = {}; + struct ucred ucred = UCRED_INVALID; /* UNIX connection sockets are anonymous, so let's use * PID/UID as pretty credentials instead */ diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index cb4a92236f..c4fafa084b 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -327,3 +327,6 @@ static inline int socket_set_recvfragsize(int fd, int af, bool b) { } int socket_get_mtu(int fd, int af, size_t *ret); + +/* an initializer for struct ucred that initialized all fields to the invalid value appropriate for each */ +#define UCRED_INVALID { .pid = 0, .uid = UID_INVALID, .gid = GID_INVALID } diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 385ec514b4..aee2d9dc59 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -1085,3 +1085,14 @@ int is_this_me(const char *username) { return uid == getuid(); } + +const char *get_home_root(void) { + const char *e; + + /* For debug purposes allow overriding where we look for home dirs */ + e = secure_getenv("SYSTEMD_HOME_ROOT"); + if (e && path_is_absolute(e) && path_is_normalized(e)) + return e; + + return "/home"; +} diff --git a/src/basic/user-util.h b/src/basic/user-util.h index fd00b47b76..ab1ce48b2d 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -112,6 +112,8 @@ bool is_nologin_shell(const char *shell); int is_this_me(const char *username); +const char *get_home_root(void); + /* A locked *and* invalid password for "struct spwd"'s .sp_pwdp and "struct passwd"'s .pw_passwd field */ #define PASSWORD_LOCKED_AND_INVALID "!*" diff --git a/src/home/homectl.c b/src/home/homectl.c index d5b699a242..3af1381eb4 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2131,6 +2131,7 @@ static int help(int argc, char *argv[], void *userdata) { " --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n" " subvolume, cifs)\n" " --image-path=PATH Path to image file/directory\n" + " --drop-caches=BOOL Whether to automatically drop caches on logout\n" "\n%4$sLUKS Storage User Record Properties:%5$s\n" " --fs-type=TYPE File system type to use in case of luks\n" " storage (btrfs, ext4, xfs)\n" @@ -2245,6 +2246,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_RECOVERY_KEY, ARG_AND_RESIZE, ARG_AND_CHANGE_PASSWORD, + ARG_DROP_CACHES, }; static const struct option options[] = { @@ -2327,6 +2329,7 @@ static int parse_argv(int argc, char *argv[]) { { "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY }, { "and-resize", required_argument, NULL, ARG_AND_RESIZE }, { "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD }, + { "drop-caches", required_argument, NULL, ARG_DROP_CACHES }, {} }; @@ -3450,6 +3453,26 @@ static int parse_argv(int argc, char *argv[]) { arg_and_change_password = true; break; + case ARG_DROP_CACHES: { + bool drop_caches; + + if (isempty(optarg)) { + r = drop_from_identity("dropCaches"); + if (r < 0) + return r; + } + + r = parse_boolean_argument("--drop-caches=", optarg, &drop_caches); + if (r < 0) + return r; + + r = json_variant_set_field_boolean(&arg_identity_extra, "dropCaches", r); + if (r < 0) + return log_error_errno(r, "Failed to set drop caches field: %m"); + + break; + } + case '?': return -EINVAL; diff --git a/src/home/homed-home.c b/src/home/homed-home.c index bbdc6940f3..d8f192650e 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -38,6 +38,9 @@ #include "user-record.h" #include "user-util.h" +/* Retry to deactivate home directories again and again every 15s until it works */ +#define RETRY_DEACTIVATE_USEC (15U * USEC_PER_SEC) + #define HOME_USERS_MAX 500 #define PENDING_OPERATIONS_MAX 100 @@ -130,6 +133,8 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) { .worker_stdout_fd = -1, .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); @@ -203,6 +208,11 @@ 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); + return mfree(h); } @@ -317,6 +327,141 @@ int home_unlink_record(Home *h) { return 0; } +static void home_unpin(Home *h) { + assert(h); + + if (h->pin_fd < 0) + return; + + h->pin_fd = safe_close(h->pin_fd); + log_debug("Successfully closed pin fd on home for %s.", h->user_name); +} + +static void home_pin(Home *h) { + const char *path; + + assert(h); + + if (h->pin_fd >= 0) /* Already pinned? */ + return; + + path = user_record_home_directory(h->record); + if (!path) { + log_warning("No home directory path to pin for %s, ignoring.", h->user_name); + return; + } + + h->pin_fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (h->pin_fd < 0) { + log_warning_errno(errno, "Couldn't open home directory '%s' for pinning, ignoring: %m", path); + return; + } + + log_debug("Successfully pinned home directory '%s'.", path); +} + +static void home_update_pin_fd(Home *h, HomeState state) { + assert(h); + + if (state < 0) + state = home_get_state(h); + + 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); + + /* Free the deactivation retry event source if we won't need it anymore. Specifically, we'll free the + * event source whenever the home directory is already deactivated (and we thus where successful) or + * if we start executing an operation that indicates that the home directory is going to be used or + * operated on again. Also, if the home is referenced again stop the timer */ + + if (HOME_STATE_MAY_RETRY_DEACTIVATE(state) && + !h->ref_event_source_dont_suspend && + !h->ref_event_source_please_suspend) + return; + + h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source); +} + +static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error); +static void home_start_retry_deactivate(Home *h); + +static int home_on_retry_deactivate(sd_event_source *s, uint64_t usec, void *userdata) { + Home *h = userdata; + HomeState state; + + assert(s); + assert(h); + + /* 15s after the last attempt to deactivate the home directory passed. Let's try it one more time. */ + + h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source); + + state = home_get_state(h); + if (!HOME_STATE_MAY_RETRY_DEACTIVATE(state)) + return 0; + + if (IN_SET(state, HOME_ACTIVE, HOME_LINGERING)) { + log_info("Again trying to deactivate home directory."); + + /* If we are not executing any operation, let's start deactivating now. Note that this will + * restart our timer again, we are gonna be called again if this doesn't work. */ + (void) home_deactivate_internal(h, /* force= */ false, NULL); + } else + /* if we are executing an operation (specifically, area already running a deactivation + * operation), then simply reque the timer, so that we retry again. */ + home_start_retry_deactivate(h); + + return 0; +} + +static void home_start_retry_deactivate(Home *h) { + int r; + + assert(h); + assert(h->manager); + + /* Alrady allocated? */ + if (h->retry_deactivate_event_source) + return; + + /* If the home directory is being used now don't start the timer */ + if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend) + return; + + r = sd_event_add_time_relative( + h->manager->event, + &h->retry_deactivate_event_source, + CLOCK_MONOTONIC, + RETRY_DEACTIVATE_USEC, + 1*USEC_PER_MINUTE, + home_on_retry_deactivate, + h); + if (r < 0) + return (void) log_warning_errno(r, "Failed to install retry-deactivate event source, ignoring: %m"); + + (void) sd_event_source_set_description(h->retry_deactivate_event_source, "retry-deactivate"); +} + static void home_set_state(Home *h, HomeState state) { HomeState old_state, new_state; @@ -331,6 +476,10 @@ static void home_set_state(Home *h, HomeState state) { home_state_to_string(old_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)) { /* If we just finished executing some operation, process the queue of pending operations. And * enqueue it for GC too. */ @@ -483,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; @@ -1029,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"); @@ -1149,6 +1306,7 @@ int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error) { case HOME_INACTIVE: case HOME_DIRTY: case HOME_ACTIVE: + case HOME_LINGERING: case HOME_LOCKED: return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_FIXATED, "Home %s is already fixated.", h->user_name); case HOME_UNFIXATED: @@ -1190,6 +1348,11 @@ int home_activate(Home *h, UserRecord *secret, sd_bus_error *error) { return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name); case HOME_ACTIVE: return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_ACTIVE, "Home %s is already active.", h->user_name); + case HOME_LINGERING: + /* If we are lingering, i.e. active but are supposed to be deactivated, then cancel this + * timer if the user explicitly asks us to be active */ + h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source); + return 0; case HOME_LOCKED: return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name); case HOME_INACTIVE: @@ -1236,6 +1399,7 @@ int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) { case HOME_INACTIVE: case HOME_DIRTY: case HOME_ACTIVE: + case HOME_LINGERING: break; default: return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name); @@ -1245,7 +1409,7 @@ int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) { if (r < 0) return r; - return home_authenticate_internal(h, secret, state == HOME_ACTIVE ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING, error); + return home_authenticate_internal(h, secret, HOME_STATE_IS_ACTIVE(state) ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING, error); } static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) { @@ -1253,12 +1417,22 @@ static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) { assert(h); + home_unpin(h); /* unpin so that we can deactivate */ + r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL); if (r < 0) - return r; + /* Operation failed before it even started, reacquire pin fd, if state still dictates so */ + home_update_pin_fd(h, _HOME_STATE_INVALID); + else { + home_set_state(h, HOME_DEACTIVATING); + r = 0; + } - home_set_state(h, HOME_DEACTIVATING); - return 0; + /* Let's start a timer to retry deactivation in 15. We'll stop the timer once we manage to deactivate + * the home directory again, or we we start any other operation. */ + home_start_retry_deactivate(h); + + return r; } int home_deactivate(Home *h, bool force, sd_bus_error *error) { @@ -1273,6 +1447,7 @@ int home_deactivate(Home *h, bool force, sd_bus_error *error) { case HOME_LOCKED: return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name); case HOME_ACTIVE: + case HOME_LINGERING: break; default: return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name); @@ -1309,6 +1484,7 @@ int home_create(Home *h, UserRecord *secret, sd_bus_error *error) { case HOME_ABSENT: break; case HOME_ACTIVE: + case HOME_LINGERING: case HOME_LOCKED: default: return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name); @@ -1347,6 +1523,7 @@ int home_remove(Home *h, sd_bus_error *error) { case HOME_DIRTY: break; case HOME_ACTIVE: + case HOME_LINGERING: default: return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name); } @@ -1485,6 +1662,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) { case HOME_INACTIVE: case HOME_DIRTY: case HOME_ACTIVE: + case HOME_LINGERING: break; default: return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name); @@ -1498,7 +1676,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) { if (r < 0) return r; - home_set_state(h, state == HOME_ACTIVE ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING); + home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING); return 0; } @@ -1520,6 +1698,7 @@ int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *e case HOME_INACTIVE: case HOME_DIRTY: case HOME_ACTIVE: + case HOME_LINGERING: break; default: return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name); @@ -1568,7 +1747,7 @@ int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *e if (r < 0) return r; - home_set_state(h, state == HOME_ACTIVE ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING); + home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING); return 0; } @@ -1616,6 +1795,7 @@ int home_passwd(Home *h, case HOME_INACTIVE: case HOME_DIRTY: case HOME_ACTIVE: + case HOME_LINGERING: break; default: return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name); @@ -1681,7 +1861,7 @@ int home_passwd(Home *h, if (r < 0) return r; - home_set_state(h, state == HOME_ACTIVE ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD); + home_set_state(h, HOME_STATE_IS_ACTIVE(state) ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD); return 0; } @@ -1700,6 +1880,7 @@ int home_unregister(Home *h, sd_bus_error *error) { case HOME_DIRTY: break; case HOME_ACTIVE: + case HOME_LINGERING: default: return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name); } @@ -1727,6 +1908,7 @@ int home_lock(Home *h, sd_bus_error *error) { case HOME_LOCKED: return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is already locked.", h->user_name); case HOME_ACTIVE: + case HOME_LINGERING: break; default: return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name); @@ -1767,6 +1949,7 @@ int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error) { case HOME_ABSENT: case HOME_INACTIVE: case HOME_ACTIVE: + case HOME_LINGERING: case HOME_DIRTY: return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_LOCKED, "Home %s is not locked.", h->user_name); case HOME_LOCKED: @@ -1789,7 +1972,7 @@ HomeState home_get_state(Home *h) { /* Otherwise, let's see if the home directory is mounted. If so, we assume for sure the home * directory is active */ if (user_record_test_home_directory(h->record) == USER_TEST_MOUNTED) - return HOME_ACTIVE; + return h->retry_deactivate_event_source ? HOME_LINGERING : HOME_ACTIVE; /* And if we see the image being gone, we report this as absent */ r = user_record_test_image_path(h->record); @@ -1802,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; } @@ -2372,6 +2576,7 @@ static int home_dispatch_acquire(Home *h, Operation *o) { break; case HOME_ACTIVE: + case HOME_LINGERING: for_state = HOME_AUTHENTICATING_FOR_ACQUIRE; call = home_authenticate_internal; break; @@ -2426,6 +2631,7 @@ static int home_dispatch_release(Home *h, Operation *o) { break; case HOME_ACTIVE: + case HOME_LINGERING: r = home_deactivate_internal(h, false, &error); break; @@ -2470,6 +2676,7 @@ static int home_dispatch_lock_all(Home *h, Operation *o) { break; case HOME_ACTIVE: + case HOME_LINGERING: log_info("Locking home %s.", h->user_name); r = home_lock(h, &error); break; @@ -2514,6 +2721,7 @@ static int home_dispatch_deactivate_all(Home *h, Operation *o) { break; case HOME_ACTIVE: + case HOME_LINGERING: log_info("Deactivating home %s.", h->user_name); r = home_deactivate_internal(h, false, &error); break; @@ -2559,6 +2767,7 @@ static int home_dispatch_pipe_eof(Home *h, Operation *o) { break; case HOME_ACTIVE: + case HOME_LINGERING: r = home_deactivate_internal(h, false, &error); if (r < 0) log_warning_errno(r, "Failed to deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r)); @@ -2600,6 +2809,7 @@ static int home_dispatch_deactivate_force(Home *h, Operation *o) { case HOME_ACTIVE: case HOME_LOCKED: + case HOME_LINGERING: r = home_deactivate_internal(h, true, &error); if (r < 0) log_warning_errno(r, "Failed to forcibly deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r)); @@ -2820,6 +3030,7 @@ static const char* const home_state_table[_HOME_STATE_MAX] = { [HOME_ACTIVATING_FOR_ACQUIRE] = "activating-for-acquire", [HOME_DEACTIVATING] = "deactivating", [HOME_ACTIVE] = "active", + [HOME_LINGERING] = "lingering", [HOME_LOCKING] = "locking", [HOME_LOCKED] = "locked", [HOME_UNLOCKING] = "unlocking", diff --git a/src/home/homed-home.h b/src/home/homed-home.h index 7f531a234c..7cd8a9edb4 100644 --- a/src/home/homed-home.h +++ b/src/home/homed-home.h @@ -21,6 +21,7 @@ typedef enum HomeState { HOME_ACTIVATING_FOR_ACQUIRE, /* activating because Acquire() was called */ HOME_DEACTIVATING, HOME_ACTIVE, /* logged in right now */ + HOME_LINGERING, /* not logged in anymore, but we didn't manage to deactivate (because some process keeps it busy?) but we'll keep trying */ HOME_LOCKING, HOME_LOCKED, HOME_UNLOCKING, @@ -43,6 +44,7 @@ typedef enum HomeState { static inline bool HOME_STATE_IS_ACTIVE(HomeState state) { return IN_SET(state, HOME_ACTIVE, + HOME_LINGERING, HOME_UPDATING_WHILE_ACTIVE, HOME_RESIZING_WHILE_ACTIVE, HOME_PASSWD_WHILE_ACTIVE, @@ -74,6 +76,33 @@ static inline bool HOME_STATE_IS_EXECUTING_OPERATION(HomeState state) { HOME_AUTHENTICATING_FOR_ACQUIRE); } +static inline bool HOME_STATE_SHALL_PIN(HomeState state) { + /* Like HOME_STATE_IS_ACTIVE() – but HOME_LINGERING is missing! */ + return IN_SET(state, + HOME_ACTIVE, + HOME_UPDATING_WHILE_ACTIVE, + HOME_RESIZING_WHILE_ACTIVE, + HOME_PASSWD_WHILE_ACTIVE, + HOME_AUTHENTICATING_WHILE_ACTIVE, + HOME_AUTHENTICATING_FOR_ACQUIRE); +} + +static inline bool HOME_STATE_MAY_RETRY_DEACTIVATE(HomeState state) { + /* Indicates when to leave the deactivate retry timer active */ + return IN_SET(state, + HOME_ACTIVE, + HOME_LINGERING, + HOME_DEACTIVATING, + HOME_LOCKING, + HOME_UNLOCKING, + HOME_UNLOCKING_FOR_ACQUIRE, + HOME_UPDATING_WHILE_ACTIVE, + HOME_RESIZING_WHILE_ACTIVE, + HOME_PASSWD_WHILE_ACTIVE, + HOME_AUTHENTICATING_WHILE_ACTIVE, + HOME_AUTHENTICATING_FOR_ACQUIRE); +} + struct Home { Manager *manager; char *user_name; @@ -126,6 +155,15 @@ struct Home { /* Used to coalesce bus PropertiesChanged events */ sd_event_source *deferred_change_event_source; + + /* An fd to the top-level home directory we keep while logged in, to keep the dir busy */ + int pin_fd; + + /* 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); @@ -152,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 070fd97d69..38283ff1ed 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -83,35 +83,38 @@ static void manager_watch_home(Manager *m) { m->inotify_event_source = sd_event_source_disable_unref(m->inotify_event_source); m->scan_slash_home = false; - if (statfs("/home/", &sfs) < 0) { + if (statfs(get_home_root(), &sfs) < 0) { log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, - "Failed to statfs() /home/ directory, disabling automatic scanning."); + "Failed to statfs() %s directory, disabling automatic scanning.", get_home_root()); return; } if (is_network_fs(&sfs)) { - log_info("/home/ is a network file system, disabling automatic scanning."); + log_info("%s is a network file system, disabling automatic scanning.", get_home_root()); return; } if (is_fs_type(&sfs, AUTOFS_SUPER_MAGIC)) { - log_info("/home/ is on autofs, disabling automatic scanning."); + log_info("%s is on autofs, disabling automatic scanning.", get_home_root()); return; } m->scan_slash_home = true; - r = sd_event_add_inotify(m->event, &m->inotify_event_source, "/home/", + r = sd_event_add_inotify(m->event, &m->inotify_event_source, get_home_root(), IN_CREATE|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF|IN_ONLYDIR|IN_MOVED_TO|IN_MOVED_FROM|IN_DELETE, on_home_inotify, m); if (r < 0) log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, - "Failed to create inotify watch on /home/, ignoring."); + "Failed to create inotify watch on %s, ignoring.", get_home_root()); (void) sd_event_source_set_description(m->inotify_event_source, "home-inotify"); + + log_info("Watching %s.", get_home_root()); } static int on_home_inotify(sd_event_source *s, const struct inotify_event *event, void *userdata) { + _cleanup_free_ char *j = NULL; Manager *m = userdata; const char *e, *n; @@ -121,15 +124,15 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event if ((event->mask & (IN_Q_OVERFLOW|IN_MOVE_SELF|IN_DELETE_SELF|IN_IGNORED|IN_UNMOUNT)) != 0) { if (FLAGS_SET(event->mask, IN_Q_OVERFLOW)) - log_debug("/home/ inotify queue overflow, rescanning."); + log_debug("%s inotify queue overflow, rescanning.", get_home_root()); else if (FLAGS_SET(event->mask, IN_MOVE_SELF)) - log_info("/home/ moved or renamed, recreating watch and rescanning."); + log_info("%s moved or renamed, recreating watch and rescanning.", get_home_root()); else if (FLAGS_SET(event->mask, IN_DELETE_SELF)) - log_info("/home/ deleted, recreating watch and rescanning."); + log_info("%s deleted, recreating watch and rescanning.", get_home_root()); else if (FLAGS_SET(event->mask, IN_UNMOUNT)) - log_info("/home/ unmounted, recreating watch and rescanning."); + log_info("%s unmounted, recreating watch and rescanning.", get_home_root()); else if (FLAGS_SET(event->mask, IN_IGNORED)) - log_info("/home/ watch invalidated, recreating watch and rescanning."); + log_info("%s watch invalidated, recreating watch and rescanning.", get_home_root()); manager_watch_home(m); (void) manager_gc_images(m); @@ -150,15 +153,19 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event if (!suitable_user_name(n)) return 0; + j = path_join(get_home_root(), event->name); + if (!j) + return log_oom(); + if ((event->mask & (IN_CREATE|IN_CLOSE_WRITE|IN_MOVED_TO)) != 0) { if (FLAGS_SET(event->mask, IN_CREATE)) - log_debug("/home/%s has been created, having a look.", event->name); + log_debug("%s has been created, having a look.", j); else if (FLAGS_SET(event->mask, IN_CLOSE_WRITE)) - log_debug("/home/%s has been modified, having a look.", event->name); + log_debug("%s has been modified, having a look.", j); else if (FLAGS_SET(event->mask, IN_MOVED_TO)) - log_debug("/home/%s has been moved in, having a look.", event->name); + log_debug("%s has been moved in, having a look.", j); - (void) manager_assess_image(m, -1, "/home/", event->name); + (void) manager_assess_image(m, -1, get_home_root(), event->name); (void) bus_manager_emit_auto_login_changed(m); } @@ -166,11 +173,11 @@ static int on_home_inotify(sd_event_source *s, const struct inotify_event *event Home *h; if (FLAGS_SET(event->mask, IN_DELETE)) - log_debug("/home/%s has been deleted, revalidating.", event->name); + log_debug("%s has been deleted, revalidating.", j); else if (FLAGS_SET(event->mask, IN_CLOSE_WRITE)) - log_debug("/home/%s has been closed after writing, revalidating.", event->name); + log_debug("%s has been closed after writing, revalidating.", j); else if (FLAGS_SET(event->mask, IN_MOVED_FROM)) - log_debug("/home/%s has been moved away, revalidating.", event->name); + log_debug("%s has been moved away, revalidating.", j); h = hashmap_get(m->homes_by_name, n); if (h) { @@ -487,7 +494,7 @@ static int search_quota(uid_t uid, const char *exclude_quota_path) { * comprehensive, but should cover most cases. Note that in an ideal world every user would be * registered in NSS and avoid our own UID range, but for all other cases, it's a good idea to be * paranoid and check quota if we can. */ - FOREACH_STRING(where, "/home/", "/tmp/", "/var/", "/var/mail/", "/var/tmp/", "/var/spool/") { + FOREACH_STRING(where, get_home_root(), "/tmp/", "/var/", "/var/mail/", "/var/tmp/", "/var/spool/") { struct dqblk req; struct stat st; @@ -914,13 +921,13 @@ int manager_enumerate_images(Manager *m) { if (!m->scan_slash_home) return 0; - d = opendir("/home/"); + d = opendir(get_home_root()); if (!d) return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, - "Failed to open /home/: %m"); + "Failed to open %s: %m", get_home_root()); - FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read /home/ directory: %m")) - (void) manager_assess_image(m, dirfd(d), "/home", de->d_name); + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s directory: %m", get_home_root())) + (void) manager_assess_image(m, dirfd(d), get_home_root(), de->d_name); return 0; } @@ -1010,13 +1017,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 +1045,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 +1103,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 +1112,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 +1133,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 c9c2476ed6..30b63e3481 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -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"); diff --git a/src/home/homework.c b/src/home/homework.c index ee1d4068ba..ae3fffed02 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -28,6 +28,7 @@ #include "rm-rf.h" #include "stat-util.h" #include "strv.h" +#include "sync-util.h" #include "tmpfile-util.h" #include "user-util.h" #include "virt.h" @@ -283,6 +284,20 @@ int user_record_authenticate( return 0; } +static void drop_caches_now(void) { + int r; + + /* Drop file system caches now. See https://www.kernel.org/doc/Documentation/sysctl/vm.txt for + * details. We write "2" into /proc/sys/vm/drop_caches to ensure dentries/inodes are flushed, but not + * more. */ + + r = write_string_file("/proc/sys/vm/drop_caches", "2\n", WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + log_warning_errno(r, "Failed to drop caches, ignoring: %m"); + else + log_debug("Dropped caches."); +} + int home_setup_undo(HomeSetup *setup) { int r = 0, q; @@ -295,6 +310,9 @@ int home_setup_undo(HomeSetup *setup) { r = q; } + if (syncfs(setup->root_fd) < 0) + log_debug_errno(errno, "Failed to synchronize home directory, ignoring: %m"); + setup->root_fd = safe_close(setup->root_fd); } @@ -345,6 +363,9 @@ int home_setup_undo(HomeSetup *setup) { setup->volume_key = mfree(setup->volume_key); setup->volume_key_size = 0; + if (setup->do_drop_caches) + drop_caches_now(); + return r; } @@ -367,6 +388,9 @@ int home_prepare( /* Makes a home directory accessible (through the root_fd file descriptor, not by path!). */ + if (!already_activated) /* If we set up the directory, we should also drop caches once we are done */ + setup->do_drop_caches = setup->do_drop_caches || user_record_drop_caches(h); + switch (user_record_storage(h)) { case USER_LUKS: @@ -827,6 +851,13 @@ static int home_deactivate(UserRecord *h, bool force) { return r; } + /* Sync explicitly, so that the drop caches logic below can work as documented */ + r = syncfs_path(AT_FDCWD, user_record_home_directory(h)); + if (r < 0) + log_debug_errno(r, "Failed to synchronize home directory, ignoring: %m"); + else + log_info("Syncing completed."); + if (umount2(user_record_home_directory(h), UMOUNT_NOFOLLOW | (force ? MNT_FORCE|MNT_DETACH : 0)) < 0) return log_error_errno(errno, "Failed to unmount %s: %m", user_record_home_directory(h)); @@ -846,6 +877,9 @@ static int home_deactivate(UserRecord *h, bool force) { if (!done) return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home is not active."); + if (user_record_drop_caches(h)) + drop_caches_now(); + log_info("Everything completed."); return 0; } @@ -1095,12 +1129,12 @@ static int determine_default_storage(UserStorage *ret) { if (r < 0) return log_error_errno(r, "Failed to determine whether we are in a container: %m"); if (r == 0) { - r = path_is_encrypted("/home"); + r = path_is_encrypted(get_home_root()); if (r > 0) - log_info("/home is encrypted, not using '%s' storage, in order to avoid double encryption.", user_storage_to_string(USER_LUKS)); + log_info("%s is encrypted, not using '%s' storage, in order to avoid double encryption.", get_home_root(), user_storage_to_string(USER_LUKS)); else { if (r < 0) - log_warning_errno(r, "Failed to determine if /home is encrypted, ignoring: %m"); + log_warning_errno(r, "Failed to determine if %s is encrypted, ignoring: %m", get_home_root()); r = dlopen_cryptsetup(); if (r < 0) @@ -1114,14 +1148,14 @@ static int determine_default_storage(UserStorage *ret) { } else log_info("Running in container, not using '%s' storage.", user_storage_to_string(USER_LUKS)); - r = path_is_fs_type("/home", BTRFS_SUPER_MAGIC); + r = path_is_fs_type(get_home_root(), BTRFS_SUPER_MAGIC); if (r < 0) - log_warning_errno(r, "Failed to determine file system of /home, ignoring: %m"); + log_warning_errno(r, "Failed to determine file system of %s, ignoring: %m", get_home_root()); if (r > 0) { - log_info("/home is on btrfs, using '%s' as storage.", user_storage_to_string(USER_SUBVOLUME)); + log_info("%s is on btrfs, using '%s' as storage.", get_home_root(), user_storage_to_string(USER_SUBVOLUME)); *ret = USER_SUBVOLUME; } else { - log_info("/home is on simple file system, using '%s' as storage.", user_storage_to_string(USER_DIRECTORY)); + log_info("%s is on simple file system, using '%s' as storage.", get_home_root(), user_storage_to_string(USER_DIRECTORY)); *ret = USER_DIRECTORY; } @@ -1268,9 +1302,21 @@ static int home_remove(UserRecord *h) { if (unlink(ip) < 0) { if (errno != ENOENT) return log_error_errno(errno, "Failed to remove %s: %m", ip); - } else + } else { + _cleanup_free_ char *parent = NULL; + deleted = true; + r = path_extract_directory(ip, &parent); + if (r < 0) + log_debug_errno(r, "Failed to determine parent directory of '%s': %m", ip); + else { + r = fsync_path_at(AT_FDCWD, parent); + if (r < 0) + log_debug_errno(r, "Failed to synchronize disk after deleting '%s', ignoring: %m", ip); + } + } + } else if (S_ISBLK(st.st_mode)) log_info("Not removing file system on block device %s.", ip); else @@ -1285,7 +1331,7 @@ static int home_remove(UserRecord *h) { case USER_FSCRYPT: assert(ip); - r = rm_rf(ip, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + r = rm_rf(ip, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_SYNCFS); if (r < 0) { if (r != -ENOENT) return log_warning_errno(r, "Failed to remove %s: %m", ip); @@ -1316,9 +1362,12 @@ static int home_remove(UserRecord *h) { deleted = true; } - if (deleted) + if (deleted) { + if (user_record_drop_caches(h)) + drop_caches_now(); + log_info("Everything completed."); - else + } else return log_notice_errno(SYNTHETIC_ERRNO(EALREADY), "Nothing to remove."); @@ -1706,6 +1755,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/home/homework.h b/src/home/homework.h index fb53fd49b0..f20a23a918 100644 --- a/src/home/homework.h +++ b/src/home/homework.h @@ -32,6 +32,7 @@ typedef struct HomeSetup { bool do_offline_fitrim; bool do_offline_fallocate; bool do_mark_clean; + bool do_drop_caches; uint64_t partition_offset; uint64_t partition_size; diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index bc0c4171b4..464f1dbb7a 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -75,7 +75,7 @@ int user_record_synthesize( if (!ip) return -ENOMEM; - hd = path_join("/home/", user_name); + hd = path_join(get_home_root(), user_name); if (!hd) return -ENOMEM; diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 0a90091a86..cbff5036a4 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -36,6 +36,7 @@ #include "syslog-util.h" #include "tmpfile-util.h" #include "unit-name.h" +#include "user-util.h" #define STDOUT_STREAMS_MAX 4096 @@ -663,6 +664,7 @@ int stdout_stream_install(Server *s, int fd, StdoutStream **ret) { *stream = (StdoutStream) { .fd = -1, .priority = LOG_INFO, + .ucred = UCRED_INVALID, }; xsprintf(stream->id_field, "_STREAM_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)); @@ -727,9 +729,9 @@ static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revent } if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) { - struct ucred u; + struct ucred u = UCRED_INVALID; - r = getpeercred(fd, &u); + (void) getpeercred(fd, &u); /* By closing fd here we make sure that the client won't wait too long for journald to * gather all the data it adds to the error message to find out that the connection has @@ -737,7 +739,7 @@ static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revent */ fd = safe_close(fd); - server_driver_message(s, r < 0 ? 0 : u.pid, NULL, LOG_MESSAGE("Too many stdout streams, refusing connection."), NULL); + server_driver_message(s, u.pid, NULL, LOG_MESSAGE("Too many stdout streams, refusing connection."), NULL); return 0; } diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 43f9a7bdaa..61c5509e1c 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 fd8f0c240e..348cd5094b 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); diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index 80f2bdd87f..d8a7c81764 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -249,6 +249,7 @@ _public_ int sd_bus_new(sd_bus **ret) { .original_pid = getpid_cached(), .n_groups = SIZE_MAX, .close_on_exit = true, + .ucred = UCRED_INVALID, }; /* We guarantee that wqueue always has space for at least one entry */ diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c index d127443c4c..4a35e61425 100644 --- a/src/libsystemd/sd-login/sd-login.c +++ b/src/libsystemd/sd-login/sd-login.c @@ -136,7 +136,7 @@ _public_ int sd_pid_get_cgroup(pid_t pid, char **cgroup) { } _public_ int sd_peer_get_session(int fd, char **session) { - struct ucred ucred = {}; + struct ucred ucred = UCRED_INVALID; int r; assert_return(fd >= 0, -EBADF); diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c index d9cd8fb2b2..a8693660fa 100644 --- a/src/shared/rm-rf.c +++ b/src/shared/rm-rf.c @@ -250,6 +250,9 @@ int rm_rf_children( ret = r; } + if (FLAGS_SET(flags, REMOVE_SYNCFS) && syncfs(dirfd(d)) < 0 && ret >= 0) + ret = -errno; + return ret; } diff --git a/src/shared/rm-rf.h b/src/shared/rm-rf.h index 577a2795e0..24fd9a2aa2 100644 --- a/src/shared/rm-rf.h +++ b/src/shared/rm-rf.h @@ -14,6 +14,7 @@ typedef enum RemoveFlags { REMOVE_MISSING_OK = 1 << 4, /* If the top-level directory is missing, ignore the ENOENT for it */ REMOVE_CHMOD = 1 << 5, /* chmod() for write access if we cannot delete or access something */ REMOVE_CHMOD_RESTORE = 1 << 6, /* Restore the old mode before returning */ + REMOVE_SYNCFS = 1 << 7, /* syncfs() the root of the specified directory after removing everything in it */ } RemoveFlags; int unlinkat_harder(int dfd, const char *filename, int unlink_flags, RemoveFlags remove_flags); diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index 29aa5c0c7c..dee6c4e5ee 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -435,6 +435,9 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { if (hr->password_change_now >= 0) printf("Pas. Ch. Now: %s\n", yes_no(hr->password_change_now)); + if (hr->drop_caches >= 0 || user_record_drop_caches(hr)) + printf(" Drop Caches: %s\n", yes_no(user_record_drop_caches(hr))); + if (!strv_isempty(hr->ssh_authorized_keys)) printf("SSH Pub. Key: %zu\n", strv_length(hr->ssh_authorized_keys)); diff --git a/src/shared/user-record.c b/src/shared/user-record.c index f4e509e13e..9b2029bfcf 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -202,6 +202,7 @@ UserRecord* user_record_new(void) { .pkcs11_protected_authentication_path_permitted = -1, .fido2_user_presence_permitted = -1, .fido2_user_verification_permitted = -1, + .drop_caches = -1, }; return h; @@ -1284,6 +1285,7 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp { "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 }, { "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 }, { "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 }, + { "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 }, { "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 }, { "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 }, { "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 }, @@ -1406,11 +1408,11 @@ int user_record_build_image_path(UserStorage storage, const char *user_name_and_ return 0; } - z = strjoin("/home/", user_name_and_realm, suffix); + z = strjoin(get_home_root(), "/", user_name_and_realm, suffix); if (!z) return -ENOMEM; - *ret = z; + *ret = path_simplify(z); return 1; } @@ -1435,7 +1437,7 @@ static int user_record_augment(UserRecord *h, JsonDispatchFlags json_flags) { return 0; if (!h->home_directory && !h->home_directory_auto) { - h->home_directory_auto = path_join("/home/", h->user_name); + h->home_directory_auto = path_join(get_home_root(), h->user_name); if (!h->home_directory_auto) return json_log_oom(h->json, json_flags); } @@ -1620,7 +1622,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla { "luksUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, luks_uuid), 0 }, { "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 }, { "luksDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_discard), 0 }, - { "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0 }, + { "luksOfflineDiscard", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, luks_offline_discard), 0 }, { "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE }, { "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE }, { "luksVolumeKeySize", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_volume_key_size), 0 }, @@ -1629,6 +1631,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla { "luksPbkdfTimeCostUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_time_cost_usec), 0 }, { "luksPbkdfMemoryCost", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_memory_cost), 0 }, { "luksPbkdfParallelThreads", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, luks_pbkdf_parallel_threads), 0 }, + { "dropCaches", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, drop_caches), 0 }, { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE }, { "rateLimitIntervalUSec", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_interval_usec), 0 }, { "rateLimitBurst", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 }, @@ -2021,6 +2024,16 @@ bool user_record_can_authenticate(UserRecord *h) { return !strv_isempty(h->hashed_password); } +bool user_record_drop_caches(UserRecord *h) { + assert(h); + + if (h->drop_caches >= 0) + return h->drop_caches; + + /* By default drop caches on fscrypt, not otherwise. */ + return user_record_storage(h) == USER_FSCRYPT; +} + uint64_t user_record_ratelimit_next_try(UserRecord *h) { assert(h); diff --git a/src/shared/user-record.h b/src/shared/user-record.h index fa58dfdb6e..975e3e175b 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -353,6 +353,7 @@ typedef struct UserRecord { int removable; int enforce_password_policy; int auto_login; + int drop_caches; uint64_t stop_delay_usec; /* How long to leave systemd --user around on log-out */ int kill_processes; /* Whether to kill user processes forcibly on log-out */ @@ -419,6 +420,7 @@ int user_record_removable(UserRecord *h); usec_t user_record_ratelimit_interval_usec(UserRecord *h); uint64_t user_record_ratelimit_burst(UserRecord *h); bool user_record_can_authenticate(UserRecord *h); +bool user_record_drop_caches(UserRecord *h); int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret); diff --git a/src/shared/varlink.c b/src/shared/varlink.c index 07a1b96f60..c34a08cf57 100644 --- a/src/shared/varlink.c +++ b/src/shared/varlink.c @@ -258,8 +258,7 @@ static int varlink_new(Varlink **ret) { .state = _VARLINK_STATE_INVALID, - .ucred.uid = UID_INVALID, - .ucred.gid = GID_INVALID, + .ucred = UCRED_INVALID, .timestamp = USEC_INFINITY, .timeout = VARLINK_DEFAULT_TIMEOUT_USEC @@ -2077,7 +2076,7 @@ static int validate_connection(VarlinkServer *server, const struct ucred *ucred) return 1; } -static int count_connection(VarlinkServer *server, struct ucred *ucred) { +static int count_connection(VarlinkServer *server, const struct ucred *ucred) { unsigned c; int r; @@ -2106,8 +2105,8 @@ static int count_connection(VarlinkServer *server, struct ucred *ucred) { int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) { _cleanup_(varlink_unrefp) Varlink *v = NULL; + struct ucred ucred = UCRED_INVALID; bool ucred_acquired; - struct ucred ucred; int r; assert_return(server, -EINVAL); |