[PATCH] crypto: qce - Fix HMAC self-test failures for empty messages

From: Bartosz Golaszewski

Date: Fri Oct 16 2026 - 10:25:16 EST


BAM DMA cannot process zero-length transfers, so the driver always holds
back at least one byte to submit to the engine and only ever finalizes
with an empty buffer when nothing is left to submit. For plain hashes
this was handled by returning the precomputed hash of the empty message
(tmpl->hash_zero), but HMAC's result depends on the key and cannot be
constant, so hmac(sha256) produced an incorrect digest for an empty
message and the crypto self-tests failed.

A zero pending buffer at finalization time does not necessarily mean the
message itself is empty, though: the caller can also reach it by
importing a state that already reflects some hashed data with nothing
currently buffered (crypto_ahash_import() followed directly by
finalization). Special-casing only a genuinely empty message would
silently compute the wrong result for an imported state in that
scenario.

Handle every zero-length finalization consistently through the software
fallback ahash instead. When nothing has been processed yet, let the
fallback compute the result from scratch using the key already propagated
to it via setkey(). Otherwise, reconstruct the fallback's running hash
state from the state kept by the driver and finalize from there,
accounting for the HMAC ipad block that the engine absorbs internally and
that never goes through update().

Cc: stable@xxxxxxxxxxxxxxx
Fixes: ec8f5d8f6f76 ("crypto: qce - Qualcomm crypto engine driver")
Tested-by: Kuldeep Singh <kuldeep.singh@xxxxxxxxxxxxxxxx>
Tested-by: Pankaj Patil <pankaj.patil@xxxxxxxxxxxxxxxx>
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@xxxxxxxxxxxxxxxx>
---
As requested by Herbert: I've split the large series fixing self tests
into smaller chunks for easier review.
---
Changes in v12:
- Split out the SHA patch into its own submission
- Link to v11: https://patch.msgid.link/20261016-qce-fixes-skcipher-v11-0-d13ddfe609a4@xxxxxxxxxxxxxxxx

Changes in v11:
- Split out the skcipher patches into a smaller series for easier review
- Drop patch "crypto: qce - Fix CTR-AES for partial block requests" as a
subsequent commit makes this adjustment effectively dead code
- Fix a mid-series build failure due to an earlier bad rebase
- Link to v10: https://patch.msgid.link/20261013-qce-fix-self-tests-v10-0-57162a38a554@xxxxxxxxxxxxxxxx

Changes in v10:
- don't pass a NULL buffer to sg_init_one()
- fix byte order of the imported hash state on empty finalization
- don't recompute the CTR counter in software for every request
- reject skcipher requests with a too short scatterlist
- check fragmentation for empty CCM decryption requests too
- don't use a static loop iterator (duh...)
- initialize the return value in qce_handle_queue()
- Link to v9: https://patch.msgid.link/20260922-qce-fix-self-tests-v9-0-b1aa742e79af@xxxxxxxxxxxxxxxx

Changes in v9:
- Rebase on top of cryptodev/master
- Rephrase Kconfig help text changes
- Link to v8: https://patch.msgid.link/20260921-qce-fix-self-tests-v8-0-107537869a72@xxxxxxxxxxxxxxxx

Changes in v8:
- Add a patch making the crypto driver *not* register algos by default
but only if the user explicitly requests it with the provided module
parameter
- Use the "offloader" name for the crypto IP instead of "accelerator"
- Fix device module table issues
- Make the core driver use a list (despite it only having a single
member now) of child auxiliary devices to register to make it clear we
have the intention of extending this once we start the work on secure
media playback
- Link to v7: https://patch.msgid.link/20260910-qce-fix-self-tests-v7-0-cdbd2718af14@xxxxxxxxxxxxxxxx

Changes in v7:
- Add follow-up changes converting the QCE crypto driver to auxiliary
bus, adding the core QCE driver under drivers/soc/ registering the
auxiliary device and removing the BROKEN Kconfig label
- Link to v6: https://patch.msgid.link/20260717-qce-fix-self-tests-v6-0-455775fe5f6c@xxxxxxxxxxxxxxxx

Changes in v6:
- Handle all zero-length HMAC finalizations (like imported state with
data already hashed), not only the genuinely empty message case
- Add an additional patch addressing the fragmented skcipher payload
issue
- Link to v5: https://patch.msgid.link/20260706-qce-fix-self-tests-v5-0-86f461ff1829@xxxxxxxxxxxxxxxx

Changes in v5:
- Dropped patch 1/8 that's already queued
- Use the pre-allocated fallback ahash for HMAC transforms (Herbert)
- Link to v4: https://patch.msgid.link/20260622-qce-fix-self-tests-v4-0-4f82ffa716c6@xxxxxxxxxxxxxxxx

Changes in v4:
- Remove remaining ECB and DES3 bits
- Pick up tags
- Link to v3: https://patch.msgid.link/20260617-qce-fix-self-tests-v3-0-ecc2b4dedcfd@xxxxxxxxxxxxxxxx

Changes in v3:
- Remove even more algorithms and dead code in patch 1/8
- Link to v2: https://patch.msgid.link/20260615-qce-fix-self-tests-v2-0-dc911f1aad42@xxxxxxxxxxxxxxxx

