From 01dc196faba581e26edf3957b6a6b31cbe6fafea Mon Sep 17 00:00:00 2001 From: omrzk <193513049+omrzk@users.noreply.github.com> Date: Mon, 2 Nov 2026 09:06:10 -0600 Subject: [PATCH 1/2] Avoid panicking when a Unix pathname cannot be converted --- src/sys/unix.rs | 7 +++---- tests/socket.rs | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 8027d325..796a2326 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -961,12 +961,11 @@ impl SockAddr { } /// Returns this address as Unix `SocketAddr` if it is an `AF_UNIX` pathname - /// address, otherwise returns `None`. + /// address that can be represented by the standard library, otherwise returns + /// `None`. pub fn as_unix(&self) -> Option { let path = self.as_pathname()?; - // SAFETY: we can represent this as a valid pathname, then so can the - // standard library. - Some(std::os::unix::net::SocketAddr::from_pathname(path).unwrap()) + std::os::unix::net::SocketAddr::from_pathname(path).ok() } /// Returns this address as a `Path` reference if it is an `AF_UNIX` diff --git a/tests/socket.rs b/tests/socket.rs index 0f390e3a..bc697242 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -173,6 +173,29 @@ fn socket_address_unix() { } } +#[test] +#[cfg(all(unix, not(target_os = "wasi")))] +fn socket_address_unix_invalid_pathname() { + // SockAddr preserves the supplied bytes, but the standard library cannot + // represent a pathname containing a null byte. + for path in ["a\0b", "a\0", "a\0b\0c"] { + let addr = SockAddr::unix(path).unwrap(); + assert_eq!(addr.as_pathname(), Some(Path::new(path))); + assert!(addr.as_unix().is_none()); + } +} + +#[test] +#[cfg(all(unix, not(target_os = "wasi")))] +fn socket_address_unix_non_utf8_pathname() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + let path = Path::new(OsStr::from_bytes(b"socket-\xff")); + let addr = SockAddr::unix(path).unwrap(); + assert_eq!(addr.as_unix().unwrap().as_pathname(), Some(path)); +} + #[test] #[cfg(not(target_os = "wasi"))] fn socket_address_unix_unnamed() { @@ -208,6 +231,7 @@ fn socket_address_unix_abstract_namespace() { // The first byte is the opening null bytes of an abstract address, should not be included. assert_eq!(addr.as_abstract_namespace(), Some(&path.as_bytes()[1..])); assert!(addr.as_pathname().is_none()); + assert!(addr.as_unix().is_none()); assert!(!addr.is_unnamed()); } From 6055b3664f76e307fad6cbb9eee444b9625126e3 Mon Sep 17 00:00:00 2001 From: omrzk <193513049+omrzk@users.noreply.github.com> Date: Mon, 2 Nov 2026 09:32:21 -0600 Subject: [PATCH 2/2] Reject embedded null bytes in Unix socket pathnames --- src/sockaddr.rs | 3 ++- src/sys/unix.rs | 6 ++++++ src/sys/windows.rs | 6 ++++++ tests/socket.rs | 39 +++++++++++++++++++++++++++++++++------ 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/sockaddr.rs b/src/sockaddr.rs index 370b3856..b38953b6 100644 --- a/src/sockaddr.rs +++ b/src/sockaddr.rs @@ -214,7 +214,8 @@ impl SockAddr { /// Constructs a `SockAddr` with the family `AF_UNIX` and the provided path. /// - /// Returns an error if the path is longer than `SUN_LEN`. + /// Returns an error if a pathname contains a null byte or the path is longer + /// than `SUN_LEN`. #[cfg(not(target_os = "wasi"))] pub fn unix

(path: P) -> io::Result where diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 796a2326..a9e876af 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -758,6 +758,12 @@ pub(crate) fn unix_sockaddr(path: &Path) -> io::Result { "path must be shorter than SUN_LEN", )); } + if bytes.first() != Some(&0) && bytes.contains(&0) { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "path must not contain null bytes", + )); + } storage.sun_family = libc::AF_UNIX as sa_family_t; // SAFETY: `bytes` and `addr.sun_path` are not overlapping and diff --git a/src/sys/windows.rs b/src/sys/windows.rs index b1fa20fc..33007296 100644 --- a/src/sys/windows.rs +++ b/src/sys/windows.rs @@ -1047,6 +1047,12 @@ pub(crate) fn unix_sockaddr(path: &Path) -> io::Result { "path must be shorter than SUN_LEN", )); } + if bytes.first() != Some(&0) && bytes.contains(&0) { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "path must not contain null bytes", + )); + } storage.sun_family = crate::sys::AF_UNIX as sa_family_t; // SAFETY: casting `[u8]` to `[i8]` is safe. diff --git a/tests/socket.rs b/tests/socket.rs index bc697242..766f79ca 100644 --- a/tests/socket.rs +++ b/tests/socket.rs @@ -174,17 +174,32 @@ fn socket_address_unix() { } #[test] -#[cfg(all(unix, not(target_os = "wasi")))] +#[cfg(not(target_os = "wasi"))] fn socket_address_unix_invalid_pathname() { - // SockAddr preserves the supplied bytes, but the standard library cannot - // represent a pathname containing a null byte. for path in ["a\0b", "a\0", "a\0b\0c"] { - let addr = SockAddr::unix(path).unwrap(); - assert_eq!(addr.as_pathname(), Some(Path::new(path))); - assert!(addr.as_unix().is_none()); + let err = SockAddr::unix(path).unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::InvalidInput); } } +#[test] +#[cfg(all(unix, not(target_os = "wasi")))] +fn socket_address_unix_padded_raw_pathname() { + let original = SockAddr::unix("socket").unwrap(); + // SAFETY: a null-terminated pathname and zero padding form a valid + // sockaddr_un. Raw callers can supply the full structure length. + let (_, addr) = unsafe { + SockAddr::try_init(|storage, len| { + std::ptr::copy_nonoverlapping(original.as_ptr(), storage, 1); + *len = mem::size_of::() as _; + Ok(()) + }) + } + .unwrap(); + assert!(addr.as_pathname().unwrap().as_os_str().len() > "socket".len()); + assert!(addr.as_unix().is_none()); +} + #[test] #[cfg(all(unix, not(target_os = "wasi")))] fn socket_address_unix_non_utf8_pathname() { @@ -235,6 +250,18 @@ fn socket_address_unix_abstract_namespace() { assert!(!addr.is_unnamed()); } +#[test] +#[cfg(any(target_os = "linux", target_os = "android", target_os = "cygwin"))] +fn socket_address_unix_abstract_null_bytes() { + for path in ["\0", "\0\0", "\0abstract\0payload"] { + let addr = SockAddr::unix(path).unwrap(); + assert_eq!(addr.as_abstract_namespace(), Some(&path.as_bytes()[1..])); + assert!(addr.as_pathname().is_none()); + assert!(addr.as_unix().is_none()); + assert!(!addr.is_unnamed()); + } +} + #[test] #[cfg(all(feature = "all", any(target_os = "android", target_os = "linux")))] fn socket_address_vsock() {