[PATCH] crypto: algif_aead - stop recvmsg looping after a complete=
From: Qiguang Wang
Date: Sat Jun 27 2026 - 17:49:55 EST
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.=C2=A0 By then af_alg_pull_tsgl()
has executed
ctx->init =3D ctx->more;
which, for a request that was not flagged MSG_MORE, resets ctx->init to
0 and drains ctx->used.=C2=A0 The second pass therefore takes the
_aead_recvmsg()/_skcipher_recvmsg() gate
if (!ctx->init || ctx->more)
err =3D af_alg_wait_for_data(sk, flags, 0);
and af_alg_wait_for_data() blocks on
ctx->init && (!ctx->more || (min && ctx->used >=3D min))
which can never become true again (ctx->init =3D=3D 0, min =3D=3D 0), so th=
e
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.=C2=A0 A plain blocking read wit=
h
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 =3D=3D 0 && ctx->used =3D=3D 0) instead of re-entering =
the
blocking wait.=C2=A0 Partial/AIO requests (ctx->used > 0), MSG_MORE streami=
ng
(ctx->more !=3D 0) and the -EIOCBQUEUED/-EBADMSG paths are unaffected: the
new check is only reached after "ret +=3D err", i.e. after a pass that
made forward progress.
Fixes: f3c802a1f300 ("crypto: algif_aead - Only wake up when ctx->more is z=
ero")
Cc: <stable@xxxxxxxxxxxxxxx>
Signed-off-by: Qiguang Wang <cyper@xxxxxxxxxxxx>
---
crypto/algif_aead.c=C2=A0=C2=A0=C2=A0=C2=A0 | 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 msg=
hdr *msg,
size_t ignored, int flags)
{
struct sock *sk =3D sock->sk;
+ struct af_alg_ctx *ctx =3D alg_sk(sk)->private;
int ret =3D 0;
=C2=A0
lock_sock(sk);
@@ -237,6 +238,17 @@ static int aead_recvmsg(struct socket *sock, struct ms=
ghdr *msg,
}
=C2=A0
ret +=3D err;
+
+ /*
+ * A request that was not flagged MSG_MORE has now been fully
+ * consumed: af_alg_pull_tsgl() reset ctx->init to ctx->more
+ * (=3D=3D 0) and drained ctx->used.=C2=A0 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;
}
=C2=A0
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,
=C2=A0=C2=A0=C2=A0 size_t ignored, int flags)
{
struct sock *sk =3D sock->sk;
+ struct af_alg_ctx *ctx =3D alg_sk(sk)->private;
int ret =3D 0;
=C2=A0
lock_sock(sk);
@@ -202,6 +203,17 @@ static int skcipher_recvmsg(struct socket *sock, struc=
t msghdr *msg,
}
=C2=A0
ret +=3D err;
+
+ /*
+ * A request that was not flagged MSG_MORE has now been fully
+ * consumed: af_alg_pull_tsgl() reset ctx->init to ctx->more
+ * (=3D=3D 0) and drained ctx->used.=C2=A0 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;
}
=C2=A0
out:
--=C2=A0
2.53.0