From: Nathan Huckleberry <nhuck@google.com>
To: Eric Biggers <ebiggers@kernel.org>
Cc: linux-block@vger.kernel.org, Jens Axboe <axboe@kernel.dk>,
linux-fscrypt@vger.kernel.org, stable@vger.kernel.org
Subject: Re: [PATCH v2 1/4] blk-mq: release crypto keyslot before reporting I/O complete
Date: Mon, 13 Mar 2023 14:26:00 -0700 [thread overview]
Message-ID: <CAJkfWY76-fUf92YZid3bOPrufXwCzM-q9L1ezqkLZ+WJiWL3jQ@mail.gmail.com> (raw)
In-Reply-To: <20230308193645.114069-2-ebiggers@kernel.org>
On Wed, Mar 8, 2023 at 11:39 AM Eric Biggers <ebiggers@kernel.org> wrote:
>
> From: Eric Biggers <ebiggers@google.com>
>
> Once all I/O using a blk_crypto_key has completed, filesystems can call
> blk_crypto_evict_key(). However, the block layer currently doesn't call
> blk_crypto_put_keyslot() until the request is being freed, which happens
> after upper layers have been told (via bio_endio()) the I/O has
> completed. This causes a race condition where blk_crypto_evict_key()
> can see 'slot_refs != 0' without there being an actual bug.
>
> This makes __blk_crypto_evict_key() hit the
> 'WARN_ON_ONCE(atomic_read(&slot->slot_refs) != 0)' and return without
> doing anything, eventually causing a use-after-free in
> blk_crypto_reprogram_all_keys(). (This is a very rare bug and has only
> been seen when per-file keys are being used with fscrypt.)
>
> There are two options to fix this: either release the keyslot before
> bio_endio() is called on the request's last bio, or make
> __blk_crypto_evict_key() ignore slot_refs. Let's go with the first
> solution, since it preserves the ability to report bugs (via
> WARN_ON_ONCE) where a key is evicted while still in-use.
>
> Fixes: a892c8d52c02 ("block: Inline encryption support for blk-mq")
> Cc: stable@vger.kernel.org
> Signed-off-by: Eric Biggers <ebiggers@google.com>
> ---
> block/blk-crypto-internal.h | 25 +++++++++++++++++++++----
> block/blk-crypto.c | 24 ++++++++++++------------
> block/blk-merge.c | 2 ++
> block/blk-mq.c | 15 ++++++++++++++-
> 4 files changed, 49 insertions(+), 17 deletions(-)
>
> diff --git a/block/blk-crypto-internal.h b/block/blk-crypto-internal.h
> index a8cdaf26851e..4f1de2495f0c 100644
> --- a/block/blk-crypto-internal.h
> +++ b/block/blk-crypto-internal.h
> @@ -65,6 +65,11 @@ static inline bool blk_crypto_rq_is_encrypted(struct request *rq)
> return rq->crypt_ctx;
> }
>
> +static inline bool blk_crypto_rq_has_keyslot(struct request *rq)
> +{
> + return rq->crypt_keyslot;
> +}
> +
> blk_status_t blk_crypto_get_keyslot(struct blk_crypto_profile *profile,
> const struct blk_crypto_key *key,
> struct blk_crypto_keyslot **slot_ptr);
> @@ -119,6 +124,11 @@ static inline bool blk_crypto_rq_is_encrypted(struct request *rq)
> return false;
> }
>
> +static inline bool blk_crypto_rq_has_keyslot(struct request *rq)
> +{
> + return false;
> +}
> +
> #endif /* CONFIG_BLK_INLINE_ENCRYPTION */
>
> void __bio_crypt_advance(struct bio *bio, unsigned int bytes);
> @@ -153,14 +163,21 @@ static inline bool blk_crypto_bio_prep(struct bio **bio_ptr)
> return true;
> }
>
> -blk_status_t __blk_crypto_init_request(struct request *rq);
> -static inline blk_status_t blk_crypto_init_request(struct request *rq)
> +blk_status_t __blk_crypto_rq_get_keyslot(struct request *rq);
> +static inline blk_status_t blk_crypto_rq_get_keyslot(struct request *rq)
> {
> if (blk_crypto_rq_is_encrypted(rq))
> - return __blk_crypto_init_request(rq);
> + return __blk_crypto_rq_get_keyslot(rq);
> return BLK_STS_OK;
> }
>
> +void __blk_crypto_rq_put_keyslot(struct request *rq);
> +static inline void blk_crypto_rq_put_keyslot(struct request *rq)
> +{
> + if (blk_crypto_rq_has_keyslot(rq))
> + __blk_crypto_rq_put_keyslot(rq);
> +}
> +
> void __blk_crypto_free_request(struct request *rq);
> static inline void blk_crypto_free_request(struct request *rq)
> {
> @@ -199,7 +216,7 @@ static inline blk_status_t blk_crypto_insert_cloned_request(struct request *rq)
> {
>
> if (blk_crypto_rq_is_encrypted(rq))
> - return blk_crypto_init_request(rq);
> + return blk_crypto_rq_get_keyslot(rq);
> return BLK_STS_OK;
> }
>
> diff --git a/block/blk-crypto.c b/block/blk-crypto.c
> index 45378586151f..d0c7feb447e9 100644
> --- a/block/blk-crypto.c
> +++ b/block/blk-crypto.c
> @@ -224,27 +224,27 @@ static bool bio_crypt_check_alignment(struct bio *bio)
> return true;
> }
>
> -blk_status_t __blk_crypto_init_request(struct request *rq)
> +blk_status_t __blk_crypto_rq_get_keyslot(struct request *rq)
> {
> return blk_crypto_get_keyslot(rq->q->crypto_profile,
> rq->crypt_ctx->bc_key,
> &rq->crypt_keyslot);
> }
>
> -/**
> - * __blk_crypto_free_request - Uninitialize the crypto fields of a request.
> - *
> - * @rq: The request whose crypto fields to uninitialize.
> - *
> - * Completely uninitializes the crypto fields of a request. If a keyslot has
> - * been programmed into some inline encryption hardware, that keyslot is
> - * released. The rq->crypt_ctx is also freed.
> - */
> -void __blk_crypto_free_request(struct request *rq)
> +void __blk_crypto_rq_put_keyslot(struct request *rq)
> {
> blk_crypto_put_keyslot(rq->crypt_keyslot);
> + rq->crypt_keyslot = NULL;
> +}
> +
> +void __blk_crypto_free_request(struct request *rq)
> +{
> + /* The keyslot, if one was needed, should have been released earlier. */
> + if (WARN_ON_ONCE(rq->crypt_keyslot))
> + __blk_crypto_rq_put_keyslot(rq);
> +
> mempool_free(rq->crypt_ctx, bio_crypt_ctx_pool);
> - blk_crypto_rq_set_defaults(rq);
> + rq->crypt_ctx = NULL;
> }
>
> /**
> diff --git a/block/blk-merge.c b/block/blk-merge.c
> index 6460abdb2426..65e75efa9bd3 100644
> --- a/block/blk-merge.c
> +++ b/block/blk-merge.c
> @@ -867,6 +867,8 @@ static struct request *attempt_merge(struct request_queue *q,
> if (!blk_discard_mergable(req))
> elv_merge_requests(q, req, next);
>
> + blk_crypto_rq_put_keyslot(next);
> +
This looks good to me, but it looks like there was a pre-existing bug
in the blk-merge code. The elv_merged_request function is only called
when the request does not merge. Does anyone know if that behavior is
correct?
> /*
> * 'next' is going away, so update stats accordingly
> */
> diff --git a/block/blk-mq.c b/block/blk-mq.c
> index d0cb2ef18fe2..49825538d932 100644
> --- a/block/blk-mq.c
> +++ b/block/blk-mq.c
> @@ -840,6 +840,12 @@ static void blk_complete_request(struct request *req)
> req->q->integrity.profile->complete_fn(req, total_bytes);
> #endif
>
> + /*
> + * Upper layers may call blk_crypto_evict_key() anytime after the last
> + * bio_endio(). Therefore, the keyslot must be released before that.
> + */
> + blk_crypto_rq_put_keyslot(req);
> +
> blk_account_io_completion(req, total_bytes);
>
> do {
> @@ -905,6 +911,13 @@ bool blk_update_request(struct request *req, blk_status_t error,
> req->q->integrity.profile->complete_fn(req, nr_bytes);
> #endif
>
> + /*
> + * Upper layers may call blk_crypto_evict_key() anytime after the last
> + * bio_endio(). Therefore, the keyslot must be released before that.
> + */
> + if (blk_crypto_rq_has_keyslot(req) && nr_bytes >= blk_rq_bytes(req))
> + __blk_crypto_rq_put_keyslot(req);
> +
> if (unlikely(error && !blk_rq_is_passthrough(req) &&
> !(req->rq_flags & RQF_QUIET)) &&
> !test_bit(GD_DEAD, &req->q->disk->state)) {
> @@ -2967,7 +2980,7 @@ void blk_mq_submit_bio(struct bio *bio)
>
> blk_mq_bio_to_request(rq, bio, nr_segs);
>
> - ret = blk_crypto_init_request(rq);
> + ret = blk_crypto_rq_get_keyslot(rq);
> if (ret != BLK_STS_OK) {
> bio->bi_status = ret;
> bio_endio(bio);
> --
> 2.39.2
>
This patch itself looks good to me.
Reviewed-by: Nathan Huckleberry <nhuck@google.com>
next prev parent reply other threads:[~2023-03-13 21:26 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-03-08 19:36 [PATCH v2 0/4] Fix blk-crypto keyslot race condition Eric Biggers
2023-03-08 19:36 ` [PATCH v2 1/4] blk-mq: release crypto keyslot before reporting I/O complete Eric Biggers
2023-03-13 21:26 ` Nathan Huckleberry [this message]
2023-03-14 18:20 ` Eric Biggers
2023-03-15 16:18 ` Christoph Hellwig
2023-03-15 16:19 ` Christoph Hellwig
2023-03-08 19:36 ` [PATCH v2 2/4] blk-crypto: make blk_crypto_evict_key() more robust Eric Biggers
2023-03-15 16:23 ` Christoph Hellwig
2023-03-15 18:26 ` Eric Biggers
2023-03-08 19:36 ` [PATCH v2 3/4] blk-crypto: remove blk_crypto_insert_cloned_request() Eric Biggers
2023-03-15 16:24 ` Christoph Hellwig
2023-03-15 18:27 ` Eric Biggers
2023-03-08 19:36 ` [PATCH v2 4/4] blk-crypto: drop the NULL check from blk_crypto_put_keyslot() Eric Biggers
2023-03-15 16:24 ` Christoph Hellwig
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=CAJkfWY76-fUf92YZid3bOPrufXwCzM-q9L1ezqkLZ+WJiWL3jQ@mail.gmail.com \
--to=nhuck@google.com \
--cc=axboe@kernel.dk \
--cc=ebiggers@kernel.org \
--cc=linux-block@vger.kernel.org \
--cc=linux-fscrypt@vger.kernel.org \
--cc=stable@vger.kernel.org \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).