public inbox for linux-s390@vger.kernel.org
 help / color / mirror / Atom feed
From: Harald Freudenberger <freude@linux.ibm.com>
To: Holger Dengler <dengler@linux.ibm.com>
Cc: linux-crypto@vger.kernel.org, linux-s390@vger.kernel.org,
	herbert@gondor.apana.org.au, fcallies@linux.ibm.com,
	ifranzki@linux.ibm.com
Subject: Re: [PATCH v3 2/3] s390/crypto: Rework protected key AES for true asynch support
Date: Tue, 06 May 2025 16:13:14 +0200	[thread overview]
Message-ID: <6d91b8776191126385925d5af5f165bc@linux.ibm.com> (raw)
In-Reply-To: <9ddc9bc9-ad88-4ba5-aebf-2fcdd01db064@linux.ibm.com>

On 2025-04-25 16:56, Holger Dengler wrote:
> On 01/04/2025 16:50, Harald Freudenberger wrote:
>> This is a complete rework of the protected key AES (PAES) 
>> implementation.
>> The goal of this rework is to implement the 4 modes (ecb, cbc, ctr, 
>> xts)
>> in a real asynchronous fashion:
>> - init(), exit() and setkey() are synchronous and don't allocate any 
>> memory.
>> - the encrypt/decrypt functions first try to do the job in a 
>> synchronous
>>   manner. If this fails, for example the protected key got invalid 
>> caused
>>   by for example a guest suspend/resume or guest migration action, the
> 
> reword: please drop one of the "for example".
> 

done

>>   encrypt/decrypt is transfered to an instance of the crypto engine 
>> (see
> 
> typo: transferred
> 

done

>>   below) for asynchronous processing.
>>   These via crypto engine postponed requests are then handled via the
>>   do_one_request() callback but may of course again run into a still
> 
> reword: please drop at least one "via". Proposal (if I got it
> correctly): "These postponed requests are then handled by the crypto
> engine by calling the do_one_request() callback ..."
> 

done

>>   not converted key or the key is getting invalid. If the key is
>>   still not converted, the first thread does the conversion and 
>> updates
>>   the key status in the transformation context. The conversion is
>>   invoked via pkey API with a new flag PKEY_XFLAG_NOMEMALLOC.
>> 
>> The pkey API used here - the function pkey_key2protkey() - uses
>> a new version of this in-kernel-API. A new flag PKEY_XFLAG_NOMEMALLOC
>> tells the PKEY layer (and subsidiary layers) that it must not allocate
>> any memory causing IO operations. Note that the patches for this
>> pkey/zcrypt/AP extensions are currently under review and yet not
>> upstream available. SO THIS PATCH DOES NOT COMPILE YET.
> 
> As the ap-rework series is now on its way, you can remove parts of
> this paragraph.
> 

done

>> This patch together with the pkey/zcrypt/AP extensions should
>> toughen the paes crypto algorithms to truly meet the requirements
>> for in-kernel skcipher implementations and the usage patterns for
>> the dm-crypt and dm-integrity layers.
>> 
>> Signed-off-by: Harald Freudenberger <freude@linux.ibm.com>
> 
> It is very hard to review this patch. If there is any chance to split
> this up into smaller pieces, please do it.
> This is the first part of the review, covering mainly common parts and
> ecb. The other modes will follow later.
> See my comments below.
> 

Sorry but I don't see how I could split this rework into digestible 
units.

>> ---
>>  arch/s390/crypto/paes_s390.c | 1725 
>> +++++++++++++++++++++++-----------
>>  1 file changed, 1183 insertions(+), 542 deletions(-)
>> 
>> diff --git a/arch/s390/crypto/paes_s390.c 
>> b/arch/s390/crypto/paes_s390.c
>> index 646cbbf0678d..1d1f1a98ec4d 100644
>> --- a/arch/s390/crypto/paes_s390.c
>> +++ b/arch/s390/crypto/paes_s390.c
...
>> +#define PK_STATE_NO_KEY		     0
>> +#define PK_STATE_CONVERT_IN_PROGRESS 1
>> +#define PK_STATE_VALID		     2
> 
> Please use an enum here.
> 

I'd like to keep the one variable pk_state with either a negative errno
value if the key conversion failed, or a >= value with one of the listed
states.

