Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

UB in PagedVec::read_range_generic #1557

Open
gio256 opened this issue Apr 4, 2025 · 1 comment
Open

UB in PagedVec::read_range_generic #1557

gio256 opened this issue Apr 4, 2025 · 1 comment

Comments

@gio256
Copy link

gio256 commented Apr 4, 2025

Summary

The following tests both trigger undefined behavior in openvm_circuit::system::memory::PagedVec::read_range_generic.

#[test]
fn test_ub_clone_from() {
    let v = PagedVec::<Vec<u8>, 4>::new(2);
    let _ = v.range_vec(0..2);
}

#[test]
fn test_ub_drop() {
    #[derive(Default, Clone)]
    struct Byte(u8);

    impl Drop for Byte {
        fn drop(&mut self) {
            let _uninit = self.0;
        }
    }

    let v = PagedVec::<Byte, 4>::new(2);
    let _ = v.range_vec(0..2);
}

This can be seen by running with Miri.

cargo +nightly miri test -p openvm-circuit --no-default-features system::memory::paged_vec::tests::test_ub_drop

Discussion

When called from PagedVec::range_vec, read_range_generic attempts to fill an uninitialized Vec with data from the PagedVec via a raw pointer. If the requested page is not initialized, the method instead fills the Vec with the result of T::default(). The first instance of this occurs here.

std::slice::from_raw_parts_mut(dst, len).fill(T::default());

The safety docs for std::slice::from_raw_parts_mut state that dst: *mut T must point to len consecutive properly initialized values of type T. It is not clear from the docs whether this is a validity invariant or a safety invariant.

While it is technically UB to construct a reference to uninitialized data (see, e.g., the std::ptr::addr_of docs), the rules here are not yet finalized (unsafe-code-guidelines#412). Therefore, creating a reference to uninitialized data using std::slice::from_raw_parts_mut is unlikely to result in miscompilation on its own, and I would assume the same for the typed copy needed to pass this reference to <[T]>::fill.

However, any "use" of this uninitialized data is clearly UB. As shown in the tests above, there are two cases that result in a use of the data in the destination slice within <[T]>::fill:

  • T implements Drop.
  • <T as Clone>::clone_from reads from self (e.g. Vec::clone_from).

Assuming that the generic parameter T in PagedVec<T, PAGE_SIZE> is typically a field element, and considering that p3_field::Field: Copy, both of these cases seem unlikely to occur in practice.

Fix

I believe the best fix here is to use MaybeUninit::fill. Unfortunately, this method is unstable, so it may be necessary to reimplement it using the approach shown here.

@jonathanpwang
Copy link
Contributor

We are likely going to rewrite large parts of Memory and thus PagedVec, so will take this into account during the rewrite. Thanks for filing!

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants