Re: [PATCH v8 02/11] block: Inline encryption support for blk-mq

Christoph Hellwig
2020-03-19T11:01:40Z

On Thu, Mar 12, 2020 at 01:02:44AM -0700, Satya Tangirala wrote:
> + if (bio_has_crypt_ctx(bio))
> + bio_crypt_clone(b, bio, gfp_mask);

FYI, what I had tried to suggest when moving the the bio_has_crypt_ctx
checks out was not to open code them, but to have inline functions.

E.g. your current bio_crypt_clone becomes __bio_crypt_clone,

and then a wrapper is added ala:

static inline void bio_crypt_clone(struct bio *dst, struct bio *src,
gfp_t gfp_mask)
{
if (bio_has_crypt_ctx(bio))
__bio_crypt_clone(dst, src, gfp_mask);
}

Which also means in all the headers you can now declare everything
unconditional as long as bio_has_crypt_ctx is stubbed out for the case
where blk crypto is disabled.

> if (bio_integrity(bio)) {
> int ret;
>
> ret = bio_integrity_clone(b, bio, gfp_mask);
> -
> if (ret < 0) {
> bio_put(b);
> return NULL;

Spurious whitespace change.

> free_and_out:
> @@ -1813,5 +1830,7 @@ int __init blk_dev_init(void)
> blk_debugfs_root = debugfs_create_dir("block", NULL);
> #endif
>
> + bio_crypt_ctx_init();
> +

Is there any good reason to explicitly call bio_crypt_ctx_init vs just
making it a local subsys_initcall?

> +bool bio_crypt_dun_is_contiguous(const struct bio_crypt_ctx *bc,
> + unsigned int bytes,
> + u64 next_dun[BLK_CRYPTO_DUN_ARRAY_SIZE])
> +{
> + int i = 0;
> + unsigned int inc = bytes >> bc->bc_key->data_unit_size_bits;
> +
> + while (i < BLK_CRYPTO_DUN_ARRAY_SIZE) {
> + if (bc->bc_dun[i] + inc != next_dun[i])
> + return false;
> + inc = ((bc->bc_dun[i] + inc) < inc);

Besides the bracing and double whitespace issue this code looks weird
to me.

So inc starts out as the number of bytes shifted to the dun size.

We then check if it matches the next dun for every entry in the
array.

But then inc is turned into a bollean for the next iteration. At that
point I'm a little lost, can you add comments or make the code more
explicit?

> + blk_crypto_rq_set_defaults(rq);
> +
> + err = blk_ksm_get_slot_for_key(rq->q->ksm,
> + bio->bi_crypt_context->bc_key,
> + &rq->crypt_keyslot);
> + if (err != BLK_STS_OK)
> + pr_warn_once("Failed to acquire keyslot for %s (err=%d).\n",
> + bio->bi_disk->disk_name, err);
> + return err;

Is this error really that important? If someone prints an error here
I'd expect the low-level driver to do that, as that is the only place
knowing what kind of error we could have here.

> +int blk_crypto_bio_prep(struct bio **bio_ptr)
> +{
> + struct bio *bio = *bio_ptr;
> +
> + /*
> + * If bio has no data, just pretend it didn't have an encryption
> + * context.
> + */
> + if (!bio_has_data(bio)) {
> + bio_crypt_free_ctx(bio);
> + return 0;
> + }

Shouldn't a submitted bio without data but with a crypt context be a
hard error?

> + bio_crypt_check_alignment(bio);
> + if (bio->bi_status != BLK_STS_OK)
> + goto fail;

Weird calling convention. Why doesn't bio_crypt_check_alignment
return a bool, and then this becomes the much more obvious:

if (!bio_crypt_check_alignment(bio)) {
bio->bi_status = BLK_STS_IOERR;
goto fail;
}

> + /*
> + * Success if device supports the encryption context, and blk-integrity
> + * isn't supported by device/is turned off.
> + */
> + if (!blk_ksm_crypto_key_supported(bio->bi_disk->queue->ksm,
> + bio->bi_crypt_context->bc_key)) {
> + bio->bi_status = BLK_STS_NOTSUPP;
> + goto fail;
> + }
> +
> + return 0;
> +fail:
> + bio_endio(*bio_ptr);
> + return -EIO;

Weird calling convention again. If the actual error is in the bio,
this should just be a bool.

> +void blk_crypto_rq_prep_clone(struct request *dst, struct request *src)
> +{
> + dst->crypt_ctx = src->crypt_ctx;
> +}

This seems reasonable to inline in the header..

> + blk_status_t err;
>
> blk_queue_bounce(q, &bio);
> __blk_queue_split(q, &bio, &nr_segs);
> @@ -2002,6 +2007,16 @@ static blk_qc_t blk_mq_make_request(struct request_queue *q, struct bio *bio)
>
> cookie = request_to_qc_t(data.hctx, rq);
>
> + if (bio_has_crypt_ctx(bio)) {
> + err = blk_crypto_init_request(rq, bio);
> + if (err != BLK_STS_OK) {

The err declaration can go into this scope. I'd also rather call it
ret as err is usually used for errno codes.

Also blk_crypto_init_request doesn't really need the bio, but just the
key. So I'd rather pass the key to avoid confusion what this function
might do with the bio.

> + bio->bi_status = err;
> + bio_endio(bio);
> + blk_mq_end_request(rq, err);
> + return BLK_QC_T_NONE;

Shoudn't the blk_mq_end_request just be a blk_mq_free_request here?

> +#ifdef CONFIG_BLOCK
> +
> +#include <linux/blk_types.h>
> +#include <linux/blkdev.h>
> +
> +struct request;
> +struct request_queue;
> +
> +#ifdef CONFIG_BLK_INLINE_ENCRYPTION

Does the #ifdef CONFIG_BLOCK buy us anything?