diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 134df7b945..fd8f252dac 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -33,6 +33,7 @@ #include "homework-mount.h" #include "id128-util.h" #include "io-util.h" +#include "keyring-util.h" #include "memory-util.h" #include "missing_magic.h" #include "mkdir.h" @@ -247,15 +248,60 @@ static int run_fsck(const char *node, const char *fstype) { return 1; } +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(key_serial_t, keyring_unlink, -1); + +static int upload_to_keyring( + UserRecord *h, + const char *password, + key_serial_t *ret_key_serial) { + + _cleanup_free_ char *name = NULL; + key_serial_t serial; + + assert(h); + assert(password); + + /* If auto-shrink-on-logout is turned on, we need to keep the key we used to unlock the LUKS volume + * around, since we'll need it when automatically resizing (since we can't ask the user there + * again). We do this by uploading it into the kernel keyring, specifically the "session" one. This + * is done under the assumption systemd-homed gets its private per-session keyring (i.e. default + * service behaviour, given that KeyringMode=private is the default). It will survive between our + * systemd-homework invocations that way. + * + * If auto-shrink-on-logout is disabled we'll skip this step, to be frugal with sensitive data. */ + + if (user_record_auto_resize_mode(h) != AUTO_RESIZE_SHRINK_AND_GROW) { /* Won't need it */ + if (ret_key_serial) + *ret_key_serial = -1; + return 0; + } + + name = strjoin("homework-user-", h->user_name); + if (!name) + return -ENOMEM; + + serial = add_key("user", name, password, strlen(password), KEY_SPEC_SESSION_KEYRING); + if (serial == -1) + return -errno; + + if (ret_key_serial) + *ret_key_serial = serial; + + return 1; +} + static int luks_try_passwords( + UserRecord *h, struct crypt_device *cd, char **passwords, void *volume_key, - size_t *volume_key_size) { + size_t *volume_key_size, + key_serial_t *ret_key_serial) { char **pp; int r; + assert(h); assert(cd); STRV_FOREACH(pp, passwords) { @@ -269,6 +315,16 @@ static int luks_try_passwords( *pp, strlen(*pp)); if (r >= 0) { + if (ret_key_serial) { + /* If ret_key_serial is non-NULL, let's try to upload the password that + * worked, and return its serial. */ + r = upload_to_keyring(h, *pp, ret_key_serial); + if (r < 0) { + log_debug_errno(r, "Failed to upload LUKS password to kernel keyring, ignoring: %m"); + *ret_key_serial = -1; + } + } + *volume_key_size = vks; return 0; } @@ -280,6 +336,7 @@ static int luks_try_passwords( } static int luks_setup( + UserRecord *h, const char *node, const char *dm_name, sd_id128_t uuid, @@ -292,8 +349,10 @@ static int luks_setup( struct crypt_device **ret, sd_id128_t *ret_found_uuid, void **ret_volume_key, - size_t *ret_volume_key_size) { + size_t *ret_volume_key_size, + key_serial_t *ret_key_serial) { + _cleanup_(keyring_unlinkp) key_serial_t key_serial = -1; _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; _cleanup_(erase_and_freep) void *vk = NULL; sd_id128_t p; @@ -301,6 +360,7 @@ static int luks_setup( char **list; int r; + assert(h); assert(node); assert(dm_name); assert(ret); @@ -352,10 +412,11 @@ static int luks_setup( r = -ENOKEY; FOREACH_POINTER(list, + cache ? cache->keyring_passswords : NULL, cache ? cache->pkcs11_passwords : NULL, cache ? cache->fido2_passwords : NULL, passwords) { - r = luks_try_passwords(cd, list, vk, &vks); + r = luks_try_passwords(h, cd, list, vk, &vks, ret_key_serial ? &key_serial : NULL); if (r != -ENOKEY) break; } @@ -382,6 +443,8 @@ static int luks_setup( *ret_volume_key = TAKE_PTR(vk); if (ret_volume_key_size) *ret_volume_key_size = vks; + if (ret_key_serial) + *ret_key_serial = TAKE_KEY_SERIAL(key_serial); return 0; } @@ -490,10 +553,11 @@ static int luks_open( r = -ENOKEY; FOREACH_POINTER(list, + cache ? cache->keyring_passswords : NULL, cache ? cache->pkcs11_passwords : NULL, cache ? cache->fido2_passwords : NULL, h->password) { - r = luks_try_passwords(setup->crypt_device, list, vk, &vks); + r = luks_try_passwords(h, setup->crypt_device, list, vk, &vks, NULL); if (r != -ENOKEY) break; } @@ -1322,7 +1386,8 @@ int home_setup_luks( log_info("Setting up loopback device %s completed.", setup->loop->node ?: ip); - r = luks_setup(setup->loop->node ?: ip, + r = luks_setup(h, + setup->loop->node ?: ip, setup->dm_name, h->luks_uuid, h->luks_cipher, @@ -1334,7 +1399,8 @@ int home_setup_luks( &setup->crypt_device, &found_luks_uuid, &volume_key, - &volume_key_size); + &volume_key_size, + &setup->key_serial); if (r < 0) return r; @@ -1523,6 +1589,7 @@ int home_activate_luks( setup->do_offline_fallocate = false; setup->do_mark_clean = false; setup->do_drop_caches = false; + TAKE_KEY_SERIAL(setup->key_serial); /* Leave key in kernel keyring */ log_info("Activation completed."); @@ -3451,9 +3518,10 @@ int home_resize_luks( int home_passwd_luks( UserRecord *h, + HomeSetupFlags flags, HomeSetup *setup, - const PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */ - char **effective_passwords /* new passwords */) { + const PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */ + char **effective_passwords /* new passwords */) { size_t volume_key_size, max_key_slots, n_effective; _cleanup_(erase_and_freep) void *volume_key = NULL; @@ -3490,11 +3558,12 @@ int home_passwd_luks( r = -ENOKEY; FOREACH_POINTER(list, + cache ? cache->keyring_passswords : NULL, cache ? cache->pkcs11_passwords : NULL, cache ? cache->fido2_passwords : NULL, h->password) { - r = luks_try_passwords(setup->crypt_device, list, volume_key, &volume_key_size); + r = luks_try_passwords(h, setup->crypt_device, list, volume_key, &volume_key_size, NULL); if (r != -ENOKEY) break; } @@ -3540,6 +3609,11 @@ int home_passwd_luks( return log_error_errno(r, "Failed to set up LUKS password: %m"); log_info("Updated LUKS key slot %zu.", i); + + /* If we changed the password, then make sure to update the copy in the keyring, so that + * auto-rebalance continues to work. We only do this if we operate on an active home dir. */ + if (i == 0 && FLAGS_SET(flags, HOME_SETUP_ALREADY_ACTIVATED)) + upload_to_keyring(h, effective_passwords[i], NULL); } return 1; diff --git a/src/home/homework-luks.h b/src/home/homework-luks.h index afbe8d4909..f0f29b78f7 100644 --- a/src/home/homework-luks.h +++ b/src/home/homework-luks.h @@ -19,7 +19,7 @@ int home_get_state_luks(UserRecord *h, HomeSetup *setup); int home_resize_luks(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_home); -int home_passwd_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache, char **effective_passwords); +int home_passwd_luks(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, const PasswordCache *cache, char **effective_passwords); int home_lock_luks(UserRecord *h, HomeSetup *setup); int home_unlock_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache); diff --git a/src/home/homework-password-cache.c b/src/home/homework-password-cache.c index 87d90c9184..c1606c5f5f 100644 --- a/src/home/homework-password-cache.c +++ b/src/home/homework-password-cache.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "homework-password-cache.h" +#include "keyring-util.h" +#include "missing_syscall.h" +#include "user-record.h" void password_cache_free(PasswordCache *cache) { if (!cache) @@ -9,3 +12,45 @@ void password_cache_free(PasswordCache *cache) { cache->pkcs11_passwords = strv_free_erase(cache->pkcs11_passwords); cache->fido2_passwords = strv_free_erase(cache->fido2_passwords); } + +void password_cache_load_keyring(UserRecord *h, PasswordCache *cache) { + _cleanup_(erase_and_freep) void *p = NULL; + _cleanup_free_ char *name = NULL; + char **strv = NULL; + key_serial_t serial; + size_t sz; + int r; + + assert(h); + assert(cache); + + /* Loads the password we need to for automatic resizing from the kernel keyring */ + + name = strjoin("homework-user-", h->user_name); + if (!name) + return (void) log_oom(); + + serial = request_key("user", name, NULL, 0); + if (serial == -1) + return (void) log_debug_errno(errno, "Failed to request key '%s', ignoring: %m", name); + + r = keyring_read(serial, &p, &sz); + if (r < 0) + return (void) log_debug_errno(r, "Failed to read keyring key '%s', ignoring: %m", name); + + if (memchr(p, 0, sz)) + return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Cached password contains embedded NUL byte, ignoring."); + + strv = new(char*, 2); + if (!strv) + return (void) log_oom(); + + strv[0] = TAKE_PTR(p); /* Note that keyring_read() will NUL terminate implicitly, hence we don't have + * to NUL terminate manually here: it's a valid string. */ + strv[1] = NULL; + + strv_free_erase(cache->keyring_passswords); + cache->keyring_passswords = strv; + + log_debug("Successfully acquired home key from kernel keyring."); +} diff --git a/src/home/homework-password-cache.h b/src/home/homework-password-cache.h index 6b4de15724..fdfbcfe4e0 100644 --- a/src/home/homework-password-cache.h +++ b/src/home/homework-password-cache.h @@ -5,6 +5,9 @@ #include "user-record.h" typedef struct PasswordCache { + /* Passwords acquired from the kernel keyring */ + char **keyring_passswords; + /* Decoding passwords from security tokens is expensive and typically requires user interaction, * hence cache any we already figured out. */ char **pkcs11_passwords; @@ -17,5 +20,9 @@ static inline bool password_cache_contains(const PasswordCache *cache, const cha if (!cache) return false; - return strv_contains(cache->pkcs11_passwords, p) || strv_contains(cache->fido2_passwords, p); + return strv_contains(cache->pkcs11_passwords, p) || + strv_contains(cache->fido2_passwords, p) || + strv_contains(cache->keyring_passswords, p); } + +void password_cache_load_keyring(UserRecord *h, PasswordCache *cache); diff --git a/src/home/homework.c b/src/home/homework.c index 351c31b73e..ac52a011ed 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -343,6 +343,34 @@ int home_setup_undo_dm(HomeSetup *setup, int level) { return ret; } +int keyring_unlink(key_serial_t k) { + + if (k == -1) /* already invalidated? */ + return -1; + + if (keyctl(KEYCTL_UNLINK, k, KEY_SPEC_SESSION_KEYRING, 0, 0) < 0) + log_debug_errno(errno, "Failed to unlink key from session kernel keyring, ignoring: %m"); + + return -1; /* Always return the key_serial_t value for "invalid" */ +} + +static int keyring_flush(UserRecord *h) { + _cleanup_free_ char *name = NULL; + long serial; + + assert(h); + + name = strjoin("homework-user-", h->user_name); + if (!name) + return log_oom(); + + serial = keyctl(KEYCTL_SEARCH, (unsigned long) KEY_SPEC_SESSION_KEYRING, (unsigned long) "user", (unsigned long) name, 0); + if (serial == -1) + return log_debug_errno(errno, "Failed to find kernel keyring entry for user, ignoring: %m"); + + return keyring_unlink(serial); +} + int home_setup_done(HomeSetup *setup) { int r = 0, q; @@ -393,6 +421,8 @@ int home_setup_done(HomeSetup *setup) { setup->temporary_image_path = mfree(setup->temporary_image_path); } + setup->key_serial = keyring_unlink(setup->key_serial); + setup->undo_mount = false; setup->undo_dm = false; setup->do_offline_fitrim = false; @@ -944,8 +974,12 @@ static int home_deactivate(UserRecord *h, bool force) { if (r < 0) return r; - if (user_record_storage(h) == USER_LUKS) + if (user_record_storage(h) == USER_LUKS) { + /* Automatically shrink on logout if that's enabled. To be able to shrink we need the + * keys to the device. */ + password_cache_load_keyring(h, &cache); (void) home_trim_luks(h, &setup); + } /* Sync explicitly, so that the drop caches logic below can work as documented */ if (syncfs(setup.root_fd) < 0) @@ -981,6 +1015,9 @@ static int home_deactivate(UserRecord *h, bool force) { done = true; } + /* Explicitly flush any per-user key from the keyring */ + (void) keyring_flush(h); + if (!done) return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home is not active."); @@ -1660,7 +1697,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) { switch (user_record_storage(h)) { case USER_LUKS: - r = home_passwd_luks(h, &setup, &cache, effective_passwords); + r = home_passwd_luks(h, flags, &setup, &cache, effective_passwords); if (r < 0) return r; break; diff --git a/src/home/homework.h b/src/home/homework.h index be77764d8e..750ad331c8 100644 --- a/src/home/homework.h +++ b/src/home/homework.h @@ -8,6 +8,8 @@ #include "homework-password-cache.h" #include "loop-util.h" +#include "missing_keyctl.h" +#include "missing_syscall.h" #include "user-record.h" #include "user-record-util.h" @@ -28,6 +30,8 @@ typedef struct HomeSetup { void *volume_key; size_t volume_key_size; + key_serial_t key_serial; + bool undo_dm:1; bool undo_mount:1; /* Whether to unmount /run/systemd/user-home-mount */ bool do_offline_fitrim:1; @@ -49,6 +53,7 @@ typedef struct HomeSetup { .image_fd = -1, \ .partition_offset = UINT64_MAX, \ .partition_size = UINT64_MAX, \ + .key_serial = -1, \ } /* Various flags for the operation of setting up a home directory */ @@ -71,6 +76,8 @@ int home_setup_done(HomeSetup *setup); int home_setup_undo_mount(HomeSetup *setup, int level); int home_setup_undo_dm(HomeSetup *setup, int level); +int keyring_unlink(key_serial_t k); + int home_setup(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_header_home); int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, PasswordCache *cache, struct statfs *ret_statfs, UserRecord **ret_new_home);