Repository navigation
Conversation
root_add_memregion() counts the regions once, but sanitize_domain() removes covered regions on every iteration, so the merge step's memmove uses a stale count and runs past the terminating entry. It stays in bounds today only because the array has ROOT_REGION_MAX + 1 slots. Recompute the count after each sanitize_domain(). Fixes: 0d56293 ("lib: sbi: Fix sbi_domain_root_add_memregion() for merging memregions") 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.
root_add_memregion()computesroot_memregs_countonce, then loopssanitize_domain()+ merge.sanitize_domain()can remove regions (any region covered by a superset region is dropped and the array shifted down), but the local count is never refreshed, so the merge step'ssbi_memmove(nreg1, nreg1 + 1, sizeof(*nreg1) * (root_memregs_count - i1))moves more entries than exist and runs past the array's terminating entry. Today this stays inside the allocation only because the array is allocated withROOT_REGION_MAX + 1slots and the stale count is bounded byROOT_REGION_MAX. Recompute the count after eachsanitize_domain().An execution that triggers it
struct sbi_domain_memregionis 24 bytes (order,base,flags); the root array has 33 slots (sbi_calloc(…, ROOT_REGION_MAX + 1)), the last one the all-zero terminator (order == 0).sbi_domain_init()and the driver cold-inits, regions are added one at a time throughsbi_domain_root_add_memregion(). SupposeNregions are registered so far (thevirtplatform registers 7 by the end of boot: firmware RW/RX, UART, CLINT MSWI, CLINT MTIMER, PLIC in two pieces, the all-RAM catch-all;Ngrows to 7).Rthat strictly covers an existing regionS(same flags,SinsideR). The ACLINT driver does this onvirt: it registers the MSWI window0x2000000/0x4000and the MTIMER window0x2004000/0xc000separately, andsanitize_domain()merges them when a covering2^16CLINT region appears. On entryroot_memregs_count = N(say 7) androot.regions[7]is the terminator.root_add_memregion()appendsRat index 7, increments the local count to 8 and writes the terminator at index 8.sanitize_domain()sorts, then findsScovered byRand removes it:sbi_memmove(®ions[i], ®ions[i+1], 24 * (count - i))with its OWN recomputed count, shifting the tail (including the terminator) down by one. The array now holds 7 live regions and the terminator at index 7.root_add_memregion()'s localroot_memregs_countis still 8.sbi_domain_for_each_memregion, which stops at the terminator) finds two adjacent same-order same-flag regions at indicesi1 - 1andi1and merges them:nreg->order++, thensbi_memmove(nreg1, nreg1 + 1, 24 * (root_memregs_count - i1)). With the stale 8 this copies8 - i1entries starting at indexi1 + 1, i.e. up to and including index 8, while the live data plus terminator end at index 7. It reads one entry beyond the terminator (index 8, inside the 33-slot allocation, all zero aftercalloc) and writes it over index 7.sbi_callocblock, pulling heap-neighbour bytes into the region table (a non-zeroorderthere extends the table with a bogus region that lands in the PMP configuration of every hart); (b) a change that drops+ 1from the allocation or reuses the slot, which the current code silently depends on.The fix recomputes the count from the array (
sbi_domain_used_memregions) after every successfulsanitize_domain()so the memmove covers exactly the remaining regions plus the terminator.Verification
Proved:
sbi_domain_root_add_memrange()on thevirtplatform's region sequence satisfies its contract against a model of the region array. The proof had to carry the out-of-range tail bytes explicitly to go through on the unfixed code; with the fix the memmove's range is the live array.