I found a soundness issue with Ptr::iter where calling it twice on the same Exclusive Ptr lets you get aliasing &mut references, which Miri confirms as UB.
Reproduction
Cargo.toml:
[package]
name = "ptr_iter_repro"
version = "0.1.0"
edition = "2024"
[dependencies]
zerocopy = "0.8.49"
src/main.rs:
use zerocopy::Ptr;
fn main() {
let mut arr = [0u8, 1, 2, 3];
let p = Ptr::from_mut(&mut arr[..]);
let a = p.iter().next().unwrap();
let b = p.iter().next().unwrap();
let m1: &mut u8 = a.as_mut();
let m2: &mut u8 = b.as_mut();
*m1 = 10;
*m2 = 20;
*m1 = 30;
println!("m1={} m2={}", *m1, *m2);
}
$ cargo miri run 06:54:33
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `/Users/squiffy/.rustup/toolchains/nightly-2025-07-20-aarch64-apple-darwin/bin/cargo-miri runner target/miri/aarch64-apple-darwin/debug/ptr_iter_repro`
error: Undefined Behavior: attempting a write access using <834> at alloc240[0x0], but that tag does not exist in the borrow stack for this location
--> src/main.rs:12:5
|
12 | *m1 = 10;
| ^^^^^^^^ this error occurs as part of an access at alloc240[0x0..0x1]
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <834> was created by a Unique retag at offsets [0x0..0x1]
--> src/main.rs:9:23
|
9 | let m1: &mut u8 = a.as_mut();
| ^^^^^^^^^^
help: <834> was later invalidated at offsets [0x0..0x1] by a Unique retag
--> src/main.rs:10:23
|
10 | let m2: &mut u8 = b.as_mut();
| ^^^^^^^^^^
= note: BACKTRACE (of the first span):
= note: inside `main` at src/main.rs:12:5: 12:13
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
error: aborting due to 1 previous error
Root Cause
The issue is that Ptr::iter takes &self:
pub fn iter(&self) -> impl Iterator<Item = Ptr<'a, T, I>> { ... }
Since it borrows rather than consumes, nothing stops you from calling it multiple times on the same Exclusive Ptr. Each call hands back fresh Ptr<'a, T, (Exclusive, ...)> values that all point to the same memory, so the exclusivity invariant is quietly broken.
Suggested Fix
I think the cleanest fix would be to split the method into shared/exclusive variants, similar to how std does iter vs iter_mut. Specifically:
iter(&self): constrained to Shared aliasing only
iter_mut(self): for Exclusive, consuming the Ptr so it can't be reused
Alternatively, iter could just consume self whenever the aliasing invariant is Exclusive.
Versions
- zerocopy 0.8.49
- rustc nightly (Miri)
- macOS / aarch64
I found a soundness issue with
Ptr::iterwhere calling it twice on the sameExclusivePtrlets you get aliasing&mutreferences, which Miri confirms as UB.Reproduction
Cargo.toml:src/main.rs:Root Cause
The issue is that
Ptr::itertakes&self:Since it borrows rather than consumes, nothing stops you from calling it multiple times on the same
ExclusivePtr. Each call hands back freshPtr<'a, T, (Exclusive, ...)>values that all point to the same memory, so the exclusivity invariant is quietly broken.Suggested Fix
I think the cleanest fix would be to split the method into shared/exclusive variants, similar to how
stddoesitervsiter_mut. Specifically:iter(&self): constrained toSharedaliasing onlyiter_mut(self): forExclusive, consuming thePtrso it can't be reusedAlternatively,
itercould just consumeselfwhenever the aliasing invariant isExclusive.Versions