fscrypt: track master key presence separately from secret

Master keys can be in one of three states: present, incompletely
removed, and absent (as per FSCRYPT_KEY_STATUS_* used in the UAPI).
Currently, the way that "present" is distinguished from "incompletely
removed" internally is by whether ->mk_secret exists or not.

With extent-based encryption, it will be necessary to allow per-extent
keys to be derived while the master key is incompletely removed, so that
I/O on open files will reliably continue working after removal of the
key has been initiated.  (We could allow I/O to sometimes fail in that
case, but that seems problematic for reasons such as writes getting
silently thrown away and diverging from the existing fscrypt semantics.)
Therefore, when the filesystem is using extent-based encryption,
->mk_secret can't be wiped when the key becomes incompletely removed.

As a prerequisite for doing that, this patch makes the "present" state
be tracked using a new field, ->mk_present.  No behavior is changed yet.

The basic idea here is borrowed from Josef Bacik's patch
"fscrypt: use a flag to indicate that the master key is being evicted"
(https://lore.kernel.org/r/e86c16dddc049ff065f877d793ad773e4c6bfad9.1696970227.git.josef@toxicpanda.com).
I reimplemented it using a "present" bool instead of an "evicted" flag,
fixed a couple bugs, and tried to update everything to be consistent.

Note: I considered adding a ->mk_status field instead, holding one of
FSCRYPT_KEY_STATUS_*.  At first that seemed nice, but it ended up being
more complex (despite simplifying FS_IOC_GET_ENCRYPTION_KEY_STATUS),
since it would have introduced redundancy and had weird locking rules.

Reviewed-by: Neal Gompa <neal@gompa.dev>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Link: https://lore.kernel.org/r/20231015061055.62673-1-ebiggers@kernel.org
Signed-off-by: Eric Biggers <ebiggers@google.com>
This commit is contained in:
Eric Biggers 2023-10-14 23:10:55 -07:00
parent 3e7807d5a7
commit 15baf55481
5 changed files with 95 additions and 68 deletions

View File

@ -1134,8 +1134,8 @@ The caller must zero all input fields, then fill in ``key_spec``:
On success, 0 is returned and the kernel fills in the output fields: On success, 0 is returned and the kernel fills in the output fields:
- ``status`` indicates whether the key is absent, present, or - ``status`` indicates whether the key is absent, present, or
incompletely removed. Incompletely removed means that the master incompletely removed. Incompletely removed means that removal has
secret has been removed, but some files are still in use; i.e., been initiated, but some files are still in use; i.e.,
`FS_IOC_REMOVE_ENCRYPTION_KEY`_ returned 0 but set the informational `FS_IOC_REMOVE_ENCRYPTION_KEY`_ returned 0 but set the informational
status flag FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY. status flag FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY.

View File

@ -475,8 +475,28 @@ struct fscrypt_master_key_secret {
* fscrypt_master_key - an in-use master key * fscrypt_master_key - an in-use master key
* *
* This represents a master encryption key which has been added to the * This represents a master encryption key which has been added to the
* filesystem and can be used to "unlock" the encrypted files which were * filesystem. There are three high-level states that a key can be in:
* encrypted with it. *
* FSCRYPT_KEY_STATUS_PRESENT
* Key is fully usable; it can be used to unlock inodes that are encrypted
* with it (this includes being able to create new inodes). ->mk_present
* indicates whether the key is in this state. ->mk_secret exists, the key
* is in the keyring, and ->mk_active_refs > 0 due to ->mk_present.
*
* FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED
* Removal of this key has been initiated, but some inodes that were
* unlocked with it are still in-use. Like ABSENT, ->mk_secret is wiped,
* and the key can no longer be used to unlock inodes. Unlike ABSENT, the
* key is still in the keyring; ->mk_decrypted_inodes is nonempty; and
* ->mk_active_refs > 0, being equal to the size of ->mk_decrypted_inodes.
*
* This state transitions to ABSENT if ->mk_decrypted_inodes becomes empty,
* or to PRESENT if FS_IOC_ADD_ENCRYPTION_KEY is called again for this key.
*
* FSCRYPT_KEY_STATUS_ABSENT
* Key is fully removed. The key is no longer in the keyring,
* ->mk_decrypted_inodes is empty, ->mk_active_refs == 0, ->mk_secret is
* wiped, and the key can no longer be used to unlock inodes.
*/ */
struct fscrypt_master_key { struct fscrypt_master_key {
@ -486,7 +506,7 @@ struct fscrypt_master_key {
*/ */
struct hlist_node mk_node; struct hlist_node mk_node;
/* Semaphore that protects ->mk_secret and ->mk_users */ /* Semaphore that protects ->mk_secret, ->mk_users, and ->mk_present */
struct rw_semaphore mk_sem; struct rw_semaphore mk_sem;
/* /*
@ -496,8 +516,8 @@ struct fscrypt_master_key {
* ->mk_direct_keys) that have been prepared continue to exist. * ->mk_direct_keys) that have been prepared continue to exist.
* A structural ref only guarantees that the struct continues to exist. * A structural ref only guarantees that the struct continues to exist.
* *
* There is one active ref associated with ->mk_secret being present, * There is one active ref associated with ->mk_present being true, and
* and one active ref for each inode in ->mk_decrypted_inodes. * one active ref for each inode in ->mk_decrypted_inodes.
* *
* There is one structural ref associated with the active refcount being * There is one structural ref associated with the active refcount being
* nonzero. Finding a key in the keyring also takes a structural ref, * nonzero. Finding a key in the keyring also takes a structural ref,
@ -509,17 +529,10 @@ struct fscrypt_master_key {
struct rcu_head mk_rcu_head; struct rcu_head mk_rcu_head;
/* /*
* The secret key material. After FS_IOC_REMOVE_ENCRYPTION_KEY is * The secret key material. Wiped as soon as it is no longer needed;
* executed, this is wiped and no new inodes can be unlocked with this * for details, see the fscrypt_master_key struct comment.
* key; however, there may still be inodes in ->mk_decrypted_inodes
* which could not be evicted. As long as some inodes still remain,
* FS_IOC_REMOVE_ENCRYPTION_KEY can be retried, or
* FS_IOC_ADD_ENCRYPTION_KEY can add the secret again.
* *
* While ->mk_secret is present, one ref in ->mk_active_refs is held. * Locking: protected by ->mk_sem.
*
* Locking: protected by ->mk_sem. The manipulation of ->mk_active_refs
* associated with this field is protected by ->mk_sem as well.
*/ */
struct fscrypt_master_key_secret mk_secret; struct fscrypt_master_key_secret mk_secret;
@ -542,7 +555,7 @@ struct fscrypt_master_key {
* *
* Locking: protected by ->mk_sem. (We don't just rely on the keyrings * Locking: protected by ->mk_sem. (We don't just rely on the keyrings
* subsystem semaphore ->mk_users->sem, as we need support for atomic * subsystem semaphore ->mk_users->sem, as we need support for atomic
* search+insert along with proper synchronization with ->mk_secret.) * search+insert along with proper synchronization with other fields.)
*/ */
struct key *mk_users; struct key *mk_users;
@ -565,20 +578,17 @@ struct fscrypt_master_key {
siphash_key_t mk_ino_hash_key; siphash_key_t mk_ino_hash_key;
bool mk_ino_hash_key_initialized; bool mk_ino_hash_key_initialized;
} __randomize_layout;
static inline bool
is_master_key_secret_present(const struct fscrypt_master_key_secret *secret)
{
/* /*
* The READ_ONCE() is only necessary for fscrypt_drop_inode(). * Whether this key is in the "present" state, i.e. fully usable. For
* fscrypt_drop_inode() runs in atomic context, so it can't take the key * details, see the fscrypt_master_key struct comment.
* semaphore and thus 'secret' can change concurrently which would be a *
* data race. But fscrypt_drop_inode() only need to know whether the * Locking: protected by ->mk_sem, but can be read locklessly using
* secret *was* present at the time of check, so READ_ONCE() suffices. * READ_ONCE(). Writers must use WRITE_ONCE() when concurrent readers
* are possible.
*/ */
return READ_ONCE(secret->size) != 0; bool mk_present;
}
} __randomize_layout;
static inline const char *master_key_spec_type( static inline const char *master_key_spec_type(
const struct fscrypt_key_specifier *spec) const struct fscrypt_key_specifier *spec)

View File

@ -187,7 +187,7 @@ int fscrypt_prepare_setflags(struct inode *inode,
return -EINVAL; return -EINVAL;
mk = ci->ci_master_key; mk = ci->ci_master_key;
down_read(&mk->mk_sem); down_read(&mk->mk_sem);
if (is_master_key_secret_present(&mk->mk_secret)) if (mk->mk_present)
err = fscrypt_derive_dirhash_key(ci, mk); err = fscrypt_derive_dirhash_key(ci, mk);
else else
err = -ENOKEY; err = -ENOKEY;

View File

@ -99,10 +99,10 @@ void fscrypt_put_master_key_activeref(struct super_block *sb,
spin_unlock(&sb->s_master_keys->lock); spin_unlock(&sb->s_master_keys->lock);
/* /*
* ->mk_active_refs == 0 implies that ->mk_secret is not present and * ->mk_active_refs == 0 implies that ->mk_present is false and
* that ->mk_decrypted_inodes is empty. * ->mk_decrypted_inodes is empty.
*/ */
WARN_ON_ONCE(is_master_key_secret_present(&mk->mk_secret)); WARN_ON_ONCE(mk->mk_present);
WARN_ON_ONCE(!list_empty(&mk->mk_decrypted_inodes)); WARN_ON_ONCE(!list_empty(&mk->mk_decrypted_inodes));
for (i = 0; i <= FSCRYPT_MODE_MAX; i++) { for (i = 0; i <= FSCRYPT_MODE_MAX; i++) {
@ -121,6 +121,18 @@ void fscrypt_put_master_key_activeref(struct super_block *sb,
fscrypt_put_master_key(mk); fscrypt_put_master_key(mk);
} }
/*
* This transitions the key state from present to incompletely removed, and then
* potentially to absent (depending on whether inodes remain).
*/
static void fscrypt_initiate_key_removal(struct super_block *sb,
struct fscrypt_master_key *mk)
{
WRITE_ONCE(mk->mk_present, false);
wipe_master_key_secret(&mk->mk_secret);
fscrypt_put_master_key_activeref(sb, mk);
}
static inline bool valid_key_spec(const struct fscrypt_key_specifier *spec) static inline bool valid_key_spec(const struct fscrypt_key_specifier *spec)
{ {
if (spec->__reserved) if (spec->__reserved)
@ -234,14 +246,13 @@ void fscrypt_destroy_keyring(struct super_block *sb)
* evicted, every key remaining in the keyring should * evicted, every key remaining in the keyring should
* have an empty inode list, and should only still be in * have an empty inode list, and should only still be in
* the keyring due to the single active ref associated * the keyring due to the single active ref associated
* with ->mk_secret. There should be no structural refs * with ->mk_present. There should be no structural
* beyond the one associated with the active ref. * refs beyond the one associated with the active ref.
*/ */
WARN_ON_ONCE(refcount_read(&mk->mk_active_refs) != 1); WARN_ON_ONCE(refcount_read(&mk->mk_active_refs) != 1);
WARN_ON_ONCE(refcount_read(&mk->mk_struct_refs) != 1); WARN_ON_ONCE(refcount_read(&mk->mk_struct_refs) != 1);
WARN_ON_ONCE(!is_master_key_secret_present(&mk->mk_secret)); WARN_ON_ONCE(!mk->mk_present);
wipe_master_key_secret(&mk->mk_secret); fscrypt_initiate_key_removal(sb, mk);
fscrypt_put_master_key_activeref(sb, mk);
} }
} }
kfree_sensitive(keyring); kfree_sensitive(keyring);
@ -439,7 +450,8 @@ static int add_new_master_key(struct super_block *sb,
} }
move_master_key_secret(&mk->mk_secret, secret); move_master_key_secret(&mk->mk_secret, secret);
refcount_set(&mk->mk_active_refs, 1); /* ->mk_secret is present */ mk->mk_present = true;
refcount_set(&mk->mk_active_refs, 1); /* ->mk_present is true */
spin_lock(&keyring->lock); spin_lock(&keyring->lock);
hlist_add_head_rcu(&mk->mk_node, hlist_add_head_rcu(&mk->mk_node,
@ -478,11 +490,18 @@ static int add_existing_master_key(struct fscrypt_master_key *mk,
return err; return err;
} }
/* Re-add the secret if needed. */ /* If the key is incompletely removed, make it present again. */
if (!is_master_key_secret_present(&mk->mk_secret)) { if (!mk->mk_present) {
if (!refcount_inc_not_zero(&mk->mk_active_refs)) if (!refcount_inc_not_zero(&mk->mk_active_refs)) {
/*
* Raced with the last active ref being dropped, so the
* key has become, or is about to become, "absent".
* Therefore, we need to allocate a new key struct.
*/
return KEY_DEAD; return KEY_DEAD;
}
move_master_key_secret(&mk->mk_secret, secret); move_master_key_secret(&mk->mk_secret, secret);
WRITE_ONCE(mk->mk_present, true);
} }
return 0; return 0;
@ -506,8 +525,8 @@ static int do_add_master_key(struct super_block *sb,
err = add_new_master_key(sb, secret, mk_spec); err = add_new_master_key(sb, secret, mk_spec);
} else { } else {
/* /*
* Found the key in ->s_master_keys. Re-add the secret if * Found the key in ->s_master_keys. Add the user to ->mk_users
* needed, and add the user to ->mk_users if needed. * if needed, and make the key "present" again if possible.
*/ */
down_write(&mk->mk_sem); down_write(&mk->mk_sem);
err = add_existing_master_key(mk, secret); err = add_existing_master_key(mk, secret);
@ -989,9 +1008,8 @@ static int try_to_lock_encrypted_files(struct super_block *sb,
* *
* If all inodes were evicted, then we unlink the fscrypt_master_key from the * If all inodes were evicted, then we unlink the fscrypt_master_key from the
* keyring. Otherwise it remains in the keyring in the "incompletely removed" * keyring. Otherwise it remains in the keyring in the "incompletely removed"
* state (without the actual secret key) where it tracks the list of remaining * state where it tracks the list of remaining inodes. Userspace can execute
* inodes. Userspace can execute the ioctl again later to retry eviction, or * the ioctl again later to retry eviction, or alternatively can re-add the key.
* alternatively can re-add the secret key again.
* *
* For more details, see the "Removing keys" section of * For more details, see the "Removing keys" section of
* Documentation/filesystems/fscrypt.rst. * Documentation/filesystems/fscrypt.rst.
@ -1053,11 +1071,10 @@ static int do_remove_key(struct file *filp, void __user *_uarg, bool all_users)
} }
} }
/* No user claims remaining. Go ahead and wipe the secret. */ /* No user claims remaining. Initiate removal of the key. */
err = -ENOKEY; err = -ENOKEY;
if (is_master_key_secret_present(&mk->mk_secret)) { if (mk->mk_present) {
wipe_master_key_secret(&mk->mk_secret); fscrypt_initiate_key_removal(sb, mk);
fscrypt_put_master_key_activeref(sb, mk);
err = 0; err = 0;
} }
inodes_remain = refcount_read(&mk->mk_active_refs) > 0; inodes_remain = refcount_read(&mk->mk_active_refs) > 0;
@ -1074,9 +1091,9 @@ static int do_remove_key(struct file *filp, void __user *_uarg, bool all_users)
} }
/* /*
* We return 0 if we successfully did something: removed a claim to the * We return 0 if we successfully did something: removed a claim to the
* key, wiped the secret, or tried locking the files again. Users need * key, initiated removal of the key, or tried locking the files again.
* to check the informational status flags if they care whether the key * Users need to check the informational status flags if they care
* has been fully removed including all files locked. * whether the key has been fully removed including all files locked.
*/ */
out_put_key: out_put_key:
fscrypt_put_master_key(mk); fscrypt_put_master_key(mk);
@ -1103,12 +1120,11 @@ EXPORT_SYMBOL_GPL(fscrypt_ioctl_remove_key_all_users);
* Retrieve the status of an fscrypt master encryption key. * Retrieve the status of an fscrypt master encryption key.
* *
* We set ->status to indicate whether the key is absent, present, or * We set ->status to indicate whether the key is absent, present, or
* incompletely removed. "Incompletely removed" means that the master key * incompletely removed. (For an explanation of what these statuses mean and
* secret has been removed, but some files which had been unlocked with it are * how they are represented internally, see struct fscrypt_master_key.) This
* still in use. This field allows applications to easily determine the state * field allows applications to easily determine the status of an encrypted
* of an encrypted directory without using a hack such as trying to open a * directory without using a hack such as trying to open a regular file in it
* regular file in it (which can confuse the "incompletely removed" state with * (which can confuse the "incompletely removed" status with absent or present).
* absent or present).
* *
* In addition, for v2 policy keys we allow applications to determine, via * In addition, for v2 policy keys we allow applications to determine, via
* ->status_flags and ->user_count, whether the key has been added by the * ->status_flags and ->user_count, whether the key has been added by the
@ -1150,7 +1166,7 @@ int fscrypt_ioctl_get_key_status(struct file *filp, void __user *uarg)
} }
down_read(&mk->mk_sem); down_read(&mk->mk_sem);
if (!is_master_key_secret_present(&mk->mk_secret)) { if (!mk->mk_present) {
arg.status = refcount_read(&mk->mk_active_refs) > 0 ? arg.status = refcount_read(&mk->mk_active_refs) > 0 ?
FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED : FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED :
FSCRYPT_KEY_STATUS_ABSENT /* raced with full removal */; FSCRYPT_KEY_STATUS_ABSENT /* raced with full removal */;

View File

@ -486,8 +486,8 @@ static int setup_file_encryption_key(struct fscrypt_inode_info *ci,
} }
down_read(&mk->mk_sem); down_read(&mk->mk_sem);
/* Has the secret been removed (via FS_IOC_REMOVE_ENCRYPTION_KEY)? */ if (!mk->mk_present) {
if (!is_master_key_secret_present(&mk->mk_secret)) { /* FS_IOC_REMOVE_ENCRYPTION_KEY has been executed on this key */
err = -ENOKEY; err = -ENOKEY;
goto out_release_key; goto out_release_key;
} }
@ -539,8 +539,8 @@ static void put_crypt_info(struct fscrypt_inode_info *ci)
/* /*
* Remove this inode from the list of inodes that were unlocked * Remove this inode from the list of inodes that were unlocked
* with the master key. In addition, if we're removing the last * with the master key. In addition, if we're removing the last
* inode from a master key struct that already had its secret * inode from an incompletely removed key, then complete the
* removed, then complete the full removal of the struct. * full removal of the key.
*/ */
spin_lock(&mk->mk_decrypted_inodes_lock); spin_lock(&mk->mk_decrypted_inodes_lock);
list_del(&ci->ci_master_key_link); list_del(&ci->ci_master_key_link);
@ -801,13 +801,14 @@ int fscrypt_drop_inode(struct inode *inode)
return 0; return 0;
/* /*
* Note: since we aren't holding the key semaphore, the result here can * We can't take ->mk_sem here, since this runs in atomic context.
* Therefore, ->mk_present can change concurrently, and our result may
* immediately become outdated. But there's no correctness problem with * immediately become outdated. But there's no correctness problem with
* unnecessarily evicting. Nor is there a correctness problem with not * unnecessarily evicting. Nor is there a correctness problem with not
* evicting while iput() is racing with the key being removed, since * evicting while iput() is racing with the key being removed, since
* then the thread removing the key will either evict the inode itself * then the thread removing the key will either evict the inode itself
* or will correctly detect that it wasn't evicted due to the race. * or will correctly detect that it wasn't evicted due to the race.
*/ */
return !is_master_key_secret_present(&ci->ci_master_key->mk_secret); return !READ_ONCE(ci->ci_master_key->mk_present);
} }
EXPORT_SYMBOL_GPL(fscrypt_drop_inode); EXPORT_SYMBOL_GPL(fscrypt_drop_inode);