Changes in v2:
- Add fixes for the full suite of crypto self-tests
- Add Fixes and Cc tags
- Link to v1: https://patch.msgid.link/20260610-qce_selftest_fix-v1-0-1b0504783a46@xxxxxxxxxxxxxxxx/
---
drivers/crypto/qce/common.h | 1 -
drivers/crypto/qce/sha.c | 62 ++++++++++++++++++++++++++++++++++-----------
2 files changed, 47 insertions(+), 16 deletions(-)

diff --git a/drivers/crypto/qce/common.h b/drivers/crypto/qce/common.h
index 9cd2e6ed8bbb0f76e24be187d8ae7e2fe2f7b932..587d349da91d1faa2bed7cd488306d4358467b2d 100644
--- a/drivers/crypto/qce/common.h
+++ b/drivers/crypto/qce/common.h
@@ -82,7 +82,6 @@ struct qce_alg_template {
struct aead_alg aead;
} alg;
struct qce_device *qce;
- const u8 *hash_zero;
const u32 digest_size;
};

diff --git a/drivers/crypto/qce/sha.c b/drivers/crypto/qce/sha.c
index b67d749db05f2b9823cd2f0953ef24f4f337df2c..738454dd35655a8376bc878444fcc3fbd7535956 100644
--- a/drivers/crypto/qce/sha.c
+++ b/drivers/crypto/qce/sha.c
@@ -7,6 +7,7 @@
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/string.h>
+#include <linux/unaligned.h>
#include <crypto/internal/hash.h>

#include "common.h"
@@ -249,18 +250,56 @@ static int qce_ahash_update(struct ahash_request *req)
return qce->async_req_enqueue(tmpl->qce, &req->base);
}

+/*
+ * BAM DMA cannot handle zero-length transfers, so the driver always holds
+ * back at least one byte to submit to the engine. A zero rctx->buflen at
+ * finalization time does not necessarily mean the message is empty: the
+ * caller may have imported a state that already reflects some hashed data
+ * with nothing currently buffered. Handle both cases through the software
+ * fallback: reconstruct the running state when there is one instead of
+ * assuming the message is empty.
+ */
+static int qce_ahash_finalize_zero(struct ahash_request *req)
+{
+ struct qce_sha_reqctx *rctx = ahash_request_ctx_dma(req);
+ HASH_FBREQ_ON_STACK(fbreq, req);
+ struct __sha256_ctx core;
+ struct scatterlist sg;
+ int ret, i;
+
+ sg_init_table(&sg, 1);
+ ahash_request_set_crypt(fbreq, &sg, req->result, 0);
+
+ if (rctx->first_blk) {
+ ret = crypto_ahash_init(fbreq) ?: crypto_ahash_finup(fbreq);
+ } else {
+ core = (struct __sha256_ctx){
+ .bytecount = rctx->count,
+ };
+
+ /* rctx->digest is the raw big-endian dump of the engine's regs */
+ for (i = 0; i < SHA256_STATE_WORDS; i++)
+ core.state.h[i] = get_unaligned_be32(rctx->digest +
+ i * sizeof(u32));
+ if (IS_SHA_HMAC(rctx->flags))
+ core.bytecount += SHA256_BLOCK_SIZE;
+
+ ret = crypto_ahash_import_core(fbreq, &core) ?:
+ crypto_ahash_finup(fbreq);
+ }
+
+ HASH_REQUEST_ZERO(fbreq);
+ return ret;
+}
+
static int qce_ahash_final(struct ahash_request *req)
{
struct qce_sha_reqctx *rctx = ahash_request_ctx_dma(req);
struct qce_alg_template *tmpl = to_ahash_tmpl(req->base.tfm);
struct qce_device *qce = tmpl->qce;

- if (!rctx->buflen) {
- if (tmpl->hash_zero)
- memcpy(req->result, tmpl->hash_zero,
- tmpl->alg.ahash.halg.digestsize);
- return 0;
- }
+ if (!rctx->buflen)
+ return qce_ahash_finalize_zero(req);

rctx->last_blk = true;

@@ -292,12 +331,8 @@ static int qce_ahash_digest(struct ahash_request *req)
rctx->first_blk = true;
rctx->last_blk = true;

- if (!rctx->nbytes_orig) {
- if (tmpl->hash_zero)
- memcpy(req->result, tmpl->hash_zero,
- tmpl->alg.ahash.halg.digestsize);
- return 0;
- }
+ if (!rctx->nbytes_orig)
+ return qce_ahash_finalize_zero(req);

return qce->async_req_enqueue(tmpl->qce, &req->base);
}
@@ -431,9 +466,6 @@ static int qce_ahash_register_one(const struct qce_ahash_def *def,
alg->halg.digestsize = def->digestsize;
alg->halg.statesize = def->statesize;

- if (IS_SHA256(def->flags))
- tmpl->hash_zero = sha256_zero_message_hash;
-
base = &alg->halg.base;
base->cra_blocksize = def->blocksize;
base->cra_priority = 175;

---
base-commit: a29888556578e4695bca25a2807308e9050ee2ac
change-id: 20261016-qce-fixes-sha-a01b32c77ec0

Best regards,
--
Bartosz Golaszewski <bartosz.golaszewski@xxxxxxxxxxxxxxxx>