[PATCH] crypto: caam - Use bounce buffer for unaligned RSA destination buffers
Changwei Zou <[email protected]> Wed, 5 Aug 2026 14:19:02 +1000
| Newsgroups | org.kernel.vger.linux-crypto,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
The CAAM RSA driver directly DMA-maps the destination buffer supplied by
the caller via req->dst without checking whether it meets the cacheline
alignment requirements of DMA-incoherent hardware such as i.MX8.
On CPUs with non-coherent DMA caches, if the destination buffer shares a
cacheline with other data (i.e. it is not cacheline-aligned), cache
writeback/invalidation during DMA can corrupt adjacent memory or cause
stale data to be read back. This manifests as intermittent -EKEYREJECTED
errors when loading signed kernel modules.
When any segment of req->dst is not cacheline-aligned, allocate a
single contiguous aligned bounce buffer covering the full dst_len,
redirect the operation to it, and scatter-copy the result back to the
original destination once the hardware has completed the operation.
The intermittent error 'Key was rejected by service' on i.MX8 with CAAM
can be triggered when loading signed kernel modules:
for i in $(seq 1 100); do
sudo modprobe xfs 2>&1 && echo "SUCCESS on attempt $i" \
&& sudo rmmod xfs || echo "FAILED on attempt $i"
done
Signed-off-by: Changwei Zou <[email protected]>
Assisted-by: OpenCode:claude-sonnet-4.6
---
drivers/crypto/caam/caampkc.c | 66 ++++++++++++++++++++++++++++++++++-
drivers/crypto/caam/caampkc.h | 6 ++++
2 files changed, 71 insertions(+), 1 deletion(-)
diff --git a/drivers/crypto/caam/caampkc.c b/drivers/crypto/caam/caampkc.c
index cb001aa1de66..70300182f2ad 100644
--- a/drivers/crypto/caam/caampkc.c
+++ b/drivers/crypto/caam/caampkc.c
@@ -59,6 +59,37 @@ static void rsa_io_unmap(struct device *dev, struct rsa_edesc *edesc,
DMA_TO_DEVICE);
}
+static int do_rsa_bounce_buf(struct akcipher_request *req)
+{
+ struct caam_rsa_req_ctx *req_ctx = akcipher_request_ctx(req);
+ int nents, err = 0;
+
+ if (!req_ctx->bounce_buf)
+ return 0;
+
+ /* Copy from aligned bounce buffer back to the original destination */
+ nents = sg_nents_for_len(req_ctx->orig_dst, req->dst_len);
+ if (nents < 0)
+ err = nents;
+ else if (sg_copy_from_buffer(req_ctx->orig_dst, nents,
+ req_ctx->bounce_buf, req->dst_len) != req->dst_len)
+ err = -EFAULT;
+
+ kfree(req_ctx->bounce_buf);
+ req_ctx->bounce_buf = NULL;
+ req->dst = req_ctx->orig_dst;
+
+ return err;
+}
+
+static inline void rsa_bounce_buf_done(struct akcipher_request *req, int *err)
+{
+ int cperr = do_rsa_bounce_buf(req);
+
+ if (!*err)
+ *err = cperr;
+}
+
static void rsa_pub_unmap(struct device *dev, struct rsa_edesc *edesc,
struct akcipher_request *req)
{
@@ -138,6 +169,7 @@ static void rsa_pub_done(struct device *dev, u32 *desc, u32 err, void *context)
rsa_pub_unmap(dev, edesc, req);
rsa_io_unmap(dev, edesc, req);
kfree(edesc);
+ rsa_bounce_buf_done(req, &ecode);
/*
* If no backlog flag, the completion of the request is done
@@ -181,6 +213,7 @@ static void rsa_priv_f_done(struct device *dev, u32 *desc, u32 err,
rsa_io_unmap(dev, edesc, req);
kfree(edesc);
+ rsa_bounce_buf_done(req, &ecode);
/*
* If no backlog flag, the completion of the request is done
@@ -291,11 +324,32 @@ static struct rsa_edesc *rsa_edesc_alloc(struct akcipher_request *req,
req_ctx->fixup_src_len);
dst_nents = sg_nents_for_len(req->dst, req->dst_len);
+ req_ctx->bounce_buf = NULL;
+ req_ctx->orig_dst = req->dst;
+ if (req->dst_len > 0) {
+ struct scatterlist *sg;
+ int i;
+
+ for_each_sg(req->dst, sg, dst_nents, i) {
+ if (!IS_ALIGNED((unsigned long)sg_virt(sg),
+ dma_get_cache_alignment())) {
+ req_ctx->bounce_buf = kmalloc(req->dst_len, flags);
+ if (!req_ctx->bounce_buf)
+ return ERR_PTR(-ENOMEM);
+ sg_init_one(&req_ctx->dst, req_ctx->bounce_buf,
+ req->dst_len);
+ req->dst = &req_ctx->dst;
+ dst_nents = 1;
+ break;
+ }
+ }
+ }
+
mapped_src_nents = dma_map_sg(dev, req_ctx->fixup_src, src_nents,
DMA_TO_DEVICE);
if (unlikely(!mapped_src_nents)) {
dev_err(dev, "unable to map source\n");
- return ERR_PTR(-ENOMEM);
+ goto bounce_fail;
}
mapped_dst_nents = dma_map_sg(dev, req->dst, dst_nents,
DMA_FROM_DEVICE);
@@ -368,6 +422,10 @@ static struct rsa_edesc *rsa_edesc_alloc(struct akcipher_request *req,
dma_unmap_sg(dev, req->dst, dst_nents, DMA_FROM_DEVICE);
src_fail:
dma_unmap_sg(dev, req_ctx->fixup_src, src_nents, DMA_TO_DEVICE);
+bounce_fail:
+ kfree(req_ctx->bounce_buf);
+ req_ctx->bounce_buf = NULL;
+ req->dst = req_ctx->orig_dst;
return ERR_PTR(-ENOMEM);
}
@@ -394,6 +452,7 @@ static int akcipher_do_one_req(struct crypto_engine *engine, void *areq)
rsa_pub_unmap(jrdev, req_ctx->edesc, req);
rsa_io_unmap(jrdev, req_ctx->edesc, req);
kfree(req_ctx->edesc);
+ rsa_bounce_buf_done(req, &ret);
} else {
ret = 0;
}
@@ -706,6 +765,7 @@ static int akcipher_enqueue_req(struct device *jrdev,
}
rsa_io_unmap(jrdev, edesc, req);
kfree(edesc);
+ rsa_bounce_buf_done(req, &ret);
}
return ret;
@@ -747,6 +807,7 @@ static int caam_rsa_enc(struct akcipher_request *req)
init_fail:
rsa_io_unmap(jrdev, edesc, req);
kfree(edesc);
+ rsa_bounce_buf_done(req, &ret);
return ret;
}
@@ -776,6 +837,7 @@ static int caam_rsa_dec_priv_f1(struct akcipher_request *req)
init_fail:
rsa_io_unmap(jrdev, edesc, req);
kfree(edesc);
+ rsa_bounce_buf_done(req, &ret);
return ret;
}
@@ -805,6 +867,7 @@ static int caam_rsa_dec_priv_f2(struct akcipher_request *req)
init_fail:
rsa_io_unmap(jrdev, edesc, req);
kfree(edesc);
+ rsa_bounce_buf_done(req, &ret);
return ret;
}
@@ -834,6 +897,7 @@ static int caam_rsa_dec_priv_f3(struct akcipher_request *req)
init_fail:
rsa_io_unmap(jrdev, edesc, req);
kfree(edesc);
+ rsa_bounce_buf_done(req, &ret);
return ret;
}
diff --git a/drivers/crypto/caam/caampkc.h b/drivers/crypto/caam/caampkc.h
index 96d03704c9be..efad91d6058f 100644
--- a/drivers/crypto/caam/caampkc.h
+++ b/drivers/crypto/caam/caampkc.h
@@ -103,6 +103,9 @@ struct caam_rsa_ctx {
* @src : input scatterlist (stripped of leading zeros)
* @fixup_src : input scatterlist (that might be stripped of leading zeros)
* @fixup_src_len : length of the fixup_src input scatterlist
+ * @dst : destination scatterlist backed by bounce buffer (if needed)
+ * @bounce_buf : DMA-aligned bounce buffer for destination (or NULL)
+ * @orig_dst : original destination scatterlist (before bounce substitution)
* @edesc : s/w-extended rsa descriptor
* @akcipher_op_done : callback used when operation is done
*/
@@ -110,6 +113,9 @@ struct caam_rsa_req_ctx {
struct scatterlist src[2];
struct scatterlist *fixup_src;
unsigned int fixup_src_len;
+ struct scatterlist dst;
+ u8 *bounce_buf;
+ struct scatterlist *orig_dst;
struct rsa_edesc *edesc;
void (*akcipher_op_done)(struct device *jrdev, u32 *desc, u32 err,
void *context);
--
2.43.0