Skip to content

Fix panic-safety in Drain::clear_remain (double-free / UAF on panicking Drop) - #1

Open
tooson9010-spec wants to merge 2 commits into
andrewwhitehead:mainfrom
tooson9010-spec:fix/drain-clear-remain-panic-safety
Open

Fix panic-safety in Drain::clear_remain (double-free / UAF on panicking Drop)#1
tooson9010-spec wants to merge 2 commits into
andrewwhitehead:mainfrom
tooson9010-spec:fix/drain-clear-remain-panic-safety

Conversation

@tooson9010-spec

Copy link
Copy Markdown

Summary

Drain::clear_remain is not panic-safe. It runs the element destructor before advancing the remain cursor, so if an element's Drop panics, the cursor is left stale. Because Splice owns a Drain field, unwinding from Splice::drop drops that Drain, whose Drop calls clear_remain again and drops the same element a second time — a double-free (CWE-415) / use-after-free (CWE-416) reachable from safe Rust via Vec::splice.

Fix

Advance remain.start to mark the range consumed before running the destructor. Once the cursor is advanced, a panicking drop can no longer leave the same element visible to the re-entrant Drain::dropclear_remain. (The raw pointer is taken first so the &mut self borrow ends before the cursor is updated.)

Verification

Existing tests still pass. The added vec_splice_panicking_drop_is_sound test splices out an element whose Drop panics, under catch_unwind, then drops the vector: under AddressSanitizer the original code reports a heap-use-after-free, and with the fix it runs clean with each element dropped exactly once. Confirmed on 0.0.5.

Note: a separate pre-existing issue in the same function

While preparing this fix I noticed clear_remain passes as_mut_slice().as_mut_ptr() (the first element) to drop_in_place while marking the whole range consumed. If remain ever holds more than one element, this looks like it would drop only the first and leak the rest. It's independent of the panic-safety fix, so I left it out of this PR — happy to address it separately, or you may prefer to fix it your own way.

tooson9010-spec and others added 2 commits August 10, 2026 14:48
…ng Drop)

clear_remain() dropped the remaining element before advancing remain.start.
If the element's Drop panics, the cursor is left stale; since Splice owns a
Drain field, unwinding from Splice::drop drops that Drain, whose Drop calls
clear_remain again and drops the same element a second time -- a double-free
(CWE-415) / use-after-free (CWE-416) reachable from safe Rust via Vec::splice.
Advance the cursor before running the destructor, and add a regression test.
Remove commented-out old code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant