[PATCH 2/2] crypto: caam - Use bounce buffer for unaligned RSA destination buffers
Changwei Zou <[email protected]>
| Newsgroups | org.kernel.vger.linux-crypto,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
The rsa-caam 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 in either its
start offset or length, allocate a single contiguous aligned bounce
buffer covering the full dst_len rounded up to a cacheline multiple,
redirect the operation to it, and copy the result back to the original
destination once the hardware has completed successfully.
Introduce a helper sg_is_dma_aligned() that checks both sg->offset and
sg->length for cacheline alignment. Checking sg->offset suffices for
the start address since physical pages are always page-aligned and
PAGE_SIZE is a multiple of dma_get_cache_alignment(). sg->length is
also checked to ensure the buffer end does not share a cacheline with
adjacent memory.
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 | 77 ++++++++++++++++++++++++++++++++++-
drivers/crypto/caam/caampkc.h | 6 +++
2 files changed, 82 insertions(+), 1 deletion(-)
diff --git a/drivers/crypto/caam/caampkc.c b/drivers/crypto/caam/caampkc.c
index 840271840cce..11c6b07f5dad 100644
--- a/drivers/crypto/caam/caampkc.c
+++ b/drivers/crypto/caam/caampkc.c
@@ -59,6 +59,40 @@ 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, int req_err)
+{
+ struct caam_rsa_req_ctx *req_ctx = akcipher_request_ctx(req);
+ int nents, err = 0;
+
+ if (!req_ctx->bounce_buf)
+ return 0;
+
+ /* Only copy back to the original destination on success */
+ if (!req_err) {
+ 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, *err);
+
+ if (!*err)
+ *err = cperr;
+}
+
static void rsa_pub_unmap(struct device *dev, struct rsa_edesc *edesc,
struct akcipher_request *req)
{
@@ -138,6 +172,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 +216,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
@@ -246,6 +282,12 @@ static int caam_rsa_count_leading_zeros(struct scatterlist *sgl,
return tbytes - nbytes;
}
+static inline bool sg_is_dma_aligned(struct scatterlist *sg)
+{
+ return IS_ALIGNED(sg->offset, dma_get_cache_alignment()) &&
+ IS_ALIGNED(sg->length, dma_get_cache_alignment());
+}
+
static struct rsa_edesc *rsa_edesc_alloc(struct akcipher_request *req,
size_t desclen)
{
@@ -291,11 +333,34 @@ 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 (!sg_is_dma_aligned(sg)) {
+ req_ctx->bounce_buf =
+ kzalloc(ALIGN(req->dst_len,
+ dma_get_cache_alignment()),
+ 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 +433,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);
}
@@ -409,6 +478,7 @@ static int akcipher_do_one_req(struct crypto_engine *engine, void *areq)
}
rsa_io_unmap(jrdev, req_ctx->edesc, req);
kfree(req_ctx->edesc);
+ rsa_bounce_buf_done(req, &ret);
} else {
ret = 0;
}
@@ -723,6 +793,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;
@@ -764,6 +835,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;
}
@@ -793,6 +865,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;
}
@@ -822,6 +895,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;
}
@@ -851,6 +925,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