Re: [PATCH v3] fscrypt: add support for the encrypted key type

Eric Biggers
2018-01-29T18:19:43Z

On Thu, Jan 25, 2018 at 04:37:48PM -0800, Eric Biggers wrote:
> On Thu, Jan 18, 2018 at 01:13:59PM +0000, André Draszik wrote:
> > -static int validate_user_key(struct fscrypt_info *crypt_info,
> > +static inline struct key *fscrypt_get_encrypted_key(const char *description)
> > +{
> > + if (IS_ENABLED(CONFIG_ENCRYPTED_KEYS))
> > + return request_key(&key_type_encrypted, description, NULL);
> > + return ERR_PTR(-ENOKEY);
> > +}
> > +
> > +static int validate_keyring_key(struct fscrypt_info *crypt_info,
> > struct fscrypt_context *ctx, u8 *raw_key,
> > const char *prefix, int min_keysize)
> > {
> > char *description;
> > struct key *keyring_key;
> > - struct fscrypt_key *master_key;
> > - const struct user_key_payload *ukp;
> > + const u8 *master_key;
> > + u32 master_key_len;
> > int res;
> >
> > description = kasprintf(GFP_NOFS, "%s%*phN", prefix,
> > @@ -83,39 +93,55 @@ static int validate_user_key(struct fscrypt_info *crypt_info,
> > return -ENOMEM;
> >
> > keyring_key = request_key(&key_type_logon, description, NULL);
> > + if (keyring_key == ERR_PTR(-ENOKEY))
> > + keyring_key = fscrypt_get_encrypted_key(description);
> > kfree(description);
> > if (IS_ERR(keyring_key))
> > return PTR_ERR(keyring_key);
> > down_read(&keyring_key->sem);
> >
> > - if (keyring_key->type != &key_type_logon) {
> > + if (keyring_key->type == &key_type_logon) {
> > + const struct user_key_payload *ukp;
> > + const struct fscrypt_key *fk;
> > +
> > + ukp = user_key_payload_locked(keyring_key);
> > + if (!ukp) {
> > + /* key was revoked before we acquired its semaphore */
> > + res = -EKEYREVOKED;
> > + goto out;
> > + }
> > + if (ukp->datalen != sizeof(struct fscrypt_key)) {
> > + res = -EINVAL;
> > + goto out;
> > + }
> > + fk = (struct fscrypt_key *)ukp->data;
> > + master_key = fk->raw;
> > + master_key_len = fk->size;
> > + } else if (keyring_key->type == &key_type_encrypted) {
> > + const struct encrypted_key_payload *ekp;
> > +
> > + ekp = keyring_key->payload.data[0];
> > + master_key = ekp->decrypted_data;
> > + master_key_len = ekp->decrypted_datalen;
> > + } else {
> > printk_once(KERN_WARNING
> > - "%s: key type must be logon\n", __func__);
> > + "%s: key type must be logon or encrypted\n",
> > + __func__);
> > res = -ENOKEY;
> > goto out;
> > }
> > - ukp = user_key_payload_locked(keyring_key);
> > - if (!ukp) {
> > - /* key was revoked before we acquired its semaphore */
> > - res = -EKEYREVOKED;
> > - goto out;
> > - }
> > - if (ukp->datalen != sizeof(struct fscrypt_key)) {
> > - res = -EINVAL;
> > - goto out;
> > - }
> > - master_key = (struct fscrypt_key *)ukp->data;
> > BUILD_BUG_ON(FS_AES_128_ECB_KEY_SIZE != FS_KEY_DERIVATION_NONCE_SIZE);
> >
> > - if (master_key->size < min_keysize || master_key->size > FS_MAX_KEY_SIZE
> > - || master_key->size % AES_BLOCK_SIZE != 0) {
> > + if (master_key_len < min_keysize || master_key_len > FS_MAX_KEY_SIZE
> > + || master_key_len % AES_BLOCK_SIZE != 0) {
> > printk_once(KERN_WARNING
> > - "%s: key size incorrect: %d\n",
> > - __func__, master_key->size);
> > + "%s: key size incorrect: %u\n",
> > + __func__, master_key_len);
> > res = -ENOKEY;
> > goto out;
> > }
>
> The code changes here look fine, but unfortunately there is a lock ordering
> problem exposed by using a key type that implements the key_type ->read()
> method. The problem is that encrypted_read() can take a page fault during
> copy_to_user() while holding the key semaphore, which then (with ext4) can
> trigger the start of a jbd2 transaction. Meanwhile
> fscrypt_get_encryption_info() can be called from within a jbd2 transaction via
> fscrypt_inherit_context(), which results in a different locking order. We
> probably will need to fix this by removing the call to
> fscrypt_get_encryption_info() from fscrypt_inherit_context().
>

Actually, a better idea might be to access the key payload under rcu_read_lock()
rather than the key semaphore. It looks like that's possible with both the
"logon" and "encrypted" key types.

Note that derive_key_aes() can sleep, so the part under the rcu_read_lock()
would have to just copy the payload to a temporary buffer, and derive_key_aes()
would be done after rcu_read_unlock(), using the temporary buffer. But I think
you can just reuse the 'raw_key' buffer, so that the encryption operation in
derive_key_aes() is done in-place.

Eric