public inbox for stable@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] crypto: krb5 - wait for async aead completion before freeing buffer
@ 2026-05-02 13:25 Michael Bommarito
  2026-05-05  5:54 ` Herbert Xu
  0 siblings, 1 reply; 2+ messages in thread
From: Michael Bommarito @ 2026-05-02 13:25 UTC (permalink / raw)
  To: Herbert Xu, David Howells, David S. Miller, linux-crypto
  Cc: Eric Biggers, Marc Dionne, linux-afs, Ilya Dryomov, Xiubo Li,
	ceph-devel, stable, linux-kernel

krb5_aead_encrypt(), krb5_aead_decrypt() in rfc3961_simplified.c and
rfc8009_encrypt(), rfc8009_decrypt() in rfc8009_aes2.c set a NULL
completion callback on the aead_request and treat any negative return
from crypto_aead_{encrypt,decrypt}() as terminal, falling through to
kfree_sensitive(buffer) where buffer == req.

The crypto API returns -EINPROGRESS when an asynchronous backend has
accepted the request and queued it for completion by a worker, and
-EBUSY when a backlog-capable backend has accepted it onto its
backlog (with CRYPTO_TFM_REQ_MAY_BACKLOG set).  In both cases the
operation will complete asynchronously and the request buffer must
remain live until the backend's completion callback fires.  Without
that flag set, -EBUSY instead means "rejected, try again later" and
no completion will fire.  In the current code the callback is NULL
and any negative return is treated as terminal, so when the
encrypt_name composition resolves to an async instance (a hardware
AEAD provider that registers an instance for the krb5 inner template,
or any future code path that takes a cryptd-wrapped instance for the
krb5 enctype name), -EINPROGRESS satisfies the "if (ret < 0)" check
and the buffer is freed while the backend's worker still holds a
pointer.  The worker subsequently dereferences req via
aead_request_complete(), reading from freed slab.

KASAN report under UML+SLUB with a faithful reproducer that drives
crypto_krb5_decrypt() through the existing API, with an async aead
backend bound to krb5->encrypt_name:

  BUG: KASAN: slab-use-after-free in t5_stub_complete+0x7d/0xc7
  Read of size 8 at addr 00000000619e9410 by task kworker/0:1/12

  Allocated by task 40:
   __kmalloc_noprof+0x1df/0x1ee
   kzalloc_noprof.constprop.0+0x19/0x1b [krb5]
   rfc8009_decrypt+0x294/0x7c4 [krb5]
   crypto_krb5_decrypt+0x93/0xa2 [krb5]

  Freed by task 40:
   kfree_sensitive+0x57/0x5c
   rfc8009_decrypt+0x790/0x7c4 [krb5]
   crypto_krb5_decrypt+0x93/0xa2 [krb5]

  The buggy address belongs to the cache kmalloc-128 of size 128

These helpers are reached today from net/rxrpc/rxgk.c and
rxgk_common.h (AFS/RxRPC RxGK packet encrypt/decrypt),
fs/afs/cm_security.c, and net/ceph/crypto.c (Linux kernel Ceph
client cephx encrypt/decrypt).  The bug is triggerable when one of
these paths uses a krb5 AEAD whose selected implementation is async
(or inherits async completion from an async child/provider).

Fix by following the standard kernel idiom for synchronous waiting on
a potentially-asynchronous AEAD: install crypto_req_done() as the
completion callback, set CRYPTO_TFM_REQ_MAY_BACKLOG so a backlogged
backend's -EBUSY indicates a queued (not rejected) request, and wrap
the crypto_aead_{encrypt,decrypt}() return through crypto_wait_req()
so the function blocks on the worker's completion before falling
through to kfree_sensitive().  This matches the crypto_wait_req()
usage pattern in net/tls/, fs/ecryptfs/, fs/smb/server/auth.c, and
other consumers that need a synchronous result over an arbitrarily-
async backend; MAY_BACKLOG is required (not optional) so that
crypto_wait_req() does not block waiting for a completion that will
never fire from a rejected request.

Regression coverage: the in-tree krb5 selftests
(CONFIG_CRYPTO_KRB5_SELFTESTS=y) cover all four touched functions for
all six supported enctypes (aes128/aes256-cts-hmac-sha1-96,
aes128/aes256-cts-hmac-sha256/384, camellia128/256-cts-cmac) via the
PRF, key-derivation, encrypt, decrypt and MIC paths; the patched
kernel reports "krb5: Selftests succeeded" when those default sync
backends are in use.  Additionally, on a separate boot of the
patched kernel with CONFIG_CRYPTO_SELFTESTS_FULL=y and without the
synthetic async-aead provider used to drive the reproducer loaded,
the kernel's testmgr emits no "alg: ... self-test failed" lines for
the authenc(hmac(sha256/sha384),cts(cbc(aes))) instances the krb5
layer instantiates.  (The reproducer boot does carry an expected
testmgr failure for that synthetic provider's algorithm name; that
is not a regression of any in-tree algorithm.)  The faithful
reproducer above no longer trips KASAN when an async backend is
bound.

Fixes: 00244da40f78 ("crypto/krb5: Implement the Kerberos5 rfc3961 encrypt and decrypt functions")
Fixes: 6c3c0e86c2ac ("crypto/krb5: Implement the AES enctypes from rfc8009")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
 crypto/krb5/rfc3961_simplified.c | 12 ++++++++----
 crypto/krb5/rfc8009_aes2.c       | 12 ++++++++----
 2 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/crypto/krb5/rfc3961_simplified.c b/crypto/krb5/rfc3961_simplified.c
index e49cbdec7c40..c4b8e9b89c7b 100644
--- a/crypto/krb5/rfc3961_simplified.c
+++ b/crypto/krb5/rfc3961_simplified.c
@@ -543,6 +543,7 @@ ssize_t krb5_aead_encrypt(const struct krb5_enctype *krb5,
 			  size_t data_offset, size_t data_len,
 			  bool preconfounded)
 {
+	DECLARE_CRYPTO_WAIT(wait);
 	struct aead_request *req;
 	ssize_t ret, done;
 	size_t bsize, base_len, secure_offset, secure_len, pad_len, cksum_offset;
@@ -588,9 +589,10 @@ ssize_t krb5_aead_encrypt(const struct krb5_enctype *krb5,
 	iv = buffer + krb5_aead_size(aead);
 
 	aead_request_set_tfm(req, aead);
-	aead_request_set_callback(req, 0, NULL, NULL);
+	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				  crypto_req_done, &wait);
 	aead_request_set_crypt(req, sg, sg, secure_len, iv);
-	ret = crypto_aead_encrypt(req);
+	ret = crypto_wait_req(crypto_aead_encrypt(req), &wait);
 	if (ret < 0)
 		goto error;
 
@@ -610,6 +612,7 @@ int krb5_aead_decrypt(const struct krb5_enctype *krb5,
 		      struct scatterlist *sg, unsigned int nr_sg,
 		      size_t *_offset, size_t *_len)
 {
+	DECLARE_CRYPTO_WAIT(wait);
 	struct aead_request *req;
 	size_t bsize;
 	void *buffer;
@@ -633,9 +636,10 @@ int krb5_aead_decrypt(const struct krb5_enctype *krb5,
 	iv = buffer + krb5_aead_size(aead);
 
 	aead_request_set_tfm(req, aead);
-	aead_request_set_callback(req, 0, NULL, NULL);
+	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				  crypto_req_done, &wait);
 	aead_request_set_crypt(req, sg, sg, *_len, iv);
-	ret = crypto_aead_decrypt(req);
+	ret = crypto_wait_req(crypto_aead_decrypt(req), &wait);
 	if (ret < 0)
 		goto error;
 
diff --git a/crypto/krb5/rfc8009_aes2.c b/crypto/krb5/rfc8009_aes2.c
index d39851fc3a4e..dda29f0bb700 100644
--- a/crypto/krb5/rfc8009_aes2.c
+++ b/crypto/krb5/rfc8009_aes2.c
@@ -175,6 +175,7 @@ static ssize_t rfc8009_encrypt(const struct krb5_enctype *krb5,
 			       size_t data_offset, size_t data_len,
 			       bool preconfounded)
 {
+	DECLARE_CRYPTO_WAIT(wait);
 	struct aead_request *req;
 	struct scatterlist bsg[2];
 	ssize_t ret, done;
@@ -227,10 +228,11 @@ static ssize_t rfc8009_encrypt(const struct krb5_enctype *krb5,
 
 	/* Hash and encrypt the message. */
 	aead_request_set_tfm(req, aead);
-	aead_request_set_callback(req, 0, NULL, NULL);
+	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				  crypto_req_done, &wait);
 	aead_request_set_ad(req, krb5_aead_ivsize(aead));
 	aead_request_set_crypt(req, bsg, bsg, secure_len, iv);
-	ret = crypto_aead_encrypt(req);
+	ret = crypto_wait_req(crypto_aead_encrypt(req), &wait);
 	if (ret < 0)
 		goto error;
 
@@ -253,6 +255,7 @@ static int rfc8009_decrypt(const struct krb5_enctype *krb5,
 			   struct scatterlist *sg, unsigned int nr_sg,
 			   size_t *_offset, size_t *_len)
 {
+	DECLARE_CRYPTO_WAIT(wait);
 	struct aead_request *req;
 	struct scatterlist bsg[2];
 	size_t bsize;
@@ -283,10 +286,11 @@ static int rfc8009_decrypt(const struct krb5_enctype *krb5,
 
 	/* Decrypt the message and verify its checksum. */
 	aead_request_set_tfm(req, aead);
-	aead_request_set_callback(req, 0, NULL, NULL);
+	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				  crypto_req_done, &wait);
 	aead_request_set_ad(req, krb5_aead_ivsize(aead));
 	aead_request_set_crypt(req, bsg, bsg, *_len, iv);
-	ret = crypto_aead_decrypt(req);
+	ret = crypto_wait_req(crypto_aead_decrypt(req), &wait);
 	if (ret < 0)
 		goto error;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-05-05  5:55 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-02 13:25 [PATCH] crypto: krb5 - wait for async aead completion before freeing buffer Michael Bommarito
2026-05-05  5:54 ` Herbert Xu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox