Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
2041: Set the length of a socket address when calling `recvmsg` on Linux r=asomers a=JarredAllen

# Background
I think I've found a bug with `recvmsg` on Linux where it doesn't set the length of a received socket address. I found this while working with unix datagram sockets with path `struct sockaddr_un` addresses, but I think this applies to any variable-length socket address type.

At present, the `sun_len` field of `UnixAddr` on Linux shows up as 0 whenever I call `recvmsg`. I think it's reading uninitialized memory (afaict the `recvmsg` function never initializes it before we call `MaybeUninit::assume_init`), though it's possible that it's being zero-initialized somewhere that I missed. Either way, this isn't the correct behavior, which should set it to the length of the `struct sockaddr_un` contents (or whatever other type of socket).

# Changes
I changed the `recvmsg` function to construct the returned socket address from the `struct sockaddr` and length returned by the libc `recvmsg` call (using the `from_raw` function the trait provides). Since the trait is sealed so downstream crates can't implement it, I believe this shouldn't be a breaking change.

# Validation
I've tested that my code (which failed due to this bug) now works. I also added a new test case which tests that we construct `UnixAddr`s correctly in the `recvmsg` function, which passes locally for me and fails if I cherry-pick it onto the current `master`.

I've also checked that `cargo test` and `cargo clippy` both still pass on `aarch64-apple-darwin` and on `aarch64-unknown-linux-musl` targets (the two targets I develop for/have access to). Hopefully your CI will confirm that everything else still works.

Co-authored-by: Jarred Allen <jarred@moveparallel.com>
Co-authored-by: Jarred Allen <jarredallen73@gmail.com>
  • Loading branch information
