Re: [PATCH v3 2/2] dm-integrity: introduce ahash support for the internal hash

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi

I looked at the patch and found two problems:

1. dm_integrity_end_io may be called in an interrupt context, so it 
shouldn't call the ahash version of integrity_sector_checksum. It should 
call the shash version. So, the shash version should be available even 
when using ahash.

2. There's a problem with memory allocation. You shouldn't allocate any 
memory from I/O processing path. Suppose that the user is swapping to a 
swap device that is backed by dm-integrity. If the system runs out of 
memory, it attempts to write some pages to the swap device, this triggers 
memory allocation (see ahash_request_alloc and kmalloc in your patch). 
This memory allocation will wait until some memory is available, causing a 
deadlock.

"sector_le = kmalloc(sizeof(__le64), GFP_KERNEL);"

- this field may be moved to the struct dm_integrity_io, you don't need to 
allocate it at all. Note that struct dm_integrity_io is already allocated 
from a mempool, so there is forward progress guarantee even if the system 
has no free memory.

"req = ahash_request_alloc(ic->internal_ahash, GFP_KERNEL);"

- this may be moved after struct dm_integrity_io. I.e. change:
"ti->per_io_data_size = sizeof(struct dm_integrity_io)" to
"ti->per_io_data_size = sizeof(struct dm_integrity_io) + sizeof(struct 
ahash_request) + crypto_ahash_reqsize(tfm)".

Then, instead of calling "req = ahash_request_alloc", you can do
req = (struct ahash_request *)(dio + 1);

