diff mbox series

[net,1/5] net: tls: handle -EBUSY on async encrypt/decrypt requests

Message ID 9681d1febfec295449a62300938ed2ae66983f28.1694018970.git.sd@queasysnail.net (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series tls: fix some issues with async encryption | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net
netdev/fixes_present success Fixes tag present in non-next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1330 this patch: 1330
netdev/cc_maintainers fail 1 blamed authors not CCed: davem@davemloft.net; 3 maintainers not CCed: edumazet@google.com pabeni@redhat.com davem@davemloft.net
netdev/build_clang success Errors and warnings before: 1353 this patch: 1353
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 1353 this patch: 1353
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 89 lines checked
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Sabrina Dubroca Sept. 6, 2023, 5:08 p.m. UTC
Since we're setting the CRYPTO_TFM_REQ_MAY_BACKLOG flag on our
requests to the crypto API, crypto_aead_{encrypt,decrypt} can return
 -EBUSY instead of -EINPROGRESS in valid situations. For example, when
the cryptd queue for AESNI is full (easy to trigger with an
artifically low cryptd.cryptd_max_cpu_qlen), requests will be enqueued
to the backlog but still processed. In that case, the async callback
will also be called twice: first with err == -EINPROGRESS, which it
seems we can just ignore, then with err == 0.

I've only tested this on AESNI with cryptd.

Fixes: a54667f6728c ("tls: Add support for encryption using async offload accelerator")
Fixes: 94524d8fc965 ("net/tls: Add support for async decryption of tls records")
Signed-off-by: Sabrina Dubroca <sd@queasysnail.net>
---
 net/tls/tls_sw.c | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

Comments

Jakub Kicinski Sept. 7, 2023, 1:50 a.m. UTC | #1
On Wed,  6 Sep 2023 19:08:31 +0200 Sabrina Dubroca wrote:
> --- a/net/tls/tls_sw.c
> +++ b/net/tls/tls_sw.c
> @@ -196,6 +196,9 @@ static void tls_decrypt_done(void *data, int err)
>  	struct sock *sk;
>  	int aead_size;
>  
> +	if (err == -EINPROGRESS)
> +		return;

Maybe a comment here clarifying that caller got -EBUSY and the callback
will fire again without an error? The flow is slightly counter-
-intuitive.

> @@ -443,6 +446,9 @@ static void tls_encrypt_done(void *data, int err)
>  	struct sock *sk;
>  	int pending;
>  
> +	if (err == -EINPROGRESS)
> +		return;

Same here? 

Reviewed-by: Jakub Kicinski <kuba@kernel.org>
Simon Horman Sept. 7, 2023, 3:11 p.m. UTC | #2
On Wed, Sep 06, 2023 at 07:08:31PM +0200, Sabrina Dubroca wrote:
> Since we're setting the CRYPTO_TFM_REQ_MAY_BACKLOG flag on our
> requests to the crypto API, crypto_aead_{encrypt,decrypt} can return
>  -EBUSY instead of -EINPROGRESS in valid situations. For example, when
> the cryptd queue for AESNI is full (easy to trigger with an
> artifically low cryptd.cryptd_max_cpu_qlen), requests will be enqueued

Hi Sabrina,

as it looks like there will be a v2, checkpatch --codespell, asked
me to suggest:

 artifically -> artificially

...
Herbert Xu Sept. 8, 2023, 6:10 a.m. UTC | #3
Sabrina Dubroca <sd@queasysnail.net> wrote:
> Since we're setting the CRYPTO_TFM_REQ_MAY_BACKLOG flag on our
> requests to the crypto API, crypto_aead_{encrypt,decrypt} can return
> -EBUSY instead of -EINPROGRESS in valid situations. For example, when
> the cryptd queue for AESNI is full (easy to trigger with an
> artifically low cryptd.cryptd_max_cpu_qlen), requests will be enqueued
> to the backlog but still processed. In that case, the async callback
> will also be called twice: first with err == -EINPROGRESS, which it
> seems we can just ignore, then with err == 0.
> 
> I've only tested this on AESNI with cryptd.
> 
> Fixes: a54667f6728c ("tls: Add support for encryption using async offload accelerator")
> Fixes: 94524d8fc965 ("net/tls: Add support for async decryption of tls records")
> Signed-off-by: Sabrina Dubroca <sd@queasysnail.net>
> ---
> net/tls/tls_sw.c | 23 +++++++++++++++--------
> 1 file changed, 15 insertions(+), 8 deletions(-)

You should only use MAY_BACKLOG if you can actually back off and
stop issuing new requests.  In that case you can only restart
issuing new requests when the EINPROGRESS notification comes in.

If that's not the case here you should drop MAY_BACKLOG altogether.

Cheers,
Sabrina Dubroca Sept. 8, 2023, 3:55 p.m. UTC | #4
Thanks for looking at this patch. In retrospect I should have cc'd you
on it.

2023-09-08, 14:10:05 +0800, Herbert Xu wrote:
> Sabrina Dubroca <sd@queasysnail.net> wrote:
> > Since we're setting the CRYPTO_TFM_REQ_MAY_BACKLOG flag on our
> > requests to the crypto API, crypto_aead_{encrypt,decrypt} can return
> > -EBUSY instead of -EINPROGRESS in valid situations. For example, when
> > the cryptd queue for AESNI is full (easy to trigger with an
> > artifically low cryptd.cryptd_max_cpu_qlen), requests will be enqueued
> > to the backlog but still processed. In that case, the async callback
> > will also be called twice: first with err == -EINPROGRESS, which it
> > seems we can just ignore, then with err == 0.
> > 
> > I've only tested this on AESNI with cryptd.
> > 
> > Fixes: a54667f6728c ("tls: Add support for encryption using async offload accelerator")
> > Fixes: 94524d8fc965 ("net/tls: Add support for async decryption of tls records")
> > Signed-off-by: Sabrina Dubroca <sd@queasysnail.net>
> > ---
> > net/tls/tls_sw.c | 23 +++++++++++++++--------
> > 1 file changed, 15 insertions(+), 8 deletions(-)
> 
> You should only use MAY_BACKLOG if you can actually back off and
> stop issuing new requests.  In that case you can only restart
> issuing new requests when the EINPROGRESS notification comes in.
> 
> If that's not the case here you should drop MAY_BACKLOG altogether.

Uh, ok, I didn't know that, thanks for explaining. When I was fixing
this code I couldn't find a mention of what the expectations for
MAY_BACKLOG are. Could you add a comment describing this in the
headers (either for #define CRYPTO_TFM_REQ_MAY_BACKLOG or
aead_request_set_callback, wherever is more appropriate). MAY_BACKLOG
is used by both tls and tipc (talking only about networking) and
neither seem to respect this need to back off.

Jakub, I guess we should drop the CRYPTO_TFM_REQ_MAY_BACKLOG for net,
and maybe consider adding it back (with the back off) in
net-next. Probably not urgent considering that nobody seems to have
run into this bug so far.

But then we have to handle ENOSPC a bit more gracefully, because right
now it looks like
 - on TX, we break the socket (tls_err_abort when tls_do_encryption returns
   an error)
 - on RX, we also break the socket, and we don't decrement
   decrypt_pending so the recv() call gets stuck

Not sure how complex the changes would be, the sendmsg and recvmsg
code is already a bit hard to follow.
Jakub Kicinski Sept. 8, 2023, 9:26 p.m. UTC | #5
On Fri, 8 Sep 2023 17:55:59 +0200 Sabrina Dubroca wrote:
> Jakub, I guess we should drop the CRYPTO_TFM_REQ_MAY_BACKLOG for net,
> and maybe consider adding it back (with the back off) in
> net-next. Probably not urgent considering that nobody seems to have
> run into this bug so far.

Someone did mention it long time ago, but I don't recall the context :S
I think it was something about the device queue filling up..

> But then we have to handle ENOSPC a bit more gracefully, because right
> now it looks like
>  - on TX, we break the socket (tls_err_abort when tls_do_encryption returns
>    an error)
>  - on RX, we also break the socket, and we don't decrement
>    decrypt_pending so the recv() call gets stuck
> 
> Not sure how complex the changes would be, the sendmsg and recvmsg
> code is already a bit hard to follow.

To keep it simple we can wait for all in-flight requests to drain if we
hit EBUSY? Basically factor this bit out:

		spin_lock_bh(&ctx->decrypt_compl_lock);
		reinit_completion(&ctx->async_wait.completion);
		pending = atomic_read(&ctx->decrypt_pending);
		spin_unlock_bh(&ctx->decrypt_compl_lock);
		ret = 0;
		if (pending)
			ret = crypto_wait_req(-EINPROGRESS, &ctx->async_wait);

and call it after we get EBUSY? We'll drain the pending queue all the
way to empty, which may not be too great for throughput, but who cares
- right now we don't handle EBUSY at all, so it must be extremely rare.
Herbert Xu Sept. 9, 2023, 12:53 a.m. UTC | #6
On Fri, Sep 08, 2023 at 02:26:02PM -0700, Jakub Kicinski wrote:
>
> and call it after we get EBUSY? We'll drain the pending queue all the
> way to empty, which may not be too great for throughput, but who cares
> - right now we don't handle EBUSY at all, so it must be extremely rare.

EBUSY means that you're sending requests to the Crypto API faster
than it can process them.  If left unhandled you will eventually
exhaust all memory.

Cheers,
Herbert Xu Sept. 12, 2023, 4:43 a.m. UTC | #7
On Fri, Sep 08, 2023 at 05:55:59PM +0200, Sabrina Dubroca wrote:
>
> Uh, ok, I didn't know that, thanks for explaining. When I was fixing
> this code I couldn't find a mention of what the expectations for
> MAY_BACKLOG are. Could you add a comment describing this in the
> headers (either for #define CRYPTO_TFM_REQ_MAY_BACKLOG or
> aead_request_set_callback, wherever is more appropriate). MAY_BACKLOG
> is used by both tls and tipc (talking only about networking) and
> neither seem to respect this need to back off.

Patches are welcome :)

