From 53a651d2d769f263560e84eb9c59645d210ef197 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Sat, 20 Jun 2026 10:32:13 +0000 Subject: [PATCH] agent-driven: Fix soundness in try_create_array --- src/foreign/alloc/ffi/c_str.rs | 7 ++++++- src/foreign/core/array.rs | 30 ++++++++++++++++++++++++++---- src/foreign/core/str.rs | 10 ++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/foreign/alloc/ffi/c_str.rs b/src/foreign/alloc/ffi/c_str.rs index a1b2383..b2de305 100644 --- a/src/foreign/alloc/ffi/c_str.rs +++ b/src/foreign/alloc/ffi/c_str.rs @@ -7,7 +7,12 @@ impl<'a> Arbitrary<'a> for CString { fn arbitrary(u: &mut Unstructured<'a>) -> Result { as Arbitrary>::arbitrary(u).map(|mut x| { x.retain(|&c| c != 0); - // SAFETY: all zero bytes have been removed + // SAFETY: + // Contract from `CString::from_vec_unchecked`: the vector must not contain + // any interior nul (zero) bytes. + // Evidence: `x.retain(|&c| c != 0)` removes all bytes equal to `0` from the + // vector `x`. Consequently, `x` contains no nul bytes, which guarantees + // it has no interior nul bytes. unsafe { Self::from_vec_unchecked(x) } }) } diff --git a/src/foreign/core/array.rs b/src/foreign/core/array.rs index 39075be..2060cf4 100644 --- a/src/foreign/core/array.rs +++ b/src/foreign/core/array.rs @@ -18,6 +18,17 @@ impl Drop for ArrayGuard { fn drop(&mut self) { debug_assert!(self.initialized <= N); let initialized_part = ptr::slice_from_raw_parts_mut(self.dst, self.initialized); + // SAFETY: + // Contract from `ptr::drop_in_place`: the pointer must be valid for reads and + // writes, and must be properly aligned. + // Evidence: + // - `self.dst` is derived from `MaybeUninit<[T; N]>` on the stack, which remains + // alive and allocated because the guard is a local variable dropped before + // the stack frame is destroyed. + // - The alignment of `self.dst` matches that of `[T; N]`, which is aligned for `T`. + // - Only the first `self.initialized` elements are dropped, which have been + // fully initialized by `ptr::write` in `try_create_array`. + // Therefore the contract of `drop_in_place` is discharged. unsafe { ptr::drop_in_place(initialized_part); } @@ -30,17 +41,28 @@ where { let mut array: MaybeUninit<[T; N]> = MaybeUninit::uninit(); let array_ptr = array.as_mut_ptr(); - let dst = array_ptr as _; + let dst = array_ptr as *mut T; let mut guard: ArrayGuard = ArrayGuard { dst, initialized: 0, }; - unsafe { - for (idx, value_ptr) in (*array.as_mut_ptr()).iter_mut().enumerate() { + for idx in 0..N { + // SAFETY: `dst` is a valid pointer to the start of the `[T; N]` array. + // `idx` is within `0..N`, so `dst.add(idx)` is within the bounds of the allocation. + // The pointer is properly aligned for `T`. + unsafe { + let value_ptr = dst.add(idx); ptr::write(value_ptr, cb(idx)?); - guard.initialized += 1; } + guard.initialized += 1; + } + unsafe { mem::forget(guard); + // SAFETY: + // Contract from `MaybeUninit::assume_init`: the value must be fully initialized. + // Evidence: the loop executes exactly `N` times, successfully writing a value + // to each of the `N` indices in the array. Since the loop completed without + // returning early or panicking, all elements of the array are initialized. Ok(array.assume_init()) } } diff --git a/src/foreign/core/str.rs b/src/foreign/core/str.rs index cfca8ad..a1346fd 100644 --- a/src/foreign/core/str.rs +++ b/src/foreign/core/str.rs @@ -12,6 +12,16 @@ fn arbitrary_str<'a>(u: &mut Unstructured<'a>, size: usize) -> Result<&'a str> { Err(e) => { let i = e.valid_up_to(); let valid = u.bytes(i).unwrap(); + // SAFETY: + // Contract from `str::from_utf8_unchecked`: the bytes must be valid UTF-8. + // Evidence: + // - `str::from_utf8` was called on the next `size` peeked bytes from `u`. + // - The call failed, returning a `Utf8Error` `e`. + // - `e.valid_up_to()` returns the length of the prefix of the peeked bytes + // which is guaranteed to be valid UTF-8. + // - `u.bytes(i)` consumes and returns exactly this valid prefix (since `u` + // was not mutated or consumed between peeking and calling `bytes`). + // - Therefore, `valid` is guaranteed to contain valid UTF-8. let s = unsafe { debug_assert!(str::from_utf8(valid).is_ok()); str::from_utf8_unchecked(valid)