homed/fscrypt: default new homes to v2 policies (#18280) (#42397)

## Summary

Closes #18280.

`systemd-homed` currently writes fscrypt v1 policies, which bind the
master key to the calling process's keyring. This means files in a
homed-managed directory aren't readable when accessed through a
container bind mount, a different mount namespace, or by any process
other than the one that first unlocked the home. Reading the file from a
context that has the key first warms the page cache, but the underlying
problem remains.

v2 policies (Linux 5.4+) route the master key through the filesystem
keyring, via `FS_IOC_ADD_ENCRYPTION_KEY` /
`FS_IOC_REMOVE_ENCRYPTION_KEY`, so the key is visible to every process
accessing the filesystem.

This PR switches `systemd-homed` to v2 by default and keeps existing v1
homes working.

## Changes

**`shared/crypto-util`**: Add `kdf_hkdf_derive` (HKDF, RFC 5869) wrapper
around OpenSSL's `EVP_KDF` "HKDF", alongside the existing SSKDF/KBKDF
helpers.

**`homed/fscrypt`**:
- Read the existing policy via `FS_IOC_GET_ENCRYPTION_POLICY_EX`,
falling back to the legacy ioctl on pre-5.4 kernels.
- `HomeSetup` now carries a full `struct fscrypt_key_specifier` instead
of a bare 8-byte descriptor. Slot decryption derives either the v1
descriptor (double SHA-512) or the v2 identifier (HKDF-SHA512 with the
kernel's info string) from the unwrapped volume key and compares against
the policy.
- Install the master key the right way per version: `add_key("logon",
...)` to thread+user keyrings for v1, `FS_IOC_ADD_ENCRYPTION_KEY` for
v2.
- `home_flush_keyring_fscrypt` opens the image directory, reads the
policy version, and either calls `FS_IOC_REMOVE_ENCRYPTION_KEY` (v2) or
walks the user keyring (v1).
- New homes default to v2; fall back to v1 only when the kernel rejects
v2.

The on-disk slot xattr format is unchanged - the volume key is the same,
only how it binds to the directory changes. There is no v1 -> v2
migration; existing v1 homes continue to unlock, rekey, and deactivate
as before.
This commit is contained in:
Yu Watanabe
2026-07-25 19:36:35 +09:00
committed by GitHub
10 changed files with 820 additions and 203 deletions
+6
View File
@@ -7,6 +7,7 @@
#include "fd-util.h"
#include "homework-blob.h"
#include "homework-directory.h"
#include "homework-fscrypt.h"
#include "homework-mount.h"
#include "homework-quota.h"
#include "log.h"
@@ -103,6 +104,11 @@ int home_activate_directory(
setup->undo_mount = false;
/* The home is now activated and staying mounted, so it owns any v2 fscrypt master key
* home_setup() installed. Disarm the rollback so home_setup_done() doesn't remove the key out
* from under the live mount. (No-op for v1 or non-fscrypt storage.) */
fscrypt_v2_key_undo_disarm(&setup->fscrypt_v2_key_undo);
setup->do_drop_caches = false;
log_info("Everything completed.");
+1
View File
@@ -9,3 +9,4 @@ typedef enum HomeSetupFlags HomeSetupFlags;
typedef struct PasswordCache PasswordCache;
typedef struct HomeSetup HomeSetup;
typedef struct FscryptV2KeyUndo FscryptV2KeyUndo;
File diff suppressed because it is too large Load Diff
+7 -1
View File
@@ -3,10 +3,16 @@
#include "homework-forward.h"
int home_setup_fscrypt(UserRecord *h, HomeSetup *setup, const PasswordCache *cache);
int home_setup_fscrypt(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, const PasswordCache *cache);
int home_create_fscrypt(UserRecord *h, HomeSetup *setup, char **effective_passwords, UserRecord **ret_home);
int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, const PasswordCache *cache, char **effective_passwords);
int home_flush_keyring_fscrypt(UserRecord *h);
/* Fire the pending v2 master-key rollback (remove the key from the filesystem keyring) if armed; called
* from home_setup_done() on every teardown/error path. No-op if unarmed or v1. */
void fscrypt_v2_key_undo_done(FscryptV2KeyUndo *u);
/* Cancel a pending v2 rollback without removing the key: the home is staying active and now owns it. */
void fscrypt_v2_key_undo_disarm(FscryptV2KeyUndo *u);
+6 -1
View File
@@ -459,6 +459,11 @@ int home_setup_done(HomeSetup *setup) {
setup->key_serial = keyring_unlink(setup->key_serial);
/* Roll back a v2 fscrypt master key that home_setup_fscrypt() installed but that no activated
* home ended up owning (passwd/update/resize of an inactive home, or any error path). On the
* activation path home_activate_directory() disarms this first, so the live home keeps its key. */
fscrypt_v2_key_undo_done(&setup->fscrypt_v2_key_undo);
setup->undo_mount = false;
setup->undo_dm = false;
setup->do_offline_fitrim = false;
@@ -514,7 +519,7 @@ int home_setup(
break;
case USER_FSCRYPT:
r = home_setup_fscrypt(h, setup, cache);
r = home_setup_fscrypt(h, flags, setup, cache);
break;
case USER_CIFS:
+18 -1
View File
@@ -9,6 +9,21 @@
#include "homework-forward.h"
#include "user-record-util.h"
/* RAII rollback state for a v2 fscrypt master-key installation. v2 keys live in the persistent
* filesystem keyring (removed only via FS_IOC_REMOVE_ENCRYPTION_KEY), so any setup that installs one
* but then tears the home back down instead of activating it must remove the key again, or it lingers
* filesystem-wide until the next deactivation or reboot. Armed by home_setup_fscrypt(), fired by
* home_setup_done() on every teardown/error path, and disarmed only once a home stays active (see
* home_activate_directory()). 'dir_fd' is a dup taken at arm time so the removal target is pinned to
* the superblock and unaffected by later rename()/mount changes. (v1 keys go to the per-thread keyring
* and die with the homework process, so they need none of this.) */
typedef struct FscryptV2KeyUndo {
int dir_fd;
uint8_t identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
} FscryptV2KeyUndo;
#define FSCRYPT_V2_KEY_UNDO_INIT { .dir_fd = -EBADF }
typedef struct HomeSetup {
char *dm_name; /* "home-<username>" */
char *dm_node; /* "/dev/mapper/home-<username>" */
@@ -21,7 +36,8 @@ typedef struct HomeSetup {
sd_id128_t found_luks_uuid;
sd_id128_t found_fs_uuid;
uint8_t fscrypt_key_descriptor[FS_KEY_DESCRIPTOR_SIZE];
struct fscrypt_key_specifier fscrypt_key_spec;
FscryptV2KeyUndo fscrypt_v2_key_undo;
void *volume_key;
size_t volume_key_size;
@@ -47,6 +63,7 @@ typedef struct HomeSetup {
{ \
.root_fd = -EBADF, \
.image_fd = -EBADF, \
.fscrypt_v2_key_undo = FSCRYPT_V2_KEY_UNDO_INIT, \
.partition_offset = UINT64_MAX, \
.partition_size = UINT64_MAX, \
.key_serial = -1, \