>> +
>> +struct s390_paes_ctx {
>> +	/* source key material used to derive a protected key from */
>> +	u8 keybuf[PAES_MAX_KEYSIZE];
>> +	unsigned int keylen;
>> +
>> +	/* cpacf function code to use with this protected key type */
>> +	long fc;
>> +
>> +	/* spinlock to atomic read/update all the following fields */
>> +	spinlock_t pk_lock;
>> +
>> +	/* see PK_STATE* defines above, < 0 holds convert failure rc  */
>> +	int pk_state;
> 
> I see no advantage to split the value range. On the contrary, it makes
> the status handling more complex.
> I would prefer to use an enum for pk_state and use another element for
> the conversion rc.
> 
>> +	/* if state is valid, pk holds the protected key */
>> +	struct paes_protkey pk;
>> +};
>> +
>> +struct s390_pxts_ctx {
>> +	/* source key material used to derive a protected key from */
>> +	u8 keybuf[2 * PAES_MAX_KEYSIZE];
>>  	unsigned int keylen;
>> +
>> +	/* cpacf function code to use with this protected key type */
>> +	long fc;
>> +
>> +	/* spinlock to atomic read/update all the following fields */
>> +	spinlock_t pk_lock;
>> +
>> +	/* see PK_STATE* defines above, < 0 holds convert failure rc  */
>> +	int pk_state;
> 
> Same here.
> 
>> +	/* if state is valid, pk[] hold(s) the protected key(s) */
>> +	struct paes_protkey pk[2];
>>  };
>> 
>>  /*
>> @@ -89,214 +122,344 @@ static inline u32 make_clrkey_token(const u8 
>> *ck, size_t cklen, u8 *dest)
>>  	return sizeof(*token) + cklen;
>>  }
>> 
>> -static inline int _key_to_kb(struct key_blob *kb,
>> -			     const u8 *key,
>> -			     unsigned int keylen)
>> +/*
>> + * key_to_ctx() - Set key value into context, maybe construct
>> + * a clear key token digestable by pkey from a clear key value.
>> + */
>> +static inline int key_to_ctx(struct s390_paes_ctx *ctx,
>> +			     const u8 *key, unsigned int keylen)
> 
> The function name implies a transformation of a key into a context,
> not just a set of a context element. What about paes_ctx_setkey()?
> 

done, this is now paes_ctx_setkey()

>>  {
>> +	if (keylen > sizeof(ctx->keybuf))
>> +		return -EINVAL;
>> +
>>  	switch (keylen) {
>>  	case 16:
>>  	case 24:
>>  	case 32:
>>  		/* clear key value, prepare pkey clear key token in keybuf */
>> -		memset(kb->keybuf, 0, sizeof(kb->keybuf));
>> -		kb->keylen = make_clrkey_token(key, keylen, kb->keybuf);
>> -		kb->key = kb->keybuf;
>> +		memset(ctx->keybuf, 0, sizeof(ctx->keybuf));
>> +		ctx->keylen = make_clrkey_token(key, keylen, ctx->keybuf);
>>  		break;
>>  	default:
>>  		/* other key material, let pkey handle this */
>> -		if (keylen <= sizeof(kb->keybuf))
>> -			kb->key = kb->keybuf;
>> -		else {
>> -			kb->key = kmalloc(keylen, GFP_KERNEL);
>> -			if (!kb->key)
>> -				return -ENOMEM;
>> -		}
>> -		memcpy(kb->key, key, keylen);
>> -		kb->keylen = keylen;
>> +		memcpy(ctx->keybuf, key, keylen);
>> +		ctx->keylen = keylen;
>>  		break;
>>  	}
>> 
>>  	return 0;
>>  }
>> 
>> -static inline int _xts_key_to_kb(struct key_blob *kb,
>> -				 const u8 *key,
>> -				 unsigned int keylen)
>> +/*
>> + * xts_key_to_ctx() - Set key value into context, maybe construct
>> + * a clear key token digestable by pkey from a clear key value.
>> + */
>> +static inline int xts_key_to_ctx(struct s390_pxts_ctx *ctx,
>> +				 const u8 *key, unsigned int keylen)
> 
> Same here, the function name implies a transformation of a key into a
> context, not just a set of a context element. What about
> pxts_ctx_setkey()?

done

