Skip to content

ThinVec::<ZST>::insert dereferences the fake length pointer (segfault from safe code) #101

Open

Description

@kris-gaudel

Since #92, a ZST ThinVec has no allocation or header:

  • self.ptr stores len + 1 as its address (len_to_ptr_unchecked), and len() / set_len_zst decode it.
  • Every length update goes through set_len_non_singleton, which dispatches to set_len_zst for ZSTs, except insert, which still ends with
self.set_header_len(old_len + 1);   // -> header_mut() -> &mut *self.ptr()

For a ZST, this forms a &mut Header at address len + 1 and writes through it. Reproducer (no unsafe):

#![forbid(unsafe_code)]

fn main() {
    let mut v: thin_vec::ThinVec<()> = thin_vec::ThinVec::new();
    v.insert(0, ());
}
  • release: SIGSEGV
  • debug: panics with assertion failed: !Self::is_zst() (the debug_assert! in header_mut)
  • Miri: Undefined Behavior: constructing invalid value of type &mut thin_vec::Header: encountered a dangling reference (0x1[noalloc] has no provenance)

For a vec of length n, the write goes to address n + 1, so it isn't limited to the null page. I ran every other public method on a ZST ThinVec under Miri, and only insert is affected.

Fix: in insert, replace self.set_header_len(old_len + 1) with self.set_len_non_singleton(old_len + 1), as push_unchecked does.

Regression test:

#[test]
fn zst_insert() {
    let mut v: ThinVec<()> = ThinVec::new();
    v.insert(0, ());
    v.insert(0, ());
    assert_eq!(v.len(), 2);
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions