Abort instead of unwinding out of an inconsistent BTreeMap::split_off - #161784
Abort instead of unwinding out of an inconsistent BTreeMap::split_off#161784matthew-demidoff wants to merge 1 commit into
Conversation
|
r? @nia-e rustbot has assigned @nia-e. Use Why was this reviewer chosen?The reviewer was selected based on:
|
This comment has been minimized.
This comment has been minimized.
de7b14a to
05c9135
Compare
There was a problem hiding this comment.
left some comments, but the overall approach seems good.
@rustbot author
| // unwinding through it double-frees the shared values (#158165). Abort | ||
| // instead, matching the panic-safety strategy used elsewhere in this | ||
| // module (see `mem::replace`). | ||
| struct PanicGuard; |
There was a problem hiding this comment.
Why not just use either a bare catch_unwind or a DropGuard?
| // inconsistent. `get_or_insert_with` (rather than `get_or_insert`) | ||
| // avoids constructing a fresh `PanicGuard` on later iterations, | ||
| // which would drop immediately and abort. | ||
| guard.get_or_insert_with(|| PanicGuard); |
There was a problem hiding this comment.
we're rearming the guard every loop iteration? hm. why not just arm it once before the loop and disarm after, or just envelop the entire loop in a catch_unwind?
|
Reminder, once the PR becomes ready for a review, use |
After the first move_suffix in Root::split_off, the source and result trees share values through two temporarily invalid structures. If a later key comparison panics, unwinding leaves the map with a stale length over a partially detached tree, and consuming iteration then double-frees the shared values, reachable from safe code. Guard the descent loop with an abort-on-panic PanicGuard, armed before the first move_suffix and forgotten once the borders are fixed, matching the strategy already used in btree::mem::replace.
05c9135 to
dc0341a
Compare
|
Sorry for the noise - I botched a force-push from a shallow clone and this PR's head got detached, so I could not reopen it. Reopened as #161975 with your feedback addressed (guard armed once before the loop; the first search stays outside it). |
…anic-safety, r=nia-e Abort instead of unwinding out of an inconsistent BTreeMap::split_off Fixes rust-lang#158165. Supersedes rust-lang#161784, which I had to abandon after a bad force-push from a shallow clone left its head detached. `BTreeMap::split_off` runs the caller's `Ord`/`Borrow` impl via `search_node` inside `Root::split_off`'s descent. After the first `move_suffix`, the two roots alias the same values through structurally invalid trees until the borders are fixed; a comparator panic there unwinds with a stale length and double-frees on later iteration or drop. It is reachable from `#![forbid(unsafe_code)]` on stable (reproducer on the issue). Guard the descent loop with an abort-on-panic `PanicGuard`, as `btree::mem::replace` already does. `catch_unwind` isnt available in `alloc` (no_std), and I kept an inline guard rather than the unstable `DropGuard`. It is armed once before the loop; the first `search_node` stays outside it, since a panic there can still unwind safely (nothing has moved yet). Verified with Miri: the reproducer goes from a double-free to a clean abort, and normal multi-level splits are unaffected. Adds a happy-path regression test. Credit to @ostrowr for the report and to rust-lang#158710 for the original approach. r? @nia-e
…anic-safety, r=nia-e Abort instead of unwinding out of an inconsistent BTreeMap::split_off Fixes rust-lang#158165. Supersedes rust-lang#161784, which I had to abandon after a bad force-push from a shallow clone left its head detached. `BTreeMap::split_off` runs the caller's `Ord`/`Borrow` impl via `search_node` inside `Root::split_off`'s descent. After the first `move_suffix`, the two roots alias the same values through structurally invalid trees until the borders are fixed; a comparator panic there unwinds with a stale length and double-frees on later iteration or drop. It is reachable from `#![forbid(unsafe_code)]` on stable (reproducer on the issue). Guard the descent loop with an abort-on-panic `PanicGuard`, as `btree::mem::replace` already does. `catch_unwind` isnt available in `alloc` (no_std), and I kept an inline guard rather than the unstable `DropGuard`. It is armed once before the loop; the first `search_node` stays outside it, since a panic there can still unwind safely (nothing has moved yet). Verified with Miri: the reproducer goes from a double-free to a clean abort, and normal multi-level splits are unaffected. Adds a happy-path regression test. Credit to @ostrowr for the report and to rust-lang#158710 for the original approach. r? @nia-e
Rollup merge of #161975 - matthew-demidoff:btree-split-off-panic-safety, r=nia-e Abort instead of unwinding out of an inconsistent BTreeMap::split_off Fixes #158165. Supersedes #161784, which I had to abandon after a bad force-push from a shallow clone left its head detached. `BTreeMap::split_off` runs the caller's `Ord`/`Borrow` impl via `search_node` inside `Root::split_off`'s descent. After the first `move_suffix`, the two roots alias the same values through structurally invalid trees until the borders are fixed; a comparator panic there unwinds with a stale length and double-frees on later iteration or drop. It is reachable from `#![forbid(unsafe_code)]` on stable (reproducer on the issue). Guard the descent loop with an abort-on-panic `PanicGuard`, as `btree::mem::replace` already does. `catch_unwind` isnt available in `alloc` (no_std), and I kept an inline guard rather than the unstable `DropGuard`. It is armed once before the loop; the first `search_node` stays outside it, since a panic there can still unwind safely (nothing has moved yet). Verified with Miri: the reproducer goes from a double-free to a clean abort, and normal multi-level splits are unaffected. Adds a happy-path regression test. Credit to @ostrowr for the report and to #158710 for the original approach. r? @nia-e
Fixes #158165.
BTreeMap::split_offruns the caller'sOrd/Borrowimpl viasearch_nodeinsideRoot::split_off's descent. After the firstmove_suffix, the two roots alias the same values through structurally invalid trees until the borders are fixed; a comparator panic there unwinds with a stale length and double-frees on later iteration or drop. It is reachable from#![forbid(unsafe_code)]on stable (reproducer on the issue).Fix: guard the descent loop with an abort-on-panic
PanicGuard, asbtree::mem::replacealready does. Verified with Miri: the reproducer goes from a double-free to a clean abort, and normal multi-level splits are unaffected. Adds a happy-path regression test.Revives the approach from #158710 (closed for inactivity); credit to @ostrowr for the report.