> 
>>  {
>>  	size_t cklen = keylen / 2;
>> 
> [...]
>> +static int ecb_paes_do_crypt(struct s390_paes_ctx *ctx,
>> +			     struct s390_pecb_req_ctx *req_ctx,
>> +			     bool maysleep)
>>  {
>> -	struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm);
>> -	int rc;
>> +	struct ecb_param *param = &req_ctx->param;
>> +	struct skcipher_walk *walk = &req_ctx->walk;
>> +	unsigned int nbytes, n, k;
>> +	int pk_state, rc;
>> +
>> +	if (!req_ctx->param_init_done) {
>> +		/* fetch and check protected key state */
>> +		spin_lock_bh(&ctx->pk_lock);
>> +		pk_state = ctx->pk_state;
>> +		memcpy(param->key, ctx->pk.protkey, PAES_256_PROTKEY_SIZE);
> 
> I would prefer to use the size of param->key instead of a constant
> value as length.
> 

I checked all these occurrences and rewrote where possible to use 
sizeof().

>> +		spin_unlock_bh(&ctx->pk_lock);
>> +		switch (pk_state) {
>> +		case PK_STATE_NO_KEY:
>> +			rc = -ENOKEY;
>> +			goto out;
>> +		case PK_STATE_CONVERT_IN_PROGRESS:
>> +			rc = -EKEYEXPIRED;
>> +			goto out;
>> +		case PK_STATE_VALID:
>> +			req_ctx->param_init_done = true;
>> +			break;
>> +		default:
>> +			rc = pk_state < 0 ? pk_state : -EIO;
>> +			goto out;
>> +		}
>> +	}
>> 
>> -	_free_kb_keybuf(&ctx->kb);
>> -	rc = _key_to_kb(&ctx->kb, in_key, key_len);
>> -	if (rc)
>> -		return rc;
>> +	rc = 0;
> 
> Modify the param block in req_ctx only if the protected key is valid.
> 
> int rc = 0;
> 
> if (!req_ctx->param_init_done) {
> 	/* fetch and check protected key state */
> 	spin_lock_bh(&ctx->pk_lock);
> 	switch (ctx->pk_state) {
> 	case PK_STATE_NO_KEY:
> 		rc = -ENOKEY;
> 		break;
> 	case PK_STATE_CONVERT_IN_PROGRESS:
> 		rc = -EKEYEXPIRED;
> 		break;
> 	case PK_STATE_VALID:
> 		memcpy(param->key, ctx->pk.protkey, sizeof(param->key));
> 		req_ctx->param_init_done = true;
> 		break;
> 	default:
> 		rc = pk_state < 0 ? pk_state : -EIO;
> 		break;
> 	}
> 	spin_unlock_bh(&ctx->pk_lock);
> 	if (rc)
> 		goto out;
> }
> 

done (for all 4 algs)

>> +
>> +	/* always walk on the ... */
> 
> What does this comment mean? I'm afraid, I don't get it.
> 

removed, but added a comment about the walk instead as suggested by 
Herbert Xu

>> +	while ((nbytes = walk->nbytes) != 0) {
>> +		/* only use complete blocks */
>> +		n = nbytes & ~(AES_BLOCK_SIZE - 1);
>> +		k = cpacf_km(ctx->fc | req_ctx->modifier, param,
>> +			     walk->dst.virt.addr, walk->src.virt.addr, n);
>> +		if (k)
>> +			rc = skcipher_walk_done(walk, nbytes - k);
>> +		if (k < n) {
>> +			if (!maysleep) {
>> +				rc = -EKEYEXPIRED;
>> +				goto out;
>> +			}
>> +			rc = paes_convert_key(ctx);
>> +			if (rc)
>> +				goto out;
>> +			spin_lock_bh(&ctx->pk_lock);
>> +			memcpy(param->key, ctx->pk.protkey, PAES_256_PROTKEY_SIZE);
>> +			spin_unlock_bh(&ctx->pk_lock);
>> +		}
>> +	}
>> 
>> -	return __ecb_paes_set_key(ctx);
>> +out:
>> +	pr_debug("rc=%d\n", rc);
>> +	return rc;
>>  }
>> 
>>  static int ecb_paes_crypt(struct skcipher_request *req, unsigned long 
>> modifier)
>>  {
>> +	struct s390_pecb_req_ctx *req_ctx = skcipher_request_ctx(req);
>>  	struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
>>  	struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm);
>> -	struct {
>> -		u8 key[PAES_256_PROTKEY_SIZE];
>> -	} param;
>> -	struct skcipher_walk walk;
>> -	unsigned int nbytes, n, k;
>> +	struct skcipher_walk *walk = &req_ctx->walk;
>>  	int rc;
>> 
>> -	rc = skcipher_walk_virt(&walk, req, false);
>> +	/*
>> +	 * First try synchronous. If this fails for any reason
>> +	 * schedule this request asynchronous via crypto engine.
>> +	 */
>> +
>> +	rc = skcipher_walk_virt(walk, req, false);
>>  	if (rc)
>> -		return rc;
>> +		goto out;
>> 
>> -	spin_lock_bh(&ctx->pk_lock);
>> -	memcpy(param.key, ctx->pk.protkey, PAES_256_PROTKEY_SIZE);
>> -	spin_unlock_bh(&ctx->pk_lock);
>> +	req_ctx->modifier = modifier;
>> +	req_ctx->param_init_done = false;
>> 
>> -	while ((nbytes = walk.nbytes) != 0) {
>> -		/* only use complete blocks */
>> -		n = nbytes & ~(AES_BLOCK_SIZE - 1);
>> -		k = cpacf_km(ctx->fc | modifier, &param,
>> -			     walk.dst.virt.addr, walk.src.virt.addr, n);
>> -		if (k)
>> -			rc = skcipher_walk_done(&walk, nbytes - k);
>> -		if (k < n) {
>> -			if (__paes_convert_key(ctx))
>> -				return skcipher_walk_done(&walk, -EIO);
>> -			spin_lock_bh(&ctx->pk_lock);
>> -			memcpy(param.key, ctx->pk.protkey, PAES_256_PROTKEY_SIZE);
>> -			spin_unlock_bh(&ctx->pk_lock);
>> -		}
>> +	rc = ecb_paes_do_crypt(ctx, req_ctx, false);
>> +	if (rc != -EKEYEXPIRED) {
>> +		if (rc)
>> +			skcipher_walk_done(walk, rc);
>> +		goto out;
>>  	}
>> +
>> +	rc = crypto_transfer_skcipher_request_to_engine(paes_crypto_engine, 
>> req);
>> +	if (rc)
>> +		goto out;
>> +
>> +	rc = -EINPROGRESS;
>> +
>> +out:
>> +	if (rc != -EINPROGRESS)
>> +		memzero_explicit(&req_ctx->param, sizeof(req_ctx->param));
>> +	pr_debug("rc=%d\n", rc);
>>  	return rc;
> 
> If took me a while to find the synchronous good case code path. I
> would prefer to handle the various cases separately, either with a
> switch/case or by explicit checks in the main path.
> 
> rc = ecb_paes_do_crypt(ctx, req_ctx, false);
> if (rc == -EKEYEXPIRED) {
> 	rc = crypto_transfer_skcipher_request_to_engine(paes_crypto_engine, 
> req);
> 	rc = rc ?: -EINPROGRESS;
> } else if (rc) {
> 	skcipher_walk_done(walk, rc);
> }
> 
> if (rc != -EINPROGRESS)
> 	memzero_explicit(&req_ctx->param, sizeof(req_ctx->param));
> pr_debug("rc=%d\n", rc);
> return rc;
> 

reworked this and 7 other places.

>>  }
>> 
>> @@ -310,112 +473,242 @@ static int ecb_paes_decrypt(struct 
>> skcipher_request *req)
>>  	return ecb_paes_crypt(req, CPACF_DECRYPT);
>>  }
>> 
>> -static struct skcipher_alg ecb_paes_alg = {
>> -	.base.cra_name		=	"ecb(paes)",
>> -	.base.cra_driver_name	=	"ecb-paes-s390",
>> -	.base.cra_priority	=	401,	/* combo: aes + ecb + 1 */
>> -	.base.cra_blocksize	=	AES_BLOCK_SIZE,
>> -	.base.cra_ctxsize	=	sizeof(struct s390_paes_ctx),
>> -	.base.cra_module	=	THIS_MODULE,
>> -	.base.cra_list		=	LIST_HEAD_INIT(ecb_paes_alg.base.cra_list),
>> -	.init			=	ecb_paes_init,
>> -	.exit			=	ecb_paes_exit,
>> -	.min_keysize		=	PAES_MIN_KEYSIZE,
>> -	.max_keysize		=	PAES_MAX_KEYSIZE,
>> -	.setkey			=	ecb_paes_set_key,
>> -	.encrypt		=	ecb_paes_encrypt,
>> -	.decrypt		=	ecb_paes_decrypt,
>> -};
>> -
>> -static int cbc_paes_init(struct crypto_skcipher *tfm)
>> +static int ecb_paes_init(struct crypto_skcipher *tfm)
>>  {
>>  	struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm);
>> 
>> -	ctx->kb.key = NULL;
>> +	memset(ctx, 0, sizeof(*ctx));
>>  	spin_lock_init(&ctx->pk_lock);
>> 
>> +	crypto_skcipher_set_reqsize(tfm, sizeof(struct s390_pecb_req_ctx));
>> +
>>  	return 0;
>>  }
>> 
>> -static void cbc_paes_exit(struct crypto_skcipher *tfm)
>> +static void ecb_paes_exit(struct crypto_skcipher *tfm)
>>  {
>>  	struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm);
>> 
>> -	_free_kb_keybuf(&ctx->kb);
>> +	memzero_explicit(ctx, sizeof(*ctx));
>>  }
>> 
>> -static inline int __cbc_paes_set_key(struct s390_paes_ctx *ctx)
>> +static int ecb_paes_do_one_request(struct crypto_engine *engine, void 
>> *areq)
>>  {
>> -	unsigned long fc;
>> +	struct skcipher_request *req = skcipher_request_cast(areq);
>> +	struct s390_pecb_req_ctx *req_ctx = skcipher_request_ctx(req);
>> +	struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
>> +	struct s390_paes_ctx *ctx = crypto_skcipher_ctx(tfm);
>> +	struct skcipher_walk *walk = &req_ctx->walk;
>>  	int rc;
>> 
>> -	rc = __paes_convert_key(ctx);
>> -	if (rc)
>> -		return rc;
>> -
>> -	/* Pick the correct function code based on the protected key type */
>> -	fc = (ctx->pk.type == PKEY_KEYTYPE_AES_128) ? CPACF_KMC_PAES_128 :
>> -		(ctx->pk.type == PKEY_KEYTYPE_AES_192) ? CPACF_KMC_PAES_192 :
>> -		(ctx->pk.type == PKEY_KEYTYPE_AES_256) ? CPACF_KMC_PAES_256 : 0;
>> +	/* walk has already been prepared */
>> 
>> -	/* Check if the function code is available */
>> -	ctx->fc = (fc && cpacf_test_func(&kmc_functions, fc)) ? fc : 0;
>> +	rc = ecb_paes_do_crypt(ctx, req_ctx, true);
>> +	if (rc != -EKEYEXPIRED) {
>> +		if (rc)
>> +			skcipher_walk_done(walk, rc);
>> +		goto complete;
>> +	}
> 
> Same here, I would prefer to reverse the logic of the error handling.
> 
>> 
>> -	return ctx->fc ? 0 : -EINVAL;
>> +	/*
>> +	 * Protected key expired, conversion is in process.
>> +	 * Trigger a re-schedule of this request by returning
>> +	 * -ENOSPC ("hardware queue is full") to the crypto engine.
>> +	 * To avoid immediately re-invocation of this callback,
>> +	 * tell the scheduler to voluntarily give up the CPU here.
>> +	 */
>> +	yield();
> 
> As mentioned by checkpatch.pl, the use of yield() should be avoided.
> Please use alternatives (e.g. cond_reschedule()).
> 

done - replaced with cond_resched()

>> +	pr_debug("rescheduling request\n");
>> +	return -ENOSPC;
>> +
>> +complete:
>> +	memzero_explicit(&req_ctx->param, sizeof(req_ctx->param));
>> +	pr_debug("request complete with rc=%d\n", rc);
>> +	local_bh_disable();
>> +	crypto_finalize_skcipher_request(engine, req, rc);
>> +	local_bh_enable();
>> +	return rc;
>>  }
> 
> [...]