integrity_sector_checksum is also called from code where there is no 
associated "dm_integrity_io" (such as integrity_recalc_inline, 
integrity_recalc) - in this situation, you can preallocate a dummy 
dm_integrity_io structure before you take any locks (i.e. "dio = 
kmalloc(sizeof(struct dm_integrity_io) + sizeof(struct ahash_request) + 
crypto_ahash_reqsize(tfm), GFP_NOIO)". If you allocated it while holding 
the range lock, the deadlock may be possible.

Another place where integrity_sector_checksum is called without 
"dm_integrity_io" is do_journal_write - in this case, you can use the 
synchronous hash version because it is not performance-critical.

Please, send a new version of your patch where these problems are fixed.

Also, test your patch with the cryptsetup testsuite - download it with 
"git clone https://gitlab.com/cryptsetup/cryptsetup"; and run "./autogen.sh 
&& ./configure && make && make check". Make sure that the ahash interface 
is preferred while testing - so that you can catch bugs like scheduling in 
interrupt that happens in dm_integrity_end_io (I'm not sure if the 
testsuite tests the 'Inline' mode of dm-integrity; maybe not).

Mikulas


On Fri, 31 Jan 2025, Harald Freudenberger wrote:

> Introduce ahash support for the "internal hash" algorithm.
> 
> Rework the dm-integrity code to be able to run the "internal hash"
> either with a synchronous ("shash") or asynchronous ("ahash") hash
> algorithm implementation.
> 
> The get_mac() function now tries to decide which of the digest
> implemenations to use if there is a choice:
> - If an ahash and shash tfm is available and both are backed by the
>   same driver name it is assumed that the shash is the faster
>   implementation and thus the shash tfm is delivered to the caller.
> - If an ahash and shash tfm is available but the backing device driver
>   divers (different driver names) it is assumed that the ahash
>   implementation is a "better" hardware based implementation and thus
>   the ahash tfm is delivered to the caller.
> - If there is no choice, for example only an ahash or an shash
>   implementation is available then this tfm is delivered to the
>   caller. Especially in cases where only an ahash implementation is
>   available this is now used instead of failing.
> - The caller can steer this choice by passing a NULL to the ahash or
>   shash parameter, thus enforcing to only allocate an algorithm of the
>   remaining possibility.
> 
> The function integrity_sector_checksum() is now only a dispatcher
> function calling one of the two new functions
> integrity_ahash_sector_checksum() or integrity_shash_sector_checksum()
> based on which tfm is allocated based on the two new fields
> internal_shash and internal_ahash in struct dm_integrity_c.
> 
> Together with this comes some slight rework around availability and
> digest size of the internal hash in use.
> 
> Signed-off-by: Harald Freudenberger <freude@xxxxxxxxxxxxx>
> ---
>  drivers/md/dm-integrity.c | 221 +++++++++++++++++++++++++++++---------
>  1 file changed, 172 insertions(+), 49 deletions(-)
> 
> diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c
> index 9ab0f0836c86..418bdc57054b 100644
> --- a/drivers/md/dm-integrity.c
> +++ b/drivers/md/dm-integrity.c
> @@ -221,7 +221,9 @@ struct dm_integrity_c {
>  
>  	int failed;
>  
> -	struct crypto_shash *internal_hash;
> +	bool have_internal_hash;
> +	struct crypto_shash *internal_shash;
> +	struct crypto_ahash *internal_ahash;
>  	unsigned int internal_hash_digestsize;
>  
>  	struct dm_target *ti;
> @@ -1635,57 +1637,128 @@ static void integrity_end_io(struct bio *bio)
>  	dec_in_flight(dio);
>  }
>  
> -static void integrity_sector_checksum(struct dm_integrity_c *ic, sector_t sector,
> -				      const char *data, char *result)
> +static bool integrity_shash_sector_checksum(struct dm_integrity_c *ic,
> +					    sector_t sector, const char *data,
> +					    char *result)
>  {
>  	__le64 sector_le = cpu_to_le64(sector);
> -	SHASH_DESC_ON_STACK(req, ic->internal_hash);
> +	SHASH_DESC_ON_STACK(req, ic->internal_shash);
>  	int r;
> -	unsigned int digest_size;
>  
> -	req->tfm = ic->internal_hash;
> +	req->tfm = ic->internal_shash;
>  
>  	r = crypto_shash_init(req);
>  	if (unlikely(r < 0)) {
>  		dm_integrity_io_error(ic, "crypto_shash_init", r);
> -		goto failed;
> +		return false;
>  	}
>  
>  	if (ic->sb->flags & cpu_to_le32(SB_FLAG_FIXED_HMAC)) {
>  		r = crypto_shash_update(req, (__u8 *)&ic->sb->salt, SALT_SIZE);
>  		if (unlikely(r < 0)) {
>  			dm_integrity_io_error(ic, "crypto_shash_update", r);
> -			goto failed;
> +			return false;
>  		}
>  	}
>  
>  	r = crypto_shash_update(req, (const __u8 *)&sector_le, sizeof(sector_le));
>  	if (unlikely(r < 0)) {
>  		dm_integrity_io_error(ic, "crypto_shash_update", r);
> -		goto failed;
> +		return false;
>  	}
>  
>  	r = crypto_shash_update(req, data, ic->sectors_per_block << SECTOR_SHIFT);
>  	if (unlikely(r < 0)) {
>  		dm_integrity_io_error(ic, "crypto_shash_update", r);
> -		goto failed;
> +		return false;
>  	}
>  
>  	r = crypto_shash_final(req, result);
>  	if (unlikely(r < 0)) {
>  		dm_integrity_io_error(ic, "crypto_shash_final", r);
> -		goto failed;
> +		return false;
>  	}
>  
> -	digest_size = ic->internal_hash_digestsize;
> -	if (unlikely(digest_size < ic->tag_size))
> -		memset(result + digest_size, 0, ic->tag_size - digest_size);
> +	if (unlikely(ic->internal_hash_digestsize < ic->tag_size))
> +		memset(result + ic->internal_hash_digestsize,
> +		       0, ic->tag_size - ic->internal_hash_digestsize);
>  
> -	return;
> +	return true;
> +}
> +
> +static bool integrity_ahash_sector_checksum(struct dm_integrity_c *ic,
> +					    sector_t sector, const char *data,
> +					    char *result)
> +{
> +	struct ahash_request *req = NULL;
> +	struct scatterlist sg[3], *s;
> +	DECLARE_CRYPTO_WAIT(wait);
> +	__le64 *sector_le = NULL;
> +	unsigned int nbytes = 0;
> +	int r = -ENOMEM;
> +
> +	req = ahash_request_alloc(ic->internal_ahash, GFP_KERNEL);
> +	if (unlikely(!req)) {
> +		dm_integrity_io_error(ic, "ahash_request_alloc", r);
> +		return false;
> +	}
> +	ahash_request_set_callback(req, 0, crypto_req_done, &wait);
> +
> +	s = sg;
> +	if (ic->sb->flags & cpu_to_le32(SB_FLAG_FIXED_HMAC)) {
> +		sg_init_table(sg, 3);
> +		sg_set_buf(s, (const __u8 *)&ic->sb->salt, SALT_SIZE);
> +		nbytes += SALT_SIZE;
> +		s++;
> +	} else {
> +		sg_init_table(sg, 2);
> +	}
> +
> +	sector_le = kmalloc(sizeof(__le64), GFP_KERNEL);
> +	if (unlikely(!sector_le)) {
> +		dm_integrity_io_error(ic, "kmalloc(sizeof(__le64))", r);
> +		goto out;
> +	}
> +	*sector_le = cpu_to_le64(sector);
> +	sg_set_buf(s, (const __u8 *)sector_le, sizeof(*sector_le));
> +	nbytes += sizeof(*sector_le);
> +	s++;
> +
> +	sg_set_buf(s, data, ic->sectors_per_block << SECTOR_SHIFT);
> +	nbytes += ic->sectors_per_block << SECTOR_SHIFT;
> +
> +	ahash_request_set_crypt(req, sg, result, nbytes);
> +
> +	r = crypto_wait_req(crypto_ahash_digest(req), &wait);
> +	if (r) {
> +		dm_integrity_io_error(ic, "crypto_ahash_digest", r);
> +		goto out;
> +	}
> +
> +	if (unlikely(ic->internal_hash_digestsize < ic->tag_size))
> +		memset(result + ic->internal_hash_digestsize,
> +		       0, ic->tag_size - ic->internal_hash_digestsize);
>  
> -failed:
> -	/* this shouldn't happen anyway, the hash functions have no reason to fail */
> -	get_random_bytes(result, ic->tag_size);
> +out:
> +	ahash_request_free(req);
> +	kfree(sector_le);
> +
> +	return r ? false : true;
> +}
> +
> +static void integrity_sector_checksum(struct dm_integrity_c *ic,
> +				      sector_t sector, const char *data,
> +				      char *result)
> +{
> +	bool r;
> +
> +	if (likely(ic->internal_shash))
> +		r = integrity_shash_sector_checksum(ic, sector, data, result);
> +	else
> +		r = integrity_ahash_sector_checksum(ic, sector, data, result);
> +
> +	if (unlikely(!r))
> +		get_random_bytes(result, ic->tag_size);
>  }
>  
>  static noinline void integrity_recheck(struct dm_integrity_io *dio, char *checksum)
> @@ -1774,7 +1847,7 @@ static void integrity_metadata(struct work_struct *w)
>  
>  	int r;
>  
> -	if (ic->internal_hash) {
> +	if (ic->have_internal_hash) {
>  		struct bvec_iter iter;
>  		struct bio_vec bv;
>  		unsigned int digest_size = ic->internal_hash_digestsize;
> @@ -1992,7 +2065,7 @@ static int dm_integrity_map(struct dm_target *ti, struct bio *bio)
>  		return DM_MAPIO_KILL;
>  
>  	bip = bio_integrity(bio);
> -	if (!ic->internal_hash) {
> +	if (!ic->have_internal_hash) {
>  		if (bip) {
>  			unsigned int wanted_tag_size = bio_sectors(bio) >> ic->sb->log2_sectors_per_block;
>  
> @@ -2073,7 +2146,7 @@ static bool __journal_read_write(struct dm_integrity_io *dio, struct bio *bio,
>  					mem_ptr += 1 << SECTOR_SHIFT;
>  				} while (++s < ic->sectors_per_block);
>  #ifdef INTERNAL_VERIFY
> -				if (ic->internal_hash) {
> +				if (ic->have_internal_hash) {
>  					char checksums_onstack[MAX_T(size_t, HASH_MAX_DIGESTSIZE, MAX_TAG_SIZE)];
>  
>  					integrity_sector_checksum(ic, logical_sector, mem + bv.bv_offset, checksums_onstack);
> @@ -2087,7 +2160,7 @@ static bool __journal_read_write(struct dm_integrity_io *dio, struct bio *bio,
>  #endif
>  			}
>  
> -			if (!ic->internal_hash) {
> +			if (!ic->have_internal_hash) {
>  				struct bio_integrity_payload *bip = bio_integrity(bio);
>  				unsigned int tag_todo = ic->tag_size;
>  				char *tag_ptr = journal_entry_tag(ic, je);
> @@ -2124,7 +2197,7 @@ static bool __journal_read_write(struct dm_integrity_io *dio, struct bio *bio,
>  					je->last_bytes[s] = js[s].commit_id;
>  				} while (++s < ic->sectors_per_block);
>  
> -				if (ic->internal_hash) {
> +				if (ic->have_internal_hash) {
>  					unsigned int digest_size = ic->internal_hash_digestsize;
>  
>  					if (unlikely(digest_size > ic->tag_size)) {
> @@ -2187,7 +2260,7 @@ static void dm_integrity_map_continue(struct dm_integrity_io *dio, bool from_map
>  	sector_t recalc_sector;
>  	struct completion read_comp;
>  	bool discard_retried = false;
> -	bool need_sync_io = ic->internal_hash && dio->op == REQ_OP_READ;
> +	bool need_sync_io = ic->have_internal_hash && dio->op == REQ_OP_READ;
>  
>  	if (unlikely(dio->op == REQ_OP_DISCARD) && ic->mode != 'D')
>  		need_sync_io = true;
> @@ -2908,7 +2981,7 @@ static void do_journal_write(struct dm_integrity_c *ic, unsigned int write_start
>  #ifndef INTERNAL_VERIFY
>  				    unlikely(from_replay) &&
>  #endif
> -				    ic->internal_hash) {
> +				    ic->have_internal_hash) {
>  					char test_tag[MAX_T(size_t, HASH_MAX_DIGESTSIZE, MAX_TAG_SIZE)];
>  
>  					integrity_sector_checksum(ic, sec + ((l - j) << ic->sb->log2_sectors_per_block),
> @@ -3905,7 +3978,7 @@ static void dm_integrity_io_hints(struct dm_target *ti, struct queue_limits *lim
>  		limits->discard_granularity = ic->sectors_per_block << SECTOR_SHIFT;
>  	}
>  
> -	if (!ic->internal_hash) {
> +	if (!ic->have_internal_hash) {
>  		struct blk_integrity *bi = &limits->integrity;
>  
>  		memset(bi, 0, sizeof(*bi));
> @@ -4213,32 +4286,76 @@ static int get_alg_and_key(const char *arg, struct alg_spec *a, char **error, ch
>  	return -ENOMEM;
>  }
>  
> -static int get_mac(struct crypto_shash **hash, struct alg_spec *a, char **error,
> +static int get_mac(struct crypto_shash **shash, struct crypto_ahash **ahash,
> +		   struct alg_spec *a, char **error,
>  		   char *error_alg, char *error_key)
>  {
> +	const char *ahash_driver_name = NULL;
>  	int r;
>  
> -	if (a->alg_string) {
> -		*hash = crypto_alloc_shash(a->alg_string, 0, CRYPTO_ALG_ALLOCATES_MEMORY);
> -		if (IS_ERR(*hash)) {
> +	if (!a->alg_string || !(shash || ahash))
> +		return 0;
> +
> +	if (ahash) {
> +		*ahash = crypto_alloc_ahash(a->alg_string, 0, 0);
> +		if (IS_ERR(*ahash)) {
>  			*error = error_alg;
> -			r = PTR_ERR(*hash);
> -			*hash = NULL;
> +			r = PTR_ERR(*ahash);
> +			*ahash = NULL;
>  			return r;
>  		}
> +		ahash_driver_name = crypto_ahash_driver_name(*ahash);
> +	}
>  
> -		if (a->key) {
> -			r = crypto_shash_setkey(*hash, a->key, a->key_size);
> -			if (r) {
> -				*error = error_key;
> -				return r;
> +	if (shash) {
> +		*shash = crypto_alloc_shash(a->alg_string, 0, CRYPTO_ALG_ALLOCATES_MEMORY);
> +		if (IS_ERR(*shash) && !ahash_driver_name) {
> +			*error = error_alg;
> +			r = PTR_ERR(*shash);
> +			*shash = NULL;
> +			return r;
> +		}
> +		if (!IS_ERR(shash) && ahash_driver_name) {
> +			if (strcmp(crypto_shash_driver_name(*shash), ahash_driver_name)) {
> +				/*
> +				 * ahash gave a different driver than shash, so probably
> +				 * this is a case of real hardware offload.  Use ahash.
> +				 */
> +				crypto_free_shash(*shash);
> +				*shash = NULL;
> +			} else {
> +				/* ahash and shash are same driver. Use shash. */
> +				crypto_free_ahash(*ahash);
> +				*ahash = NULL;
>  			}
> -		} else if (crypto_shash_get_flags(*hash) & CRYPTO_TFM_NEED_KEY) {
> +		}
> +	}
> +
> +	/* either *ahash or *shash is set now */
> +
> +	if (a->key) {
> +		r = shash && *shash ?
> +			crypto_shash_setkey(*shash, a->key, a->key_size) :
> +			crypto_ahash_setkey(*ahash, a->key, a->key_size);
> +		if (r) {
>  			*error = error_key;
> -			return -ENOKEY;
> +			return r;
>  		}
> +	} else if ((shash && *shash ?
> +		    crypto_shash_get_flags(*shash) :
> +		    crypto_ahash_get_flags(*ahash))
> +		   & CRYPTO_TFM_NEED_KEY) {
> +		*error = error_key;
> +		return -ENOKEY;
>  	}
>  
> +	pr_debug("Using %s %s (driver name %s)\n",
> +		 ahash && *ahash ? "ahash" : "shash",
> +		 a->alg_string,
> +		 ahash && *ahash ?
> +		 crypto_ahash_driver_name(*ahash) :
> +		 crypto_shash_driver_name(*shash));
> +
>  	return 0;
>  }
>  
> @@ -4693,20 +4810,24 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv
>  		buffer_sectors = 1;
>  	ic->log2_buffer_sectors = min((int)__fls(buffer_sectors), 31 - SECTOR_SHIFT);
>  
> -	r = get_mac(&ic->internal_hash, &ic->internal_hash_alg, &ti->error,
> +	r = get_mac(&ic->internal_shash, &ic->internal_ahash,
> +		    &ic->internal_hash_alg, &ti->error,
>  		    "Invalid internal hash", "Error setting internal hash key");
>  	if (r)
>  		goto bad;
> -	if (ic->internal_hash)
> -		ic->internal_hash_digestsize = crypto_shash_digestsize(ic->internal_hash);
> +	ic->have_internal_hash = ic->internal_shash || ic->internal_ahash;
> +	if (ic->have_internal_hash)
> +		ic->internal_hash_digestsize = ic->internal_shash ?
> +			crypto_shash_digestsize(ic->internal_shash) :
> +			crypto_ahash_digestsize(ic->internal_ahash);
>  
> -	r = get_mac(&ic->journal_mac, &ic->journal_mac_alg, &ti->error,
> +	r = get_mac(&ic->journal_mac, NULL, &ic->journal_mac_alg, &ti->error,
>  		    "Invalid journal mac", "Error setting journal mac key");
>  	if (r)
>  		goto bad;
>  
>  	if (!ic->tag_size) {
> -		if (!ic->internal_hash) {
> +		if (!ic->have_internal_hash) {
>  			ti->error = "Unknown tag size";
>  			r = -EINVAL;
>  			goto bad;
> @@ -4770,13 +4891,13 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv
>  		}
>  	}
>  
> -	if (ic->mode == 'B' && !ic->internal_hash) {
> +	if (ic->mode == 'B' && !ic->have_internal_hash) {
>  		r = -EINVAL;
>  		ti->error = "Bitmap mode can be only used with internal hash";
>  		goto bad;
>  	}
>  
> -	if (ic->discard && !ic->internal_hash) {
> +	if (ic->discard && !ic->have_internal_hash) {
>  		r = -EINVAL;
>  		ti->error = "Discard can be only used with internal hash";
>  		goto bad;
> @@ -5038,7 +5159,7 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv
>  		ic->sb->recalc_sector = cpu_to_le64(0);
>  	}
>  
> -	if (ic->internal_hash) {
> +	if (ic->have_internal_hash) {
>  		ic->recalc_wq = alloc_workqueue("dm-integrity-recalc", WQ_MEM_RECLAIM, 1);
>  		if (!ic->recalc_wq) {
>  			ti->error = "Cannot allocate workqueue";
> @@ -5229,8 +5350,10 @@ static void dm_integrity_dtr(struct dm_target *ti)
>  	if (ic->sb)
>  		free_pages_exact(ic->sb, SB_SECTORS << SECTOR_SHIFT);
>  
> -	if (ic->internal_hash)
> -		crypto_free_shash(ic->internal_hash);
> +	if (ic->internal_shash)
> +		crypto_free_shash(ic->internal_shash);
> +	if (ic->internal_ahash)
> +		crypto_free_ahash(ic->internal_ahash);
>  	free_alg(&ic->internal_hash_alg);
>  
>  	if (ic->journal_crypt)
> -- 
> 2.43.0
> 





[Index of Archives]     [DM Crypt]     [Fedora Desktop]     [ATA RAID]     [Fedora Marketing]     [Fedora Packaging]     [Fedora SELinux]     [Yosemite Discussion]     [KDE Users]     [Fedora Docs]

  Powered by Linux