Summary
A scalar unaligned load that crosses a page boundary can fault on its second split while still asserting the LSU DA forwarding-valid signal. OpenC906 suppresses architectural writeback for the same instruction, but advertises its merged result as a producer to younger instructions.
I reproduced this on current OpenC906 main, b0c06eb1f8b3bae663bd8b87eac89ff48e68a57f, using the real smart-run testbench with unmodified OpenC906 RTL.
Reproducer
The complete self-checking assembly (including page-table setup, DCache/uTLB prewarming, U-mode entry, and trap handler) and the VCD monitor are here:
https://gist.github.com/YzhDDDing/4b6c94cb495a72154b70d3fc86ffb1f1
The minimal trigger is:
# setup performed by M/S mode:
# VA 0x41000: U-readable
# VA 0x42000: S-readable, U=0
# protected word at 0x42000 is resident in the uTLB and DCache
# enter U-mode
li x20, 0x41ffc
li x23, 0x5566778811223344
li x5, 0x13579bdf2468ace0
ld x5, 0(x20) # low word from U page; tail from S-only page
nop # align the younger consumer with DA bypass
beq x5, x23, target # consumes the transient forwarded operand
# the precise page-fault handler skips the load and branch
Build lsu_unalign_page_fault.S with smart_run/tests/lib/Makefile, using CPU_ARCH_FLAG_0=c906fd, and run with VCD dumping. The default source configuration selects the conditional-branch consumer.
Expected behavior
An excepting load has no architectural result. Once the second split is known to fault, it must not be advertised as a valid bypass producer, and a younger consumer must not receive its merged data.
At minimum, this invariant should hold:
da_expt_vld -> !da_xx_fwd_vld
Observed behavior
The base replay maps VPN 0x41 and leaves VPN 0x42 inaccessible to U mode. The first split returns the low word; the second split reaches 0x42000 and raises a page fault. At DA, the same dynamic load has:
da_inst_vld = 1
da_expt_vld = 1
da_xx_fwd_vld = 1
da_xx_fwd_dst_reg = x5
lsu_rtu_wb_vld = 0
The original replay counted seven such bad-forward samples.
I also ran a two-execution A/B test with identical instructions, page tables, addresses, cache state, and branch operand. Only the protected S-only word changed:
| Observation |
Run A |
Run B |
| protected tail |
0x55667788 |
0xefbeadde |
| merged x5 value |
0x5566778811223344 |
0xefbeadde11223344 |
| page fault / DCache hit |
1 / 1 |
1 / 1 |
| DA exception / forward / WB |
1 / 1 / 0 |
1 / 1 / 0 |
| younger BJU raw equality predicate |
1 |
0 |
| architectural redirect |
0 |
0 |
In both runs, the fault/request occurs at time 17229, the protected DCache tail reaches merge at 17505, and DA forwarding plus BJU consumption occurs at 17507. The destination register, value, and ordering bind the acquisition and forwarding events to the same dynamic load.
The architectural self-check reports TEST PASS: precise exception recovery prevents writeback, the branch's final-valid gate prevents redirect, and no dependent store becomes architecturally valid.
RTL analysis
The trigger is enabled by scalar unaligned split handling in aq_lsu_ag.v:1103-1109. The last-split load exception is propagated by ag_dc_expt_vld. DC can nevertheless move second-split data into DA at aq_lsu_dc.v:2217-2222, where da_expt_vld and da_data are registered.
The direct validity conflict is visible in adjacent assignments:
aq_lsu_dc.v:2412-2419
assign da_xx_fwd_vld = da_inst_vld & !da_vfls;
assign lsu_rtu_wb_vld = da_inst_vld & !da_vfls & !da_expt_vld;
Thus the exception bit invalidates retirement but not the DA bypass producer.
Conceptually, the full trigger is a two-stage conflict sequence:
- Access classification: the permission path rejects the protected tail, while the hot data path still returns and merges it.
- Exception forwarding: the retirement path rejects the excepting result, while the bypass path marks it valid and a younger BJU operation consumes it.
Security-impact boundary
The stock RTL demonstrates cross-privilege transient acquisition and internal branch-predicate encoding. In additional pre-silicon experiments, a model-level whole-core switching receiver recovered two calibration-excluded 32-bit test words with 64/64 correct adaptive predicate decisions.
No stock U-mode cache, PMU, cycle-count, or digital-pin oracle was found, and no silicon/FPGA power or EM receiver was tested. This report therefore does not claim measured external exfiltration, architectural corruption, or privilege escalation.
Suggested fix direction
The minimal producer-validity fix is:
assign da_xx_fwd_vld = da_inst_vld & !da_vfls & !da_expt_vld;
This removes the demonstrated younger-branch predicate path. If the threat model includes physical leakage, masking only the DA valid bit is incomplete: the protected tail has already entered the DCache/merge/DA data path. A stronger fix should block a permission-denied tail request or sanitize its data before it enters merge and clocked DA state.
Please also consider an assertion that da_expt_vld && da_inst_vld excludes every scalar forwarding-valid output.
Summary
A scalar unaligned load that crosses a page boundary can fault on its second split while still asserting the LSU DA forwarding-valid signal. OpenC906 suppresses architectural writeback for the same instruction, but advertises its merged result as a producer to younger instructions.
I reproduced this on current OpenC906
main,b0c06eb1f8b3bae663bd8b87eac89ff48e68a57f, using the real smart-run testbench with unmodified OpenC906 RTL.Reproducer
The complete self-checking assembly (including page-table setup, DCache/uTLB prewarming, U-mode entry, and trap handler) and the VCD monitor are here:
https://gist.github.com/YzhDDDing/4b6c94cb495a72154b70d3fc86ffb1f1
The minimal trigger is:
Build
lsu_unalign_page_fault.Swithsmart_run/tests/lib/Makefile, usingCPU_ARCH_FLAG_0=c906fd, and run with VCD dumping. The default source configuration selects the conditional-branch consumer.Expected behavior
An excepting load has no architectural result. Once the second split is known to fault, it must not be advertised as a valid bypass producer, and a younger consumer must not receive its merged data.
At minimum, this invariant should hold:
Observed behavior
The base replay maps VPN
0x41and leaves VPN0x42inaccessible to U mode. The first split returns the low word; the second split reaches0x42000and raises a page fault. At DA, the same dynamic load has:The original replay counted seven such bad-forward samples.
I also ran a two-execution A/B test with identical instructions, page tables, addresses, cache state, and branch operand. Only the protected S-only word changed:
0x556677880xefbeadde0x55667788112233440xefbeadde11223344In both runs, the fault/request occurs at time 17229, the protected DCache tail reaches merge at 17505, and DA forwarding plus BJU consumption occurs at 17507. The destination register, value, and ordering bind the acquisition and forwarding events to the same dynamic load.
The architectural self-check reports
TEST PASS: precise exception recovery prevents writeback, the branch's final-valid gate prevents redirect, and no dependent store becomes architecturally valid.RTL analysis
The trigger is enabled by scalar unaligned split handling in
aq_lsu_ag.v:1103-1109. The last-split load exception is propagated byag_dc_expt_vld. DC can nevertheless move second-split data into DA ataq_lsu_dc.v:2217-2222, whereda_expt_vldandda_dataare registered.The direct validity conflict is visible in adjacent assignments:
aq_lsu_dc.v:2412-2419Thus the exception bit invalidates retirement but not the DA bypass producer.
Conceptually, the full trigger is a two-stage conflict sequence:
Security-impact boundary
The stock RTL demonstrates cross-privilege transient acquisition and internal branch-predicate encoding. In additional pre-silicon experiments, a model-level whole-core switching receiver recovered two calibration-excluded 32-bit test words with 64/64 correct adaptive predicate decisions.
No stock U-mode cache, PMU, cycle-count, or digital-pin oracle was found, and no silicon/FPGA power or EM receiver was tested. This report therefore does not claim measured external exfiltration, architectural corruption, or privilege escalation.
Suggested fix direction
The minimal producer-validity fix is:
This removes the demonstrated younger-branch predicate path. If the threat model includes physical leakage, masking only the DA valid bit is incomplete: the protected tail has already entered the DCache/merge/DA data path. A stronger fix should block a permission-denied tail request or sanitize its data before it enters merge and clocked DA state.
Please also consider an assertion that
da_expt_vld && da_inst_vldexcludes every scalar forwarding-valid output.