You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
MsgHdrMut::with_addr derives msg_name from a shared reborrow, so recvmsg writes through SharedReadOnly provenance #673
MsgHdrMut was added in #447 specifically so that the address the kernel writes into is passed as &mut SockAddr. The API change landed, but the implementation still routes that &mut through a
function that takes &SockAddr, so the pointer stored in msg_name ends up with shared
(read-only) provenance. recvmsg then writes through it.
I think this is unsound when using the non-mutable version in combination with recvmsg, e.g. recvmsg(fd, &MsgHdr::new().with_addr(&addr)) will write to addr even though we only supplied
a read-only reference, which is UB.
That is exactly the problem; it just survived inside MsgHdrMut as well.
The code (0.6.5 and master)
// src/lib.rs:699-703 (0.6.5: 692-696)#[allow(clippy::needless_pass_by_ref_mut)]pubfnwith_addr(mutself,addr:&'addrmutSockAddr) -> Self{
sys::set_msghdr_name(&mutself.inner, addr);// &mut SockAddr -> &SockAddrself}// src/sys/unix.rs:828-831 (0.6.5: 773-776); src/sys/windows.rs is the same shapepub(crate)fnset_msghdr_name(msg:&mutmsghdr,name:&SockAddr){
msg.msg_name = name.as_ptr()as*mut_;// SockAddr::as_ptr(&self) -> *const _
msg.msg_namelen = name.len();}
SockAddr only has as_ptr(&self), so msg_name is always derived from a shared reference even
on the mutable path. The #[allow(clippy::needless_pass_by_ref_mut)] is a symptom of the same
thing: clippy correctly observes that the &mut is never used mutably on the Rust side, because
the only mutable use happens inside the kernel.
Reproduction
Miri cannot run recvmsg, so this test obtains the pointer through the crate's own code path
and only models the kernel's write. Dropped into src/lib.rs of 0.6.5:
Stacked Borrows (cargo miri test --lib f05_repro):
error: Undefined Behavior: attempting a write access using <138399> at alloc...[0x0],
but that tag only grants SharedReadOnly permission for this location
--> src/lib.rs:762:18
help: <138399> was created by a SharedReadOnly retag at offsets [0x0..0x80]
--> src/sockaddr.rs:253:9
|
253 | &self.storage as *const sockaddr_storage as *const SockAddrStorage
Tree Borrows (MIRIFLAGS=-Zmiri-tree-borrows):
error: Undefined Behavior: write access through <134125> at alloc...[0x0] is forbidden
= help: the accessed tag <134125> has state Frozen which forbids this child write access
help: the accessed tag <134125> was created here, in the initial state Frozen
--> src/sockaddr.rs:253:9
Both models point at SockAddr::as_ptr as the origin.
Reachable with no unsafe in user code: Socket::recvmsg(&self, msg: &mut MsgHdrMut, flags) is a
safe pub fn, and so are MsgHdrMut::new, with_addr and with_buffers.
I have not observed a miscompilation from this; current rustc keeps the accesses. I am
reporting it as unsoundness.
Not covered by existing reports
RUSTSEC-2020-0079 is the old SocketAddr memory-layout assumption — unrelated.
There are no Stacked Borrows or Miri issues on this repo (I searched the tracker), and Add Socket::{send,recv}msg and MsgHdr(Mut) #447 is
where MsgHdrMut was introduced to address the &MsgHdr case.
Suggested fix
Give SockAddr a mutable accessor and a mutable setter, and use it on the MsgHdrMut path:
// src/sockaddr.rs, next to as_ptrpub(crate)fnas_mut_ptr(&mutself) -> *mutSockAddrStorage{&mutself.storageas*mutsockaddr_storageas*mutSockAddrStorage}// src/sys/unix.rs (and the matching shape in src/sys/windows.rs)pub(crate)fnset_msghdr_name_mut(msg:&mutmsghdr,name:&mutSockAddr){
msg.msg_namelen = name.len();
msg.msg_name = name.as_mut_ptr()as*mut_;}// src/lib.rs — the clippy allow can go away toopubfnwith_addr(mutself,addr:&'addrmutSockAddr) -> Self{
sys::set_msghdr_name_mut(&mutself.inner, addr);self}
MsgHdr::with_addr (the send path) keeps using set_msghdr_name, which is correct there since sendmsg only reads.
I applied exactly this to a local copy of 0.6.5:
$ cargo miri test --lib f05_repro # Stacked Borrows
test result: ok. 1 passed; 0 failed
$ MIRIFLAGS=-Zmiri-tree-borrows cargo miri test --lib f05_repro # Tree Borrows
test result: ok. 1 passed; 0 failed
$ cargo test --lib
test result: ok. 11 passed; 0 failed
I only verified the Unix side; src/sys/windows.rs needs the same split. Happy to open a PR, and
happy to coordinate before anything is published.
Context
Found while developing a static checker for Rust's aliasing rules; I verified this one by hand with
Miri before reporting.
Thanks for the quick turnaround. I checked #674 locally and it resolves it:
Your regression_673 test fails on master with attempting a write access ... but that tag only grants SharedReadOnly permission, and passes on this branch under both Stacked Borrows and Tree Borrows (cargo miri test --lib regression_673, with and without -Zmiri-tree-borrows).
cargo test --lib 11/11 and cargo fmt --check are clean.
For what it's worth I also swept the rest of the crate for the same shape: MsgHdrMut::with_buffers and with_control already used as_mut_ptr, and the remaining as_ptr() as *mut _ casts are on the send side (MsgHdr::with_buffers/with_control, sys::windows buffers, the BPF filter array) where the kernel only reads, so with_addr really was the only one.
MsgHdrMutwas added in #447 specifically so that the address the kernel writes into is passed as&mut SockAddr. The API change landed, but the implementation still routes that&mutthrough afunction that takes
&SockAddr, so the pointer stored inmsg_nameends up with shared(read-only) provenance.
recvmsgthen writes through it.In #447 you wrote:
That is exactly the problem; it just survived inside
MsgHdrMutas well.The code (0.6.5 and master)
SockAddronly hasas_ptr(&self), somsg_nameis always derived from a shared reference evenon the mutable path. The
#[allow(clippy::needless_pass_by_ref_mut)]is a symptom of the samething: clippy correctly observes that the
&mutis never used mutably on the Rust side, becausethe only mutable use happens inside the kernel.
Reproduction
Miri cannot run
recvmsg, so this test obtains the pointer through the crate's own code pathand only models the kernel's write. Dropped into
src/lib.rsof 0.6.5:Stacked Borrows (
cargo miri test --lib f05_repro):Tree Borrows (
MIRIFLAGS=-Zmiri-tree-borrows):Both models point at
SockAddr::as_ptras the origin.Reachable with no
unsafein user code:Socket::recvmsg(&self, msg: &mut MsgHdrMut, flags)is asafe
pub fn, and so areMsgHdrMut::new,with_addrandwith_buffers.I have not observed a miscompilation from this; current rustc keeps the accesses. I am
reporting it as unsoundness.
Not covered by existing reports
SocketAddrmemory-layout assumption — unrelated.where
MsgHdrMutwas introduced to address the&MsgHdrcase.Suggested fix
Give
SockAddra mutable accessor and a mutable setter, and use it on theMsgHdrMutpath:MsgHdr::with_addr(the send path) keeps usingset_msghdr_name, which is correct there sincesendmsgonly reads.I applied exactly this to a local copy of 0.6.5:
I only verified the Unix side;
src/sys/windows.rsneeds the same split. Happy to open a PR, andhappy to coordinate before anything is published.
Context
Found while developing a static checker for Rust's aliasing rules; I verified this one by hand with
Miri before reporting.