A bit of history: at the beginning we always dropped requests
that we couldn't queue because the only user was IPsec so this
is the expected behaviour.

When storage crypto support was added there was a need for reliable
handling of resource constraints so that's why MAY_BACKLOG was added.
However, the expectation is obviously that you must stop sending new
requests once you run into the resource constraint.

> Jakub, I guess we should drop the CRYPTO_TFM_REQ_MAY_BACKLOG for net,
> and maybe consider adding it back (with the back off) in
> net-next. Probably not urgent considering that nobody seems to have
> run into this bug so far.

I think that would be the prudent action.

Thanks,
Sabrina Dubroca Sept. 12, 2023, 3:37 p.m. UTC | #8
2023-09-12, 12:43:32 +0800, Herbert Xu wrote:
> On Fri, Sep 08, 2023 at 05:55:59PM +0200, Sabrina Dubroca wrote:
> >
> > Uh, ok, I didn't know that, thanks for explaining. When I was fixing
> > this code I couldn't find a mention of what the expectations for
> > MAY_BACKLOG are. Could you add a comment describing this in the
> > headers (either for #define CRYPTO_TFM_REQ_MAY_BACKLOG or
> > aead_request_set_callback, wherever is more appropriate). MAY_BACKLOG
> > is used by both tls and tipc (talking only about networking) and
> > neither seem to respect this need to back off.
> 
> Patches are welcome :)

