From: cyper@tutanota.com
To: Herbert <herbert@gondor.apana.org.au>
Cc: Davem <davem@davemloft.net>,
Linux Crypto <linux-crypto@vger.kernel.org>,
Linux Kernel <linux-kernel@vger.kernel.org>
Subject: [PATCH] crypto: algif_aead - stop recvmsg looping after a completed request
Date: Sun, 28 Jun 2026 17:34:15 +0200 (CEST) [thread overview]
Message-ID: <OwDrEgL--F-9@tutanota.com> (raw)
From e0ed18c8ad9a7d2ecf939f0b97e2be0567180c1d Mon Sep 17 00:00:00 2001
From: Qiguang Wang <cyper@tutanota.com>
Date: Sat, 27 Jun 2026 21:49:55 +0000
Subject: [PATCH] crypto: algif_aead - stop recvmsg looping after a completed
request
To: Herbert Xu <herbert@gondor.apana.org.au>
Cc: "David S. Miller" <davem@davemloft.net>,
linux-crypto@vger.kernel.org,
linux-kernel@vger.kernel.org
A blocking recvmsg()/read() into an output buffer larger than the cipher
result hangs forever.
After the first pass of the "while (msg_data_left(msg))" loop in
aead_recvmsg() (and the identical loop in skcipher_recvmsg()) produces
the result, af_alg_get_rsgl() has consumed only as many bytes from the
output iterator as the cipher produced, so msg_data_left() is still
non-zero and the loop runs a second pass. By then af_alg_pull_tsgl()
has executed
ctx->init = ctx->more;
which, for a request that was not flagged MSG_MORE, resets ctx->init to
0 and drains ctx->used. The second pass therefore takes the
_aead_recvmsg()/_skcipher_recvmsg() gate
if (!ctx->init || ctx->more)
err = af_alg_wait_for_data(sk, flags, 0);
and af_alg_wait_for_data() blocks on
ctx->init && (!ctx->more || (min && ctx->used >= min))
which can never become true again (ctx->init == 0, min == 0), so the
task sleeps in MAX_SCHEDULE_TIMEOUT forever even though the result was
already produced in pass 1.
The sleep is interruptible, so any signal -- or a poll(POLLIN) issued
before the read -- makes recvmsg return the bytes already accumulated in
ret, which is why the hang is easy to miss. A plain blocking read with
an oversized buffer hangs deterministically; it reproduces with stock
gcm(aes).
Fix both loops by stopping once a non-MSG_MORE request has been fully
consumed (ctx->more == 0 && ctx->used == 0) instead of re-entering the
blocking wait. Partial/AIO requests (ctx->used > 0), MSG_MORE streaming
(ctx->more != 0) and the -EIOCBQUEUED/-EBADMSG paths are unaffected: the
new check is only reached after "ret += err", i.e. after a pass that
made forward progress.
Fixes: f3c802a1f300 ("crypto: algif_aead - Only wake up when ctx->more is zero")
Cc: <stable@vger.kernel.org>
Signed-off-by: Qiguang Wang <cyper@tutanota.com>
---
crypto/algif_aead.c | 12 ++++++++++++
crypto/algif_skcipher.c | 12 ++++++++++++
2 files changed, 24 insertions(+)
diff --git a/crypto/algif_aead.c b/crypto/algif_aead.c
index 787aac8aeb24..d0756aef476d 100644
--- a/crypto/algif_aead.c
+++ b/crypto/algif_aead.c
@@ -216,6 +216,7 @@ static int aead_recvmsg(struct socket *sock, struct msghdr *msg,
size_t ignored, int flags)
{
struct sock *sk = sock->sk;
+ struct af_alg_ctx *ctx = alg_sk(sk)->private;
int ret = 0;
lock_sock(sk);
@@ -237,6 +238,17 @@ static int aead_recvmsg(struct socket *sock, struct msghdr *msg,
}
ret += err;
+
+ /*
+ * A request that was not flagged MSG_MORE has now been fully
+ * consumed: af_alg_pull_tsgl() reset ctx->init to ctx->more
+ * (== 0) and drained ctx->used. Stop here instead of looping
+ * back into a blocking af_alg_wait_for_data() that can never
+ * complete, which is what happens when the supplied output
+ * buffer is larger than the cipher result.
+ */
+ if (!ctx->more && !ctx->used)
+ break;
}
out:
diff --git a/crypto/algif_skcipher.c b/crypto/algif_skcipher.c
index df20bdfe1f1f..c3a5968baef4 100644
--- a/crypto/algif_skcipher.c
+++ b/crypto/algif_skcipher.c
@@ -181,6 +181,7 @@ static int skcipher_recvmsg(struct socket *sock, struct msghdr *msg,
size_t ignored, int flags)
{
struct sock *sk = sock->sk;
+ struct af_alg_ctx *ctx = alg_sk(sk)->private;
int ret = 0;
lock_sock(sk);
@@ -202,6 +203,17 @@ static int skcipher_recvmsg(struct socket *sock, struct msghdr *msg,
}
ret += err;
+
+ /*
+ * A request that was not flagged MSG_MORE has now been fully
+ * consumed: af_alg_pull_tsgl() reset ctx->init to ctx->more
+ * (== 0) and drained ctx->used. Stop here instead of looping
+ * back into a blocking af_alg_wait_for_data() that can never
+ * complete, which is what happens when the supplied output
+ * buffer is larger than the cipher result.
+ */
+ if (!ctx->more && !ctx->used)
+ break;
}
out:
--
2.53.0
reply other threads:[~2026-06-28 15:34 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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=OwDrEgL--F-9@tutanota.com \
--to=cyper@tutanota.com \
--cc=davem@davemloft.net \
--cc=herbert@gondor.apana.org.au \
--cc=linux-crypto@vger.kernel.org \
--cc=linux-kernel@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 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.