From 5fa2f76766d0dc60ce9f34d9e956e79c0fa5c563 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Wed, 8 Jul 2026 17:02:56 -0300 Subject: [PATCH] zeroed_fe_vec: reinterpret via from_raw_parts, not Vec transmute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zeroed_fe_vec built its Vec with mem::transmute::, Vec>. That relies on Vec's field layout being identical across element types, which the language does not guarantee (Vec is #[repr(Rust)], and the std transmute docs flag exactly this Vec-to-Vec pattern as a Bad Idea). It happens to work on the pinned toolchain, but a layout change (e.g. -Zrandomize-layout) could swap the ptr/cap words and make it UB. Rebuild the Vec from its raw parts instead: same calloc'd allocation, same element size/align (already const-asserted), so the dealloc Layout matches and there is no dependence on Vec's internal layout. Behavior is unchanged — the zeroed_fe_vec_matches_fe_zero test and full prove+verify still pass. --- prover/src/tables/types.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/prover/src/tables/types.rs b/prover/src/tables/types.rs index 3581dc8ec..85eaca17a 100644 --- a/prover/src/tables/types.rs +++ b/prover/src/tables/types.rs @@ -57,10 +57,22 @@ pub fn zeroed_fe_vec(len: usize) -> Vec { const _: () = assert!(core::mem::size_of::() == core::mem::size_of::()); const _: () = assert!(core::mem::align_of::() == core::mem::align_of::()); let zeros: Vec = vec![0u64; len]; + // Reinterpret the buffer as `Vec` via its raw parts rather than + // `mem::transmute::, Vec>`. `Vec`'s field layout is unspecified + // and may depend on its element type, so transmuting one `Vec` to another + // relies on that unspecified layout (the std `mem::transmute` docs call this + // out and recommend `from_raw_parts`). Rebuilding from `(ptr, len, cap)` + // reuses the same allocation and carries no `Vec`-layout assumption. + let mut zeros = core::mem::ManuallyDrop::new(zeros); // SAFETY: `FE` is `#[repr(transparent)]` over `u64` with identical size and // alignment (asserted above), and `0u64` is exactly `FE::zero()`'s bit - // pattern (no Montgomery form), so reinterpreting the buffer is valid. - unsafe { core::mem::transmute::, Vec>(zeros) } + // pattern (Goldilocks has no Montgomery form), so the zeroed `u64` buffer is + // a valid `[FE]` of all `FE::zero()`. `len`/`capacity` are element counts and + // the element sizes are equal, so they carry over unchanged; the eventual + // dealloc uses the same `size * capacity` and alignment as the original + // allocation. `ManuallyDrop` stops the source `Vec` from freeing the buffer + // that the returned `Vec` now owns. + unsafe { Vec::from_raw_parts(zeros.as_mut_ptr() as *mut FE, zeros.len(), zeros.capacity()) } } #[cfg(test)]