Skip to content

[LSU] Faulting cross-page unaligned loads remain valid on the DA forwarding path #25

Description

@YzhDDDing

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:

  1. Access classification: the permission path rejects the protected tail, while the hot data path still returns and merges it.
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions