Repository navigation
Conversation
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@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This bug was found in the process of formally verifying the correctness of OpenSBI on top of the Sail RISC-V semantics using Lean.
Non-boot harts leave
_wait_for_boot_harton a plain load of_boot_statusand 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 unrelocatedplatform.hart_index2idpointer, and fault through it beforemtvecis set. Onefence r, rwafter the loop closes it.An execution that triggers it
Setup:
fw_dynamicloaded 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 model used to find this)._wait_for_boot_hartwhile hart 0 is still relocating. Hart 1's loop body isld t1, _boot_status; div; div; div; bne. Its load of_boot_statusreturns the pre-done value, it loops.lwu platform.hart_count,ld platform.hart_index2id, may be satisfied now, from memory that hart 0 has not yet rewritten:platform.hart_index2idstill holds its link-time value0x449f8.platform.hart_index2idto0x800449f8via theR_RISCV_RELATIVEpass, executesfence rw, rw, stores_boot_status = BOOT_STATUS_BOOT_HART_DONE.bnefalls through. The already-performed load ofplatform.hart_index2idis not re-executed:s9 = 0x449f8.lwu a5, 0(s9): a load from0x449f8, which is outside every PMA region on thevirtplatform. The access faults._start_warm'scsrw mtvec;mtvecholds its reset value. The trap vectors to an undefined address and the hart is lost. The system boots with 7 harts, or hangs insmp_initwaiting for it.The release on the boot hart (
fence rw, rwbefore 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 afence rw, rwinside the loop before the load (05602e2) and was reverted (69d794c) 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
_startto_start_warmsatisfies 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.