Ok. I thought it'd be better if you wrote that patch since if I write
it, it'll be a c/p (or rephrase) of what you wrote. But fine, I'll go
ahead and do that :)

> A bit of history: at the beginning we always dropped requests
> that we couldn't queue because the only user was IPsec so this
> is the expected behaviour.
> 
> When storage crypto support was added there was a need for reliable
> handling of resource constraints so that's why MAY_BACKLOG was added.
> However, the expectation is obviously that you must stop sending new
> requests once you run into the resource constraint.
> 
> > Jakub, I guess we should drop the CRYPTO_TFM_REQ_MAY_BACKLOG for net,
> > and maybe consider adding it back (with the back off) in
> > net-next. Probably not urgent considering that nobody seems to have
> > run into this bug so far.
> 
> I think that would be the prudent action.

We'd have to do pretty much what Jakub suggested [1] (handle ENOSPC by
waiting for all current requests) and then resubmit the failed
request. So I think keeping the MAY_BACKLOG flag and handling EBUSY
this way is simpler. With this, we send one request to the backlog,
then we wait until the queue drains.

[1] https://lore.kernel.org/netdev/20230908142602.2ced0631@kernel.org/
Herbert Xu Sept. 14, 2023, 9 a.m. UTC | #9
On Tue, Sep 12, 2023 at 05:37:23PM +0200, Sabrina Dubroca wrote:
>
> We'd have to do pretty much what Jakub suggested [1] (handle ENOSPC by
> waiting for all current requests) and then resubmit the failed
> request. So I think keeping the MAY_BACKLOG flag and handling EBUSY
> this way is simpler. With this, we send one request to the backlog,
> then we wait until the queue drains.
> 
> [1] https://lore.kernel.org/netdev/20230908142602.2ced0631@kernel.org/

