Description
recvmmsg currently mutates state through shared references, which is immediately UB. This is caused by using shared references in the function signature, where mutable references are requried.
Detailed Explanation
Currently, recvmmsg essentially does the following:
pub fn recvmmsg<'a, XS>(
// elided ...
slices: XS,
)
where
XS: IntoIterator<Item = &'a I>,
I: AsRef<[IoSliceMut<'a>]> + 'a,
{
for slice in slices.into_iter() {
let ptr_mut = slice.as_ref().as_ptr() as *mut libc::iovec;
// Continue using `ptr_mut` in a mutable context
}
}
This boils down to: (stripping one layer of slices)
pub fn recvmmsg<'a>(
// elided ...
slices: &'a [&'a mut [u8]],
) {
for slice in slices.iter() {
let ptr = &**slice as *const [u8];
let ptr_mut = ptr.cast_mut();
// Continue using `ptr_mut` in a mutable context
}
}
Although we have a mutable reference to the inner slice, it is hidden behind a shared reference, which behaves exactly like it was borrowed immutably in first place. Casting a shared reference to a *mut _ pointer and mutating the value through that pointer is always UB.
Example demonstrating this bug
use std::{
io::IoSliceMut,
net::{SocketAddr, UdpSocket},
os::fd::AsRawFd,
};
use crossbeam::thread::scope;
use nix::sys::socket::{recvmmsg, MsgFlags, MultiHeaders, SockaddrIn};
fn main() {
let mut buf = [0u8; 2];
let i = [IoSliceMut::new(&mut buf[..])];
let xs = [&i];
scope(|s| {
s.spawn(|_| loop {
let socket = UdpSocket::bind("0.0.0.0:42".parse::<SocketAddr>().unwrap()).unwrap();
let slices = xs.iter();
let mut h = MultiHeaders::<SockaddrIn>::preallocate(1, None);
// This thread is mutating `buf` ...
recvmmsg(socket.as_raw_fd(), &mut h, slices, MsgFlags::empty(), None).unwrap();
});
s.spawn(|_| loop {
let slices = xs.iter();
for slice in slices {
// While this thread tries to read `buf`
println!("{:?}", slice);
}
});
})
.unwrap();
}
Proposal
Change the function signature of recvmmsg to:
pub fn recvmmsg<'a, XS, S, I>(
fd: RawFd,
data: &'a mut MultiHeaders<S>,
slices: XS,
flags: MsgFlags,
mut timeout: Option<crate::sys::time::TimeSpec>,
) -> crate::Result<MultiResults<'a, S>>
where
XS: IntoIterator<Item = &'a mut I>,
I: AsMut<[IoSliceMut<'a>]> + 'a,
or (preferably, but inconsistent with sendmmsg)
pub fn recvmmsg<'a, XS, S, I>(
fd: RawFd,
data: &'a mut MultiHeaders<S>,
slices: XS,
flags: MsgFlags,
mut timeout: Option<crate::sys::time::TimeSpec>,
) -> crate::Result<MultiResults<'a, S>>
where
XS: IntoIterator<Item = I>,
I: AsMut<[IoSliceMut<'a>]>,
Description
recvmmsgcurrently mutates state through shared references, which is immediately UB. This is caused by using shared references in the function signature, where mutable references are requried.Detailed Explanation
Currently,
recvmmsgessentially does the following:This boils down to: (stripping one layer of slices)
Although we have a mutable reference to the inner slice, it is hidden behind a shared reference, which behaves exactly like it was borrowed immutably in first place. Casting a shared reference to a
*mut _pointer and mutating the value through that pointer is always UB.Example demonstrating this bug
Proposal
Change the function signature of
recvmmsgto:or (preferably, but inconsistent with
sendmmsg)