* Re: [BUG] crypto: hisilicon/hpre: RSA request never completes during signed module verification on Kunpeng arm64
2026-08-27 7:07 ` liulongfang
@ 2026-08-27 8:40 ` Evanshenf
2026-08-28 9:26 ` liulongfang
0 siblings, 1 reply; 5+ messages in thread
From: Evanshenf @ 2026-08-27 8:40 UTC (permalink / raw)
To: liulongfang, linux-crypto
Cc: Weili Qian, Zhou Wang, Herbert Xu, David S. Miller
[-- Attachment #1: Type: text/plain, Size: 2448 bytes --]
Hello Longfang and all,
Thank you for the suggestion. I tested the alignment hypothesis on the affected system with an
instrumented HPRE module built against the exact 7.0.14-6-pve headers.
The diagnostic patch detects in-place or unaligned RSA requests and routes source and destination
through separate dma_alloc_coherent() buffers. Per-PF counters prove that this path was used.
Results:
0000:3e:00.0: send=250, recv=250, bounce=250
0000:3a:00.0: send=250, recv=250, bounce=250
0000:7e:00.0: send=250, recv=250, bounce=250
0000:7a:00.0: first request hung, send=1, recv=0, bounce=1
For the failing PF:
SEND_SQE_TO_ACC=1, ACC_FINISH=1, CQE_OUT=1, EQE_OUT=1
Linux completion IRQ count=0, HPRE recv_cnt=0
The task remained blocked in rsassa_pkcs1_verify(), at the same point as the original incident.
Thus, in-place and unaligned buffers are present, but separate coherent bounce buffers do not fix
the deterministic completion loss on 0000:7a:00.0. Alignment is not a sufficient root cause. The
new evidence narrows the fault to the completion-notification path after accelerator completion
and before the HPRE response callback.
Could you please help clarify:
1. What are the exact meanings of QM_DFX_ACC_FINISH_CNT, QM_CQE_OUT, and QM_EQE_OUT?
2. With ACC_FINISH=1, CQE_OUT=1, EQE_OUT=1, and Linux IRQ=0, which additional QM/HPRE/GIC ITS
registers would be most useful?
3. Is there a known revision-30 19e5:a258 issue affecting one PF/NUMA node or its MSI path?
4. Is there a safe way to poll/recover the affected CQ/QP and complete the request with an error
while preventing a late-callback use-after-free?
I attached the complete report, experiment patch, and a sanitized evidence archive containing
four-PF counters, QM/PCI/MSI state, raw IRQ counts, and the blocked-task stack. The archive contains
no management addresses, credentials, serial numbers, or unrelated logs.
Attachment SHA-256:
report: 610f08baf93177b0069a406c55279ed73fc1e9cd417ce64c813d8e29fa0e2108
patch: 811332a88a7338cc144b60268d9e9be4bae233ce6a0fdab587bead29ceacec50
data: 42567764d09204758b0f5e01c9b4d93fab4ed0d2a1a15afef64803d8b7c74815
The host is back on the safe baseline with hisi_hpre blacklisted and rsa-generic selected. A
200-cycle signed-module verification test across all four NUMA nodes passed after recovery.
Regards,
Evanshenf
GitHub: https://github.com/Evanshenf
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: HPRE-RSA-completion-loss-validation-report.md --]
[-- Type: text/markdown, Size: 11778 bytes --]
# HPRE RSA Completion-Loss Validation Report
Date: 2026-08-27
Reporter: Evanshenf `<archwse@gmail.com>`
Related report: [Proxmox Bug 7980](https://bugzilla.proxmox.com/show_bug.cgi?id=7980)
## 1. Executive summary
I tested the alignment/in-place hypothesis from the original HPRE RSA completion-loss report by
building an instrumented `hisi_hpre` module against the exact headers of the affected PVE kernel.
The diagnostic module forces risky RSA requests through two independent coherent DMA bounce
buffers and exposes per-PF counters proving which path was taken.
The experiment produced the following result:
- The RSASSA child request was both in-place and unaligned in every tested module-signature
verification.
- The diagnostic path definitely replaced the original source and destination mapping with two
independent `dma_alloc_coherent()` buffers.
- Three HPRE PFs completed 250 signed-module verification cycles each.
- PF `0000:7a:00.0` hung on its first request, exactly as in the original incident.
- On the failing PF, QM reported that the request reached the accelerator and that accelerator
completion, `CQE_OUT`, and `EQE_OUT` had each advanced by one. However, the Linux completion MSI
counter and the HPRE driver `recv_cnt` both remained zero.
Therefore, buffer alignment and in-place operation are real conditions, but a coherent bounce
buffer is not sufficient to fix this failure. The new evidence narrows the fault to the completion
notification path after accelerator completion and before the HPRE response callback. Additional
QM/HPRE/GIC ITS interpretation from the hardware/driver maintainers is needed to distinguish the
exact boundary.
## 2. Test environment
| Item | Value |
| --- | --- |
| Architecture | arm64 |
| CPU | TaiShan-v120 / Kunpeng 920, two sockets, 320 logical CPUs |
| NUMA topology | Four nodes, 80 logical CPUs per node |
| OS | Debian 13 with PVE 9.2.9 |
| Kernel | `7.0.14-6-pve` |
| Kernel package | `proxmox-kernel-7.0.14-6-pve-signed 7.0.14-6` |
| Kernel compiler | GCC 14.2.0 |
| Diagnostic module compiler | GCC 14.3.0 |
| HPRE device | `19e5:a258`, revision `30`, four PFs |
| IOMMU | Translated domain, strict TLB invalidation |
| Test payload | PVE-signed `xfs.ko`; no XFS filesystem was mounted |
PF and NUMA mapping:
```text
0000:3e:00.0 -> NUMA 0 -> CPUs 0-79
0000:3a:00.0 -> NUMA 1 -> CPUs 80-159
0000:7e:00.0 -> NUMA 2 -> CPUs 160-239
0000:7a:00.0 -> NUMA 3 -> CPUs 240-319
```
The PVE package metadata identifies an Ubuntu kernel baseline. The HPRE source used for the
diagnostic build was taken from tag `Ubuntu-7.0.0-28.28i2`, peeled commit
`660d1f05673c4e02cc3c2c056afc0dc14c0075f0`. The installed and rebuilt baseline modules were also
compared at the disassembly level for `hpre_hw_data_init()`, `hpre_rsa_enc()`, and
`hpre_rsa_dec()`; the relevant data paths match.
## 3. Diagnostic change
This is an experiment patch, not a proposed production fix.
The module parameter `rsa_bounce_mode` implements:
```text
0 = retain direct DMA and only observe risky requests
1 = use bounce buffers for in-place or unaligned RSA requests
2 = force bounce buffers for every RSA request
```
Mode 1 was used for this test. When the request is in-place or unaligned, the original direct
`dma_map_single()` path is bypassed for both source and destination. The existing
`hpre_prepare_dma_buf()` path allocates separate coherent source and destination buffers. The
source is copied into its coherent buffer, the device writes to a different coherent destination
buffer, and the destination is copied back only in the normal completion callback.
The following per-PF counters were added:
```text
rsa_bounce_cnt
rsa_inplace_cnt
rsa_unaligned_cnt
rsa_direct_hazard_cnt
```
These counters are important because they prove that the failing request used the intended bounce
path rather than silently falling back to the original direct mapping.
## 4. Method
The diagnostic module was loaded transiently. It did not replace the production module under
`/lib/modules`, and the persistent HPRE blacklist was not removed.
For each NUMA node, the test process was pinned to one representative CPU and repeatedly loaded
and unloaded the signed XFS module. The planned count was 250 cycles per node:
```text
CPU 0 -> PF 0000:3e:00.0
CPU 80 -> PF 0000:3a:00.0
CPU 160 -> PF 0000:7e:00.0
CPU 240 -> PF 0000:7a:00.0
```
On failure, the process stack, HPRE and QM debugfs state, PCI configuration, MSI counts and
affinities, and relevant kernel logs were captured before recovery.
## 5. Results
### 5.1 Driver and bounce counters
| PF | Test CPU | Completed cycles | send | recv | bounce | in-place | unaligned | direct hazard |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| `0000:3e:00.0` | 0 | 250 | 250 | 250 | 250 | 250 | 250 | 0 |
| `0000:3a:00.0` | 80 | 250 | 250 | 250 | 250 | 250 | 250 | 0 |
| `0000:7e:00.0` | 160 | 250 | 250 | 250 | 250 | 250 | 250 | 0 |
| `0000:7a:00.0` | 240 | 0; first request hung | 1 | 0 | 1 | 1 | 1 | 0 |
For the failing PF, `bounce=1` and `direct_hazard=0` prove that the failing request used separate
coherent DMA buffers. The failure is therefore reproducible after removing direct mapping,
in-place DMA, and original-buffer alignment from the device data path.
### 5.2 Completion-path counters
| PF | SEND_SQE_TO_ACC | ACC_FINISH | CQE_OUT | EQE_OUT | Linux completion IRQ | HPRE recv |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| `0000:3e:00.0` | 250 | 250 | 250 | 250 | 250 | 250 |
| `0000:3a:00.0` | 250 | 250 | 250 | 250 | 250 | 250 |
| `0000:7e:00.0` | 250 | 250 | 250 | 250 | 250 | 250 |
| `0000:7a:00.0` | 1 | 1 | 1 | 1 | 0 | 0 |
The failing request was accepted and reached accelerator completion. QM's `CQE_OUT` and
`EQE_OUT` counters also advanced, but Linux did not receive the completion vector and the HPRE
callback was never entered.
For `0000:7a:00.0`, all of the following remained zero:
```text
send_busy_cnt
send_fail_cnt
invalid_req_cnt
over_thrhld_cnt
qm_abnormal_irq
qm_aeq_irq
qm_err_irq
qm_mb_err
qm_create_qp_err
qm_dev_timeout
```
QM continued to report:
```text
status = work
qm_state = 1
dev_state = 0
current_q = 0
```
### 5.3 MSI and PCI state
| PF | Completion IRQ | Initial effective CPU | Observed IRQ count |
| --- | ---: | ---: | ---: |
| `0000:3e:00.0` | 1374 | 31 | 250 |
| `0000:3a:00.0` | 1370 | 149 | 250 |
| `0000:7e:00.0` | 1382 | 186 | 250 |
| `0000:7a:00.0` | 1378 | 265 | 0 |
The failing PF reported MSI enabled with four vectors, no mask bits, and no pending bits:
```text
MSI: Enable+ Count=4/4 Maskable+ 64bit+
Masking: 00000000
Pending: 00000000
```
PCI Status and Device Status did not report correctable, non-fatal, fatal, or unsupported-request
errors. The platform ACPI `_OSC` reports that AER control is not supported by the platform, so the
absence of an AER log is not treated as conclusive evidence that the PCIe layer is fault-free.
After the request had hung, IRQ 1378 affinity was moved from its NUMA-node mask, whose effective
CPU was 265, directly to CPU 240. The IRQ count remained zero and the waiter did not recover. This
shows that there was no pending completion that could be recovered by retargeting, but it does not
alone distinguish failure to generate an MSI from loss in the ITS mapping/delivery path.
### 5.4 Blocked task
The first request on CPU 240 entered uninterruptible sleep:
```text
Name: modprobe
State: D (disk sleep)
Command: modprobe xfs
rsassa_pkcs1_verify+0x284/0x3a0
public_key_verify_signature+0x1e4/0x2b0
public_key_verify_signature_2+0x20/0x48
verify_signature+0x48/0xc0
pkcs7_validate_trust+0x23c/0x300
verify_pkcs7_message_sig+0x10c/0x190
verify_pkcs7_signature+0x78/0xe0
mod_verify_sig+0xac/0x150
module_sig_check+0x104/0x190
load_module+0x48/0x2a68
init_module_from_file+0x110/0x168
__arm64_sys_finit_module+0x220/0x398
```
This is the same blocking point as the original production incident.
## 6. Conclusions
### 6.1 Alignment hypothesis
The experiment confirms that the original RSASSA request is in-place and unaligned. However, the
same failure occurs after the original SG is completely removed from the device DMA path and
replaced by separate coherent source and destination buffers. Alignment or in-place DMA is
therefore not a sufficient root cause for this incident.
This result does not argue against the generic RSASSA cacheline-alignment fix. It only shows that
such a fix does not resolve the deterministic completion loss on PF `0000:7a:00.0` on this system.
### 6.2 Narrowed failure boundary
The observed boundary is:
```text
RSA request
-> hisi_qp_send() success
-> QM SEND_SQE_TO_ACC +1
-> HPRE/QM ACC_FINISH +1
-> QM CQE_OUT / EQE_OUT +1
-> Linux MSI completion IRQ +0 <-- failure interval
-> HPRE response callback / recv_cnt +0
-> akcipher_request_complete() not called
-> synchronous RSASSA waiter blocked indefinitely
```
The exact hardware meaning of `CQE_OUT` and `EQE_OUT` is needed before assigning the bug to the QM
MSI generator, PCIe MSI write, GIC ITS mapping, or the Linux QM interrupt/queue handling path.
### 6.3 Missing recovery behavior
`hpre_is_bd_timeout()` is only called from the response callback. It records elapsed time after a
response has arrived; it is not a request timer and cannot recover a lost completion. If the
callback never occurs, the synchronous crypto waiter can remain blocked indefinitely.
This is a separate recoverability issue. Any timeout/reset design must also prevent a late
completion from using a freed request, DMA buffer, or crypto request object.
## 7. Recovery verification
Because the request was in uninterruptible sleep, a normal reboot stalled during shutdown. After
all evidence had been captured, the host was reset through its management controller.
The safe production baseline remains:
```text
hisi_hpre: not loaded and persistently blacklisted
RSA implementation: pkcs1(rsa-generic,sha512)
```
After recovery, signed XFS module load/unload was repeated 50 times on each of CPUs 0, 80, 160,
and 240, for 200 total software-RSA verifications. All 200 passed in 65 seconds. PVE services and
the hosted workloads were restored successfully.
## 8. Questions for the HPRE/QM maintainers
1. What are the exact hardware semantics of `QM_DFX_ACC_FINISH_CNT`, `QM_CQE_OUT`, and
`QM_EQE_OUT`?
2. With `ACC_FINISH=1`, `CQE_OUT=1`, `EQE_OUT=1`, and Linux completion IRQ count 0, which
additional QM, HPRE, or GIC ITS registers should be captured?
3. Is there a known revision-30 `19e5:a258` issue affecting one PF/NUMA node or its MSI path?
4. Is there a safe diagnostic method to poll the CQ/EQ and distinguish a written completion with
a lost MSI from an incomplete CQ/EQ writeback?
5. Is there an existing or recommended mechanism to time out and reset one QP/PF, complete the
request with an error, and fall back to software while safely rejecting a late callback?
## 9. Artifact hashes
| Artifact | SHA-256 |
| --- | --- |
| Diagnostic module | `08325bc5f5085ba661d592804cf4e55aa8d122e5045e00323166595c81eb7d14` |
| Experiment patch | `811332a88a7338cc144b60268d9e9be4bae233ce6a0fdab587bead29ceacec50` |
| Full internal failure archive | `9a91f6545ca1ea84290cd8b29cc74100682d700f8610a978b566c6943f58b74c` |
| Software-RSA recovery log | `7cf8da71d108b4f7384f2923ed32ce39999255d73e32ea8024389d71db536397` |
The external evidence bundle accompanying this report is sanitized and contains only the patch,
counter summaries, selected QM/PCI/IRQ state, and the blocked-task stack. It does not contain
management addresses, credentials, serial numbers, or unrelated system logs.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: hpre-rsa-bounce-experiment.patch --]
[-- Type: text/x-patch, Size: 5962 bytes --]
diff -ruN /home/arch/pve-hpre-lab/source-ubuntu-7.0.0-28.28i2/hpre/hpre.h /home/arch/pve-hpre-lab/candidate-hpre-bounce/hpre/hpre.h
--- /home/arch/pve-hpre-lab/source-ubuntu-7.0.0-28.28i2/hpre/hpre.h 2026-08-27 07:35:44.898728169 +0000
+++ /home/arch/pve-hpre-lab/candidate-hpre-bounce/hpre/hpre.h 2026-08-27 07:50:54.916306496 +0000
@@ -40,6 +40,10 @@
HPRE_OVER_THRHLD_CNT,
HPRE_OVERTIME_THRHLD,
HPRE_INVALID_REQ_CNT,
+ HPRE_RSA_BOUNCE_CNT,
+ HPRE_RSA_INPLACE_CNT,
+ HPRE_RSA_UNALIGNED_CNT,
+ HPRE_RSA_DIRECT_HAZARD_CNT,
HPRE_DFX_FILE_NUM
};
diff -ruN /home/arch/pve-hpre-lab/source-ubuntu-7.0.0-28.28i2/hpre/hpre_crypto.c /home/arch/pve-hpre-lab/candidate-hpre-bounce/hpre/hpre_crypto.c
--- /home/arch/pve-hpre-lab/source-ubuntu-7.0.0-28.28i2/hpre/hpre_crypto.c 2026-08-27 07:35:37.811771791 +0000
+++ /home/arch/pve-hpre-lab/candidate-hpre-bounce/hpre/hpre_crypto.c 2026-08-27 07:51:07.245128952 +0000
@@ -140,6 +140,11 @@
struct timespec64 req_time;
};
+static unsigned int rsa_bounce_mode = 1;
+module_param(rsa_bounce_mode, uint, 0644);
+MODULE_PARM_DESC(rsa_bounce_mode,
+ "RSA DMA mode: 0=direct/observe, 1=conditional bounce, 2=force bounce");
+
static inline unsigned int hpre_align_sz(void)
{
return ((crypto_dma_align() - 1) | (HPRE_ALIGN_SZ - 1)) + 1;
@@ -211,7 +216,7 @@
static int hpre_hw_data_init(struct hpre_asym_request *hpre_req,
struct scatterlist *data, unsigned int len,
- int is_src, int is_dh)
+ int is_src, int is_dh, bool force_bounce)
{
struct hpre_sqe *msg = &hpre_req->req;
struct hpre_ctx *ctx = hpre_req->ctx;
@@ -219,7 +224,7 @@
int ret;
/* when the data is dh's source, we should format it */
- if ((sg_is_last(data) && len == ctx->key_sz) &&
+ if (!force_bounce && (sg_is_last(data) && len == ctx->key_sz) &&
((is_dh && !is_src) || !is_dh))
ret = hpre_get_data_dma_addr(hpre_req, data, len, is_src, &tmp);
else
@@ -487,14 +492,16 @@
return ret;
if (req->src) {
- ret = hpre_hw_data_init(hpre_req, req->src, req->src_len, 1, 1);
+ ret = hpre_hw_data_init(hpre_req, req->src, req->src_len,
+ 1, 1, false);
if (unlikely(ret))
goto clear_all;
} else {
msg->in = cpu_to_le64(ctx->dh.dma_g);
}
- ret = hpre_hw_data_init(hpre_req, req->dst, req->dst_len, 0, 1);
+ ret = hpre_hw_data_init(hpre_req, req->dst, req->dst_len, 0, 1,
+ false);
if (unlikely(ret))
goto clear_all;
@@ -746,6 +753,51 @@
}
}
+static bool hpre_sg_dma_unaligned(struct scatterlist *sg)
+{
+ if (!sg || !sg_is_last(sg))
+ return false;
+
+ return !IS_ALIGNED((unsigned long)sg_virt(sg), hpre_align_sz());
+}
+
+static bool hpre_rsa_needs_bounce(struct hpre_ctx *ctx,
+ struct akcipher_request *req)
+{
+ struct hpre_dfx *dfx = ctx->hpre->debug.dfx;
+ unsigned int mode = READ_ONCE(rsa_bounce_mode);
+ bool inplace = false;
+ bool unaligned;
+ bool hazard;
+
+ if (mode > 2)
+ mode = 1;
+
+ if (req->src && req->dst) {
+ inplace = req->src == req->dst;
+ if (!inplace && sg_is_last(req->src) && sg_is_last(req->dst))
+ inplace = sg_virt(req->src) == sg_virt(req->dst);
+ }
+
+ unaligned = hpre_sg_dma_unaligned(req->src) ||
+ hpre_sg_dma_unaligned(req->dst);
+
+ if (inplace)
+ atomic64_inc(&dfx[HPRE_RSA_INPLACE_CNT].value);
+ if (unaligned)
+ atomic64_inc(&dfx[HPRE_RSA_UNALIGNED_CNT].value);
+
+ hazard = inplace || unaligned;
+ if (mode == 0 && hazard)
+ atomic64_inc(&dfx[HPRE_RSA_DIRECT_HAZARD_CNT].value);
+ if (mode == 2 || (mode == 1 && hazard)) {
+ atomic64_inc(&dfx[HPRE_RSA_BOUNCE_CNT].value);
+ return true;
+ }
+
+ return false;
+}
+
static int hpre_rsa_enc(struct akcipher_request *req)
{
struct crypto_akcipher *tfm = crypto_akcipher_reqtfm(req);
@@ -753,6 +805,7 @@
void *tmp = akcipher_request_ctx(req);
struct hpre_asym_request *hpre_req = PTR_ALIGN(tmp, hpre_align_sz());
struct hpre_sqe *msg = &hpre_req->req;
+ bool force_bounce;
int ret;
/* For unsupported key size and unavailable devices, use soft tfm instead */
@@ -772,12 +825,15 @@
msg->dw0 |= cpu_to_le32(HPRE_ALG_NC_NCRT);
msg->key = cpu_to_le64(ctx->rsa.dma_pubkey);
+ force_bounce = hpre_rsa_needs_bounce(ctx, req);
- ret = hpre_hw_data_init(hpre_req, req->src, req->src_len, 1, 0);
+ ret = hpre_hw_data_init(hpre_req, req->src, req->src_len, 1, 0,
+ force_bounce);
if (unlikely(ret))
goto clear_all;
- ret = hpre_hw_data_init(hpre_req, req->dst, req->dst_len, 0, 0);
+ ret = hpre_hw_data_init(hpre_req, req->dst, req->dst_len, 0, 0,
+ force_bounce);
if (unlikely(ret))
goto clear_all;
@@ -799,6 +855,7 @@
void *tmp = akcipher_request_ctx(req);
struct hpre_asym_request *hpre_req = PTR_ALIGN(tmp, hpre_align_sz());
struct hpre_sqe *msg = &hpre_req->req;
+ bool force_bounce;
int ret;
/* For unsupported key size and unavailable devices, use soft tfm instead */
@@ -826,11 +883,14 @@
HPRE_ALG_NC_NCRT);
}
- ret = hpre_hw_data_init(hpre_req, req->src, req->src_len, 1, 0);
+ force_bounce = hpre_rsa_needs_bounce(ctx, req);
+ ret = hpre_hw_data_init(hpre_req, req->src, req->src_len, 1, 0,
+ force_bounce);
if (unlikely(ret))
goto clear_all;
- ret = hpre_hw_data_init(hpre_req, req->dst, req->dst_len, 0, 0);
+ ret = hpre_hw_data_init(hpre_req, req->dst, req->dst_len, 0, 0,
+ force_bounce);
if (unlikely(ret))
goto clear_all;
diff -ruN /home/arch/pve-hpre-lab/source-ubuntu-7.0.0-28.28i2/hpre/hpre_main.c /home/arch/pve-hpre-lab/candidate-hpre-bounce/hpre/hpre_main.c
--- /home/arch/pve-hpre-lab/source-ubuntu-7.0.0-28.28i2/hpre/hpre_main.c 2026-08-27 07:35:41.081751785 +0000
+++ /home/arch/pve-hpre-lab/candidate-hpre-bounce/hpre/hpre_main.c 2026-08-27 07:50:54.920306438 +0000
@@ -350,7 +350,11 @@
"send_busy_cnt",
"over_thrhld_cnt",
"overtime_thrhld",
- "invalid_req_cnt"
+ "invalid_req_cnt",
+ "rsa_bounce_cnt",
+ "rsa_inplace_cnt",
+ "rsa_unaligned_cnt",
+ "rsa_direct_hazard_cnt"
};
/* define the HPRE's dfx regs region and region length */
[-- Attachment #4: hpre-rsa-completion-loss-evidence-20260827.tar.gz --]
[-- Type: application/gzip, Size: 33603 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread