[PATCH v2 2/8] crypto: qce - Fix HMAC self-test failures for empty messages
From: Bartosz Golaszewski
Date: Mon Jun 15 2026 - 11:56:35 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 dc962296139da334c00237e44290356023cd7420..00e1a8f6d4ec905cfb035db958a71566b1abb0a7 100644
--- a/drivers/crypto/qce/sha.c
+++ b/drivers/crypto/qce/sha.c
@@ -274,6 +274,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);
@@ -284,6 +314,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;
}
@@ -321,6 +353,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;
}
@@ -344,6 +378,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;
@@ -401,6 +446,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;
@@ -479,7 +554,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 a22695361f1654cc94325ec5d886a158fa4bfb9c..5ba6b786f450cbae52988cb39cd68d5795fd19db 100644
--- a/drivers/crypto/qce/sha.h
+++ b/drivers/crypto/qce/sha.h
@@ -18,6 +18,7 @@
struct qce_sha_ctx {
u8 authkey[QCE_SHA_MAX_BLOCKSIZE];
+ struct crypto_ahash *fallback;
};
/**
--
2.47.3