[PATCH] firmware: fw_base: add an acquire fence after the _boot_status wait loop
Anup Patel
anup at brainfault.org
Tue Nov 3 19:31:42 PST 2026
On Wed, Oct 7, 2026 at 1:06 AM Nickolai Zeldovich
<nickolai at csail.mit.edu> wrote:
>
> From: Nickolai Zeldovich <nickolai.zeldovich at gmail.com>
>
> Non-boot harts exit _wait_for_boot_hart on a plain load of _boot_status
> and go on to read platform.hart_index2id and other data the boot hart
> relocated before publishing. Nothing orders those loads after the
> status load, so under RVWMO a hart can read the unrelocated pointer and
> fault through it before mtvec is set.
>
> Pair the boot hart's release fence with a "fence r, rw" after the loop.
>
> Signed-off-by: Nickolai Zeldovich <nickolai.zeldovich at gmail.com>
> Co-Authored-By: Claude Fable 5.1 <noreply at anthropic.com>
Use Assisted-by instead of Co-Authored-By for LLMs.
> ---
> This bug was found in the process of formally verifying the correctness
> of OpenSBI on top of the Sail RISC-V semantics using Lean.
>
> Summary:
>
> Non-boot harts leave _wait_for_boot_hart on a plain load of _boot_status
> and immediately read data the boot hart produced before publishing.
> Nothing on the waiting side orders that load before the later loads.
> Under RVWMO a waiter can observe the "done" status and still read pre-
> relocation values, including the unrelocated platform.hart_index2id
> pointer, and fault through it before mtvec is set. One fence r, rw after
> the loop closes it.
>
> An execution that triggers it:
>
> Setup: fw_dynamic loaded at an address different from its link address,
> so the boot hart runs the self-relocation pass; eight harts; RVWMO
> hardware (or any model that lets a plain load be satisfied early, e.g.
> the Sail/MachCSL model used to find this).
>
> 1. Hart 1 reaches _wait_for_boot_hart while hart 0 is still relocating.
> Hart 1's loop body is ld t1, _boot_status; div; div; div; bne. Its
> load of _boot_status returns the pre-done value, it loops.
> 2. Hart 1's core speculatively issues the *next* iteration's loads
> early. RVWMO allows a later load to be performed before an earlier
> load to a different address when nothing orders them. In particular
> the loads that follow the loop exit, lwu platform.hart_count, ld
> platform.hart_index2id, may be satisfied now, from memory that hart 0
> has not yet rewritten: platform.hart_index2id still holds its link-
> time value 0x449f8.
> 3. Hart 0 finishes: it fixes up platform.hart_index2id to 0x800449f8 via
> the R_RISCV_RELATIVE pass, executes fence rw, rw, stores _boot_status
> = BOOT_STATUS_BOOT_HART_DONE.
> 4. Hart 1's loop load now returns DONE, the bne falls through. The
> already-performed load of platform.hart_index2id is not re-executed:
> s9 = 0x449f8.
> 5. Hart 1 executes lwu a5, 0(s9): a load from 0x449f8, which is outside
> every PMA region on the virt platform. The access faults.
> 6. Hart 1 has not yet executed _start_warm's csrw mtvec; mtvec holds its
> reset value. The trap vectors to an undefined address and the hart is
> lost. The system boots with 7 harts, or hangs in smp_init waiting for
> it.
>
> The release on the boot hart (fence rw, rw before the status store) is
> correct; the bug is the missing acquire on the waiting side. The fence
> added here sits *after* the loop and runs once per hart. A 2019 attempt
> placed a fence rw, rw inside the loop before the load (05602e2bf481) and
> was reverted (69d794cbcbab) after a warm-reset regression on the HiFive
> Unleashed whose root cause was never found; that fence did not provide
> the needed ordering anyway.
>
> Verification:
>
> Proved: with the fence, the non-boot harts' path from _start to
> _start_warm satisfies its contract against the Sail RISC-V model under
> an RVWMO-faithful memory model. Without the fence the stale read is an
> allowed execution and the contract is unprovable.
>
> firmware/fw_base.S | 11 +++++++++++ 1 file changed, 11 insertions(+)
>
> FYI, the same change is also filed as GitHub pull request #429:
> https://github.com/riscv-software-src/opensbi/pull/429
Missing "---" over here.
>
> diff --git a/firmware/fw_base.S b/firmware/fw_base.S
> index a76ddc1b..294787a2 100644
> --- a/firmware/fw_base.S
> +++ b/firmware/fw_base.S
> @@ -407,6 +407,17 @@ _wait_for_boot_hart:
> div t2, t2, zero
> div t2, t2, zero
> bne t0, t1, _wait_for_boot_hart
> + /*
> + * Acquire: order the _boot_status load above before every later
> + * load (platform.hart_count, platform.hart_index2id, the scratch
> + * space, ...). The boot hart publishes with "fence rw, rw" before
> + * its store to _boot_status, but without a matching acquire here
> + * RVWMO lets a waiting hart that saw BOOT_STATUS_BOOT_HART_DONE
> + * still read pre-relocation values, e.g. the unrelocated
> + * platform.hart_index2id pointer, and fault through it before
> + * mtvec is set.
> + */
> + fence r, rw
>
> _start_warm:
> /* Reset all registers except ra, a0, a1, a2, a3 and a4 for non-boot HART */
> --
> 2.55.0
>
>
> --
> opensbi mailing list
> opensbi at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/opensbi
I have taken care of the above comments at the time of merging.
Reviewed-by: Anup Patel <anup at brainfault.org>
Applied this patch to the riscv/opensbi repo.
Thanks,
Anup
More information about the opensbi
mailing list