Repository navigation
Conversation
sbi_ipi_add_device() links the new node into ipi_dev_node_list with plain stores while warm harts already walk the list with plain loads in sbi_ipi_raw_clear(true). A reader can see the node pointer before the node's fields and the device's per-hart data are visible. Publish the node after smp_wmb() and pair it with smp_rmb() after each node load in the all-devices walk. 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.
sbi_ipi_add_device()links a freshly allocated node intoipi_dev_node_listwith plain stores, and warm harts walk that list with plain loads insbi_ipi_raw_clear(true)while the cold hart is still inserting. A reader can see the new node pointer and then read the node's fields, or the device's per-hart data, before the writer's stores to them are visible. A release before the publishing stores and an acquire after the reader's load of each node close it.An execution that triggers it
Setup: eight harts booting; the cold hart runs
sbi_init's cold path; the other seven were released atwake_coldboot_harts()and runinit_warmboot. RVWMO hardware.sbi_ipi_init(true)→fdt_ipi_init→aclint_mswi_cold_init→sbi_ipi_add_device(&aclint_mswi). It callssbi_zallocfor the node (entry), then storesentry->dev = &aclint_mswiand the node'sprevandnextlinks, then storesipi_dev_node_list.prev->next = &entry->head(the publish). No fence between the two groups.init_warmboot→wait_for_coldboot(returns:coldboot_donewas released earlier) →sbi_ipi_raw_clear(true)→sbi_list_for_each_entry(entry, &ipi_dev_node_list, head): loadsipi_dev_node_list.next.&entry->head(store from step 1's last group), but the stores toentry->devandentry->head.nextare not yet visible to hart 3.entry->dev: it reads thesbi_zalloc'd zero, or garbage if the allocation reused memory. It then evaluatesentry->dev->ipi_clear: a load through a NULL or wild pointer. Load through NULL faults in M-mode (no PMP region at 0 onvirt): hart 3 traps through_trap_handlerintosbi_trap_error, prints a register dump and hangs. The system comes up with 7 harts.entry->head.nextstale (zero) instead of&ipi_dev_node_list, so the walk never reaches the list head and dereferences address 0 on the next iteration.dev->ipi_clearcorrectly butmswi_ipi_clearthen reads the hart's ownaclint_mswi_datapointer from its scratch, written by the cold hart inaclint_mswi_cold_initbefore the add. Without an acquire the dependent read of the scratch word is unordered too, and a stale NULL there makesmswi_ipi_clearreturn early without clearing a pending IPI, leaving the hart'smsiplatched: its firstwfiinsbi_hsm_hart_waitreturns immediately and it spins instead of sleeping, and a laterHART_STARTIPI is indistinguishable from the stale one.The walk is designed to tolerate an empty or partially populated list; it is not safe against a node whose contents are not yet visible. The all-devices walk in
sbi_ipi_raw_clearis the only reader that runs concurrently withsbi_ipi_add_device;sbi_ipi_raw_sendwalks the list only after cold boot.Verification
Proved: with the fences, every warm hart's
sbi_ipi_raw_clear(true)satisfies its contract against the Sail RISC-V model under an RVWMO-faithful memory model, in both interleavings (empty list, node present). Without them the reader has no ordering on the node's fields and the contract is unprovable.