* [PATCH 5.15 0/3] blk-crypto fixes for 5.15
@ 2023-05-04 4:03 Eric Biggers
2023-05-04 4:03 ` [PATCH 5.15 1/3] blk-mq: release crypto keyslot before reporting I/O complete Eric Biggers
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Eric Biggers @ 2023-05-04 4:03 UTC (permalink / raw)
To: stable; +Cc: linux-block
This series backports some blk-crypto fixes to 5.15.
Eric Biggers (3):
blk-mq: release crypto keyslot before reporting I/O complete
blk-crypto: make blk_crypto_evict_key() return void
blk-crypto: make blk_crypto_evict_key() more robust
block/blk-core.c | 7 ++++
block/blk-crypto-internal.h | 25 +++++++++++---
block/blk-crypto.c | 69 +++++++++++++++++++++----------------
block/blk-merge.c | 2 ++
block/blk-mq.c | 2 +-
block/keyslot-manager.c | 43 +++++++++++------------
drivers/md/dm-table.c | 19 +++-------
include/linux/blk-crypto.h | 4 +--
8 files changed, 99 insertions(+), 72 deletions(-)
base-commit: 8a7f2a5c5aa1648edb4f2029c6ec33870afb7a95
--
2.40.1
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 5.15 1/3] blk-mq: release crypto keyslot before reporting I/O complete
2023-05-04 4:03 [PATCH 5.15 0/3] blk-crypto fixes for 5.15 Eric Biggers
@ 2023-05-04 4:03 ` Eric Biggers
2023-05-04 4:03 ` [PATCH 5.15 2/3] blk-crypto: make blk_crypto_evict_key() return void Eric Biggers
2023-05-04 4:03 ` [PATCH 5.15 3/3] blk-crypto: make blk_crypto_evict_key() more robust Eric Biggers
2 siblings, 0 replies; 4+ messages in thread
From: Eric Biggers @ 2023-05-04 4:03 UTC (permalink / raw)
To: stable; +Cc: linux-block, Nathan Huckleberry, Christoph Hellwig, Jens Axboe
From: Eric Biggers <ebiggers@google.com>
commit 9cd1e566676bbcb8a126acd921e4e194e6339603 upstream.
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
Reviewed-by: Nathan Huckleberry <nhuck@google.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Link: https://lore.kernel.org/r/20230315183907.53675-2-ebiggers@kernel.org
Signed-off-by: Jens Axboe <axboe@kernel.dk>
---
block/blk-core.c | 7 +++++++
block/blk-crypto-internal.h | 25 +++++++++++++++++++++----
block/blk-crypto.c | 24 ++++++++++++------------
block/blk-merge.c | 2 ++
block/blk-mq.c | 2 +-
5 files changed, 43 insertions(+), 17 deletions(-)
diff --git a/block/blk-core.c b/block/blk-core.c
index ed6271dcc1b16..0c4a4e42ad870 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -1421,6 +1421,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)))
print_req_error(req, error, __func__);
diff --git a/block/blk-crypto-internal.h b/block/blk-crypto-internal.h
index 0d36aae538d7b..8e08345576203 100644
--- a/block/blk-crypto-internal.h
+++ b/block/blk-crypto-internal.h
@@ -60,6 +60,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;
+}
+
#else /* CONFIG_BLK_INLINE_ENCRYPTION */
static inline bool bio_crypt_rq_ctx_compatible(struct request *rq,
@@ -93,6 +98,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);
@@ -127,14 +137,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)
{
@@ -173,7 +190,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 103c2e2d50d67..183d7439cf416 100644
--- a/block/blk-crypto.c
+++ b/block/blk-crypto.c
@@ -216,26 +216,26 @@ 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_ksm_get_slot_for_key(rq->q->ksm, 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_ksm_put_slot(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 d1435b6572977..1affc5fd35f0c 100644
--- a/block/blk-merge.c
+++ b/block/blk-merge.c
@@ -818,6 +818,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);
+
/*
* 'next' is going away, so update stats accordingly
*/
diff --git a/block/blk-mq.c b/block/blk-mq.c
index 01e281801453d..bbbbcd2c19418 100644
--- a/block/blk-mq.c
+++ b/block/blk-mq.c
@@ -2228,7 +2228,7 @@ blk_qc_t 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.40.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 5.15 2/3] blk-crypto: make blk_crypto_evict_key() return void
2023-05-04 4:03 [PATCH 5.15 0/3] blk-crypto fixes for 5.15 Eric Biggers
2023-05-04 4:03 ` [PATCH 5.15 1/3] blk-mq: release crypto keyslot before reporting I/O complete Eric Biggers
@ 2023-05-04 4:03 ` Eric Biggers
2023-05-04 4:03 ` [PATCH 5.15 3/3] blk-crypto: make blk_crypto_evict_key() more robust Eric Biggers
2 siblings, 0 replies; 4+ messages in thread
From: Eric Biggers @ 2023-05-04 4:03 UTC (permalink / raw)
To: stable; +Cc: linux-block, Christoph Hellwig, Jens Axboe
From: Eric Biggers <ebiggers@google.com>
commit 70493a63ba04f754f7a7dd53a4fcc82700181490 upstream.
blk_crypto_evict_key() is only called in contexts such as inode eviction
where failure is not an option. So there is nothing the caller can do
with errors except log them. (dm-table.c does "use" the error code, but
only to pass on to upper layers, so it doesn't really count.)
Just make blk_crypto_evict_key() return void and log errors itself.
Cc: stable@vger.kernel.org
Signed-off-by: Eric Biggers <ebiggers@google.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Link: https://lore.kernel.org/r/20230315183907.53675-2-ebiggers@kernel.org
Signed-off-by: Jens Axboe <axboe@kernel.dk>
---
block/blk-crypto.c | 22 ++++++++++------------
drivers/md/dm-table.c | 19 +++++--------------
include/linux/blk-crypto.h | 4 ++--
3 files changed, 17 insertions(+), 28 deletions(-)
diff --git a/block/blk-crypto.c b/block/blk-crypto.c
index 183d7439cf416..686c657f91917 100644
--- a/block/blk-crypto.c
+++ b/block/blk-crypto.c
@@ -13,6 +13,7 @@
#include <linux/blkdev.h>
#include <linux/keyslot-manager.h>
#include <linux/module.h>
+#include <linux/ratelimit.h>
#include <linux/slab.h>
#include "blk-crypto-internal.h"
@@ -393,20 +394,17 @@ int blk_crypto_start_using_key(const struct blk_crypto_key *key,
* Upper layers (filesystems) must call this function to ensure that a key is
* evicted from any hardware that it might have been programmed into. The key
* must not be in use by any in-flight IO when this function is called.
- *
- * Return: 0 on success or if key is not present in the q's ksm, -err on error.
*/
-int blk_crypto_evict_key(struct request_queue *q,
- const struct blk_crypto_key *key)
+void blk_crypto_evict_key(struct request_queue *q,
+ const struct blk_crypto_key *key)
{
- if (blk_ksm_crypto_cfg_supported(q->ksm, &key->crypto_cfg))
- return blk_ksm_evict_key(q->ksm, key);
+ int err;
- /*
- * If the request queue's associated inline encryption hardware didn't
- * have support for the key, then the key might have been programmed
- * into the fallback keyslot manager, so try to evict from there.
- */
- return blk_crypto_fallback_evict_key(key);
+ if (blk_ksm_crypto_cfg_supported(q->ksm, &key->crypto_cfg))
+ err = blk_ksm_evict_key(q->ksm, key);
+ else
+ err = blk_crypto_fallback_evict_key(key);
+ if (err)
+ pr_warn_ratelimited("error %d evicting key\n", err);
}
EXPORT_SYMBOL_GPL(blk_crypto_evict_key);
diff --git a/drivers/md/dm-table.c b/drivers/md/dm-table.c
index 2111daaacabaf..46ec4590f62f6 100644
--- a/drivers/md/dm-table.c
+++ b/drivers/md/dm-table.c
@@ -1191,21 +1191,12 @@ struct dm_keyslot_manager {
struct mapped_device *md;
};
-struct dm_keyslot_evict_args {
- const struct blk_crypto_key *key;
- int err;
-};
-
static int dm_keyslot_evict_callback(struct dm_target *ti, struct dm_dev *dev,
sector_t start, sector_t len, void *data)
{
- struct dm_keyslot_evict_args *args = data;
- int err;
+ const struct blk_crypto_key *key = data;
- err = blk_crypto_evict_key(bdev_get_queue(dev->bdev), args->key);
- if (!args->err)
- args->err = err;
- /* Always try to evict the key from all devices. */
+ blk_crypto_evict_key(bdev_get_queue(dev->bdev), key);
return 0;
}
@@ -1220,7 +1211,6 @@ static int dm_keyslot_evict(struct blk_keyslot_manager *ksm,
struct dm_keyslot_manager,
ksm);
struct mapped_device *md = dksm->md;
- struct dm_keyslot_evict_args args = { key };
struct dm_table *t;
int srcu_idx;
int i;
@@ -1233,10 +1223,11 @@ static int dm_keyslot_evict(struct blk_keyslot_manager *ksm,
ti = dm_table_get_target(t, i);
if (!ti->type->iterate_devices)
continue;
- ti->type->iterate_devices(ti, dm_keyslot_evict_callback, &args);
+ ti->type->iterate_devices(ti, dm_keyslot_evict_callback,
+ (void *)key);
}
dm_put_live_table(md, srcu_idx);
- return args.err;
+ return 0;
}
static const struct blk_ksm_ll_ops dm_ksm_ll_ops = {
diff --git a/include/linux/blk-crypto.h b/include/linux/blk-crypto.h
index 69b24fe92cbf1..5e96bad548047 100644
--- a/include/linux/blk-crypto.h
+++ b/include/linux/blk-crypto.h
@@ -97,8 +97,8 @@ int blk_crypto_init_key(struct blk_crypto_key *blk_key, const u8 *raw_key,
int blk_crypto_start_using_key(const struct blk_crypto_key *key,
struct request_queue *q);
-int blk_crypto_evict_key(struct request_queue *q,
- const struct blk_crypto_key *key);
+void blk_crypto_evict_key(struct request_queue *q,
+ const struct blk_crypto_key *key);
bool blk_crypto_config_supported(struct request_queue *q,
const struct blk_crypto_config *cfg);
--
2.40.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 5.15 3/3] blk-crypto: make blk_crypto_evict_key() more robust
2023-05-04 4:03 [PATCH 5.15 0/3] blk-crypto fixes for 5.15 Eric Biggers
2023-05-04 4:03 ` [PATCH 5.15 1/3] blk-mq: release crypto keyslot before reporting I/O complete Eric Biggers
2023-05-04 4:03 ` [PATCH 5.15 2/3] blk-crypto: make blk_crypto_evict_key() return void Eric Biggers
@ 2023-05-04 4:03 ` Eric Biggers
2 siblings, 0 replies; 4+ messages in thread
From: Eric Biggers @ 2023-05-04 4:03 UTC (permalink / raw)
To: stable; +Cc: linux-block, Christoph Hellwig, Jens Axboe
From: Eric Biggers <ebiggers@google.com>
commit 5c7cb94452901a93e90c2230632e2c12a681bc92 upstream.
If blk_crypto_evict_key() sees that the key is still in-use (due to a
bug) or that ->keyslot_evict failed, it currently just returns while
leaving the key linked into the keyslot management structures.
However, blk_crypto_evict_key() is only called in contexts such as inode
eviction where failure is not an option. So actually the caller
proceeds with freeing the blk_crypto_key regardless of the return value
of blk_crypto_evict_key().
These two assumptions don't match, and the result is that there can be a
use-after-free in blk_crypto_reprogram_all_keys() after one of these
errors occurs. (Note, these errors *shouldn't* happen; we're just
talking about what happens if they do anyway.)
Fix this by making blk_crypto_evict_key() unlink the key from the
keyslot management structures even on failure.
Also improve some comments.
Fixes: 1b2628397058 ("block: Keyslot Manager for Inline Encryption")
Cc: stable@vger.kernel.org
Signed-off-by: Eric Biggers <ebiggers@google.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Link: https://lore.kernel.org/r/20230315183907.53675-2-ebiggers@kernel.org
Signed-off-by: Jens Axboe <axboe@kernel.dk>
---
block/blk-crypto.c | 29 +++++++++++++++++++--------
block/keyslot-manager.c | 43 ++++++++++++++++++++---------------------
2 files changed, 42 insertions(+), 30 deletions(-)
diff --git a/block/blk-crypto.c b/block/blk-crypto.c
index 686c657f91917..5029a50807d5d 100644
--- a/block/blk-crypto.c
+++ b/block/blk-crypto.c
@@ -385,15 +385,20 @@ int blk_crypto_start_using_key(const struct blk_crypto_key *key,
}
/**
- * blk_crypto_evict_key() - Evict a key from any inline encryption hardware
- * it may have been programmed into
- * @q: The request queue who's associated inline encryption hardware this key
- * might have been programmed into
- * @key: The key to evict
+ * blk_crypto_evict_key() - Evict a blk_crypto_key from a request_queue
+ * @q: a request_queue on which I/O using the key may have been done
+ * @key: the key to evict
*
- * Upper layers (filesystems) must call this function to ensure that a key is
- * evicted from any hardware that it might have been programmed into. The key
- * must not be in use by any in-flight IO when this function is called.
+ * For a given request_queue, this function removes the given blk_crypto_key
+ * from the keyslot management structures and evicts it from any underlying
+ * hardware keyslot(s) or blk-crypto-fallback keyslot it may have been
+ * programmed into.
+ *
+ * Upper layers must call this before freeing the blk_crypto_key. It must be
+ * called for every request_queue the key may have been used on. The key must
+ * no longer be in use by any I/O when this function is called.
+ *
+ * Context: May sleep.
*/
void blk_crypto_evict_key(struct request_queue *q,
const struct blk_crypto_key *key)
@@ -404,6 +409,14 @@ void blk_crypto_evict_key(struct request_queue *q,
err = blk_ksm_evict_key(q->ksm, key);
else
err = blk_crypto_fallback_evict_key(key);
+ /*
+ * An error can only occur here if the key failed to be evicted from a
+ * keyslot (due to a hardware or driver issue) or is allegedly still in
+ * use by I/O (due to a kernel bug). Even in these cases, the key is
+ * still unlinked from the keyslot management structures, and the caller
+ * is allowed and expected to free it right away. There's nothing
+ * callers can do to handle errors, so just log them and return void.
+ */
if (err)
pr_warn_ratelimited("error %d evicting key\n", err);
}
diff --git a/block/keyslot-manager.c b/block/keyslot-manager.c
index 2c4a55bea6ca1..2a7a36551cfae 100644
--- a/block/keyslot-manager.c
+++ b/block/keyslot-manager.c
@@ -343,25 +343,16 @@ bool blk_ksm_crypto_cfg_supported(struct blk_keyslot_manager *ksm,
return true;
}
-/**
- * blk_ksm_evict_key() - Evict a key from the lower layer device.
- * @ksm: The keyslot manager to evict from
- * @key: The key to evict
- *
- * Find the keyslot that the specified key was programmed into, and evict that
- * slot from the lower layer device. The slot must not be in use by any
- * in-flight IO when this function is called.
- *
- * Context: Process context. Takes and releases ksm->lock.
- * Return: 0 on success or if there's no keyslot with the specified key, -EBUSY
- * if the keyslot is still in use, or another -errno value on other
- * error.
+/*
+ * This is an internal function that evicts a key from an inline encryption
+ * device that can be either a real device or the blk-crypto-fallback "device".
+ * It is used only by blk_crypto_evict_key(); see that function for details.
*/
int blk_ksm_evict_key(struct blk_keyslot_manager *ksm,
const struct blk_crypto_key *key)
{
struct blk_ksm_keyslot *slot;
- int err = 0;
+ int err;
if (blk_ksm_is_passthrough(ksm)) {
if (ksm->ksm_ll_ops.keyslot_evict) {
@@ -375,22 +366,30 @@ int blk_ksm_evict_key(struct blk_keyslot_manager *ksm,
blk_ksm_hw_enter(ksm);
slot = blk_ksm_find_keyslot(ksm, key);
- if (!slot)
- goto out_unlock;
+ if (!slot) {
+ /*
+ * Not an error, since a key not in use by I/O is not guaranteed
+ * to be in a keyslot. There can be more keys than keyslots.
+ */
+ err = 0;
+ goto out;
+ }
if (WARN_ON_ONCE(atomic_read(&slot->slot_refs) != 0)) {
+ /* BUG: key is still in use by I/O */
err = -EBUSY;
- goto out_unlock;
+ goto out_remove;
}
err = ksm->ksm_ll_ops.keyslot_evict(ksm, key,
blk_ksm_get_slot_idx(slot));
- if (err)
- goto out_unlock;
-
+out_remove:
+ /*
+ * Callers free the key even on error, so unlink the key from the hash
+ * table and clear slot->key even on error.
+ */
hlist_del(&slot->hash_node);
slot->key = NULL;
- err = 0;
-out_unlock:
+out:
blk_ksm_hw_exit(ksm);
return err;
}
--
2.40.1
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2023-05-04 4:03 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-05-04 4:03 [PATCH 5.15 0/3] blk-crypto fixes for 5.15 Eric Biggers
2023-05-04 4:03 ` [PATCH 5.15 1/3] blk-mq: release crypto keyslot before reporting I/O complete Eric Biggers
2023-05-04 4:03 ` [PATCH 5.15 2/3] blk-crypto: make blk_crypto_evict_key() return void Eric Biggers
2023-05-04 4:03 ` [PATCH 5.15 3/3] blk-crypto: make blk_crypto_evict_key() more robust Eric Biggers
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.