[PATCH v3 2/8] crypto: qce - Fix HMAC self-test failures for empty messages

From: Bartosz Golaszewski

Date: Wed Jun 17 2026 - 12:05:12 EST


BAM DMA cannot process zero-length transfers. For plain hashes this is
handled by returning the precomputed hash of the empty message
(tmpl->hash_zero), but for keyed HMAC the result depends on the key and
cannot be a constant. As a result, hmac(sha256) produced an incorrect
digest for an empty message and the crypto self-tests failed.

Allocate a software fallback ahash for the HMAC transforms and use it to
compute the digest whenever the message is empty (in both the .final()
and .digest() paths). The fallback is allocated in a dedicated cra_init
for the HMAC algorithms and is excluded from matching the crypto engine's
own algorithm to avoid recursion. It is kept keyed in sync with the
hardware transform in .setkey().

Cc: stable@xxxxxxxxxxxxxxx
Fixes: ec8f5d8f6f76 ("crypto: qce - Qualcomm crypto engine driver")
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@xxxxxxxxxxxxxxxx>
---
drivers/crypto/qce/sha.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++-
drivers/crypto/qce/sha.h | 1 +
2 files changed, 84 insertions(+), 1 deletion(-)

diff --git a/drivers/crypto/qce/sha.c b/drivers/crypto/qce/sha.c
index 0a3f88aaf5169ea7b47a549bbc10ea87d3ae7a2b..d4d0bf88dea6bf1c58ee103cdccbbbfc266110e1 100644
--- a/drivers/crypto/qce/sha.c
+++ b/drivers/crypto/qce/sha.c
@@ -270,6 +270,36 @@ 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. For plain hashes the result of
+ * an empty message is a known constant (hash_zero), for keyed HMAC it depends
+ * on the key, so compute it with the software fallback.
+ */
+static int qce_ahash_hmac_zero(struct ahash_request *req)
+{
+ struct crypto_ahash *tfm = crypto_ahash_reqtfm(req);
+ struct qce_sha_ctx *ctx = crypto_tfm_ctx(crypto_ahash_tfm(tfm));
+ struct ahash_request *subreq;
+ struct crypto_wait wait;
+ struct scatterlist sg;
+ int ret;
+
+ subreq = ahash_request_alloc(ctx->fallback, GFP_ATOMIC);
+ if (!subreq)
+ return -ENOMEM;
+
+ crypto_init_wait(&wait);
+ ahash_request_set_callback(subreq, CRYPTO_TFM_REQ_MAY_BACKLOG,
+ crypto_req_done, &wait);
+ sg_init_one(&sg, NULL, 0);
+ ahash_request_set_crypt(subreq, &sg, req->result, 0);
+
+ ret = crypto_wait_req(crypto_ahash_digest(subreq), &wait);
+
+ ahash_request_free(subreq);
+ return ret;
+}
+
static int qce_ahash_final(struct ahash_request *req)
{
struct qce_sha_reqctx *rctx = ahash_request_ctx_dma(req);
@@ -280,6 +310,8 @@ static int qce_ahash_final(struct ahash_request *req)
if (tmpl->hash_zero)
memcpy(req->result, tmpl->hash_zero,
tmpl->alg.ahash.halg.digestsize);
+ else if (IS_SHA_HMAC(rctx->flags))
+ return qce_ahash_hmac_zero(req);
return 0;
}

@@ -317,6 +349,8 @@ static int qce_ahash_digest(struct ahash_request *req)
if (tmpl->hash_zero)
memcpy(req->result, tmpl->hash_zero,
tmpl->alg.ahash.halg.digestsize);
+ else if (IS_SHA_HMAC(rctx->flags))
+ return qce_ahash_hmac_zero(req);
return 0;
}

@@ -340,6 +374,17 @@ static int qce_ahash_hmac_setkey(struct crypto_ahash *tfm, const u8 *key,
blocksize = crypto_tfm_alg_blocksize(crypto_ahash_tfm(tfm));
memset(ctx->authkey, 0, sizeof(ctx->authkey));

+ /*
+ * Keep the software fallback keyed in sync - it is used for empty
+ * messages, which the DMA engine cannot process.
+ */
+ crypto_ahash_clear_flags(ctx->fallback, CRYPTO_TFM_REQ_MASK);
+ crypto_ahash_set_flags(ctx->fallback,
+ crypto_ahash_get_flags(tfm) & CRYPTO_TFM_REQ_MASK);
+ ret = crypto_ahash_setkey(ctx->fallback, key, keylen);
+ if (ret)
+ return ret;
+
if (keylen <= blocksize) {
memcpy(ctx->authkey, key, keylen);
return 0;
@@ -395,6 +440,36 @@ static int qce_ahash_cra_init(struct crypto_tfm *tfm)
return 0;
}

+static int qce_ahash_hmac_cra_init(struct crypto_tfm *tfm)
+{
+ struct qce_sha_ctx *ctx = crypto_tfm_ctx(tfm);
+ struct crypto_ahash *fallback;
+ int ret;
+
+ ret = qce_ahash_cra_init(tfm);
+ if (ret)
+ return ret;
+
+ /*
+ * The fallback is used to compute HMACs of empty messages, which the
+ * DMA engine cannot process.
+ */
+ fallback = crypto_alloc_ahash(crypto_tfm_alg_name(tfm), 0,
+ CRYPTO_ALG_NEED_FALLBACK);
+ if (IS_ERR(fallback))
+ return PTR_ERR(fallback);
+
+ ctx->fallback = fallback;
+ return 0;
+}
+
+static void qce_ahash_hmac_cra_exit(struct crypto_tfm *tfm)
+{
+ struct qce_sha_ctx *ctx = crypto_tfm_ctx(tfm);
+
+ crypto_free_ahash(ctx->fallback);
+}
+
struct qce_ahash_def {
unsigned long flags;
const char *name;
@@ -462,7 +537,14 @@ static int qce_ahash_register_one(const struct qce_ahash_def *def,
base->cra_ctxsize = sizeof(struct qce_sha_ctx);
base->cra_alignmask = 0;
base->cra_module = THIS_MODULE;
- base->cra_init = qce_ahash_cra_init;
+
+ if (IS_SHA_HMAC(def->flags)) {
+ base->cra_flags |= CRYPTO_ALG_NEED_FALLBACK;
+ base->cra_init = qce_ahash_hmac_cra_init;
+ base->cra_exit = qce_ahash_hmac_cra_exit;
+ } else {
+ base->cra_init = qce_ahash_cra_init;
+ }

strscpy(base->cra_name, def->name);
strscpy(base->cra_driver_name, def->drv_name);
diff --git a/drivers/crypto/qce/sha.h b/drivers/crypto/qce/sha.h
index cb822fc334dc187cf1c66e2a332822a596ebcef3..2fa173ff2b2ec4031710ab6e3b14c28b04e0a746 100644
--- a/drivers/crypto/qce/sha.h
+++ b/drivers/crypto/qce/sha.h
@@ -17,6 +17,7 @@

struct qce_sha_ctx {
u8 authkey[QCE_SHA_MAX_BLOCKSIZE];
+ struct crypto_ahash *fallback;
};

/**

--
2.47.3