Repository: nix-rust/nix (Rust)

Issue:
`recvmmsg` may cause UB
## 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:

```rust
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)

```rust
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
```rust
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:

```rust
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`)

```rust
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>]>,
```


Discussion:
As a side note, I'm not convinced that `RecvMsg` containing a reference to the underlying IO slices is a good thing, as this defeats the purpose of using references (and slices) as arguments to this function. 

Even worse, `RecvMsg` only gives the caller a shared reference to the data, which is tied to the lifetime of the IO slice. Until `RecvMsg` gets dropped, the caller is unable to obtain a mutable reference to the buffer in safe rust and would be forced to either copy the data into a new buffer before modifying it or obtain a mutable reference using unsafe code.

In the context of `recvmmsg`, the caller first would have to fully exhaust and drop the `MultiResults` Iterator before the IO slice can be mutably borrowed again.
More than anything else, the recvmsg family of functions makes me want to quit my programming job and start a career cleaning toilets.  That's why I've procrastinated on this issue for so long.  But I've spent some time today reviewing all this stuff, and I think that you're right.  I think there are a bunch of other issues with our sendmsg/recvmsg APIs too, but we can fix them separately.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 69f628d8a0..d876b76017 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,9 @@ This project adheres to [Semantic Versioning](https://semver.org/).
 - Fix `SigSet` incorrect implementation of `Eq`, `PartialEq` and `Hash`
   ([#1946](https://github.com/nix-rust/nix/pull/1946))
 
+- Fixed the function signature of `recvmmsg`, potentially causing UB
+  ([#2119](https://github.com/nix-rust/nix/issues/2119))
+
 ### Changed
 
 - The following APIs now take an implementation of `AsFd` rather than a
@@ -33,6 +36,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
   relaxed lifetime requirements relative to 0.27.1.
   ([#2136](https://github.com/nix-rust/nix/pull/2136))
 
+- Simplified the function signatures of `recvmmsg` and `sendmmsg`
+
 ## [0.27.1] - 2023-08-28
 
 ### Fixed
diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs
index e03b1d690d..69933bd1a9 100644
--- a/src/sys/socket/mod.rs
+++ b/src/sys/socket/mod.rs
@@ -1552,11 +1552,11 @@ pub fn sendmmsg<'a, XS, AS, C, I, S>(
     flags: MsgFlags
 ) -> crate::Result<MultiResults<'a, S>>
     where
-        XS: IntoIterator<Item = &'a I>,
+        XS: IntoIterator<Item = I>,
         AS: AsRef<[Option<S>]>,
-        I: AsRef<[IoSlice<'a>]> + 'a,
-        C: AsRef<[ControlMessage<'a>]> + 'a,
-        S: SockaddrLike + 'a
+        I: AsRef<[IoSlice<'a>]>,
+        C: AsRef<[ControlMessage<'a>]>,
+        S: SockaddrLike,
 {
 
     let mut count = 0;
@@ -1564,11 +1564,11 @@ pub fn sendmmsg<'a, XS, AS, C, I, S>(
 
     for (i, ((slice, addr), mmsghdr)) in slices.into_iter().zip(addrs.as_ref()).zip(data.items.iter_mut() ).enumerate() {
         let p = &mut mmsghdr.msg_hdr;
-        p.msg_iov = slice.as_ref().as_ptr() as *mut libc::iovec;
+        p.msg_iov = slice.as_ref().as_ptr().cast_mut().cast();
         p.msg_iovlen = slice.as_ref().len() as _;
 
         p.msg_namelen = addr.as_ref().map_or(0, S::len);
-        p.msg_name = addr.as_ref().map_or(ptr::null(), S::as_ptr) as _;
+        p.msg_name = addr.as_ref().map_or(ptr::null(), S::as_ptr).cast_mut().cast();
 
         // Encode each cmsg.  This must happen after initializing the header because
         // CMSG_NEXT_HDR and friends read the msg_control and msg_controllen fields.
@@ -1583,9 +1583,16 @@ pub fn sendmmsg<'a, XS, AS, C, I, S>(
             pmhdr = unsafe { CMSG_NXTHDR(p, pmhdr) };
         }
 
-        count = i+1;
+        // Doing an unchecked addition is alright here, as the only way to obtain an instance of `MultiHeaders`
+        // is through the `preallocate` function, which takes an `usize` as an argument to define its size,
+        // which also provides an upper bound for the size of this zipped iterator. Thus, `i < usize::MAX` or in
+        // other words: `count` doesn't overflow
+        count = i + 1;
     }
 
+    // SAFETY: all pointers are guaranteed to be valid for the scope of this function. `count` does represent the
+    // maximum number of messages that can be sent safely (i.e. `count` is the minimum of the sizes of `slices`,
+    // `data.items` and `addrs`)
     let sent = Errno::result(unsafe {
         libc::sendmmsg(
             fd,
@@ -1711,14 +1718,19 @@ pub fn recvmmsg<'a, XS, S, I>(
     mut timeout: Option<crate::sys::time::TimeSpec>,
 ) -> crate::Result<MultiResults<'a, S>>
 where
-    XS: IntoIterator<Item = &'a I>,
-    I: AsRef<[IoSliceMut<'a>]> + 'a,
+    XS: IntoIterator<Item = I>,
+    I: AsMut<[IoSliceMut<'a>]>,
 {
     let mut count = 0;
-    for (i, (slice, mmsghdr)) in slices.into_iter().zip(data.items.iter_mut()).enumerate() {
+    for (i, (mut slice, mmsghdr)) in slices.into_iter().zip(data.items.iter_mut()).enumerate() {
         let p = &mut mmsghdr.msg_hdr;
-        p.msg_iov = slice.as_ref().as_ptr() as *mut libc::iovec;
-        p.msg_iovlen = slice.as_ref().len() as _;
+        p.msg_iov = slice.as_mut().as_mut_ptr().cast();
+        p.msg_iovlen = slice.as_mut().len() as _;
+
+        // Doing an unchecked addition is alright here, as the only way to obtain an instance of `MultiHeaders`
+        // is through the `preallocate` function, which takes an `usize` as an argument to define its size,
+        // which also provides an upper bound for the size of this zipped iterator. Thus, `i < usize::MAX` or in
+        // other words: `count` doesn't overflow
         count = i + 1;
     }
 
@@ -1726,6 +1738,8 @@ where
         .as_mut()
         .map_or_else(std::ptr::null_mut, |t| t as *mut _ as *mut libc::timespec);
 
+    // SAFETY: all pointers are guaranteed to be valid for the scope of this function. `count` does represent the
+    // maximum number of messages that can be received safely (i.e. `count` is the minimum of the sizes of `slices` and `data.items`)
     let received = Errno::result(unsafe {
         libc::recvmmsg(
             fd,
@@ -1743,6 +1757,7 @@ where
     })
 }
 
+/// Iterator over results of [`recvmmsg`]/[`sendmmsg`]
 #[cfg(any(
     target_os = "linux",
     target_os = "android",
@@ -1750,9 +1765,6 @@ where
     target_os = "netbsd",
 ))]
 #[derive(Debug)]
-/// Iterator over results of [`recvmmsg`]/[`sendmmsg`]
-///
-///
 pub struct MultiResults<'a, S> {
     // preallocated structures
     rmm: &'a MultiHeaders<S>,
@@ -1903,7 +1915,7 @@ mod test {
 
         let t = sys::time::TimeSpec::from_duration(std::time::Duration::from_secs(10));
 
-        let recv = super::recvmmsg(rsock.as_raw_fd(), &mut data, recv_iovs.iter(), flags, Some(t))?;
+        let recv = super::recvmmsg(rsock.as_raw_fd(), &mut data, recv_iovs.iter_mut(), flags, Some(t))?;
 
         for rmsg in recv {
             #[cfg(not(any(qemu, target_arch = "aarch64")))]


Test patch:
diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs
index df83189981..4b55a71243 100644
--- a/test/sys/test_socket.rs
+++ b/test/sys/test_socket.rs
@@ -564,7 +564,7 @@ mod recvfrom {
         let res: Vec<RecvMsg<SockaddrIn>> = recvmmsg(
             rsock.as_raw_fd(),
             &mut data,
-            msgs.iter(),
+            msgs.iter_mut(),
             MsgFlags::empty(),
             None,
         )
@@ -652,7 +652,7 @@ mod recvfrom {
         let res: Vec<RecvMsg<SockaddrIn>> = recvmmsg(
             rsock.as_raw_fd(),
             &mut data,
-            msgs.iter(),
+            msgs.iter_mut(),
             MsgFlags::MSG_DONTWAIT,
             None,
         )
@@ -2324,12 +2324,17 @@ fn test_recvmmsg_timestampns() {
     // Receive the message
     let mut buffer = vec![0u8; message.len()];
     let cmsgspace = nix::cmsg_space!(TimeSpec);
-    let iov = vec![[IoSliceMut::new(&mut buffer)]];
+    let mut iov = vec![[IoSliceMut::new(&mut buffer)]];
     let mut data = MultiHeaders::preallocate(1, Some(cmsgspace));
-    let r: Vec<RecvMsg<()>> =
-        recvmmsg(in_socket.as_raw_fd(), &mut data, iov.iter(), flags, None)
-            .unwrap()
-            .collect();
+    let r: Vec<RecvMsg<()>> = recvmmsg(
+        in_socket.as_raw_fd(),
+        &mut data,
+        iov.iter_mut(),
+        flags,
+        None,
+    )
+    .unwrap()
+    .collect();
     let rtime = match r[0].cmsgs().next() {
         Some(ControlMessageOwned::ScmTimestampns(rtime)) => rtime,
         Some(_) => panic!("Unexpected control message"),