From: Eric Biggers <ebiggers@kernel.org>
To: Satya Tangirala <satyat@google.com>
Cc: linux-block@vger.kernel.org, linux-scsi@vger.kernel.org,
linux-fscrypt@vger.kernel.org, linux-fsdevel@vger.kernel.org,
linux-f2fs-devel@lists.sourceforge.net,
Barani Muthukumaran <bmuthuku@qti.qualcomm.com>,
Kuohong Wang <kuohong.wang@mediatek.com>,
Kim Boojin <boojin.kim@samsung.com>
Subject: Re: [PATCH v4 3/8] block: blk-crypto for Inline Encryption
Date: Tue, 27 Aug 2019 15:34:17 -0700 [thread overview]
Message-ID: <20190827223415.GC27166@gmail.com> (raw)
In-Reply-To: <20190821075714.65140-4-satyat@google.com>
On Wed, Aug 21, 2019 at 12:57:09AM -0700, Satya Tangirala wrote:
> diff --git a/block/blk-crypto.c b/block/blk-crypto.c
> new file mode 100644
> index 000000000000..c8f06264a0f5
> --- /dev/null
> +++ b/block/blk-crypto.c
> @@ -0,0 +1,737 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019 Google LLC
> + */
> +
> +/*
> + * Refer to Documentation/block/inline-encryption.txt for detailed explanation.
> + */
> +
> +#ifdef pr_fmt
> +#undef pr_fmt
> +#endif
This is the beginning of the file, so the
#ifdef pr_fmt
#undef pr_fmt
#endif
is unnecessary.
> +static struct blk_crypto_keyslot {
> + struct crypto_skcipher *tfm;
> + enum blk_crypto_mode_num crypto_mode;
> + u8 key[BLK_CRYPTO_MAX_KEY_SIZE];
> + struct crypto_skcipher *tfms[ARRAY_SIZE(blk_crypto_modes)];
> +} *blk_crypto_keyslots;
It would be helpful if there was a comment somewhere explaining what's going on
with the crypto tfms now, like:
/*
* Allocating a crypto tfm during I/O can deadlock, so we have to preallocate
* all a mode's tfms when that mode starts being used. Since each mode may need
* all the keyslots at some point, each mode needs its own tfm for each keyslot;
* thus, a keyslot may contain tfms for multiple modes. However, to match the
* behavior of real inline encryption hardware (which only supports a single
* encryption context per keyslot), we only allow one tfm per keyslot to be used
* at a time. Unused tfms have their keys cleared.
*/
Otherwise it's not at all obvious what's going on.
> +
> +static struct mutex tfms_lock[ARRAY_SIZE(blk_crypto_modes)];
> +static bool tfms_inited[ARRAY_SIZE(blk_crypto_modes)];
> +
> +struct work_mem {
> + struct work_struct crypto_work;
> + struct bio *bio;
> +};
> +
> +/* The following few vars are only used during the crypto API fallback */
> +static struct keyslot_manager *blk_crypto_ksm;
> +static struct workqueue_struct *blk_crypto_wq;
> +static mempool_t *blk_crypto_page_pool;
> +static struct kmem_cache *blk_crypto_work_mem_cache;
> +
> +bool bio_crypt_swhandled(struct bio *bio)
> +{
> + return bio_has_crypt_ctx(bio) &&
> + bio->bi_crypt_context->processing_ksm == blk_crypto_ksm;
> +}
> +
> +static const u8 zeroes[BLK_CRYPTO_MAX_KEY_SIZE];
> +static void evict_keyslot(unsigned int slot)
> +{
> + struct blk_crypto_keyslot *slotp = &blk_crypto_keyslots[slot];
> + enum blk_crypto_mode_num crypto_mode = slotp->crypto_mode;
> +
> + /* Clear the key in the skcipher */
> + crypto_skcipher_setkey(slotp->tfms[crypto_mode], zeroes,
> + blk_crypto_modes[crypto_mode].keysize);
> + memzero_explicit(slotp->key, BLK_CRYPTO_MAX_KEY_SIZE);
> +}
Unfortunately setting the all-zeroes key won't work, because the all-zeroes key
fails the "weak key" check for XTS, as its two halves are the same.
Presumably this wasn't noticed during testing because the return value of
crypto_skcipher_setkey() is ignored. So I suggest adding a WARN_ON():
err = crypto_skcipher_setkey(slotp->tfms[crypto_mode], blank_key,
blk_crypto_modes[crypto_mode].keysize);
WARN_ON(err);
Then for the actual fix, maybe set a random key instead of an all-zeroes one?
> +
> +static int blk_crypto_keyslot_program(void *priv, const u8 *key,
> + enum blk_crypto_mode_num crypto_mode,
> + unsigned int data_unit_size,
> + unsigned int slot)
> +{
> + struct blk_crypto_keyslot *slotp = &blk_crypto_keyslots[slot];
> + const struct blk_crypto_mode *mode = &blk_crypto_modes[crypto_mode];
> + size_t keysize = mode->keysize;
> + int err;
> +
> + if (crypto_mode != slotp->crypto_mode) {
> + evict_keyslot(slot);
> + slotp->crypto_mode = crypto_mode;
> + }
Currently the crypto_mode of every blk_crypto_keyslot starts out as AES_256_XTS
(0). So if the user starts by choosing some other mode, this will immediately
call evict_keyslot() and crash dereferencing a NULL pointer.
To fix this, how about initializing all the modes to
BLK_ENCRYPTION_MODE_INVALID?
Then here the code would need to be:
if (crypto_mode != slotp->crypto_mode &&
slotp->crypto_mode != BLK_ENCRYPTION_MODE_INVALID)
evict_keyslot(slot);
And evict_keyslot() should invalidate the crypto_mode:
static void evict_keyslot(unsigned int slot)
{
...
slotp->crypto_mode = BLK_ENCRYPTION_MODE_INVALID;
}
> +
> +static int blk_crypto_keyslot_evict(void *priv, const u8 *key,
> + enum blk_crypto_mode_num crypto_mode,
> + unsigned int data_unit_size,
> + unsigned int slot)
> +{
> + evict_keyslot(slot);
> + return 0;
> +}
It might be useful to have a WARN_ON() here if the keyslot isn't in use
(i.e., if slotp->crypto_mode == BLK_ENCRYPTION_MODE_INVALID).
> +int blk_crypto_submit_bio(struct bio **bio_ptr)
> +{
> + struct bio *bio = *bio_ptr;
> + struct request_queue *q;
> + int err;
> + struct bio_crypt_ctx *crypt_ctx;
> +
> + if (!bio_has_crypt_ctx(bio) || !bio_has_data(bio))
> + return 0;
> +
> + /*
> + * When a read bio is marked for sw decryption, its bi_iter is saved
> + * so that when we decrypt the bio later, we know what part of it was
> + * marked for sw decryption (when the bio is passed down after
> + * blk_crypto_submit bio, it may be split or advanced so we cannot rely
> + * on the bi_iter while decrypting in blk_crypto_endio)
> + */
> + if (bio_crypt_swhandled(bio))
> + return 0;
> +
> + err = bio_crypt_check_alignment(bio);
> + if (err)
> + goto out;
Need to set ->bi_status if bio_crypt_check_alignment() fails.
> +bool blk_crypto_endio(struct bio *bio)
> +{
> + if (!bio_has_crypt_ctx(bio))
> + return true;
> +
> + if (bio_crypt_swhandled(bio)) {
> + /*
> + * The only bios that are swhandled when they reach here
> + * are those with bio_data_dir(bio) == READ, since WRITE
> + * bios that are encrypted by the crypto API fallback are
> + * handled by blk_crypto_encrypt_endio.
> + */
> +
> + /* If there was an IO error, don't decrypt. */
> + if (bio->bi_status)
> + return true;
> +
> + blk_crypto_queue_decrypt_bio(bio);
> + return false;
> + }
> +
> + if (bio_has_crypt_ctx(bio) && bio_crypt_has_keyslot(bio))
> + bio_crypt_ctx_release_keyslot(bio);
No need to check bio_has_crypt_ctx(bio) here, as it was already checked above.
> +int blk_crypto_mode_alloc_ciphers(enum blk_crypto_mode_num mode_num)
> +{
> + struct blk_crypto_keyslot *slotp;
> + int err = 0;
> + int i;
> +
> + /* Fast path */
> + if (likely(READ_ONCE(tfms_inited[mode_num]))) {
> + /*
> + * Ensure that updates to blk_crypto_keyslots[i].tfms[mode_num]
> + * for each i are visible before we try to access them.
> + */
> + smp_rmb();
> + return 0;
> + }
I think we want smp_load_acquire() here.
/* pairs with smp_store_release() below */
if (smp_load_acquire(&tfms_inited[mode_num]))
return 0;
> +
> + mutex_lock(&tfms_lock[mode_num]);
> + if (likely(tfms_inited[mode_num]))
> + goto out;
> +
> + for (i = 0; i < blk_crypto_num_keyslots; i++) {
> + slotp = &blk_crypto_keyslots[i];
> + slotp->tfms[mode_num] = crypto_alloc_skcipher(
> + blk_crypto_modes[mode_num].cipher_str,
> + 0, 0);
> + if (IS_ERR(slotp->tfms[mode_num])) {
> + err = PTR_ERR(slotp->tfms[mode_num]);
> + slotp->tfms[mode_num] = NULL;
> + goto out_free_tfms;
> + }
> +
> + crypto_skcipher_set_flags(slotp->tfms[mode_num],
> + CRYPTO_TFM_REQ_FORBID_WEAK_KEYS);
> + }
> +
> + /*
> + * Ensure that updates to blk_crypto_keyslots[i].tfms[mode_num]
> + * for each i are visible before we set tfms_inited[mode_num].
> + */
> + smp_wmb();
> + WRITE_ONCE(tfms_inited[mode_num], true);
> + goto out;
... and smp_store_release() here.
/* pairs with smp_load_acquire() above */
smp_store_release(&tfms_inited[mode_num], true);
goto out;
- Eric
WARNING: multiple messages have this Message-ID (diff)
From: Eric Biggers <ebiggers@kernel.org>
To: Satya Tangirala <satyat@google.com>
Cc: linux-scsi@vger.kernel.org, Kim Boojin <boojin.kim@samsung.com>,
Kuohong Wang <kuohong.wang@mediatek.com>,
Barani Muthukumaran <bmuthuku@qti.qualcomm.com>,
linux-f2fs-devel@lists.sourceforge.net,
linux-block@vger.kernel.org, linux-fscrypt@vger.kernel.org,
linux-fsdevel@vger.kernel.org
Subject: Re: [f2fs-dev] [PATCH v4 3/8] block: blk-crypto for Inline Encryption
Date: Tue, 27 Aug 2019 15:34:17 -0700 [thread overview]
Message-ID: <20190827223415.GC27166@gmail.com> (raw)
In-Reply-To: <20190821075714.65140-4-satyat@google.com>
On Wed, Aug 21, 2019 at 12:57:09AM -0700, Satya Tangirala wrote:
> diff --git a/block/blk-crypto.c b/block/blk-crypto.c
> new file mode 100644
> index 000000000000..c8f06264a0f5
> --- /dev/null
> +++ b/block/blk-crypto.c
> @@ -0,0 +1,737 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2019 Google LLC
> + */
> +
> +/*
> + * Refer to Documentation/block/inline-encryption.txt for detailed explanation.
> + */
> +
> +#ifdef pr_fmt
> +#undef pr_fmt
> +#endif
This is the beginning of the file, so the
#ifdef pr_fmt
#undef pr_fmt
#endif
is unnecessary.
> +static struct blk_crypto_keyslot {
> + struct crypto_skcipher *tfm;
> + enum blk_crypto_mode_num crypto_mode;
> + u8 key[BLK_CRYPTO_MAX_KEY_SIZE];
> + struct crypto_skcipher *tfms[ARRAY_SIZE(blk_crypto_modes)];
> +} *blk_crypto_keyslots;
It would be helpful if there was a comment somewhere explaining what's going on
with the crypto tfms now, like:
/*
* Allocating a crypto tfm during I/O can deadlock, so we have to preallocate
* all a mode's tfms when that mode starts being used. Since each mode may need
* all the keyslots at some point, each mode needs its own tfm for each keyslot;
* thus, a keyslot may contain tfms for multiple modes. However, to match the
* behavior of real inline encryption hardware (which only supports a single
* encryption context per keyslot), we only allow one tfm per keyslot to be used
* at a time. Unused tfms have their keys cleared.
*/
Otherwise it's not at all obvious what's going on.
> +
> +static struct mutex tfms_lock[ARRAY_SIZE(blk_crypto_modes)];
> +static bool tfms_inited[ARRAY_SIZE(blk_crypto_modes)];
> +
> +struct work_mem {
> + struct work_struct crypto_work;
> + struct bio *bio;
> +};
> +
> +/* The following few vars are only used during the crypto API fallback */
> +static struct keyslot_manager *blk_crypto_ksm;
> +static struct workqueue_struct *blk_crypto_wq;
> +static mempool_t *blk_crypto_page_pool;
> +static struct kmem_cache *blk_crypto_work_mem_cache;
> +
> +bool bio_crypt_swhandled(struct bio *bio)
> +{
> + return bio_has_crypt_ctx(bio) &&
> + bio->bi_crypt_context->processing_ksm == blk_crypto_ksm;
> +}
> +
> +static const u8 zeroes[BLK_CRYPTO_MAX_KEY_SIZE];
> +static void evict_keyslot(unsigned int slot)
> +{
> + struct blk_crypto_keyslot *slotp = &blk_crypto_keyslots[slot];
> + enum blk_crypto_mode_num crypto_mode = slotp->crypto_mode;
> +
> + /* Clear the key in the skcipher */
> + crypto_skcipher_setkey(slotp->tfms[crypto_mode], zeroes,
> + blk_crypto_modes[crypto_mode].keysize);
> + memzero_explicit(slotp->key, BLK_CRYPTO_MAX_KEY_SIZE);
> +}
Unfortunately setting the all-zeroes key won't work, because the all-zeroes key
fails the "weak key" check for XTS, as its two halves are the same.
Presumably this wasn't noticed during testing because the return value of
crypto_skcipher_setkey() is ignored. So I suggest adding a WARN_ON():
err = crypto_skcipher_setkey(slotp->tfms[crypto_mode], blank_key,
blk_crypto_modes[crypto_mode].keysize);
WARN_ON(err);
Then for the actual fix, maybe set a random key instead of an all-zeroes one?
> +
> +static int blk_crypto_keyslot_program(void *priv, const u8 *key,
> + enum blk_crypto_mode_num crypto_mode,
> + unsigned int data_unit_size,
> + unsigned int slot)
> +{
> + struct blk_crypto_keyslot *slotp = &blk_crypto_keyslots[slot];
> + const struct blk_crypto_mode *mode = &blk_crypto_modes[crypto_mode];
> + size_t keysize = mode->keysize;
> + int err;
> +
> + if (crypto_mode != slotp->crypto_mode) {
> + evict_keyslot(slot);
> + slotp->crypto_mode = crypto_mode;
> + }
Currently the crypto_mode of every blk_crypto_keyslot starts out as AES_256_XTS
(0). So if the user starts by choosing some other mode, this will immediately
call evict_keyslot() and crash dereferencing a NULL pointer.
To fix this, how about initializing all the modes to
BLK_ENCRYPTION_MODE_INVALID?
Then here the code would need to be:
if (crypto_mode != slotp->crypto_mode &&
slotp->crypto_mode != BLK_ENCRYPTION_MODE_INVALID)
evict_keyslot(slot);
And evict_keyslot() should invalidate the crypto_mode:
static void evict_keyslot(unsigned int slot)
{
...
slotp->crypto_mode = BLK_ENCRYPTION_MODE_INVALID;
}
> +
> +static int blk_crypto_keyslot_evict(void *priv, const u8 *key,
> + enum blk_crypto_mode_num crypto_mode,
> + unsigned int data_unit_size,
> + unsigned int slot)
> +{
> + evict_keyslot(slot);
> + return 0;
> +}
It might be useful to have a WARN_ON() here if the keyslot isn't in use
(i.e., if slotp->crypto_mode == BLK_ENCRYPTION_MODE_INVALID).
> +int blk_crypto_submit_bio(struct bio **bio_ptr)
> +{
> + struct bio *bio = *bio_ptr;
> + struct request_queue *q;
> + int err;
> + struct bio_crypt_ctx *crypt_ctx;
> +
> + if (!bio_has_crypt_ctx(bio) || !bio_has_data(bio))
> + return 0;
> +
> + /*
> + * When a read bio is marked for sw decryption, its bi_iter is saved
> + * so that when we decrypt the bio later, we know what part of it was
> + * marked for sw decryption (when the bio is passed down after
> + * blk_crypto_submit bio, it may be split or advanced so we cannot rely
> + * on the bi_iter while decrypting in blk_crypto_endio)
> + */
> + if (bio_crypt_swhandled(bio))
> + return 0;
> +
> + err = bio_crypt_check_alignment(bio);
> + if (err)
> + goto out;
Need to set ->bi_status if bio_crypt_check_alignment() fails.
> +bool blk_crypto_endio(struct bio *bio)
> +{
> + if (!bio_has_crypt_ctx(bio))
> + return true;
> +
> + if (bio_crypt_swhandled(bio)) {
> + /*
> + * The only bios that are swhandled when they reach here
> + * are those with bio_data_dir(bio) == READ, since WRITE
> + * bios that are encrypted by the crypto API fallback are
> + * handled by blk_crypto_encrypt_endio.
> + */
> +
> + /* If there was an IO error, don't decrypt. */
> + if (bio->bi_status)
> + return true;
> +
> + blk_crypto_queue_decrypt_bio(bio);
> + return false;
> + }
> +
> + if (bio_has_crypt_ctx(bio) && bio_crypt_has_keyslot(bio))
> + bio_crypt_ctx_release_keyslot(bio);
No need to check bio_has_crypt_ctx(bio) here, as it was already checked above.
> +int blk_crypto_mode_alloc_ciphers(enum blk_crypto_mode_num mode_num)
> +{
> + struct blk_crypto_keyslot *slotp;
> + int err = 0;
> + int i;
> +
> + /* Fast path */
> + if (likely(READ_ONCE(tfms_inited[mode_num]))) {
> + /*
> + * Ensure that updates to blk_crypto_keyslots[i].tfms[mode_num]
> + * for each i are visible before we try to access them.
> + */
> + smp_rmb();
> + return 0;
> + }
I think we want smp_load_acquire() here.
/* pairs with smp_store_release() below */
if (smp_load_acquire(&tfms_inited[mode_num]))
return 0;
> +
> + mutex_lock(&tfms_lock[mode_num]);
> + if (likely(tfms_inited[mode_num]))
> + goto out;
> +
> + for (i = 0; i < blk_crypto_num_keyslots; i++) {
> + slotp = &blk_crypto_keyslots[i];
> + slotp->tfms[mode_num] = crypto_alloc_skcipher(
> + blk_crypto_modes[mode_num].cipher_str,
> + 0, 0);
> + if (IS_ERR(slotp->tfms[mode_num])) {
> + err = PTR_ERR(slotp->tfms[mode_num]);
> + slotp->tfms[mode_num] = NULL;
> + goto out_free_tfms;
> + }
> +
> + crypto_skcipher_set_flags(slotp->tfms[mode_num],
> + CRYPTO_TFM_REQ_FORBID_WEAK_KEYS);
> + }
> +
> + /*
> + * Ensure that updates to blk_crypto_keyslots[i].tfms[mode_num]
> + * for each i are visible before we set tfms_inited[mode_num].
> + */
> + smp_wmb();
> + WRITE_ONCE(tfms_inited[mode_num], true);
> + goto out;
... and smp_store_release() here.
/* pairs with smp_load_acquire() above */
smp_store_release(&tfms_inited[mode_num], true);
goto out;
- Eric
_______________________________________________
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel
next prev parent reply other threads:[~2019-08-27 22:34 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-08-21 7:57 [PATCH v4 0/8] Inline Encryption Support Satya Tangirala
2019-08-21 7:57 ` [f2fs-dev] " Satya Tangirala via Linux-f2fs-devel
2019-08-21 7:57 ` [PATCH v4 1/8] block: Keyslot Manager for Inline Encryption Satya Tangirala
2019-08-21 7:57 ` [f2fs-dev] " Satya Tangirala via Linux-f2fs-devel
2019-08-27 20:49 ` Eric Biggers
2019-08-27 21:15 ` Eric Biggers
2019-08-27 21:15 ` [f2fs-dev] " Eric Biggers
2019-08-21 7:57 ` [PATCH v4 2/8] block: Add encryption context to struct bio Satya Tangirala
2019-08-21 7:57 ` [f2fs-dev] " Satya Tangirala via Linux-f2fs-devel
2019-09-24 10:57 ` Stanley Chu
2019-09-24 10:57 ` [f2fs-dev] " Stanley Chu
2019-08-21 7:57 ` [PATCH v4 3/8] block: blk-crypto for Inline Encryption Satya Tangirala
2019-08-21 7:57 ` [f2fs-dev] " Satya Tangirala via Linux-f2fs-devel
2019-08-26 18:17 ` Jonathan Corbet
2019-08-26 18:17 ` [f2fs-dev] " Jonathan Corbet
2019-08-27 22:34 ` Eric Biggers [this message]
2019-08-27 22:34 ` Eric Biggers
2019-08-21 7:57 ` [PATCH v4 4/8] scsi: ufs: UFS driver v2.1 spec crypto additions Satya Tangirala
2019-08-21 7:57 ` [f2fs-dev] " Satya Tangirala via Linux-f2fs-devel
2019-08-21 7:57 ` [PATCH v4 5/8] scsi: ufs: UFS crypto API Satya Tangirala
2019-08-21 7:57 ` [f2fs-dev] " Satya Tangirala via Linux-f2fs-devel
2019-08-27 23:25 ` Eric Biggers
2019-08-27 23:25 ` [f2fs-dev] " Eric Biggers
2019-08-21 7:57 ` [PATCH v4 6/8] scsi: ufs: Add inline encryption support to UFS Satya Tangirala
2019-08-21 7:57 ` [f2fs-dev] " Satya Tangirala via Linux-f2fs-devel
2019-08-21 7:57 ` [PATCH v4 7/8] fscrypt: wire up fscrypt to use blk-crypto Satya Tangirala
2019-08-21 7:57 ` [f2fs-dev] " Satya Tangirala via Linux-f2fs-devel
2019-08-28 0:07 ` Eric Biggers
2019-08-28 0:07 ` [f2fs-dev] " Eric Biggers
2019-08-21 7:57 ` [PATCH v4 8/8] f2fs: Wire up f2fs to use inline encryption via fscrypt Satya Tangirala
2019-08-21 7:57 ` [f2fs-dev] " Satya Tangirala via Linux-f2fs-devel
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190827223415.GC27166@gmail.com \
--to=ebiggers@kernel.org \
--cc=bmuthuku@qti.qualcomm.com \
--cc=boojin.kim@samsung.com \
--cc=kuohong.wang@mediatek.com \
--cc=linux-block@vger.kernel.org \
--cc=linux-f2fs-devel@lists.sourceforge.net \
--cc=linux-fscrypt@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-scsi@vger.kernel.org \
--cc=satyat@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.