Skip to content

Commit 15baf55

Browse files
committed
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 <[email protected]> Reviewed-by: Josef Bacik <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Eric Biggers <[email protected]>
1 parent 3e7807d commit 15baf55

File tree

5 files changed

+95
-68
lines changed

5 files changed

+95
-68
lines changed

Documentation/filesystems/fscrypt.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,8 +1134,8 @@ The caller must zero all input fields, then fill in ``key_spec``:
11341134
On success, 0 is returned and the kernel fills in the output fields:
11351135

11361136
- ``status`` indicates whether the key is absent, present, or
1137-
incompletely removed. Incompletely removed means that the master
1138-
secret has been removed, but some files are still in use; i.e.,
1137+
incompletely removed. Incompletely removed means that removal has
1138+
been initiated, but some files are still in use; i.e.,
11391139
`FS_IOC_REMOVE_ENCRYPTION_KEY`_ returned 0 but set the informational
11401140
status flag FSCRYPT_KEY_REMOVAL_STATUS_FLAG_FILES_BUSY.
11411141

fs/crypto/fscrypt_private.h

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,28 @@ struct fscrypt_master_key_secret {
475475
* fscrypt_master_key - an in-use master key
476476
*
477477
* This represents a master encryption key which has been added to the
478-
* filesystem and can be used to "unlock" the encrypted files which were
479-
* encrypted with it.
478+
* filesystem. There are three high-level states that a key can be in:
479+
*
480+
* FSCRYPT_KEY_STATUS_PRESENT
481+
* Key is fully usable; it can be used to unlock inodes that are encrypted
482+
* with it (this includes being able to create new inodes). ->mk_present
483+
* indicates whether the key is in this state. ->mk_secret exists, the key
484+
* is in the keyring, and ->mk_active_refs > 0 due to ->mk_present.
485+
*
486+
* FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED
487+
* Removal of this key has been initiated, but some inodes that were
488+
* unlocked with it are still in-use. Like ABSENT, ->mk_secret is wiped,
489+
* and the key can no longer be used to unlock inodes. Unlike ABSENT, the
490+
* key is still in the keyring; ->mk_decrypted_inodes is nonempty; and
491+
* ->mk_active_refs > 0, being equal to the size of ->mk_decrypted_inodes.
492+
*
493+
* This state transitions to ABSENT if ->mk_decrypted_inodes becomes empty,
494+
* or to PRESENT if FS_IOC_ADD_ENCRYPTION_KEY is called again for this key.
495+
*
496+
* FSCRYPT_KEY_STATUS_ABSENT
497+
* Key is fully removed. The key is no longer in the keyring,
498+
* ->mk_decrypted_inodes is empty, ->mk_active_refs == 0, ->mk_secret is
499+
* wiped, and the key can no longer be used to unlock inodes.
480500
*/
481501
struct fscrypt_master_key {
482502

@@ -486,7 +506,7 @@ struct fscrypt_master_key {
486506
*/
487507
struct hlist_node mk_node;
488508

489-
/* Semaphore that protects ->mk_secret and ->mk_users */
509+
/* Semaphore that protects ->mk_secret, ->mk_users, and ->mk_present */
490510
struct rw_semaphore mk_sem;
491511

492512
/*
@@ -496,8 +516,8 @@ struct fscrypt_master_key {
496516
* ->mk_direct_keys) that have been prepared continue to exist.
497517
* A structural ref only guarantees that the struct continues to exist.
498518
*
499-
* There is one active ref associated with ->mk_secret being present,
500-
* and one active ref for each inode in ->mk_decrypted_inodes.
519+
* There is one active ref associated with ->mk_present being true, and
520+
* one active ref for each inode in ->mk_decrypted_inodes.
501521
*
502522
* There is one structural ref associated with the active refcount being
503523
* nonzero. Finding a key in the keyring also takes a structural ref,
@@ -509,17 +529,10 @@ struct fscrypt_master_key {
509529
struct rcu_head mk_rcu_head;
510530

511531
/*
512-
* The secret key material. After FS_IOC_REMOVE_ENCRYPTION_KEY is
513-
* executed, this is wiped and no new inodes can be unlocked with this
514-
* key; however, there may still be inodes in ->mk_decrypted_inodes
515-
* which could not be evicted. As long as some inodes still remain,
516-
* FS_IOC_REMOVE_ENCRYPTION_KEY can be retried, or
517-
* FS_IOC_ADD_ENCRYPTION_KEY can add the secret again.
532+
* The secret key material. Wiped as soon as it is no longer needed;
533+
* for details, see the fscrypt_master_key struct comment.
518534
*
519-
* While ->mk_secret is present, one ref in ->mk_active_refs is held.
520-
*
521-
* Locking: protected by ->mk_sem. The manipulation of ->mk_active_refs
522-
* associated with this field is protected by ->mk_sem as well.
535+
* Locking: protected by ->mk_sem.
523536
*/
524537
struct fscrypt_master_key_secret mk_secret;
525538

@@ -542,7 +555,7 @@ struct fscrypt_master_key {
542555
*
543556
* Locking: protected by ->mk_sem. (We don't just rely on the keyrings
544557
* subsystem semaphore ->mk_users->sem, as we need support for atomic
545-
* search+insert along with proper synchronization with ->mk_secret.)
558+
* search+insert along with proper synchronization with other fields.)
546559
*/
547560
struct key *mk_users;
548561

@@ -565,20 +578,17 @@ struct fscrypt_master_key {
565578
siphash_key_t mk_ino_hash_key;
566579
bool mk_ino_hash_key_initialized;
567580

568-
} __randomize_layout;
569-
570-
static inline bool
571-
is_master_key_secret_present(const struct fscrypt_master_key_secret *secret)
572-
{
573581
/*
574-
* The READ_ONCE() is only necessary for fscrypt_drop_inode().
575-
* fscrypt_drop_inode() runs in atomic context, so it can't take the key
576-
* semaphore and thus 'secret' can change concurrently which would be a
577-
* data race. But fscrypt_drop_inode() only need to know whether the
578-
* secret *was* present at the time of check, so READ_ONCE() suffices.
582+
* Whether this key is in the "present" state, i.e. fully usable. For
583+
* details, see the fscrypt_master_key struct comment.
584+
*
585+
* Locking: protected by ->mk_sem, but can be read locklessly using
586+
* READ_ONCE(). Writers must use WRITE_ONCE() when concurrent readers
587+
* are possible.
579588
*/
580-
return READ_ONCE(secret->size) != 0;
581-
}
589+
bool mk_present;
590+
591+
} __randomize_layout;
582592

583593
static inline const char *master_key_spec_type(
584594
const struct fscrypt_key_specifier *spec)

fs/crypto/hooks.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ int fscrypt_prepare_setflags(struct inode *inode,
187187
return -EINVAL;
188188
mk = ci->ci_master_key;
189189
down_read(&mk->mk_sem);
190-
if (is_master_key_secret_present(&mk->mk_secret))
190+
if (mk->mk_present)
191191
err = fscrypt_derive_dirhash_key(ci, mk);
192192
else
193193
err = -ENOKEY;

fs/crypto/keyring.c

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,10 @@ void fscrypt_put_master_key_activeref(struct super_block *sb,
9999
spin_unlock(&sb->s_master_keys->lock);
100100

101101
/*
102-
* ->mk_active_refs == 0 implies that ->mk_secret is not present and
103-
* that ->mk_decrypted_inodes is empty.
102+
* ->mk_active_refs == 0 implies that ->mk_present is false and
103+
* ->mk_decrypted_inodes is empty.
104104
*/
105-
WARN_ON_ONCE(is_master_key_secret_present(&mk->mk_secret));
105+
WARN_ON_ONCE(mk->mk_present);
106106
WARN_ON_ONCE(!list_empty(&mk->mk_decrypted_inodes));
107107

108108
for (i = 0; i <= FSCRYPT_MODE_MAX; i++) {
@@ -121,6 +121,18 @@ void fscrypt_put_master_key_activeref(struct super_block *sb,
121121
fscrypt_put_master_key(mk);
122122
}
123123

124+
/*
125+
* This transitions the key state from present to incompletely removed, and then
126+
* potentially to absent (depending on whether inodes remain).
127+
*/
128+
static void fscrypt_initiate_key_removal(struct super_block *sb,
129+
struct fscrypt_master_key *mk)
130+
{
131+
WRITE_ONCE(mk->mk_present, false);
132+
wipe_master_key_secret(&mk->mk_secret);
133+
fscrypt_put_master_key_activeref(sb, mk);
134+
}
135+
124136
static inline bool valid_key_spec(const struct fscrypt_key_specifier *spec)
125137
{
126138
if (spec->__reserved)
@@ -234,14 +246,13 @@ void fscrypt_destroy_keyring(struct super_block *sb)
234246
* evicted, every key remaining in the keyring should
235247
* have an empty inode list, and should only still be in
236248
* the keyring due to the single active ref associated
237-
* with ->mk_secret. There should be no structural refs
238-
* beyond the one associated with the active ref.
249+
* with ->mk_present. There should be no structural
250+
* refs beyond the one associated with the active ref.
239251
*/
240252
WARN_ON_ONCE(refcount_read(&mk->mk_active_refs) != 1);
241253
WARN_ON_ONCE(refcount_read(&mk->mk_struct_refs) != 1);
242-
WARN_ON_ONCE(!is_master_key_secret_present(&mk->mk_secret));
243-
wipe_master_key_secret(&mk->mk_secret);
244-
fscrypt_put_master_key_activeref(sb, mk);
254+
WARN_ON_ONCE(!mk->mk_present);
255+
fscrypt_initiate_key_removal(sb, mk);
245256
}
246257
}
247258
kfree_sensitive(keyring);
@@ -439,7 +450,8 @@ static int add_new_master_key(struct super_block *sb,
439450
}
440451

441452
move_master_key_secret(&mk->mk_secret, secret);
442-
refcount_set(&mk->mk_active_refs, 1); /* ->mk_secret is present */
453+
mk->mk_present = true;
454+
refcount_set(&mk->mk_active_refs, 1); /* ->mk_present is true */
443455

444456
spin_lock(&keyring->lock);
445457
hlist_add_head_rcu(&mk->mk_node,
@@ -478,11 +490,18 @@ static int add_existing_master_key(struct fscrypt_master_key *mk,
478490
return err;
479491
}
480492

481-
/* Re-add the secret if needed. */
482-
if (!is_master_key_secret_present(&mk->mk_secret)) {
483-
if (!refcount_inc_not_zero(&mk->mk_active_refs))
493+
/* If the key is incompletely removed, make it present again. */
494+
if (!mk->mk_present) {
495+
if (!refcount_inc_not_zero(&mk->mk_active_refs)) {
496+
/*
497+
* Raced with the last active ref being dropped, so the
498+
* key has become, or is about to become, "absent".
499+
* Therefore, we need to allocate a new key struct.
500+
*/
484501
return KEY_DEAD;
502+
}
485503
move_master_key_secret(&mk->mk_secret, secret);
504+
WRITE_ONCE(mk->mk_present, true);
486505
}
487506

488507
return 0;
@@ -506,8 +525,8 @@ static int do_add_master_key(struct super_block *sb,
506525
err = add_new_master_key(sb, secret, mk_spec);
507526
} else {
508527
/*
509-
* Found the key in ->s_master_keys. Re-add the secret if
510-
* needed, and add the user to ->mk_users if needed.
528+
* Found the key in ->s_master_keys. Add the user to ->mk_users
529+
* if needed, and make the key "present" again if possible.
511530
*/
512531
down_write(&mk->mk_sem);
513532
err = add_existing_master_key(mk, secret);
@@ -989,9 +1008,8 @@ static int try_to_lock_encrypted_files(struct super_block *sb,
9891008
*
9901009
* If all inodes were evicted, then we unlink the fscrypt_master_key from the
9911010
* keyring. Otherwise it remains in the keyring in the "incompletely removed"
992-
* state (without the actual secret key) where it tracks the list of remaining
993-
* inodes. Userspace can execute the ioctl again later to retry eviction, or
994-
* alternatively can re-add the secret key again.
1011+
* state where it tracks the list of remaining inodes. Userspace can execute
1012+
* the ioctl again later to retry eviction, or alternatively can re-add the key.
9951013
*
9961014
* For more details, see the "Removing keys" section of
9971015
* Documentation/filesystems/fscrypt.rst.
@@ -1053,11 +1071,10 @@ static int do_remove_key(struct file *filp, void __user *_uarg, bool all_users)
10531071
}
10541072
}
10551073

1056-
/* No user claims remaining. Go ahead and wipe the secret. */
1074+
/* No user claims remaining. Initiate removal of the key. */
10571075
err = -ENOKEY;
1058-
if (is_master_key_secret_present(&mk->mk_secret)) {
1059-
wipe_master_key_secret(&mk->mk_secret);
1060-
fscrypt_put_master_key_activeref(sb, mk);
1076+
if (mk->mk_present) {
1077+
fscrypt_initiate_key_removal(sb, mk);
10611078
err = 0;
10621079
}
10631080
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)
10741091
}
10751092
/*
10761093
* We return 0 if we successfully did something: removed a claim to the
1077-
* key, wiped the secret, or tried locking the files again. Users need
1078-
* to check the informational status flags if they care whether the key
1079-
* has been fully removed including all files locked.
1094+
* key, initiated removal of the key, or tried locking the files again.
1095+
* Users need to check the informational status flags if they care
1096+
* whether the key has been fully removed including all files locked.
10801097
*/
10811098
out_put_key:
10821099
fscrypt_put_master_key(mk);
@@ -1103,12 +1120,11 @@ EXPORT_SYMBOL_GPL(fscrypt_ioctl_remove_key_all_users);
11031120
* Retrieve the status of an fscrypt master encryption key.
11041121
*
11051122
* We set ->status to indicate whether the key is absent, present, or
1106-
* incompletely removed. "Incompletely removed" means that the master key
1107-
* secret has been removed, but some files which had been unlocked with it are
1108-
* still in use. This field allows applications to easily determine the state
1109-
* of an encrypted directory without using a hack such as trying to open a
1110-
* regular file in it (which can confuse the "incompletely removed" state with
1111-
* absent or present).
1123+
* incompletely removed. (For an explanation of what these statuses mean and
1124+
* how they are represented internally, see struct fscrypt_master_key.) This
1125+
* field allows applications to easily determine the status of an encrypted
1126+
* directory without using a hack such as trying to open a regular file in it
1127+
* (which can confuse the "incompletely removed" status with absent or present).
11121128
*
11131129
* In addition, for v2 policy keys we allow applications to determine, via
11141130
* ->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)
11501166
}
11511167
down_read(&mk->mk_sem);
11521168

1153-
if (!is_master_key_secret_present(&mk->mk_secret)) {
1169+
if (!mk->mk_present) {
11541170
arg.status = refcount_read(&mk->mk_active_refs) > 0 ?
11551171
FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED :
11561172
FSCRYPT_KEY_STATUS_ABSENT /* raced with full removal */;

fs/crypto/keysetup.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -486,8 +486,8 @@ static int setup_file_encryption_key(struct fscrypt_inode_info *ci,
486486
}
487487
down_read(&mk->mk_sem);
488488

489-
/* Has the secret been removed (via FS_IOC_REMOVE_ENCRYPTION_KEY)? */
490-
if (!is_master_key_secret_present(&mk->mk_secret)) {
489+
if (!mk->mk_present) {
490+
/* FS_IOC_REMOVE_ENCRYPTION_KEY has been executed on this key */
491491
err = -ENOKEY;
492492
goto out_release_key;
493493
}
@@ -539,8 +539,8 @@ static void put_crypt_info(struct fscrypt_inode_info *ci)
539539
/*
540540
* Remove this inode from the list of inodes that were unlocked
541541
* with the master key. In addition, if we're removing the last
542-
* inode from a master key struct that already had its secret
543-
* removed, then complete the full removal of the struct.
542+
* inode from an incompletely removed key, then complete the
543+
* full removal of the key.
544544
*/
545545
spin_lock(&mk->mk_decrypted_inodes_lock);
546546
list_del(&ci->ci_master_key_link);
@@ -801,13 +801,14 @@ int fscrypt_drop_inode(struct inode *inode)
801801
return 0;
802802

803803
/*
804-
* Note: since we aren't holding the key semaphore, the result here can
804+
* We can't take ->mk_sem here, since this runs in atomic context.
805+
* Therefore, ->mk_present can change concurrently, and our result may
805806
* immediately become outdated. But there's no correctness problem with
806807
* unnecessarily evicting. Nor is there a correctness problem with not
807808
* evicting while iput() is racing with the key being removed, since
808809
* then the thread removing the key will either evict the inode itself
809810
* or will correctly detect that it wasn't evicted due to the race.
810811
*/
811-
return !is_master_key_secret_present(&ci->ci_master_key->mk_secret);
812+
return !READ_ONCE(ci->ci_master_key->mk_present);
812813
}
813814
EXPORT_SYMBOL_GPL(fscrypt_drop_inode);

0 commit comments

Comments
 (0)