From f39e84c456bd10f8f1f91bccdeb87c14d0cf1146 Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Sat, 15 Aug 2026 17:51:00 -0400 Subject: [PATCH 1/6] Fix the maximum path length allowable on MacOS, NetBSD, and DragonFlyBSD for Unix Domain sockets --- library/std/src/os/unix/net/addr.rs | 136 ++++++++++++++++++++--- library/std/src/os/unix/net/ancillary.rs | 7 +- library/std/src/os/unix/net/tests.rs | 31 ++++++ 3 files changed, 155 insertions(+), 19 deletions(-) diff --git a/library/std/src/os/unix/net/addr.rs b/library/std/src/os/unix/net/addr.rs index e5fcb93b88308..5933999bf58e7 100644 --- a/library/std/src/os/unix/net/addr.rs +++ b/library/std/src/os/unix/net/addr.rs @@ -13,21 +13,54 @@ use crate::{fmt, io, mem, ptr}; #[allow(non_camel_case_types)] mod libc { pub use core::ffi::c_int; + pub type sa_family_t = u8; pub type socklen_t = u32; pub struct sockaddr; #[derive(Clone)] pub struct sockaddr_un { + pub sun_family: sa_family_t, pub sun_path: [u8; 1], } } -const SUN_PATH_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_path); +cfg_if::cfg_if! { + if #[cfg(any(target_os = "macos", target_os = "dragonfly"))] { + // MacOS and DragonFly utilize `SOCK_MAXADDRLEN` to define + // the maximum size that the `sockaddr_un` struct could be. + // SOCK_MAXADDRLEN = 255 on these platforms, and it's based on + // sizeof(sa_len) (a u8) + sizeof(sun_family) (a u8) + 253 bytes + // (excluding nul). We add +1 for nul byte just in case. + // See https://github.com/rust-lang/rust/issues/160684 + pub(crate) const SOCK_MAX_SIZE: usize = libc::SOCK_MAXADDRLEN as usize + 1; + } else if #[cfg(any(target_os = "netbsd"))] { + // NetBSD uses `UCHAR_MAX` (essentially a `u8::MAX`) + 1 as defined by `sockaddr_big` here: + // https://github.com/IIJ-NetBSD/netbsd-src/blob/master/sys/sys/socket.h#L272-L287 + pub(crate) const SOCK_MAX_SIZE: usize = u8::MAX as usize + 1; + } else { + pub(crate) const SOCK_MAX_SIZE: usize = size_of::(); + } +} -#[rustdoc_skip_body] -pub(super) fn sockaddr_un(path: &Path) -> io::Result<(libc::sockaddr_un, libc::socklen_t)> { +// Offset to `libc::sockaddr_un.sun_path` +const SUN_PATH_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_path); +// This represents the maximum number of characters + 1 allowed to be +// stored in the socket path (e.g. 254 for NetBSD because it allows 253 +// valid characters for its path, 104 for FreeBSD because it allows up to +// 103 characters for its path) +pub(crate) const SUN_PATH_MAX_LEN: usize = SOCK_MAX_SIZE - SUN_PATH_OFFSET; +pub(super) fn sockaddr_un(path: &Path) -> io::Result<([u8; SOCK_MAX_SIZE], libc::socklen_t)> { // SAFETY: All zeros is a valid representation for `sockaddr_un`. - let mut addr: libc::sockaddr_un = unsafe { mem::zeroed() }; - addr.sun_family = libc::AF_UNIX as libc::sa_family_t; + let mut addr: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; + + // SAFETY: All Unix platforms should have a `libc::sockaddr_un` struct, + // `sun_family` field, and a `sa_family_t` type. `offset_of!` macro + // guarantees that we would be writing `AF_UNIX` into the correct spot. + unsafe { + addr.as_mut_ptr() + .byte_add(mem::offset_of!(libc::sockaddr_un, sun_family)) + .cast::() + .write_unaligned(libc::AF_UNIX as libc::sa_family_t); + } let bytes = path.as_os_str().as_bytes(); @@ -38,21 +71,53 @@ pub(super) fn sockaddr_un(path: &Path) -> io::Result<(libc::sockaddr_un, libc::s )); } - if bytes.len() >= addr.sun_path.len() { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "path must be shorter than SUN_LEN", - )); + if bytes.len() >= SUN_PATH_MAX_LEN { + cfg_if::cfg_if! { + if #[cfg(any(target_os = "macos", target_os = "dragonfly"))] { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "path must be shorter than SOCK_MAXADDRLEN - 1", + )); + } else if #[cfg(any(target_os = "netbsd"))] { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "path must be shorter than UCHAR_MAX - 1", + )); + } else { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "path must be shorter than SUN_LEN", + )); + } + } } + // SAFETY: `bytes` and `addr.sun_path` are not overlapping and // both point to valid memory. // NOTE: We zeroed the memory above, so the path is already null // terminated. unsafe { - ptr::copy_nonoverlapping(bytes.as_ptr(), addr.sun_path.as_mut_ptr().cast(), bytes.len()) + ptr::copy_nonoverlapping( + bytes.as_ptr(), + addr.as_mut_ptr().byte_add(SUN_PATH_OFFSET), + bytes.len(), + ) }; let mut len = SUN_PATH_OFFSET + bytes.len(); + #[cfg(any( + target_os = "dragonfly", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + ))] + { + const _: () = assert!((SUN_PATH_MAX_LEN as u16) <= (u8::MAX as u16)); + // For these platforms, `sockaddr_un` has a `sun_len` (u8) field that should be initialized with a value + // (nul byte excluding) + addr[mem::offset_of!(libc::sockaddr_un, sun_len)] = len as u8; + } + match bytes.get(0) { Some(&0) | None => {} Some(_) => { @@ -65,6 +130,14 @@ pub(super) fn sockaddr_un(path: &Path) -> io::Result<(libc::sockaddr_un, libc::s } } } + + #[cfg(target_os = "freebsd")] + { + // For these platforms, `sockaddr_un` has a `sun_len` (u8) field that should be initialized with a value + // (nul byte including) + addr[mem::offset_of!(libc::sockaddr_un, sun_len)] = len as u8; + } + Ok((addr, len as libc::socklen_t)) } @@ -94,8 +167,13 @@ enum AddressKind<'a> { #[derive(Clone)] #[stable(feature = "unix_socket", since = "1.10.0")] pub struct SocketAddr { - pub(super) addr: libc::sockaddr_un, + /// Size of the socket address, `sun_family` and `sun_path` + /// fields from `libc::sockaddr_un` included pub(super) len: libc::socklen_t, + /// Stack allocated buffer that contains full size of what `libc::sockaddr_un` + /// could be (as `sun_path` field defined for `sockaddr_un` does not represent + /// the maximum path length of a Unix Domain socket name) + pub(super) addr: [u8; SOCK_MAX_SIZE], } impl SocketAddr { @@ -139,7 +217,31 @@ impl SocketAddr { )); } - Ok(SocketAddr { addr, len }) + let mut sockaddr: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; + let addr_ptr = ptr::addr_of!(addr) as *const u8; + cfg_if::cfg_if! { + if #[cfg(all(unix, any(target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly")))] { + // SAFETY: `addr_ptr` and `sockaddr` are not overlapping and + // both point to valid memory. + // NOTE: We zeroed the memory above, so the `.sun_path` is already null + // terminated. + unsafe { + // BSD platforms have a `sun_len` field for their `sockaddr_un` struct + // which tells us the size of the socket address + ptr::copy_nonoverlapping(addr_ptr, sockaddr.as_mut_ptr(), addr.sun_len as usize) + }; + } else { + // SAFETY: `addr` and `sockaddr` are not overlapping and + // both point to valid memory. + // NOTE: We zeroed the memory above, so the path is already null + // terminated. + unsafe { + // The `SOCK_MAX_SIZE` internally is the sizeof(libc::sockaddr_un) + ptr::copy_nonoverlapping(addr_ptr, sockaddr.as_mut_ptr(), SOCK_MAX_SIZE) + }; + } + }; + Ok(SocketAddr { len, addr: sockaddr }) } /// Constructs a `SockAddr` with the family `AF_UNIX` and the provided path. @@ -176,7 +278,7 @@ impl SocketAddr { where P: AsRef, { - sockaddr_un(path.as_ref()).map(|(addr, len)| SocketAddr { addr, len }) + sockaddr_un(path.as_ref()).map(|(addr, len)| SocketAddr { len, addr }) } /// Returns `true` if the address is unnamed. @@ -257,15 +359,15 @@ impl SocketAddr { #[rustdoc_skip_body] fn address(&self) -> AddressKind<'_> { let len = self.len as usize - SUN_PATH_OFFSET; - let path = unsafe { mem::transmute::<&[libc::c_char], &[u8]>(&self.addr.sun_path) }; + let path = &self.addr[SUN_PATH_OFFSET..]; // macOS seems to return a len of 16 and a zeroed sun_path for unnamed addresses if len == 0 || (cfg!(not(any(target_os = "linux", target_os = "android", target_os = "cygwin"))) - && self.addr.sun_path[0] == 0) + && path[0] == 0) { AddressKind::Unnamed - } else if self.addr.sun_path[0] == 0 { + } else if path[0] == 0 { AddressKind::Abstract(ByteStr::from_bytes(&path[1..len])) } else { // linux adds a trailing NUL and counts it in the length, freebsd, netbsd diff --git a/library/std/src/os/unix/net/ancillary.rs b/library/std/src/os/unix/net/ancillary.rs index acd564eb9c157..05ae42b03f558 100644 --- a/library/std/src/os/unix/net/ancillary.rs +++ b/library/std/src/os/unix/net/ancillary.rs @@ -70,8 +70,11 @@ pub(super) fn send_vectored_with_ancillary_to( ancillary: &mut SocketAncillary<'_>, ) -> io::Result { unsafe { - let (mut msg_name, msg_namelen) = - if let Some(path) = path { sockaddr_un(path)? } else { (zeroed(), 0) }; + let (mut msg_name, msg_namelen) = if let Some(path) = path { + sockaddr_un(path)? + } else { + ([0; crate::os::unix::net::SOCK_MAX_SIZE], 0) + }; let mut msg: libc::msghdr = zeroed(); msg.msg_name = (&raw mut msg_name) as *mut _; diff --git a/library/std/src/os/unix/net/tests.rs b/library/std/src/os/unix/net/tests.rs index 4014767461389..fdaf4994ba6a2 100644 --- a/library/std/src/os/unix/net/tests.rs +++ b/library/std/src/os/unix/net/tests.rs @@ -868,3 +868,34 @@ fn test_send_vectored_with_ancillary_unix_datagram() { unreachable!("must be ScmRights"); } } + +#[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets +#[cfg_attr(target_os = "vxworks", ignore = "Unix sockets are not implemented in VxWorks")] +fn test_unix_datagram_max_path() { + let dir = tmpdir(); + // +1 for separator byte on join + let dir_len = dir.path().as_os_str().len() + 1; + // SUN_PATH_MAX_LEN differs based on platform (e.g. for macos this should be 254, for + // linux this should be 108). We test whether a socket can bind to the maximally allowed + // path size, which is SUN_PATH_MAX_LEN - 1 + let sock = format!( + "sock{}", + vec!['a'; SUN_PATH_MAX_LEN.saturating_sub(5 + dir_len)].into_iter().collect::() + ); + let sock2 = format!( + "sock{}", + vec!['b'; SUN_PATH_MAX_LEN.saturating_sub(5 + dir_len)].into_iter().collect::() + ); + let path1 = dir.path().join(sock); + let path2 = dir.path().join(sock2); + + let sock1 = or_panic!(UnixDatagram::bind(&path1)); + let sock2 = or_panic!(UnixDatagram::bind(&path2)); + + let msg = b"hello world"; + or_panic!(sock1.send_to(msg, &path2)); + let mut buf = [0; 11]; + or_panic!(sock2.recv_from(&mut buf)); + assert_eq!(msg, &buf[..]); +} From 7485d584854abd59febfb3ba7d756d83393d2051 Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Sun, 6 Sep 2026 20:40:17 -0400 Subject: [PATCH 2/6] Refactored sockaddr_un function to SocketAddr::from_path (and SocketAddr::default, SocketAddr::set_path, SocketAddr:set_len, SocketAddr::sun_family_from_addr), use safer methods (e.g. copy_from_slice over ptr::copy_nonoverlapping), and updated a few tests in unix/net/tests --- library/std/src/os/unix/net/addr.rs | 269 ++++++++++------------- library/std/src/os/unix/net/ancillary.rs | 17 +- library/std/src/os/unix/net/datagram.rs | 34 ++- library/std/src/os/unix/net/listener.rs | 16 +- library/std/src/os/unix/net/stream.rs | 18 +- library/std/src/os/unix/net/tests.rs | 20 +- 6 files changed, 189 insertions(+), 185 deletions(-) diff --git a/library/std/src/os/unix/net/addr.rs b/library/std/src/os/unix/net/addr.rs index 5933999bf58e7..12b13f4a0b82e 100644 --- a/library/std/src/os/unix/net/addr.rs +++ b/library/std/src/os/unix/net/addr.rs @@ -6,7 +6,7 @@ use crate::os::net::linux_ext; use crate::os::unix::ffi::OsStrExt; use crate::path::Path; use crate::sys::cvt; -use crate::{fmt, io, mem, ptr}; +use crate::{fmt, io, mem}; // FIXME(#43348): Make libc adapt #[doc(cfg(...))] so we don't need these fake definitions here? #[cfg(not(unix))] @@ -41,105 +41,15 @@ cfg_if::cfg_if! { } } +// Offset to `libc::sockaddr_un.sun_family` +pub(crate) const SUN_FAMILY_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_family); // Offset to `libc::sockaddr_un.sun_path` -const SUN_PATH_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_path); +pub(crate) const SUN_PATH_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_path); // This represents the maximum number of characters + 1 allowed to be // stored in the socket path (e.g. 254 for NetBSD because it allows 253 // valid characters for its path, 104 for FreeBSD because it allows up to // 103 characters for its path) pub(crate) const SUN_PATH_MAX_LEN: usize = SOCK_MAX_SIZE - SUN_PATH_OFFSET; -pub(super) fn sockaddr_un(path: &Path) -> io::Result<([u8; SOCK_MAX_SIZE], libc::socklen_t)> { - // SAFETY: All zeros is a valid representation for `sockaddr_un`. - let mut addr: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; - - // SAFETY: All Unix platforms should have a `libc::sockaddr_un` struct, - // `sun_family` field, and a `sa_family_t` type. `offset_of!` macro - // guarantees that we would be writing `AF_UNIX` into the correct spot. - unsafe { - addr.as_mut_ptr() - .byte_add(mem::offset_of!(libc::sockaddr_un, sun_family)) - .cast::() - .write_unaligned(libc::AF_UNIX as libc::sa_family_t); - } - - let bytes = path.as_os_str().as_bytes(); - - if bytes.contains(&0) { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "paths must not contain interior null bytes", - )); - } - - if bytes.len() >= SUN_PATH_MAX_LEN { - cfg_if::cfg_if! { - if #[cfg(any(target_os = "macos", target_os = "dragonfly"))] { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "path must be shorter than SOCK_MAXADDRLEN - 1", - )); - } else if #[cfg(any(target_os = "netbsd"))] { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "path must be shorter than UCHAR_MAX - 1", - )); - } else { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "path must be shorter than SUN_LEN", - )); - } - } - } - - // SAFETY: `bytes` and `addr.sun_path` are not overlapping and - // both point to valid memory. - // NOTE: We zeroed the memory above, so the path is already null - // terminated. - unsafe { - ptr::copy_nonoverlapping( - bytes.as_ptr(), - addr.as_mut_ptr().byte_add(SUN_PATH_OFFSET), - bytes.len(), - ) - }; - - let mut len = SUN_PATH_OFFSET + bytes.len(); - #[cfg(any( - target_os = "dragonfly", - target_os = "macos", - target_os = "netbsd", - target_os = "openbsd", - ))] - { - const _: () = assert!((SUN_PATH_MAX_LEN as u16) <= (u8::MAX as u16)); - // For these platforms, `sockaddr_un` has a `sun_len` (u8) field that should be initialized with a value - // (nul byte excluding) - addr[mem::offset_of!(libc::sockaddr_un, sun_len)] = len as u8; - } - - match bytes.get(0) { - Some(&0) | None => {} - Some(_) => { - // on QNX7.1 and QNX8 the `len` value returned by the SUN_LEN - // macro in its libc does not include the null byte in the count so - // don't add it here to match what a C program passes to bind(2) and - // similar functions - if cfg!(not(any(target_os = "qnx", target_env = "nto71"))) { - len += 1 - } - } - } - - #[cfg(target_os = "freebsd")] - { - // For these platforms, `sockaddr_un` has a `sun_len` (u8) field that should be initialized with a value - // (nul byte including) - addr[mem::offset_of!(libc::sockaddr_un, sun_len)] = len as u8; - } - - Ok((addr, len as libc::socklen_t)) -} enum AddressKind<'a> { Unnamed, @@ -177,29 +87,106 @@ pub struct SocketAddr { } impl SocketAddr { + pub(super) fn default() -> SocketAddr { + let mut addr: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; + let sun_family = (libc::AF_UNIX as libc::sa_family_t).to_ne_bytes(); + addr[SUN_FAMILY_OFFSET..SUN_FAMILY_OFFSET + size_of::()] + .copy_from_slice(&sun_family); + + SocketAddr { len: SUN_PATH_OFFSET as libc::socklen_t, addr } + } + pub(super) fn new(f: F) -> io::Result where - F: FnOnce(*mut libc::sockaddr, *mut libc::socklen_t) -> libc::c_int, + F: FnOnce(*mut [u8; SOCK_MAX_SIZE], *mut libc::socklen_t) -> libc::c_int, { - unsafe { - let mut addr: libc::sockaddr_un = mem::zeroed(); - let mut len = size_of::() as libc::socklen_t; - cvt(f((&raw mut addr) as *mut _, &mut len))?; - SocketAddr::from_parts(addr, len) + let mut addr: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; + let mut len = size_of::() as libc::socklen_t; + cvt(f((&raw mut addr) as *mut _, &mut len))?; + SocketAddr::from_parts(addr, len) + } + + pub(super) fn from_path(path: &Path) -> io::Result { + let mut sockaddr = SocketAddr::default(); + + let bytes = path.as_os_str().as_bytes(); + + if bytes.contains(&0) { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "paths must not contain interior null bytes", + )); + } + + if bytes.len() >= SUN_PATH_MAX_LEN { + const LEN_EXCEEDED_MSG: &'static str = + cfg_select! { + any(target_os = "macos", target_os = "dragonfly") => { + "path must be shorter than SOCK_MAXADDRLEN - 1" + } + target_os = "netbsd" => "path must be shorter than UCHAR_MAX - 1", + _ => "path must be shorter than SUN_LEN", + }; + return Err(io::const_error!(io::ErrorKind::InvalidInput, LEN_EXCEEDED_MSG)); + } + + sockaddr.set_path(bytes); + + let mut len = SUN_PATH_OFFSET + bytes.len(); + #[cfg(any( + target_os = "dragonfly", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "freebsd" + ))] + { + const _: () = assert!((SUN_PATH_MAX_LEN as usize) <= (u8::MAX as usize)); + // For these platforms, `sockaddr_un` has a `sun_len` (u8) field that should be initialized with a value + // (nul byte excluding) + sockaddr.addr[mem::offset_of!(libc::sockaddr_un, sun_len)] = len as u8; } + + match bytes.get(0) { + Some(&0) | None => {} + Some(_) => { + // on QNX7.1 and QNX8 the `len` value returned by the SUN_LEN + // macro in its libc does not include the null byte in the count so + // don't add it here to match what a C program passes to bind(2) and + // similar functions + // For BSD-based platforms, they do not count the nul terminating byte + // in the address length + if cfg!(not(any( + target_os = "qnx", + target_env = "nto71", + target_os = "dragonfly", + target_os = "macos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "freebsd" + ))) { + len += 1 + } + } + } + + // Even though len here is a `usize` and `libc::socklen_t` is a `u32` + // our len value should be limited to whatever value a `u32` can hold + sockaddr.set_len(len as libc::socklen_t); + + Ok(sockaddr) } #[rustdoc_skip_body] pub(super) fn from_parts( - addr: libc::sockaddr_un, + addr: [u8; SOCK_MAX_SIZE], mut len: libc::socklen_t, ) -> io::Result { if cfg!(target_os = "openbsd") { // on OpenBSD, getsockname(2) returns the actual size of the socket address, // and not the len of the content. Figure out the length for ourselves. // https://marc.info/?l=openbsd-bugs&m=170105481926736&w=2 - let sun_path: &[u8] = - unsafe { mem::transmute::<&[libc::c_char], &[u8]>(&addr.sun_path) }; + let sun_path = &addr[SUN_PATH_OFFSET..]; len = core::slice::memchr::memchr(0, sun_path) .map_or(len, |new_len| (new_len + SUN_PATH_OFFSET) as libc::socklen_t); } @@ -210,38 +197,27 @@ impl SocketAddr { // When there is a datagram from unnamed unix socket // linux returns zero bytes of address len = SUN_PATH_OFFSET as libc::socklen_t; // i.e., zero-length address - } else if addr.sun_family != libc::AF_UNIX as libc::sa_family_t { + } else if SocketAddr::sun_family_from_addr(&addr) != libc::AF_UNIX as libc::sa_family_t { return Err(io::const_error!( io::ErrorKind::InvalidInput, "file descriptor did not correspond to a Unix socket", )); } - let mut sockaddr: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; - let addr_ptr = ptr::addr_of!(addr) as *const u8; - cfg_if::cfg_if! { - if #[cfg(all(unix, any(target_os = "macos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly")))] { - // SAFETY: `addr_ptr` and `sockaddr` are not overlapping and - // both point to valid memory. - // NOTE: We zeroed the memory above, so the `.sun_path` is already null - // terminated. - unsafe { - // BSD platforms have a `sun_len` field for their `sockaddr_un` struct - // which tells us the size of the socket address - ptr::copy_nonoverlapping(addr_ptr, sockaddr.as_mut_ptr(), addr.sun_len as usize) - }; - } else { - // SAFETY: `addr` and `sockaddr` are not overlapping and - // both point to valid memory. - // NOTE: We zeroed the memory above, so the path is already null - // terminated. - unsafe { - // The `SOCK_MAX_SIZE` internally is the sizeof(libc::sockaddr_un) - ptr::copy_nonoverlapping(addr_ptr, sockaddr.as_mut_ptr(), SOCK_MAX_SIZE) - }; - } - }; - Ok(SocketAddr { len, addr: sockaddr }) + Ok(SocketAddr { len, addr }) + } + + fn sun_family_from_addr(addr: &[u8; SOCK_MAX_SIZE]) -> libc::sa_family_t { + let sun_family_array = addr[SUN_FAMILY_OFFSET..SUN_FAMILY_OFFSET + size_of::()].try_into().expect("Slice should have exactly the same number of bytes extracted as the size of libc::sa_family_t"); + libc::sa_family_t::from_ne_bytes(sun_family_array) + } + + fn set_path(&mut self, path_bytes: &[u8]) { + self.addr[SUN_PATH_OFFSET..SUN_PATH_OFFSET + path_bytes.len()].copy_from_slice(path_bytes); + } + + fn set_len(&mut self, len: libc::socklen_t) { + self.len = len; } /// Constructs a `SockAddr` with the family `AF_UNIX` and the provided path. @@ -278,7 +254,7 @@ impl SocketAddr { where P: AsRef, { - sockaddr_un(path.as_ref()).map(|(addr, len)| SocketAddr { len, addr }) + SocketAddr::from_path(path.as_ref()) } /// Returns `true` if the address is unnamed. @@ -393,25 +369,24 @@ impl linux_ext::addr::SocketAddrExt for SocketAddr { N: AsRef<[u8]>, { let name = name.as_ref(); - unsafe { - let mut addr: libc::sockaddr_un = mem::zeroed(); - addr.sun_family = libc::AF_UNIX as libc::sa_family_t; - - if name.len() + 1 > addr.sun_path.len() { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "abstract socket name must be shorter than SUN_LEN", - )); - } + let mut sockaddr = SocketAddr::default(); - crate::ptr::copy_nonoverlapping( - name.as_ptr(), - addr.sun_path.as_mut_ptr().add(1) as *mut u8, - name.len(), - ); - let len = (SUN_PATH_OFFSET + 1 + name.len()) as libc::socklen_t; - SocketAddr::from_parts(addr, len) + if name.len() + 1 > SUN_PATH_MAX_LEN { + const LEN_EXCEEDED_MSG: &'static str = + cfg_select! { + any(target_os = "macos", target_os = "dragonfly") => { + "path must be shorter than SOCK_MAXADDRLEN - 2" + } + target_os = "netbsd" => "path must be shorter than UCHAR_MAX - 2", + _ => "path must be shorter than SUN_LEN - 1", + }; + + return Err(io::const_error!(io::ErrorKind::InvalidInput, LEN_EXCEEDED_MSG,)); } + + sockaddr.addr[SUN_PATH_OFFSET + 1..SUN_PATH_OFFSET + 1 + name.len()].copy_from_slice(name); + sockaddr.len = (SUN_PATH_OFFSET + 1 + name.len()) as libc::socklen_t; + Ok(sockaddr) } } diff --git a/library/std/src/os/unix/net/ancillary.rs b/library/std/src/os/unix/net/ancillary.rs index 05ae42b03f558..f8d0372d2386f 100644 --- a/library/std/src/os/unix/net/ancillary.rs +++ b/library/std/src/os/unix/net/ancillary.rs @@ -1,16 +1,16 @@ // FIXME: This is currently disabled on *BSD. -use super::{SocketAddr, sockaddr_un}; +use super::SocketAddr; use crate::io::{self, IoSlice, IoSliceMut}; use crate::macros::rustdoc_skip_body; use crate::marker::PhantomData; use crate::mem::zeroed; use crate::os::unix::io::RawFd; +use crate::os::unix::net::SOCK_MAX_SIZE; use crate::path::Path; use crate::ptr::{eq, read_unaligned}; use crate::slice::from_raw_parts; use crate::sys::net::Socket; - // FIXME(#43348): Make libc adapt #[doc(cfg(...))] so we don't need these fake definitions here? #[cfg(all( doc, @@ -38,7 +38,7 @@ pub(super) fn recv_vectored_with_ancillary_from( ancillary: &mut SocketAncillary<'_>, ) -> io::Result<(usize, bool, io::Result)> { unsafe { - let mut msg_name: libc::sockaddr_un = zeroed(); + let mut msg_name: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; let mut msg: libc::msghdr = zeroed(); msg.msg_name = (&raw mut msg_name) as *mut _; msg.msg_namelen = size_of::() as libc::socklen_t; @@ -70,15 +70,12 @@ pub(super) fn send_vectored_with_ancillary_to( ancillary: &mut SocketAncillary<'_>, ) -> io::Result { unsafe { - let (mut msg_name, msg_namelen) = if let Some(path) = path { - sockaddr_un(path)? - } else { - ([0; crate::os::unix::net::SOCK_MAX_SIZE], 0) - }; + let mut sockaddr = + if let Some(path) = path { SocketAddr::from_path(path)? } else { zeroed() }; let mut msg: libc::msghdr = zeroed(); - msg.msg_name = (&raw mut msg_name) as *mut _; - msg.msg_namelen = msg_namelen; + msg.msg_name = (&raw mut sockaddr.addr) as *mut _; + msg.msg_namelen = sockaddr.len; msg.msg_iov = bufs.as_ptr() as *mut _; msg.msg_iovlen = bufs.len() as _; msg.msg_controllen = ancillary.length as _; diff --git a/library/std/src/os/unix/net/datagram.rs b/library/std/src/os/unix/net/datagram.rs index 901263a7aa123..f26ac76d07514 100644 --- a/library/std/src/os/unix/net/datagram.rs +++ b/library/std/src/os/unix/net/datagram.rs @@ -14,7 +14,7 @@ ))] use libc::MSG_NOSIGNAL; -use super::{SocketAddr, sockaddr_un}; +use super::SocketAddr; #[cfg(any(doc, target_os = "android", target_os = "linux", target_os = "cygwin"))] use super::{SocketAncillary, recv_vectored_with_ancillary_from, send_vectored_with_ancillary_to}; #[cfg(any(doc, target_os = "android", target_os = "linux", target_os = "cygwin"))] @@ -101,9 +101,13 @@ impl UnixDatagram { pub fn bind>(path: P) -> io::Result { unsafe { let socket = UnixDatagram::unbound()?; - let (addr, len) = sockaddr_un(path.as_ref())?; + let sockaddr = SocketAddr::from_path(path.as_ref())?; - cvt(libc::bind(socket.as_raw_fd(), (&raw const addr) as *const _, len as _))?; + cvt(libc::bind( + socket.as_raw_fd(), + (&raw const sockaddr.addr) as *const _, + sockaddr.len as _, + ))?; Ok(socket) } @@ -219,9 +223,13 @@ impl UnixDatagram { #[stable(feature = "unix_socket", since = "1.10.0")] pub fn connect>(&self, path: P) -> io::Result<()> { unsafe { - let (addr, len) = sockaddr_un(path.as_ref())?; + let sockaddr = SocketAddr::from_path(path.as_ref())?; - cvt(libc::connect(self.as_raw_fd(), (&raw const addr) as *const _, len))?; + cvt(libc::connect( + self.as_raw_fd(), + (&raw const sockaddr.addr) as *const _, + sockaddr.len, + ))?; } Ok(()) } @@ -301,7 +309,9 @@ impl UnixDatagram { #[stable(feature = "unix_socket", since = "1.10.0")] #[rustdoc_skip_body] pub fn local_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { libc::getsockname(self.as_raw_fd(), addr, len) }) + SocketAddr::new(|addr, len| unsafe { + libc::getsockname(self.as_raw_fd(), addr as *mut libc::sockaddr, len) + }) } /// Returns the address of this socket's peer. @@ -327,7 +337,9 @@ impl UnixDatagram { #[stable(feature = "unix_socket", since = "1.10.0")] #[rustdoc_skip_body] pub fn peer_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { libc::getpeername(self.as_raw_fd(), addr, len) }) + SocketAddr::new(|addr, len| unsafe { + libc::getpeername(self.as_raw_fd(), addr as *mut libc::sockaddr, len) + }) } fn recv_from_flags( @@ -342,7 +354,7 @@ impl UnixDatagram { buf.as_mut_ptr() as *mut _, buf.len(), flags, - addr, + addr as *mut libc::sockaddr, len, ); if count > 0 { @@ -534,15 +546,15 @@ impl UnixDatagram { #[stable(feature = "unix_socket", since = "1.10.0")] pub fn send_to>(&self, buf: &[u8], path: P) -> io::Result { unsafe { - let (addr, len) = sockaddr_un(path.as_ref())?; + let sockaddr = SocketAddr::from_path(path.as_ref())?; let count = cvt(libc::sendto( self.as_raw_fd(), buf.as_ptr() as *const _, buf.len(), MSG_NOSIGNAL, - (&raw const addr) as *const _, - len, + (&raw const sockaddr.addr) as *const _, + sockaddr.len, ))?; Ok(count as usize) } diff --git a/library/std/src/os/unix/net/listener.rs b/library/std/src/os/unix/net/listener.rs index cc0bcdf9e614c..f1f7be1754f33 100644 --- a/library/std/src/os/unix/net/listener.rs +++ b/library/std/src/os/unix/net/listener.rs @@ -6,7 +6,7 @@ use crate::os::unix::io::{ use crate::path::Path; use crate::sys::net::Socket; use crate::sys::{AsInner, FromInner, IntoInner, cvt}; -use crate::{fmt, io, mem}; +use crate::{fmt, io}; /// A structure representing a Unix domain socket server. /// @@ -76,7 +76,7 @@ impl UnixListener { pub fn bind>(path: P) -> io::Result { unsafe { let inner = Socket::new(libc::AF_UNIX, libc::SOCK_STREAM)?; - let (addr, len) = sockaddr_un(path.as_ref())?; + let sockaddr = SocketAddr::from_path(path.as_ref())?; #[cfg(any( target_os = "windows", target_os = "redox", @@ -107,7 +107,11 @@ impl UnixListener { )))] const backlog: libc::c_int = libc::SOMAXCONN; - cvt(libc::bind(inner.as_inner().as_raw_fd(), (&raw const addr) as *const _, len as _))?; + cvt(libc::bind( + inner.as_inner().as_raw_fd(), + (&raw const sockaddr.addr) as *const _, + sockaddr.len as _, + ))?; cvt(libc::listen(inner.as_inner().as_raw_fd(), backlog))?; Ok(UnixListener(inner)) @@ -181,7 +185,7 @@ impl UnixListener { /// ``` #[stable(feature = "unix_socket", since = "1.10.0")] pub fn accept(&self) -> io::Result<(UnixStream, SocketAddr)> { - let mut storage: libc::sockaddr_un = unsafe { mem::zeroed() }; + let mut storage: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; let mut len = size_of_val(&storage) as libc::socklen_t; let sock = self.0.accept((&raw mut storage) as *mut _, &mut len)?; let addr = SocketAddr::from_parts(storage, len)?; @@ -228,7 +232,9 @@ impl UnixListener { #[stable(feature = "unix_socket", since = "1.10.0")] #[rustdoc_skip_body] pub fn local_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { libc::getsockname(self.as_raw_fd(), addr, len) }) + SocketAddr::new(|addr, len| unsafe { + libc::getsockname(self.as_raw_fd(), addr as *mut libc::sockaddr, len) + }) } /// Moves the socket into or out of nonblocking mode. diff --git a/library/std/src/os/unix/net/stream.rs b/library/std/src/os/unix/net/stream.rs index 3ad76f77aa475..106656fd8a1c4 100644 --- a/library/std/src/os/unix/net/stream.rs +++ b/library/std/src/os/unix/net/stream.rs @@ -21,7 +21,7 @@ cfg_select! { } } -use super::{SocketAddr, sockaddr_un}; +use super::SocketAddr; #[cfg(any(doc, target_os = "android", target_os = "linux", target_os = "cygwin"))] use super::{SocketAncillary, recv_vectored_with_ancillary_from, send_vectored_with_ancillary_to}; #[cfg(any( @@ -119,9 +119,13 @@ impl UnixStream { pub fn connect>(path: P) -> io::Result { unsafe { let inner = Socket::new(libc::AF_UNIX, libc::SOCK_STREAM)?; - let (addr, len) = sockaddr_un(path.as_ref())?; + let sockaddr = SocketAddr::from_path(path.as_ref())?; - cvt(libc::connect(inner.as_raw_fd(), (&raw const addr) as *const _, len))?; + cvt(libc::connect( + inner.as_raw_fd(), + (&raw const sockaddr.addr) as *const _, + sockaddr.len, + ))?; Ok(UnixStream(inner)) } } @@ -228,7 +232,9 @@ impl UnixStream { #[stable(feature = "unix_socket", since = "1.10.0")] #[rustdoc_skip_body] pub fn local_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { libc::getsockname(self.as_raw_fd(), addr, len) }) + SocketAddr::new(|addr, len| unsafe { + libc::getsockname(self.as_raw_fd(), addr as *mut libc::sockaddr, len) + }) } /// Returns the socket address of the remote half of this connection. @@ -248,7 +254,9 @@ impl UnixStream { #[stable(feature = "unix_socket", since = "1.10.0")] #[rustdoc_skip_body] pub fn peer_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { libc::getpeername(self.as_raw_fd(), addr, len) }) + SocketAddr::new(|addr, len| unsafe { + libc::getpeername(self.as_raw_fd(), addr as *mut libc::sockaddr, len) + }) } /// Gets the peer credentials for this Unix domain socket. diff --git a/library/std/src/os/unix/net/tests.rs b/library/std/src/os/unix/net/tests.rs index fdaf4994ba6a2..ebd38711b3a0e 100644 --- a/library/std/src/os/unix/net/tests.rs +++ b/library/std/src/os/unix/net/tests.rs @@ -35,10 +35,12 @@ fn sock_addr_from_pathname() { fn sock_addr_without_trailing_nul() { const PATH: &[u8] = b"/path/to/socket"; - // SAFETY: all zeros is a valid representation for `sockaddr_un`. - let mut addr: libc::sockaddr_un = unsafe { crate::mem::zeroed() }; - addr.sun_family = libc::AF_UNIX as libc::sa_family_t; - for (dst, &src) in addr.sun_path.iter_mut().zip(PATH) { + let mut addr: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; + let sun_family = (libc::AF_UNIX as libc::sa_family_t).to_ne_bytes(); + addr[SUN_FAMILY_OFFSET..SUN_FAMILY_OFFSET + size_of::()] + .copy_from_slice(&sun_family); + + for (dst, &src) in addr[SUN_PATH_OFFSET..].iter_mut().zip(PATH) { *dst = src as _; } let offset = crate::mem::offset_of!(libc::sockaddr_un, sun_path); @@ -204,10 +206,14 @@ fn iter() { #[cfg_attr(target_os = "vxworks", ignore = "Unix sockets are not implemented in VxWorks")] fn long_path() { let dir = tmpdir(); - let socket_path = dir.path().join( - "asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfa\ - sasdfasdfasdasdfasdfasdfadfasdfasdfasdfasdfasdf", + // +1 for separator byte on join + let dir_len = dir.path().as_os_str().len() + 1; + let sock = format!( + "sock{}", + vec!['a'; SUN_PATH_MAX_LEN.saturating_sub(dir_len)].into_iter().collect::() ); + let socket_path = dir.path().join(sock); + match UnixStream::connect(&socket_path) { Err(ref e) if e.kind() == io::ErrorKind::InvalidInput => {} Err(e) => panic!("unexpected error {e}"), From f3dca0c6527be549956f15a330b713a59f07906f Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Sun, 20 Sep 2026 02:02:47 -0400 Subject: [PATCH 3/6] Updated msghdr's msg_namelen to be SOCK_MAX_SIZE within recv_vectored_with_ancillary_from --- library/std/src/os/unix/net/ancillary.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/os/unix/net/ancillary.rs b/library/std/src/os/unix/net/ancillary.rs index f8d0372d2386f..b2021f32883b4 100644 --- a/library/std/src/os/unix/net/ancillary.rs +++ b/library/std/src/os/unix/net/ancillary.rs @@ -41,7 +41,7 @@ pub(super) fn recv_vectored_with_ancillary_from( let mut msg_name: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; let mut msg: libc::msghdr = zeroed(); msg.msg_name = (&raw mut msg_name) as *mut _; - msg.msg_namelen = size_of::() as libc::socklen_t; + msg.msg_namelen = SOCK_MAX_SIZE as libc::socklen_t; msg.msg_iov = bufs.as_mut_ptr().cast(); msg.msg_iovlen = bufs.len() as _; msg.msg_controllen = ancillary.buffer.len() as _; From 73e4b831a6c30205f7033a1e3752c318a880d370 Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Thu, 24 Sep 2026 01:38:07 -0400 Subject: [PATCH 4/6] Created an internal helper SockaddrBuf struct and refactored SockAddr API to utilize SockaddrBuf as a medium to pass as a mutable or const input to C socket interop calls --- library/std/src/os/unix/net/addr.rs | 270 +++++++++++++++-------- library/std/src/os/unix/net/ancillary.rs | 21 +- library/std/src/os/unix/net/datagram.rs | 54 ++--- library/std/src/os/unix/net/listener.rs | 28 +-- library/std/src/os/unix/net/stream.rs | 24 +- library/std/src/os/unix/net/tests.rs | 25 --- 6 files changed, 232 insertions(+), 190 deletions(-) diff --git a/library/std/src/os/unix/net/addr.rs b/library/std/src/os/unix/net/addr.rs index 12b13f4a0b82e..a61ae6df312e7 100644 --- a/library/std/src/os/unix/net/addr.rs +++ b/library/std/src/os/unix/net/addr.rs @@ -24,8 +24,8 @@ mod libc { } cfg_if::cfg_if! { - if #[cfg(any(target_os = "macos", target_os = "dragonfly"))] { - // MacOS and DragonFly utilize `SOCK_MAXADDRLEN` to define + if #[cfg(any(target_vendor = "apple", target_os = "dragonfly"))] { + // Apple and DragonFly utilize `SOCK_MAXADDRLEN` to define // the maximum size that the `sockaddr_un` struct could be. // SOCK_MAXADDRLEN = 255 on these platforms, and it's based on // sizeof(sa_len) (a u8) + sizeof(sun_family) (a u8) + 253 bytes @@ -41,15 +41,25 @@ cfg_if::cfg_if! { } } -// Offset to `libc::sockaddr_un.sun_family` +// Offset to `libc::sockaddr_un.sun_family` in bytes pub(crate) const SUN_FAMILY_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_family); -// Offset to `libc::sockaddr_un.sun_path` +// Offset to `libc::sockaddr_un.sun_path` in bytes pub(crate) const SUN_PATH_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_path); -// This represents the maximum number of characters + 1 allowed to be +// This represents the maximum number of bytes + 1 allowed to be // stored in the socket path (e.g. 254 for NetBSD because it allows 253 // valid characters for its path, 104 for FreeBSD because it allows up to // 103 characters for its path) pub(crate) const SUN_PATH_MAX_LEN: usize = SOCK_MAX_SIZE - SUN_PATH_OFFSET; +// Platform-dependent error message when user provides a longer socket address path +// than what the OS allows for. +const LEN_EXCEEDED_MSG: &'static str = + cfg_select! { + any(target_os = "macos", target_os = "dragonfly") => { + "path must be shorter than SOCK_MAXADDRLEN - 1" + } + target_os = "netbsd" => "path must be shorter than UCHAR_MAX - 1", + _ => "path must be shorter than SUN_LEN", + }; enum AddressKind<'a> { Unnamed, @@ -57,13 +67,21 @@ enum AddressKind<'a> { Abstract(&'a ByteStr), } -/// An address associated with a Unix socket. +/// An internal helper struct that provides a `len` field describing the actual filled size of the socket +/// address and a `buf` field that contains an internal platform-agnostic struct of `libc::sockaddr_un`. /// -/// # Examples +/// On BSD platforms, `libc::sockaddr_un` is defined as: +/// struct sockaddr_un { +/// sun_len: u8, +/// sun_family: sa_family_t, +/// sun_path: [c_char; 104], +/// } /// -#[cfg_attr(target_family = "unix", doc = "```")] -#[cfg_attr(not(target_family = "unix"), doc = "```ignore (needs unix)")] -/// use std::os::unix::net::UnixListener; +/// On Linux and other platforms, `libc::sockaddr_un` is defined as: +/// struct sockaddr_un { +/// sun_family: sa_family_t, +/// sun_path: [c_char; 108], +/// } /// /// let socket = match UnixListener::bind("/tmp/sock") { /// Ok(sock) => sock, @@ -75,62 +93,42 @@ enum AddressKind<'a> { /// let addr = socket.local_addr().expect("`UnixListener::local_addr` should not fail"); /// ``` #[derive(Clone)] -#[stable(feature = "unix_socket", since = "1.10.0")] -pub struct SocketAddr { +#[repr(C)] +pub(super) struct SockaddrBuf { /// Size of the socket address, `sun_family` and `sun_path` /// fields from `libc::sockaddr_un` included - pub(super) len: libc::socklen_t, + len: libc::socklen_t, /// Stack allocated buffer that contains full size of what `libc::sockaddr_un` /// could be (as `sun_path` field defined for `sockaddr_un` does not represent /// the maximum path length of a Unix Domain socket name) - pub(super) addr: [u8; SOCK_MAX_SIZE], + buf: [u8; SOCK_MAX_SIZE], + /// Make it sound to cast the struct to/from a `sockaddr_un` + align: [libc::sockaddr_un; 0], } -impl SocketAddr { - pub(super) fn default() -> SocketAddr { - let mut addr: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; +impl SockaddrBuf { + /* API for use in Rust */ + + /// This returns an empty Unix Domain socket address with a + /// length value containing the current size of the `SockaddrUn` + pub(super) fn default() -> SockaddrBuf { + let len = 0; + let mut sockaddr_un: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; let sun_family = (libc::AF_UNIX as libc::sa_family_t).to_ne_bytes(); - addr[SUN_FAMILY_OFFSET..SUN_FAMILY_OFFSET + size_of::()] + sockaddr_un[SUN_FAMILY_OFFSET..SUN_FAMILY_OFFSET + size_of::()] .copy_from_slice(&sun_family); - SocketAddr { len: SUN_PATH_OFFSET as libc::socklen_t, addr } + SockaddrBuf { len, buf: sockaddr_un, align: unsafe { [mem::zeroed(); 0] } } } - pub(super) fn new(f: F) -> io::Result - where - F: FnOnce(*mut [u8; SOCK_MAX_SIZE], *mut libc::socklen_t) -> libc::c_int, - { - let mut addr: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; - let mut len = size_of::() as libc::socklen_t; - cvt(f((&raw mut addr) as *mut _, &mut len))?; - SocketAddr::from_parts(addr, len) + /// Extracts the `sun_path` value from the socket address buffer. + fn path(&self) -> &[u8] { + &self.buf[SUN_PATH_OFFSET..] } - pub(super) fn from_path(path: &Path) -> io::Result { - let mut sockaddr = SocketAddr::default(); - - let bytes = path.as_os_str().as_bytes(); - - if bytes.contains(&0) { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "paths must not contain interior null bytes", - )); - } - - if bytes.len() >= SUN_PATH_MAX_LEN { - const LEN_EXCEEDED_MSG: &'static str = - cfg_select! { - any(target_os = "macos", target_os = "dragonfly") => { - "path must be shorter than SOCK_MAXADDRLEN - 1" - } - target_os = "netbsd" => "path must be shorter than UCHAR_MAX - 1", - _ => "path must be shorter than SUN_LEN", - }; - return Err(io::const_error!(io::ErrorKind::InvalidInput, LEN_EXCEEDED_MSG)); - } - - sockaddr.set_path(bytes); + /// Sets the socket address path for `SockaddrUn` in `SocketAddr`. + fn set_path(&mut self, bytes: &[u8]) { + self.buf[SUN_PATH_OFFSET..SUN_PATH_OFFSET + bytes.len()].copy_from_slice(bytes); let mut len = SUN_PATH_OFFSET + bytes.len(); #[cfg(any( @@ -172,52 +170,98 @@ impl SocketAddr { // Even though len here is a `usize` and `libc::socklen_t` is a `u32` // our len value should be limited to whatever value a `u32` can hold - sockaddr.set_len(len as libc::socklen_t); + self.len = len as libc::socklen_t; + } - Ok(sockaddr) + /// Extracts the `sun_family` value from the socket address buffer. + fn sun_family(&self) -> libc::sa_family_t { + let sun_family_array = self.buf[SUN_FAMILY_OFFSET..SUN_FAMILY_OFFSET + size_of::()] + .try_into() + .expect("Slice should have exactly the same number of bytes extracted as the size of libc::sa_family_t"); + libc::sa_family_t::from_ne_bytes(sun_family_array) } - #[rustdoc_skip_body] - pub(super) fn from_parts( - addr: [u8; SOCK_MAX_SIZE], - mut len: libc::socklen_t, - ) -> io::Result { + /* API for use in sockets in abstract namespace + interop with C */ + + /// Types passed to libc calls that read a socket address. + pub(super) fn as_libc_input(&self) -> (*const libc::sockaddr, libc::socklen_t) { + (self.buf.as_ptr().cast(), self.len) + } + + /// Types passed to libc calls that write a socket address. Length is first set to the + /// max allowed. + /// + /// Note that using this API _must_ be followed by a call to `update_from_libc`. + // Note that returning two raw pointers from a single `&mut self` function call rather than + // two `&mut self -> *mut T` function calls is required to pass Miri with stacked borrows. + pub(super) fn as_max_libc_output(&mut self) -> (*mut libc::sockaddr, *mut libc::socklen_t) { + // Even though len here is a `usize` and `libc::socklen_t` is a `u32` + // our len value should be limited to whatever value a `u32` can hold + self.len = SOCK_MAX_SIZE as libc::socklen_t; + + (self.buf.as_mut_ptr().cast(), &mut self.len) + } + + /// Some platforms encode things differently. Call this after a `libc` call to adjust length + /// and validate as needed. + pub(super) fn update_from_libc(&mut self) -> io::Result<()> { + // let mut len = self.len; if cfg!(target_os = "openbsd") { // on OpenBSD, getsockname(2) returns the actual size of the socket address, // and not the len of the content. Figure out the length for ourselves. // https://marc.info/?l=openbsd-bugs&m=170105481926736&w=2 - let sun_path = &addr[SUN_PATH_OFFSET..]; - len = core::slice::memchr::memchr(0, sun_path) - .map_or(len, |new_len| (new_len + SUN_PATH_OFFSET) as libc::socklen_t); + let sun_path = self.path(); + self.len = core::slice::memchr::memchr(0, sun_path) + .map_or(self.len, |new_len| (new_len + SUN_PATH_OFFSET) as libc::socklen_t); } - len = len.min(size_of::() as libc::socklen_t); - - if len == 0 { + if self.len == 0 { // When there is a datagram from unnamed unix socket // linux returns zero bytes of address - len = SUN_PATH_OFFSET as libc::socklen_t; // i.e., zero-length address - } else if SocketAddr::sun_family_from_addr(&addr) != libc::AF_UNIX as libc::sa_family_t { + self.len = SUN_PATH_OFFSET as libc::socklen_t; // i.e., zero-length address + } else if self.sun_family() != libc::AF_UNIX as libc::sa_family_t { return Err(io::const_error!( io::ErrorKind::InvalidInput, "file descriptor did not correspond to a Unix socket", )); } - Ok(SocketAddr { len, addr }) - } - - fn sun_family_from_addr(addr: &[u8; SOCK_MAX_SIZE]) -> libc::sa_family_t { - let sun_family_array = addr[SUN_FAMILY_OFFSET..SUN_FAMILY_OFFSET + size_of::()].try_into().expect("Slice should have exactly the same number of bytes extracted as the size of libc::sa_family_t"); - libc::sa_family_t::from_ne_bytes(sun_family_array) + Ok(()) } +} - fn set_path(&mut self, path_bytes: &[u8]) { - self.addr[SUN_PATH_OFFSET..SUN_PATH_OFFSET + path_bytes.len()].copy_from_slice(path_bytes); - } +/// An address associated with a Unix socket. +/// +/// # Examples +/// +#[cfg_attr(target_family = "unix", doc = "```")] +#[cfg_attr(not(target_family = "unix"), doc = "```ignore (needs unix)")] +/// use std::os::unix::net::UnixListener; +/// +/// let socket = match UnixListener::bind("/tmp/sock") { +/// Ok(sock) => sock, +/// Err(e) => { +/// println!("Couldn't bind: {e:?}"); +/// return +/// } +/// }; +/// let addr = socket.local_addr().expect("Couldn't get local address"); +/// ``` +#[derive(Clone)] +#[stable(feature = "unix_socket", since = "1.10.0")] +pub struct SocketAddr { + pub(super) sock: SockaddrBuf, +} - fn set_len(&mut self, len: libc::socklen_t) { - self.len = len; +impl SocketAddr { + pub(super) fn new(f: F) -> io::Result + where + F: FnOnce(&mut SockaddrBuf) -> libc::c_int, + { + let mut sock = SockaddrBuf::default(); + cvt(f(&mut sock))?; + sock.update_from_libc()?; + Ok(SocketAddr { sock }) } /// Constructs a `SockAddr` with the family `AF_UNIX` and the provided path. @@ -257,6 +301,27 @@ impl SocketAddr { SocketAddr::from_path(path.as_ref()) } + /// Constructs a Unix Domain socket address from a given `Path` + pub(super) fn from_path(path: &Path) -> io::Result { + let bytes = path.as_os_str().as_bytes(); + + if bytes.contains(&0) { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "paths must not contain interior null bytes", + )); + } + + if bytes.len() >= SUN_PATH_MAX_LEN { + return Err(io::const_error!(io::ErrorKind::InvalidInput, LEN_EXCEEDED_MSG)); + } + + let mut sock = SockaddrBuf::default(); + sock.set_path(bytes); + + Ok(SocketAddr { sock }) + } + /// Returns `true` if the address is unnamed. /// /// # Examples @@ -334,8 +399,8 @@ impl SocketAddr { #[rustdoc_skip_body] fn address(&self) -> AddressKind<'_> { - let len = self.len as usize - SUN_PATH_OFFSET; - let path = &self.addr[SUN_PATH_OFFSET..]; + let len = self.sock.len as usize - SUN_PATH_OFFSET; + let path = self.sock.path(); // macOS seems to return a len of 16 and a zeroed sun_path for unnamed addresses if len == 0 @@ -369,24 +434,39 @@ impl linux_ext::addr::SocketAddrExt for SocketAddr { N: AsRef<[u8]>, { let name = name.as_ref(); - let mut sockaddr = SocketAddr::default(); - + let mut sock = SockaddrBuf::default(); + + // Abstract socket address paths are not nul terminated! + // (Hence the > instead of >=) + // See Linux manpage: https://man7.org/linux/man-pages/man7/unix.7.html + // "The socket's address in this namespace is given by the additional + // bytes in sun_path that are covered by the specified length of the + // address structure. (Null bytes in the name have no special + // significance.)" if name.len() + 1 > SUN_PATH_MAX_LEN { - const LEN_EXCEEDED_MSG: &'static str = - cfg_select! { - any(target_os = "macos", target_os = "dragonfly") => { - "path must be shorter than SOCK_MAXADDRLEN - 2" - } - target_os = "netbsd" => "path must be shorter than UCHAR_MAX - 2", - _ => "path must be shorter than SUN_LEN - 1", - }; - - return Err(io::const_error!(io::ErrorKind::InvalidInput, LEN_EXCEEDED_MSG,)); + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "path must be shorter than SUN_LEN - 1", + )); } - sockaddr.addr[SUN_PATH_OFFSET + 1..SUN_PATH_OFFSET + 1 + name.len()].copy_from_slice(name); - sockaddr.len = (SUN_PATH_OFFSET + 1 + name.len()) as libc::socklen_t; - Ok(sockaddr) + let (addr, len) = sock.as_max_libc_output(); + + // SAFETY: `name` and `addr` are not overlapping and point to valid + // memory. the checks above prevents any out of bounds write from + // occurring to `addr`. + // Note: we do this over using `SockaddrBuf::set_path` because on Linux + // it appends a nul byte and accounts that in the socket address size/length + // Our true path length for abstract socket address is `1 + name.len()` + unsafe { + crate::ptr::copy_nonoverlapping( + name.as_ptr(), + (addr as *mut u8).add(SUN_PATH_OFFSET + 1), + name.len(), + ); + *len = (SUN_PATH_OFFSET + 1 + name.len()) as libc::socklen_t + } + Ok(SocketAddr { sock }) } } diff --git a/library/std/src/os/unix/net/ancillary.rs b/library/std/src/os/unix/net/ancillary.rs index b2021f32883b4..f2e2718f7caa9 100644 --- a/library/std/src/os/unix/net/ancillary.rs +++ b/library/std/src/os/unix/net/ancillary.rs @@ -1,6 +1,6 @@ // FIXME: This is currently disabled on *BSD. -use super::SocketAddr; +use super::{SockaddrBuf, SocketAddr}; use crate::io::{self, IoSlice, IoSliceMut}; use crate::macros::rustdoc_skip_body; use crate::marker::PhantomData; @@ -38,9 +38,10 @@ pub(super) fn recv_vectored_with_ancillary_from( ancillary: &mut SocketAncillary<'_>, ) -> io::Result<(usize, bool, io::Result)> { unsafe { - let mut msg_name: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; + let mut sock = SockaddrBuf::default(); + let (addr, len) = sock.as_max_libc_output(); let mut msg: libc::msghdr = zeroed(); - msg.msg_name = (&raw mut msg_name) as *mut _; + msg.msg_name = addr as *mut _; msg.msg_namelen = SOCK_MAX_SIZE as libc::socklen_t; msg.msg_iov = bufs.as_mut_ptr().cast(); msg.msg_iovlen = bufs.len() as _; @@ -56,9 +57,11 @@ pub(super) fn recv_vectored_with_ancillary_from( ancillary.truncated = msg.msg_flags & libc::MSG_CTRUNC == libc::MSG_CTRUNC; let truncated = msg.msg_flags & libc::MSG_TRUNC == libc::MSG_TRUNC; - let addr = SocketAddr::from_parts(msg_name, msg.msg_namelen); - Ok((count, truncated, addr)) + *len = msg.msg_namelen; + sock.update_from_libc()?; + + Ok((count, truncated, Ok(SocketAddr { sock }))) } } @@ -70,12 +73,12 @@ pub(super) fn send_vectored_with_ancillary_to( ancillary: &mut SocketAncillary<'_>, ) -> io::Result { unsafe { - let mut sockaddr = - if let Some(path) = path { SocketAddr::from_path(path)? } else { zeroed() }; + let sockaddr = if let Some(path) = path { SocketAddr::from_path(path)? } else { zeroed() }; + let (addr, len) = sockaddr.sock.as_libc_input(); let mut msg: libc::msghdr = zeroed(); - msg.msg_name = (&raw mut sockaddr.addr) as *mut _; - msg.msg_namelen = sockaddr.len; + msg.msg_name = addr as *mut _; + msg.msg_namelen = len; msg.msg_iov = bufs.as_ptr() as *mut _; msg.msg_iovlen = bufs.len() as _; msg.msg_controllen = ancillary.length as _; diff --git a/library/std/src/os/unix/net/datagram.rs b/library/std/src/os/unix/net/datagram.rs index f26ac76d07514..b822c78b1f331 100644 --- a/library/std/src/os/unix/net/datagram.rs +++ b/library/std/src/os/unix/net/datagram.rs @@ -102,12 +102,9 @@ impl UnixDatagram { unsafe { let socket = UnixDatagram::unbound()?; let sockaddr = SocketAddr::from_path(path.as_ref())?; + let (addr, len) = sockaddr.sock.as_libc_input(); - cvt(libc::bind( - socket.as_raw_fd(), - (&raw const sockaddr.addr) as *const _, - sockaddr.len as _, - ))?; + cvt(libc::bind(socket.as_raw_fd(), addr, len))?; Ok(socket) } @@ -138,11 +135,8 @@ impl UnixDatagram { pub fn bind_addr(socket_addr: &SocketAddr) -> io::Result { unsafe { let socket = UnixDatagram::unbound()?; - cvt(libc::bind( - socket.as_raw_fd(), - (&raw const socket_addr.addr) as *const _, - socket_addr.len as _, - ))?; + let (addr, len) = socket_addr.sock.as_libc_input(); + cvt(libc::bind(socket.as_raw_fd(), addr, len))?; Ok(socket) } } @@ -224,12 +218,9 @@ impl UnixDatagram { pub fn connect>(&self, path: P) -> io::Result<()> { unsafe { let sockaddr = SocketAddr::from_path(path.as_ref())?; + let (addr, len) = sockaddr.sock.as_libc_input(); - cvt(libc::connect( - self.as_raw_fd(), - (&raw const sockaddr.addr) as *const _, - sockaddr.len, - ))?; + cvt(libc::connect(self.as_raw_fd(), addr, len))?; } Ok(()) } @@ -260,11 +251,8 @@ impl UnixDatagram { #[stable(feature = "unix_socket_abstract", since = "1.70.0")] pub fn connect_addr(&self, socket_addr: &SocketAddr) -> io::Result<()> { unsafe { - cvt(libc::connect( - self.as_raw_fd(), - (&raw const socket_addr.addr) as *const _, - socket_addr.len, - ))?; + let (addr, len) = socket_addr.sock.as_libc_input(); + cvt(libc::connect(self.as_raw_fd(), addr, len))?; } Ok(()) } @@ -309,8 +297,9 @@ impl UnixDatagram { #[stable(feature = "unix_socket", since = "1.10.0")] #[rustdoc_skip_body] pub fn local_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { - libc::getsockname(self.as_raw_fd(), addr as *mut libc::sockaddr, len) + SocketAddr::new(|sock| unsafe { + let (addr, len) = sock.as_max_libc_output(); + libc::getsockname(self.as_raw_fd(), addr, len) }) } @@ -337,8 +326,9 @@ impl UnixDatagram { #[stable(feature = "unix_socket", since = "1.10.0")] #[rustdoc_skip_body] pub fn peer_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { - libc::getpeername(self.as_raw_fd(), addr as *mut libc::sockaddr, len) + SocketAddr::new(|sockaddr| unsafe { + let (addr, len) = sockaddr.as_max_libc_output(); + libc::getpeername(self.as_raw_fd(), addr, len) }) } @@ -348,13 +338,15 @@ impl UnixDatagram { flags: core::ffi::c_int, ) -> io::Result<(usize, SocketAddr)> { let mut count = 0; - let addr = SocketAddr::new(|addr, len| unsafe { + let addr = SocketAddr::new(|sockaddr| unsafe { + let (addr, len) = sockaddr.as_max_libc_output(); + count = libc::recvfrom( self.as_raw_fd(), buf.as_mut_ptr() as *mut _, buf.len(), flags, - addr as *mut libc::sockaddr, + addr, len, ); if count > 0 { @@ -547,14 +539,15 @@ impl UnixDatagram { pub fn send_to>(&self, buf: &[u8], path: P) -> io::Result { unsafe { let sockaddr = SocketAddr::from_path(path.as_ref())?; + let (addr, len) = sockaddr.sock.as_libc_input(); let count = cvt(libc::sendto( self.as_raw_fd(), buf.as_ptr() as *const _, buf.len(), MSG_NOSIGNAL, - (&raw const sockaddr.addr) as *const _, - sockaddr.len, + addr, + len, ))?; Ok(count as usize) } @@ -584,13 +577,14 @@ impl UnixDatagram { #[stable(feature = "unix_socket_abstract", since = "1.70.0")] pub fn send_to_addr(&self, buf: &[u8], socket_addr: &SocketAddr) -> io::Result { unsafe { + let (addr, len) = socket_addr.sock.as_libc_input(); let count = cvt(libc::sendto( self.as_raw_fd(), buf.as_ptr() as *const _, buf.len(), MSG_NOSIGNAL, - (&raw const socket_addr.addr) as *const _, - socket_addr.len, + addr, + len, ))?; Ok(count as usize) } diff --git a/library/std/src/os/unix/net/listener.rs b/library/std/src/os/unix/net/listener.rs index f1f7be1754f33..4b3f6471816f2 100644 --- a/library/std/src/os/unix/net/listener.rs +++ b/library/std/src/os/unix/net/listener.rs @@ -77,6 +77,7 @@ impl UnixListener { unsafe { let inner = Socket::new(libc::AF_UNIX, libc::SOCK_STREAM)?; let sockaddr = SocketAddr::from_path(path.as_ref())?; + let (addr, len) = sockaddr.sock.as_libc_input(); #[cfg(any( target_os = "windows", target_os = "redox", @@ -107,11 +108,7 @@ impl UnixListener { )))] const backlog: libc::c_int = libc::SOMAXCONN; - cvt(libc::bind( - inner.as_inner().as_raw_fd(), - (&raw const sockaddr.addr) as *const _, - sockaddr.len as _, - ))?; + cvt(libc::bind(inner.as_inner().as_raw_fd(), addr, len))?; cvt(libc::listen(inner.as_inner().as_raw_fd(), backlog))?; Ok(UnixListener(inner)) @@ -145,15 +142,12 @@ impl UnixListener { pub fn bind_addr(socket_addr: &SocketAddr) -> io::Result { unsafe { let inner = Socket::new(libc::AF_UNIX, libc::SOCK_STREAM)?; + let (addr, len) = socket_addr.sock.as_libc_input(); #[cfg(target_os = "linux")] const backlog: core::ffi::c_int = -1; #[cfg(not(target_os = "linux"))] const backlog: core::ffi::c_int = 128; - cvt(libc::bind( - inner.as_raw_fd(), - (&raw const socket_addr.addr) as *const _, - socket_addr.len as _, - ))?; + cvt(libc::bind(inner.as_raw_fd(), addr, len))?; cvt(libc::listen(inner.as_raw_fd(), backlog))?; Ok(UnixListener(inner)) } @@ -185,11 +179,10 @@ impl UnixListener { /// ``` #[stable(feature = "unix_socket", since = "1.10.0")] pub fn accept(&self) -> io::Result<(UnixStream, SocketAddr)> { - let mut storage: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; - let mut len = size_of_val(&storage) as libc::socklen_t; - let sock = self.0.accept((&raw mut storage) as *mut _, &mut len)?; - let addr = SocketAddr::from_parts(storage, len)?; - Ok((UnixStream(sock), addr)) + let mut sockaddr_buf = SockaddrBuf::default(); + let (addr, len) = sockaddr_buf.as_max_libc_output(); + let sock = self.0.accept(addr, len)?; + Ok((UnixStream(sock), SocketAddr { sock: sockaddr_buf })) } /// Creates a new independently owned handle to the underlying socket. @@ -232,8 +225,9 @@ impl UnixListener { #[stable(feature = "unix_socket", since = "1.10.0")] #[rustdoc_skip_body] pub fn local_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { - libc::getsockname(self.as_raw_fd(), addr as *mut libc::sockaddr, len) + SocketAddr::new(|sockaddr| unsafe { + let (addr, len) = sockaddr.as_max_libc_output(); + libc::getsockname(self.as_raw_fd(), addr, len) }) } diff --git a/library/std/src/os/unix/net/stream.rs b/library/std/src/os/unix/net/stream.rs index 106656fd8a1c4..4a60c4f99081e 100644 --- a/library/std/src/os/unix/net/stream.rs +++ b/library/std/src/os/unix/net/stream.rs @@ -120,12 +120,9 @@ impl UnixStream { unsafe { let inner = Socket::new(libc::AF_UNIX, libc::SOCK_STREAM)?; let sockaddr = SocketAddr::from_path(path.as_ref())?; + let (addr, len) = sockaddr.sock.as_libc_input(); - cvt(libc::connect( - inner.as_raw_fd(), - (&raw const sockaddr.addr) as *const _, - sockaddr.len, - ))?; + cvt(libc::connect(inner.as_raw_fd(), addr, len))?; Ok(UnixStream(inner)) } } @@ -158,11 +155,8 @@ impl UnixStream { pub fn connect_addr(socket_addr: &SocketAddr) -> io::Result { unsafe { let inner = Socket::new(libc::AF_UNIX, libc::SOCK_STREAM)?; - cvt(libc::connect( - inner.as_raw_fd(), - (&raw const socket_addr.addr) as *const _, - socket_addr.len, - ))?; + let (addr, len) = socket_addr.sock.as_libc_input(); + cvt(libc::connect(inner.as_raw_fd(), addr, len))?; Ok(UnixStream(inner)) } } @@ -232,8 +226,9 @@ impl UnixStream { #[stable(feature = "unix_socket", since = "1.10.0")] #[rustdoc_skip_body] pub fn local_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { - libc::getsockname(self.as_raw_fd(), addr as *mut libc::sockaddr, len) + SocketAddr::new(|sockaddr| unsafe { + let (addr, len) = sockaddr.as_max_libc_output(); + libc::getsockname(self.as_raw_fd(), addr, len) }) } @@ -254,8 +249,9 @@ impl UnixStream { #[stable(feature = "unix_socket", since = "1.10.0")] #[rustdoc_skip_body] pub fn peer_addr(&self) -> io::Result { - SocketAddr::new(|addr, len| unsafe { - libc::getpeername(self.as_raw_fd(), addr as *mut libc::sockaddr, len) + SocketAddr::new(|sockaddr| unsafe { + let (addr, len) = sockaddr.as_max_libc_output(); + libc::getpeername(self.as_raw_fd(), addr, len) }) } diff --git a/library/std/src/os/unix/net/tests.rs b/library/std/src/os/unix/net/tests.rs index ebd38711b3a0e..d0d1cba5f825c 100644 --- a/library/std/src/os/unix/net/tests.rs +++ b/library/std/src/os/unix/net/tests.rs @@ -29,31 +29,6 @@ fn sock_addr_from_pathname() { assert_eq!(address.as_pathname(), Some(Path::new("/path/to/socket"))); } -// the trailing NUL is not counted in the reported length on freebsd, netbsd -// and qnx, and a caller may bind(2) without one anywhere -#[test] -fn sock_addr_without_trailing_nul() { - const PATH: &[u8] = b"/path/to/socket"; - - let mut addr: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; - let sun_family = (libc::AF_UNIX as libc::sa_family_t).to_ne_bytes(); - addr[SUN_FAMILY_OFFSET..SUN_FAMILY_OFFSET + size_of::()] - .copy_from_slice(&sun_family); - - for (dst, &src) in addr[SUN_PATH_OFFSET..].iter_mut().zip(PATH) { - *dst = src as _; - } - let offset = crate::mem::offset_of!(libc::sockaddr_un, sun_path); - - // length excluding the NUL, as reported by freebsd, netbsd and qnx - let address = or_panic!(SocketAddr::from_parts(addr, (offset + PATH.len()) as _)); - assert_eq!(address.as_pathname(), Some(Path::new("/path/to/socket"))); - - // length including the NUL, as reported by linux - let address = or_panic!(SocketAddr::from_parts(addr, (offset + PATH.len() + 1) as _)); - assert_eq!(address.as_pathname(), Some(Path::new("/path/to/socket"))); -} - #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn sock_addr_pathname_fills_sun_path() { From 9ca57c8ef39aeede77599aec2f1490cee20814e2 Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Thu, 24 Sep 2026 02:59:14 -0400 Subject: [PATCH 5/6] Refactored SockAddrExt::from_abstract_name and SockAddr::address to mask construction and access to certain fields of the sockaddr_un through SockaddrBuf::abstract_path and SockaddrBuf::set_abstract_path --- library/std/src/os/unix/net/addr.rs | 45 +++++++++++++++------------- library/std/src/os/unix/net/tests.rs | 19 ------------ 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/library/std/src/os/unix/net/addr.rs b/library/std/src/os/unix/net/addr.rs index a61ae6df312e7..eb8582a593be5 100644 --- a/library/std/src/os/unix/net/addr.rs +++ b/library/std/src/os/unix/net/addr.rs @@ -110,14 +110,17 @@ impl SockaddrBuf { /* API for use in Rust */ /// This returns an empty Unix Domain socket address with a - /// length value containing the current size of the `SockaddrUn` + /// length value containing the current size of the `sockaddr_un` pub(super) fn default() -> SockaddrBuf { - let len = 0; let mut sockaddr_un: [u8; SOCK_MAX_SIZE] = [0; SOCK_MAX_SIZE]; let sun_family = (libc::AF_UNIX as libc::sa_family_t).to_ne_bytes(); sockaddr_un[SUN_FAMILY_OFFSET..SUN_FAMILY_OFFSET + size_of::()] .copy_from_slice(&sun_family); + // Size of the socket address is not 0 since we have an initialized + // `sun_family`/`sun_len` + let len = SUN_PATH_OFFSET as libc::socklen_t; + SockaddrBuf { len, buf: sockaddr_un, align: unsafe { [mem::zeroed(); 0] } } } @@ -126,7 +129,7 @@ impl SockaddrBuf { &self.buf[SUN_PATH_OFFSET..] } - /// Sets the socket address path for `SockaddrUn` in `SocketAddr`. + /// Sets the socket address path for `sockaddr_un` in `SocketaddrBuf`. fn set_path(&mut self, bytes: &[u8]) { self.buf[SUN_PATH_OFFSET..SUN_PATH_OFFSET + bytes.len()].copy_from_slice(bytes); @@ -173,6 +176,21 @@ impl SockaddrBuf { self.len = len as libc::socklen_t; } + /// Extracts the `sun_path` value from the abstract socket address buffer (excludes initial + /// nul byte). + fn abstract_path(&self) -> &[u8] { + &self.buf[SUN_PATH_OFFSET + 1..self.len as usize] + } + + #[cfg(any(target_os = "android", target_os = "linux", target_os = "cygwin"))] + /// Sets the abstract socket address path for `sockaddr_un` in `SocketaddrBuf`. + /// Only used on Android, Linux, and Cygwin. + fn set_abstract_path(&mut self, bytes: &[u8]) { + // +1 because the first byte in the sun_path for abstract sockets is a nul byte + self.buf[SUN_PATH_OFFSET + 1..SUN_PATH_OFFSET + 1 + bytes.len()].copy_from_slice(bytes); + self.len = (SUN_PATH_OFFSET + 1 + bytes.len()) as libc::socklen_t; + } + /// Extracts the `sun_family` value from the socket address buffer. fn sun_family(&self) -> libc::sa_family_t { let sun_family_array = self.buf[SUN_FAMILY_OFFSET..SUN_FAMILY_OFFSET + size_of::()] @@ -305,7 +323,7 @@ impl SocketAddr { pub(super) fn from_path(path: &Path) -> io::Result { let bytes = path.as_os_str().as_bytes(); - if bytes.contains(&0) { + if core::slice::memchr::memchr(0, bytes).is_some() { return Err(io::const_error!( io::ErrorKind::InvalidInput, "paths must not contain interior null bytes", @@ -409,7 +427,7 @@ impl SocketAddr { { AddressKind::Unnamed } else if path[0] == 0 { - AddressKind::Abstract(ByteStr::from_bytes(&path[1..len])) + AddressKind::Abstract(ByteStr::from_bytes(self.sock.abstract_path())) } else { // linux adds a trailing NUL and counts it in the length, freebsd, netbsd // and qnx do not, and a caller may bind(2) without one either. unix(7) @@ -450,22 +468,7 @@ impl linux_ext::addr::SocketAddrExt for SocketAddr { )); } - let (addr, len) = sock.as_max_libc_output(); - - // SAFETY: `name` and `addr` are not overlapping and point to valid - // memory. the checks above prevents any out of bounds write from - // occurring to `addr`. - // Note: we do this over using `SockaddrBuf::set_path` because on Linux - // it appends a nul byte and accounts that in the socket address size/length - // Our true path length for abstract socket address is `1 + name.len()` - unsafe { - crate::ptr::copy_nonoverlapping( - name.as_ptr(), - (addr as *mut u8).add(SUN_PATH_OFFSET + 1), - name.len(), - ); - *len = (SUN_PATH_OFFSET + 1 + name.len()) as libc::socklen_t - } + sock.set_abstract_path(name); Ok(SocketAddr { sock }) } } diff --git a/library/std/src/os/unix/net/tests.rs b/library/std/src/os/unix/net/tests.rs index d0d1cba5f825c..3e44f39c0d131 100644 --- a/library/std/src/os/unix/net/tests.rs +++ b/library/std/src/os/unix/net/tests.rs @@ -29,25 +29,6 @@ fn sock_addr_from_pathname() { assert_eq!(address.as_pathname(), Some(Path::new("/path/to/socket"))); } -#[test] -#[cfg(any(target_os = "android", target_os = "linux"))] -fn sock_addr_pathname_fills_sun_path() { - use crate::ffi::OsStr; - use crate::os::unix::ffi::OsStrExt; - - let mut addr: libc::sockaddr_un = unsafe { crate::mem::zeroed() }; - addr.sun_family = libc::AF_UNIX as libc::sa_family_t; - let mut path = vec![b'a'; addr.sun_path.len()]; - path[0] = b'/'; - for (dst, &src) in addr.sun_path.iter_mut().zip(&path) { - *dst = src as _; - } - let offset = crate::mem::offset_of!(libc::sockaddr_un, sun_path); - - let address = or_panic!(SocketAddr::from_parts(addr, (offset + path.len() + 1) as _)); - assert_eq!(address.as_pathname(), Some(Path::new(OsStr::from_bytes(&path)))); -} - #[test] #[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets #[cfg_attr(target_os = "vxworks", ignore = "Unix sockets are not implemented in VxWorks")] From 0063fe1e61d6648a0e5ee81c37e163c73eae62cc Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Thu, 24 Sep 2026 16:45:55 -0400 Subject: [PATCH 6/6] More refactoring on SockaddrBuf's API and SocketAddr's internal implementations as well as minor changes or refactoring unix/net/listener.rs + unix/net/ancillar.rs --- library/std/src/os/unix/net/addr.rs | 173 +++++++++++++---------- library/std/src/os/unix/net/ancillary.rs | 8 +- library/std/src/os/unix/net/listener.rs | 6 +- 3 files changed, 102 insertions(+), 85 deletions(-) diff --git a/library/std/src/os/unix/net/addr.rs b/library/std/src/os/unix/net/addr.rs index eb8582a593be5..72999dc85d315 100644 --- a/library/std/src/os/unix/net/addr.rs +++ b/library/std/src/os/unix/net/addr.rs @@ -23,35 +23,50 @@ mod libc { } } -cfg_if::cfg_if! { - if #[cfg(any(target_vendor = "apple", target_os = "dragonfly"))] { +enum AddressKind<'a> { + Unnamed, + Pathname(&'a Path), + Abstract(&'a ByteStr), +} + +/// The max socket address size in bytes +const SOCK_MAX_SIZE: usize = cfg_select! { + any(target_vendor = "apple", target_os = "dragonfly") => { // Apple and DragonFly utilize `SOCK_MAXADDRLEN` to define // the maximum size that the `sockaddr_un` struct could be. // SOCK_MAXADDRLEN = 255 on these platforms, and it's based on // sizeof(sa_len) (a u8) + sizeof(sun_family) (a u8) + 253 bytes // (excluding nul). We add +1 for nul byte just in case. // See https://github.com/rust-lang/rust/issues/160684 - pub(crate) const SOCK_MAX_SIZE: usize = libc::SOCK_MAXADDRLEN as usize + 1; - } else if #[cfg(any(target_os = "netbsd"))] { + libc::SOCK_MAXADDRLEN as usize + 1 + } + any(target_os = "netbsd") => { // NetBSD uses `UCHAR_MAX` (essentially a `u8::MAX`) + 1 as defined by `sockaddr_big` here: - // https://github.com/IIJ-NetBSD/netbsd-src/blob/master/sys/sys/socket.h#L272-L287 - pub(crate) const SOCK_MAX_SIZE: usize = u8::MAX as usize + 1; - } else { - pub(crate) const SOCK_MAX_SIZE: usize = size_of::(); + // https://github.com/IIJ-NetBSD/netbsd-src/blob/cc2b5d89fa44/sys/sys/socket.h#L272-L287 + libc::c_uchar::MAX as usize + 1 } -} - -// Offset to `libc::sockaddr_un.sun_family` in bytes -pub(crate) const SUN_FAMILY_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_family); -// Offset to `libc::sockaddr_un.sun_path` in bytes -pub(crate) const SUN_PATH_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_path); -// This represents the maximum number of bytes + 1 allowed to be -// stored in the socket path (e.g. 254 for NetBSD because it allows 253 -// valid characters for its path, 104 for FreeBSD because it allows up to -// 103 characters for its path) + _ => { + // All other platforms max socket address size is based on + // the size of their sockaddr_un struct + size_of::() + } +}; + +// A check to see if it's safe to downcast SOCK_MAX_SIZE to libc::socklen_t +// (a u32) +const _: () = assert!(SOCK_MAX_SIZE <= libc::socklen_t::MAX as usize); + +/// Offset to `libc::sockaddr_un.sun_family` in bytes +const SUN_FAMILY_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_family); +/// Offset to `libc::sockaddr_un.sun_path` in bytes +const SUN_PATH_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_path); +/// This represents the maximum number of bytes + 1 allowed to be +/// stored in the socket path (e.g. 254 for NetBSD because it allows 253 +/// valid characters for its path, 104 for FreeBSD because it allows up to +/// 103 characters for its path) pub(crate) const SUN_PATH_MAX_LEN: usize = SOCK_MAX_SIZE - SUN_PATH_OFFSET; -// Platform-dependent error message when user provides a longer socket address path -// than what the OS allows for. +/// Platform-dependent error message when user provides a longer socket address path +/// than what the OS allows for. const LEN_EXCEEDED_MSG: &'static str = cfg_select! { any(target_os = "macos", target_os = "dragonfly") => { @@ -61,12 +76,6 @@ const LEN_EXCEEDED_MSG: &'static str = _ => "path must be shorter than SUN_LEN", }; -enum AddressKind<'a> { - Unnamed, - Pathname(&'a Path), - Abstract(&'a ByteStr), -} - /// An internal helper struct that provides a `len` field describing the actual filled size of the socket /// address and a `buf` field that contains an internal platform-agnostic struct of `libc::sockaddr_un`. /// @@ -83,15 +92,15 @@ enum AddressKind<'a> { /// sun_path: [c_char; 108], /// } /// -/// let socket = match UnixListener::bind("/tmp/sock") { -/// Ok(sock) => sock, -/// Err(e) => { -/// println!("Couldn't bind: {e:?}"); -/// return -/// } -/// }; -/// let addr = socket.local_addr().expect("`UnixListener::local_addr` should not fail"); -/// ``` +/// Note: +/// * `sa_family_t` is a u8 on BSD platforms and a u16 on Linux/other platforms. +/// * The fix-sized array value for `sun_path` field could be different across +/// other platforms (for Linux it's 108, but this may be a different on other platforms). +/// +/// Although `sockaddr_un.sun_path` is restricted to 104 characters on BSD platforms, +/// DragonFlyBSD/NetBSD/Apple actually allow `sun_path` to hold up to 253 non-nul bytes. +/// Therefore, this `SockaddrUn` struct aims to hold a buffer that contains the maximum +/// socket address size for each platform. #[derive(Clone)] #[repr(C)] pub(super) struct SockaddrBuf { @@ -121,31 +130,48 @@ impl SockaddrBuf { // `sun_family`/`sun_len` let len = SUN_PATH_OFFSET as libc::socklen_t; - SockaddrBuf { len, buf: sockaddr_un, align: unsafe { [mem::zeroed(); 0] } } + SockaddrBuf { len, buf: sockaddr_un, align: [] } } /// Extracts the `sun_path` value from the socket address buffer. + /// + /// On QNX, NTO, DragonFlyBSD, NetBSD, OpenBSD, FreeBSD, and Apple family + /// the nul byte is not part of `SockaddrBuf.len` + /// + /// On all other platforms (e.g. Linux), the nul byte is a part of `SockaddrBuf.len` fn path(&self) -> &[u8] { - &self.buf[SUN_PATH_OFFSET..] + &self.buf[SUN_PATH_OFFSET..self.len as usize] } /// Sets the socket address path for `sockaddr_un` in `SocketaddrBuf`. - fn set_path(&mut self, bytes: &[u8]) { + fn set_path(&mut self, bytes: &[u8]) -> io::Result<()> { + if core::slice::memchr::memchr(0, bytes).is_some() { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "paths must not contain interior null bytes", + )); + } + + if bytes.len() >= SUN_PATH_MAX_LEN { + return Err(io::const_error!(io::ErrorKind::InvalidInput, LEN_EXCEEDED_MSG)); + } + self.buf[SUN_PATH_OFFSET..SUN_PATH_OFFSET + bytes.len()].copy_from_slice(bytes); + self.buf[SUN_PATH_OFFSET + bytes.len()] = 0; let mut len = SUN_PATH_OFFSET + bytes.len(); #[cfg(any( target_os = "dragonfly", - target_os = "macos", + target_vendor = "apple", target_os = "netbsd", target_os = "openbsd", target_os = "freebsd" ))] { - const _: () = assert!((SUN_PATH_MAX_LEN as usize) <= (u8::MAX as usize)); + const _: () = assert!(SUN_PATH_MAX_LEN as usize <= u8::MAX as usize); // For these platforms, `sockaddr_un` has a `sun_len` (u8) field that should be initialized with a value // (nul byte excluding) - sockaddr.addr[mem::offset_of!(libc::sockaddr_un, sun_len)] = len as u8; + self.buf[mem::offset_of!(libc::sockaddr_un, sun_len)] = len as u8; } match bytes.get(0) { @@ -161,7 +187,7 @@ impl SockaddrBuf { target_os = "qnx", target_env = "nto71", target_os = "dragonfly", - target_os = "macos", + target_vendor = "apple", target_os = "netbsd", target_os = "openbsd", target_os = "freebsd" @@ -174,6 +200,8 @@ impl SockaddrBuf { // Even though len here is a `usize` and `libc::socklen_t` is a `u32` // our len value should be limited to whatever value a `u32` can hold self.len = len as libc::socklen_t; + + Ok(()) } /// Extracts the `sun_path` value from the abstract socket address buffer (excludes initial @@ -185,10 +213,26 @@ impl SockaddrBuf { #[cfg(any(target_os = "android", target_os = "linux", target_os = "cygwin"))] /// Sets the abstract socket address path for `sockaddr_un` in `SocketaddrBuf`. /// Only used on Android, Linux, and Cygwin. - fn set_abstract_path(&mut self, bytes: &[u8]) { + fn set_abstract_path(&mut self, bytes: &[u8]) -> io::Result<()> { + // Abstract socket address paths are not nul terminated! + // (Hence the > instead of >=) + // See Linux manpage: https://man7.org/linux/man-pages/man7/unix.7.html + // "The socket's address in this namespace is given by the additional + // bytes in sun_path that are covered by the specified length of the + // address structure. (Null bytes in the name have no special + // significance.)" + if bytes.len() + 1 > SUN_PATH_MAX_LEN { + return Err(io::const_error!( + io::ErrorKind::InvalidInput, + "path must be shorter than SUN_LEN - 1", + )); + } + // +1 because the first byte in the sun_path for abstract sockets is a nul byte self.buf[SUN_PATH_OFFSET + 1..SUN_PATH_OFFSET + 1 + bytes.len()].copy_from_slice(bytes); self.len = (SUN_PATH_OFFSET + 1 + bytes.len()) as libc::socklen_t; + + Ok(()) } /// Extracts the `sun_family` value from the socket address buffer. @@ -223,14 +267,15 @@ impl SockaddrBuf { /// Some platforms encode things differently. Call this after a `libc` call to adjust length /// and validate as needed. pub(super) fn update_from_libc(&mut self) -> io::Result<()> { - // let mut len = self.len; if cfg!(target_os = "openbsd") { - // on OpenBSD, getsockname(2) returns the actual size of the socket address, - // and not the len of the content. Figure out the length for ourselves. + // on OpenBSD, getsockname(2) doesn't re-adjust the len field of the socket address, + // so it doesn't reflect the true len of the content. Figure out the length for ourselves. // https://marc.info/?l=openbsd-bugs&m=170105481926736&w=2 let sun_path = self.path(); - self.len = core::slice::memchr::memchr(0, sun_path) - .map_or(self.len, |new_len| (new_len + SUN_PATH_OFFSET) as libc::socklen_t); + + if let Some(new_len) = core::slice::memchr::memchr(0, sun_path) { + self.len = (new_len + SUN_PATH_OFFSET) as libc::socklen_t; + } } if self.len == 0 { @@ -322,21 +367,8 @@ impl SocketAddr { /// Constructs a Unix Domain socket address from a given `Path` pub(super) fn from_path(path: &Path) -> io::Result { let bytes = path.as_os_str().as_bytes(); - - if core::slice::memchr::memchr(0, bytes).is_some() { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "paths must not contain interior null bytes", - )); - } - - if bytes.len() >= SUN_PATH_MAX_LEN { - return Err(io::const_error!(io::ErrorKind::InvalidInput, LEN_EXCEEDED_MSG)); - } - let mut sock = SockaddrBuf::default(); - sock.set_path(bytes); - + sock.set_path(bytes)?; Ok(SocketAddr { sock }) } @@ -453,22 +485,7 @@ impl linux_ext::addr::SocketAddrExt for SocketAddr { { let name = name.as_ref(); let mut sock = SockaddrBuf::default(); - - // Abstract socket address paths are not nul terminated! - // (Hence the > instead of >=) - // See Linux manpage: https://man7.org/linux/man-pages/man7/unix.7.html - // "The socket's address in this namespace is given by the additional - // bytes in sun_path that are covered by the specified length of the - // address structure. (Null bytes in the name have no special - // significance.)" - if name.len() + 1 > SUN_PATH_MAX_LEN { - return Err(io::const_error!( - io::ErrorKind::InvalidInput, - "path must be shorter than SUN_LEN - 1", - )); - } - - sock.set_abstract_path(name); + sock.set_abstract_path(name)?; Ok(SocketAddr { sock }) } } diff --git a/library/std/src/os/unix/net/ancillary.rs b/library/std/src/os/unix/net/ancillary.rs index f2e2718f7caa9..77f86816ad384 100644 --- a/library/std/src/os/unix/net/ancillary.rs +++ b/library/std/src/os/unix/net/ancillary.rs @@ -6,11 +6,11 @@ use crate::macros::rustdoc_skip_body; use crate::marker::PhantomData; use crate::mem::zeroed; use crate::os::unix::io::RawFd; -use crate::os::unix::net::SOCK_MAX_SIZE; use crate::path::Path; use crate::ptr::{eq, read_unaligned}; use crate::slice::from_raw_parts; use crate::sys::net::Socket; + // FIXME(#43348): Make libc adapt #[doc(cfg(...))] so we don't need these fake definitions here? #[cfg(all( doc, @@ -41,8 +41,8 @@ pub(super) fn recv_vectored_with_ancillary_from( let mut sock = SockaddrBuf::default(); let (addr, len) = sock.as_max_libc_output(); let mut msg: libc::msghdr = zeroed(); - msg.msg_name = addr as *mut _; - msg.msg_namelen = SOCK_MAX_SIZE as libc::socklen_t; + msg.msg_name = addr.cast(); + msg.msg_namelen = len.read(); msg.msg_iov = bufs.as_mut_ptr().cast(); msg.msg_iovlen = bufs.len() as _; msg.msg_controllen = ancillary.buffer.len() as _; @@ -77,7 +77,7 @@ pub(super) fn send_vectored_with_ancillary_to( let (addr, len) = sockaddr.sock.as_libc_input(); let mut msg: libc::msghdr = zeroed(); - msg.msg_name = addr as *mut _; + msg.msg_name = addr.cast_mut().cast(); msg.msg_namelen = len; msg.msg_iov = bufs.as_ptr() as *mut _; msg.msg_iovlen = bufs.len() as _; diff --git a/library/std/src/os/unix/net/listener.rs b/library/std/src/os/unix/net/listener.rs index 4b3f6471816f2..e8e2387ccf282 100644 --- a/library/std/src/os/unix/net/listener.rs +++ b/library/std/src/os/unix/net/listener.rs @@ -1,4 +1,4 @@ -use super::{SocketAddr, UnixStream, sockaddr_un}; +use super::{SockaddrBuf, SocketAddr, UnixStream}; use crate::macros::rustdoc_skip_body; use crate::os::unix::io::{ AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd, unsafe_impl_fd_asrefs, @@ -76,8 +76,6 @@ impl UnixListener { pub fn bind>(path: P) -> io::Result { unsafe { let inner = Socket::new(libc::AF_UNIX, libc::SOCK_STREAM)?; - let sockaddr = SocketAddr::from_path(path.as_ref())?; - let (addr, len) = sockaddr.sock.as_libc_input(); #[cfg(any( target_os = "windows", target_os = "redox", @@ -107,6 +105,8 @@ impl UnixListener { target_vendor = "apple", )))] const backlog: libc::c_int = libc::SOMAXCONN; + let sockaddr = SocketAddr::from_path(path.as_ref())?; + let (addr, len) = sockaddr.sock.as_libc_input(); cvt(libc::bind(inner.as_inner().as_raw_fd(), addr, len))?; cvt(libc::listen(inner.as_inner().as_raw_fd(), backlog))?;