Thanks, see v4 for the changes.

  reply	other threads:[~2025-05-06 14:13 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-04-01 14:50 [PATCH v3 0/3] Rework protected key AES for true asynch support Harald Freudenberger
2025-04-01 14:50 ` [PATCH v3 1/3] s390/cpacf: Rework cpacf_pcc() to return condition code Harald Freudenberger
2025-04-14 10:14   ` Holger Dengler
2025-04-01 14:50 ` [PATCH v3 2/3] s390/crypto: Rework protected key AES for true asynch support Harald Freudenberger
2025-04-02  4:04   ` Herbert Xu
2025-05-06 14:02     ` Harald Freudenberger
2025-05-07  3:35       ` Herbert Xu
2025-04-25 14:56   ` Holger Dengler
2025-05-06 14:13     ` Harald Freudenberger [this message]
2025-04-01 14:50 ` [PATCH v3 3/3] Documentation: crypto_engine: Update and extend crypto engine doc Harald Freudenberger

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=6d91b8776191126385925d5af5f165bc@linux.ibm.com \
    --to=freude@linux.ibm.com \
    --cc=dengler@linux.ibm.com \
    --cc=fcallies@linux.ibm.com \
    --cc=herbert@gondor.apana.org.au \
    --cc=ifranzki@linux.ibm.com \
    --cc=linux-crypto@vger.kernel.org \
    --cc=linux-s390@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