You need to handle something like ENOSPC anyhow because the underlying
driver may experience a real failure (e.g., someone unplugged the
hardware) which would have pretty much the same effect of ENOSPC
(i.e., an error with no retries).  So if the tls code can't cope with
that then it has to be fixed.

Cheers,
diff mbox series

Patch

diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index 1ed4a611631f..4f3dd0403efb 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -196,6 +196,9 @@  static void tls_decrypt_done(void *data, int err)
 	struct sock *sk;
 	int aead_size;
 
+	if (err == -EINPROGRESS)
+		return;
+
 	aead_size = sizeof(*aead_req) + crypto_aead_reqsize(aead);
 	aead_size = ALIGN(aead_size, __alignof__(*dctx));
 	dctx = (void *)((u8 *)aead_req + aead_size);
@@ -261,7 +264,7 @@  static int tls_do_decryption(struct sock *sk,
 	}
 
 	ret = crypto_aead_decrypt(aead_req);
-	if (ret == -EINPROGRESS) {
+	if (ret == -EINPROGRESS || ret == -EBUSY) {
 		if (darg->async)
 			return 0;
 
@@ -443,6 +446,9 @@  static void tls_encrypt_done(void *data, int err)
 	struct sock *sk;
 	int pending;
 
+	if (err == -EINPROGRESS)
+		return;
+
 	msg_en = &rec->msg_encrypted;
 
 	sk = rec->sk;
@@ -544,7 +550,7 @@  static int tls_do_encryption(struct sock *sk,
 	atomic_inc(&ctx->encrypt_pending);
 
 	rc = crypto_aead_encrypt(aead_req);
-	if (!rc || rc != -EINPROGRESS) {
+	if (!rc || (rc != -EINPROGRESS && rc != -EBUSY)) {
 		atomic_dec(&ctx->encrypt_pending);
 		sge->offset -= prot->prepend_size;
 		sge->length += prot->prepend_size;
@@ -552,7 +558,7 @@  static int tls_do_encryption(struct sock *sk,
 
 	if (!rc) {
 		WRITE_ONCE(rec->tx_ready, true);
-	} else if (rc != -EINPROGRESS) {
+	} else if (rc != -EINPROGRESS && rc != -EBUSY) {
 		list_del(&rec->list);
 		return rc;
 	}
@@ -779,7 +785,7 @@  static int tls_push_record(struct sock *sk, int flags,
 	rc = tls_do_encryption(sk, tls_ctx, ctx, req,
 			       msg_pl->sg.size + prot->tail_size, i);
 	if (rc < 0) {
-		if (rc != -EINPROGRESS) {
+		if (rc != -EINPROGRESS && rc != -EBUSY) {
 			tls_err_abort(sk, -EBADMSG);
 			if (split) {
 				tls_ctx->pending_open_record_frags = true;
@@ -990,7 +996,7 @@  static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
 	if (unlikely(msg->msg_controllen)) {
 		ret = tls_process_cmsg(sk, msg, &record_type);
 		if (ret) {
-			if (ret == -EINPROGRESS)
+			if (ret == -EINPROGRESS || ret == -EBUSY)
 				num_async++;
 			else if (ret != -EAGAIN)
 				goto send_end;
@@ -1071,7 +1077,7 @@  static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
 						  record_type, &copied,
 						  msg->msg_flags);
 			if (ret) {
-				if (ret == -EINPROGRESS)
+				if (ret == -EINPROGRESS || ret == -EBUSY)
 					num_async++;
 				else if (ret == -ENOMEM)
 					goto wait_for_memory;
@@ -1125,7 +1131,7 @@  static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
 						  record_type, &copied,
 						  msg->msg_flags);
 			if (ret) {
-				if (ret == -EINPROGRESS)
+				if (ret == -EINPROGRESS || ret == -EBUSY)
 					num_async++;
 				else if (ret == -ENOMEM)
 					goto wait_for_memory;
@@ -1248,6 +1254,7 @@  void tls_sw_splice_eof(struct socket *sock)
 			goto unlock;
 		retrying = true;
 		goto retry;
+	case -EBUSY:
 	case -EINPROGRESS:
 		break;
 	default:
@@ -2106,7 +2113,7 @@  int tls_sw_recvmsg(struct sock *sk,
 		__skb_queue_purge(&ctx->async_hold);
 
 		if (ret) {
-			if (err >= 0 || err == -EINPROGRESS)
+			if (err >= 0 || err == -EINPROGRESS || err == -EBUSY)
 				err = ret;
 			decrypted = 0;
 			goto end;