Skip to content

Commit c375a10

Browse files
Merge #2041
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 <[email protected]> Co-authored-by: Jarred Allen <[email protected]>
2 parents 1d61638 + 2d60044 commit c375a10

File tree

4 files changed

+104
-5
lines changed

4 files changed

+104
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).
4141
### Fixed
4242
- Fix: send `ETH_P_ALL` in htons format
4343
([#1925](https://github.com/nix-rust/nix/pull/1925))
44+
- Fix: `recvmsg` now sets the length of the received `sockaddr_un` field
45+
correctly on Linux platforms. ([#2041](https://github.com/nix-rust/nix/pull/2041))
4446
- Fix potentially invalid conversions in
4547
`SockaddrIn::from<std::net::SocketAddrV4>`,
4648
`SockaddrIn6::from<std::net::SockaddrV6>`, `IpMembershipRequest::new`, and

src/sys/socket/addr.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,22 @@ impl SockaddrLike for UnixAddr {
765765
{
766766
mem::size_of::<libc::sockaddr_un>() as libc::socklen_t
767767
}
768+
769+
unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> {
770+
// `new_length` is only used on some platforms, so it must be provided even when not used
771+
#![allow(unused_variables)]
772+
cfg_if! {
773+
if #[cfg(any(target_os = "android",
774+
target_os = "fuchsia",
775+
target_os = "illumos",
776+
target_os = "linux",
777+
target_os = "redox",
778+
))] {
779+
self.sun_len = new_length as u8;
780+
}
781+
};
782+
Ok(())
783+
}
768784
}
769785

770786
impl AsRef<libc::sockaddr_un> for UnixAddr {
@@ -914,7 +930,32 @@ pub trait SockaddrLike: private::SockaddrLikePriv {
914930
{
915931
mem::size_of::<Self>() as libc::socklen_t
916932
}
933+
934+
/// Set the length of this socket address
935+
///
936+
/// This method may only be called on socket addresses whose lengths are dynamic, and it
937+
/// returns an error if called on a type whose length is static.
938+
///
939+
/// # Safety
940+
///
941+
/// `new_length` must be a valid length for this type of address. Specifically, reads of that
942+
/// length from `self` must be valid.
943+
#[doc(hidden)]
944+
unsafe fn set_length(&mut self, _new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> {
945+
Err(SocketAddressLengthNotDynamic)
946+
}
947+
}
948+
949+
/// The error returned by [`SockaddrLike::set_length`] on an address whose length is statically
950+
/// fixed.
951+
#[derive(Copy, Clone, Debug)]
952+
pub struct SocketAddressLengthNotDynamic;
953+
impl fmt::Display for SocketAddressLengthNotDynamic {
954+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
955+
f.write_str("Attempted to set length on socket whose length is statically fixed")
956+
}
917957
}
958+
impl std::error::Error for SocketAddressLengthNotDynamic {}
918959

919960
impl private::SockaddrLikePriv for () {
920961
fn as_mut_ptr(&mut self) -> *mut libc::sockaddr {
@@ -1360,6 +1401,15 @@ impl SockaddrLike for SockaddrStorage {
13601401
None => mem::size_of_val(self) as libc::socklen_t,
13611402
}
13621403
}
1404+
1405+
unsafe fn set_length(&mut self, new_length: usize) -> std::result::Result<(), SocketAddressLengthNotDynamic> {
1406+
match self.as_unix_addr_mut() {
1407+
Some(addr) => {
1408+
addr.set_length(new_length)
1409+
},
1410+
None => Err(SocketAddressLengthNotDynamic),
1411+
}
1412+
}
13631413
}
13641414

13651415
macro_rules! accessors {
@@ -1678,7 +1728,7 @@ impl PartialEq for SockaddrStorage {
16781728
}
16791729
}
16801730

1681-
mod private {
1731+
pub(super) mod private {
16821732
pub trait SockaddrLikePriv {
16831733
/// Returns a mutable raw pointer to the inner structure.
16841734
///
@@ -2215,7 +2265,6 @@ mod datalink {
22152265
&self.0
22162266
}
22172267
}
2218-
22192268
}
22202269
}
22212270

src/sys/socket/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,7 +1613,7 @@ impl<S> MultiHeaders<S> {
16131613
{
16141614
// we will be storing pointers to addresses inside mhdr - convert it into boxed
16151615
// slice so it can'be changed later by pushing anything into self.addresses
1616-
let mut addresses = vec![std::mem::MaybeUninit::uninit(); num_slices].into_boxed_slice();
1616+
let mut addresses = vec![std::mem::MaybeUninit::<S>::uninit(); num_slices].into_boxed_slice();
16171617

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

@@ -1918,7 +1918,7 @@ unsafe fn read_mhdr<'a, 'i, S>(
19181918
mhdr: msghdr,
19191919
r: isize,
19201920
msg_controllen: usize,
1921-
address: S,
1921+
mut address: S,
19221922
) -> RecvMsg<'a, 'i, S>
19231923
where S: SockaddrLike
19241924
{
@@ -1934,6 +1934,11 @@ unsafe fn read_mhdr<'a, 'i, S>(
19341934
}.as_ref()
19351935
};
19361936

1937+
// Ignore errors if this socket address has statically-known length
1938+
//
1939+
// This is to ensure that unix socket addresses have their length set appropriately.
1940+
let _ = address.set_length(mhdr.msg_namelen as usize);
1941+
19371942
RecvMsg {
19381943
bytes: r as usize,
19391944
cmsghdr,
@@ -1969,7 +1974,7 @@ unsafe fn pack_mhdr_to_receive<S>(
19691974
// initialize it.
19701975
let mut mhdr = mem::MaybeUninit::<msghdr>::zeroed();
19711976
let p = mhdr.as_mut_ptr();
1972-
(*p).msg_name = (*address).as_mut_ptr() as *mut c_void;
1977+
(*p).msg_name = address as *mut c_void;
19731978
(*p).msg_namelen = S::size();
19741979
(*p).msg_iov = iov_buffer as *mut iovec;
19751980
(*p).msg_iovlen = iov_buffer_len as _;

test/sys/test_socket.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,49 @@ pub fn test_socketpair() {
202202
assert_eq!(&buf[..], b"hello");
203203
}
204204

205+
#[test]
206+
pub fn test_recvmsg_sockaddr_un() {
207+
use nix::sys::socket::{
208+
self, bind, socket, AddressFamily, MsgFlags, SockFlag, SockType,
209+
};
210+
211+
let tempdir = tempfile::tempdir().unwrap();
212+
let sockname = tempdir.path().join("sock");
213+
let sock = socket(
214+
AddressFamily::Unix,
215+
SockType::Datagram,
216+
SockFlag::empty(),
217+
None,
218+
)
219+
.expect("socket failed");
220+
let sockaddr = UnixAddr::new(&sockname).unwrap();
221+
bind(sock, &sockaddr).expect("bind failed");
222+
223+
// Send a message
224+
let send_buffer = "hello".as_bytes();
225+
if let Err(e) = socket::sendmsg(
226+
sock,
227+
&[std::io::IoSlice::new(send_buffer)],
228+
&[],
229+
MsgFlags::empty(),
230+
Some(&sockaddr),
231+
) {
232+
crate::skip!("Couldn't send ({e:?}), so skipping test");
233+
}
234+
235+
// Receive the message
236+
let mut recv_buffer = [0u8; 32];
237+
let received = socket::recvmsg(
238+
sock,
239+
&mut [std::io::IoSliceMut::new(&mut recv_buffer)],
240+
None,
241+
MsgFlags::empty(),
242+
)
243+
.unwrap();
244+
// Check the address in the received message
245+
assert_eq!(sockaddr, received.address.unwrap());
246+
}
247+
205248
#[test]
206249
pub fn test_std_conversions() {
207250
use nix::sys::socket::*;

0 commit comments

Comments
 (0)