3 people authored and asomers committed Aug 26, 2023
1 parent 6ee9df5 commit 4a96a1c
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 5 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ This project adheres to [Semantic Versioning](https://semver.org/).
## [Unreleased] - ReleaseDate

### Fixed
- Fix: send `ETH_P_ALL` in htons format
([#1925](https://github.com/nix-rust/nix/pull/1925))
- Fix: `recvmsg` now sets the length of the received `sockaddr_un` field
correctly on Linux platforms. ([#2041](https://github.com/nix-rust/nix/pull/2041))
- Fix potentially invalid conversions in
`SockaddrIn::from<std::net::SocketAddrV4>`,
`SockaddrIn6::from<std::net::SockaddrV6>`, `IpMembershipRequest::new`, and
`Ipv6MembershipRequest::new` with future Rust versions.
([#2061](https://github.com/nix-rust/nix/pull/2061))

### Removed

- Removed deprecated IoVec API.
([#1855](https://github.com/nix-rust/nix/pull/1855))
- Removed deprecated net APIs.
([#1861](https://github.com/nix-rust/nix/pull/1861))
- `nix::sys::signalfd::signalfd` is deprecated. Use
`nix::sys::signalfd::SignalFd` instead.
([#1938](https://github.com/nix-rust/nix/pull/1938))

## [0.26.2] - 2023-01-18
### Fixed
Expand Down
53 changes: 51 additions & 2 deletions src/sys/socket/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,22 @@ impl SockaddrLike for UnixAddr {
{
mem::size_of::<libc::sockaddr_un>() as libc::socklen_t
}

unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> {
// `new_length` is only used on some platforms, so it must be provided even when not used
#![allow(unused_variables)]
cfg_if! {
if #[cfg(any(target_os = "android",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux",
target_os = "redox",
))] {
self.sun_len = new_length as u8;
}
};
Ok(())
}
}

impl AsRef<libc::sockaddr_un> for UnixAddr {
Expand Down Expand Up @@ -1198,7 +1214,32 @@ pub trait SockaddrLike: private::SockaddrLikePriv {
{
mem::size_of::<Self>() as libc::socklen_t
}

/// Set the length of this socket address
///
/// This method may only be called on socket addresses whose lengths are dynamic, and it
/// returns an error if called on a type whose length is static.
///
/// # Safety
///
/// `new_length` must be a valid length for this type of address. Specifically, reads of that
/// length from `self` must be valid.
#[doc(hidden)]
unsafe fn set_length(&mut self, _new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> {
Err(SocketAddressLengthNotDynamic)
}
}

/// The error returned by [`SockaddrLike::set_length`] on an address whose length is statically
/// fixed.
#[derive(Copy, Clone, Debug)]
pub struct SocketAddressLengthNotDynamic;
impl fmt::Display for SocketAddressLengthNotDynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Attempted to set length on socket whose length is statically fixed")
}
}
impl std::error::Error for SocketAddressLengthNotDynamic {}

impl private::SockaddrLikePriv for () {
fn as_mut_ptr(&mut self) -> *mut libc::sockaddr {
Expand Down Expand Up @@ -1645,6 +1686,15 @@ impl SockaddrLike for SockaddrStorage {
None => mem::size_of_val(self) as libc::socklen_t
}
}

unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> {
match self.as_unix_addr_mut() {
Some(addr) => {
addr.set_length(new_length)
},
None => Err(SocketAddressLengthNotDynamic),
}
}
}

macro_rules! accessors {
Expand Down Expand Up @@ -1961,7 +2011,7 @@ impl PartialEq for SockaddrStorage {
}
}

mod private {
pub(super) mod private {
pub trait SockaddrLikePriv {
/// Returns a mutable raw pointer to the inner structure.
///
Expand Down Expand Up @@ -2850,7 +2900,6 @@ mod datalink {
&self.0
}
}

}
}

Expand Down
11 changes: 8 additions & 3 deletions src/sys/socket/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1611,7 +1611,7 @@ impl<S> MultiHeaders<S> {
{
// we will be storing pointers to addresses inside mhdr - convert it into boxed
// slice so it can'be changed later by pushing anything into self.addresses
let mut addresses = vec![std::mem::MaybeUninit::uninit(); num_slices].into_boxed_slice();
let mut addresses = vec![std::mem::MaybeUninit::<S>::uninit(); num_slices].into_boxed_slice();

let msg_controllen = cmsg_buffer.as_ref().map_or(0, |v| v.capacity());

Expand Down Expand Up @@ -1916,7 +1916,7 @@ unsafe fn read_mhdr<'a, 'i, S>(
mhdr: msghdr,
r: isize,
msg_controllen: usize,
address: S,
mut address: S,
) -> RecvMsg<'a, 'i, S>
where S: SockaddrLike
{
Expand All @@ -1932,6 +1932,11 @@ unsafe fn read_mhdr<'a, 'i, S>(
}.as_ref()
};

// Ignore errors if this socket address has statically-known length
//
// This is to ensure that unix socket addresses have their length set appropriately.
let _ = address.set_length(mhdr.msg_namelen as usize);

RecvMsg {
bytes: r as usize,
cmsghdr,
Expand Down Expand Up @@ -1967,7 +1972,7 @@ unsafe fn pack_mhdr_to_receive<S>(
// initialize it.
let mut mhdr = mem::MaybeUninit::<msghdr>::zeroed();
let p = mhdr.as_mut_ptr();
(*p).msg_name = (*address).as_mut_ptr() as *mut c_void;
(*p).msg_name = address as *mut c_void;
(*p).msg_namelen = S::size();
(*p).msg_iov = iov_buffer as *mut iovec;
(*p).msg_iovlen = iov_buffer_len as _;
Expand Down
43 changes: 43 additions & 0 deletions test/sys/test_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,49 @@ pub fn test_socketpair() {
assert_eq!(&buf[..], b"hello");
}

#[test]
pub fn test_recvmsg_sockaddr_un() {
use nix::sys::socket::{
self, bind, socket, AddressFamily, MsgFlags, SockFlag, SockType,
};

let tempdir = tempfile::tempdir().unwrap();
let sockname = tempdir.path().join("sock");
let sock = socket(
AddressFamily::Unix,
SockType::Datagram,
SockFlag::empty(),
None,
)
.expect("socket failed");
let sockaddr = UnixAddr::new(&sockname).unwrap();
bind(sock, &sockaddr).expect("bind failed");

// Send a message
let send_buffer = "hello".as_bytes();
if let Err(e) = socket::sendmsg(
sock,
&[std::io::IoSlice::new(send_buffer)],
&[],
MsgFlags::empty(),
Some(&sockaddr),
) {
crate::skip!("Couldn't send ({e:?}), so skipping test");
}

// Receive the message
let mut recv_buffer = [0u8; 32];
let received = socket::recvmsg(
sock,
&mut [std::io::IoSliceMut::new(&mut recv_buffer)],
None,
MsgFlags::empty(),
)
.unwrap();
// Check the address in the received message
assert_eq!(sockaddr, received.address.unwrap());
}

#[test]
pub fn test_std_conversions() {
use nix::sys::socket::*;
Expand Down

0 comments on commit 4a96a1c

Please sign in to comment.