Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ jobs:
cargo update --package=regex --precise=1.9.0
cargo update --package=half --precise=2.2.1
cargo update --package=flate2 --precise=1.0.35
cargo update --package=textwrap --precise=0.16.1

- run: >
rustup target add
Expand Down Expand Up @@ -529,6 +530,7 @@ jobs:
cargo update --package=regex --precise=1.9.0
cargo update --package=half --precise=2.2.1
cargo update --package=flate2 --precise=1.0.35
cargo update --package=textwrap --precise=0.16.1

- run: |
cargo test --verbose --features=all-apis --release --workspace -- --nocapture
Expand Down
76 changes: 63 additions & 13 deletions src/backend/libc/net/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use {
core::hash::{Hash, Hasher},
core::slice,
};
#[cfg(all(unix, feature = "alloc"))]
use {crate::ffi::CString, alloc::borrow::Cow, alloc::vec::Vec};

/// `struct sockaddr_un`
#[cfg(unix)]
Expand All @@ -35,9 +37,12 @@ impl SocketAddrUnix {
#[inline]
fn _new(path: &CStr) -> io::Result<Self> {
let mut unix = Self::init();
let bytes = path.to_bytes_with_nul();
let mut bytes = path.to_bytes_with_nul();
if bytes.len() > unix.sun_path.len() {
return Err(io::Errno::NAMETOOLONG);
bytes = path.to_bytes(); // without NUL
if bytes.len() > unix.sun_path.len() {
return Err(io::Errno::NAMETOOLONG);
}
}
for (i, b) in bytes.iter().enumerate() {
unix.sun_path[i] = *b as c::c_char;
Expand Down Expand Up @@ -129,12 +134,52 @@ impl SocketAddrUnix {

/// For a filesystem path address, return the path.
#[inline]
pub fn path(&self) -> Option<&CStr> {
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn path(&self) -> Option<Cow<'_, CStr>> {
let bytes = self.bytes()?;
if !bytes.is_empty() && bytes[0] != 0 {
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
// NUL-terminated.
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) })
if self.unix.sun_path.len() == bytes.len() {
// SAFETY: no NULs are contained in bytes
unsafe { self.path_with_termination(bytes) }
} else {
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
// NUL-terminated.
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }.into())
}
} else {
None
}
}

/// If the `sun_path` field is not NUL-terminated, terminate it.
///
/// SAFETY: the input `bytes` must not contain any NULs
#[cfg(feature = "alloc")]
unsafe fn path_with_termination(&self, bytes: &[u8]) -> Option<Cow<'_, CStr>> {
let mut owned = Vec::with_capacity(bytes.len() + 1);
owned.extend_from_slice(bytes);
owned.push(b'\0');
// SAFETY: `from_vec_with_nul_unchecked` since the string is
// NUL-terminated and `bytes` does not conain any NULs.
Some(Cow::Owned(
CString::from_vec_with_nul_unchecked(owned).into(),
))
}

/// For a filesystem path address, return the path as a byte sequence,
/// excluding the NUL terminator.
#[inline]
pub fn path_bytes(&self) -> Option<&[u8]> {
let bytes = self.bytes()?;
if !bytes.is_empty() && bytes[0] != 0 {
if self.unix.sun_path.len() == self.len() - offsetof_sun_path() {
// There is no NUL terminator.
Some(bytes)
} else {
// Remove the NUL terminator.
Some(&bytes[..bytes.len() - 1])
}
} else {
None
}
Expand Down Expand Up @@ -231,16 +276,21 @@ impl Hash for SocketAddrUnix {
#[cfg(unix)]
impl fmt::Debug for SocketAddrUnix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "alloc")]
if let Some(path) = self.path() {
path.fmt(f)
} else {
#[cfg(linux_kernel)]
if let Some(name) = self.abstract_name() {
return name.fmt(f);
return path.fmt(f);
}
if let Some(bytes) = self.path_bytes() {
if let Ok(s) = core::str::from_utf8(bytes) {
return s.fmt(f);
}

"(unnamed)".fmt(f)
return bytes.fmt(f);
}
#[cfg(linux_kernel)]
if let Some(name) = self.abstract_name() {
return name.fmt(f);
}
"(unnamed)".fmt(f)
}
}

Expand Down
74 changes: 63 additions & 11 deletions src/backend/linux_raw/net/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ use crate::{io, path};
use core::cmp::Ordering;
use core::hash::{Hash, Hasher};
use core::{fmt, slice};
#[cfg(feature = "alloc")]
use {crate::ffi::CString, alloc::borrow::Cow, alloc::vec::Vec};

/// `struct sockaddr_un`
#[derive(Clone)]
Expand All @@ -33,9 +35,12 @@ impl SocketAddrUnix {
#[inline]
fn _new(path: &CStr) -> io::Result<Self> {
let mut unix = Self::init();
let bytes = path.to_bytes_with_nul();
let mut bytes = path.to_bytes_with_nul();
if bytes.len() > unix.sun_path.len() {
return Err(io::Errno::NAMETOOLONG);
bytes = path.to_bytes(); // without NUL
if bytes.len() > unix.sun_path.len() {
return Err(io::Errno::NAMETOOLONG);
}
}
for (i, b) in bytes.iter().enumerate() {
unix.sun_path[i] = bitcast!(*b);
Expand Down Expand Up @@ -91,12 +96,52 @@ impl SocketAddrUnix {

/// For a filesystem path address, return the path.
#[inline]
pub fn path(&self) -> Option<&CStr> {
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn path(&self) -> Option<Cow<'_, CStr>> {
let bytes = self.bytes()?;
if !bytes.is_empty() && bytes[0] != 0 {
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
// NUL-terminated.
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) })
if self.unix.sun_path.len() == bytes.len() {
// SAFETY: no NULs are contained in bytes
unsafe { self.path_with_termination(bytes) }
} else {
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
// NUL-terminated.
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }.into())
}
} else {
None
}
}

/// If the `sun_path` field is not NUL-terminated, terminate it.
///
/// SAFETY: the input `bytes` must not contain any NULs
#[cfg(feature = "alloc")]
unsafe fn path_with_termination(&self, bytes: &[u8]) -> Option<Cow<'_, CStr>> {
let mut owned = Vec::with_capacity(bytes.len() + 1);
owned.extend_from_slice(bytes);
owned.push(b'\0');
// SAFETY: `from_vec_with_nul_unchecked` since the string is
// NUL-terminated and `bytes` does not conain any NULs.
Some(Cow::Owned(
CString::from_vec_with_nul_unchecked(owned).into(),
))
}

/// For a filesystem path address, return the path as a byte sequence,
/// excluding the NUL terminator.
#[inline]
pub fn path_bytes(&self) -> Option<&[u8]> {
let bytes = self.bytes()?;
if !bytes.is_empty() && bytes[0] != 0 {
if self.unix.sun_path.len() == self.len() - offsetof_sun_path() {
// There is no NUL terminator.
Some(bytes)
} else {
// Remove the NUL terminator.
Some(&bytes[..bytes.len() - 1])
}
} else {
None
}
Expand Down Expand Up @@ -178,13 +223,20 @@ impl Hash for SocketAddrUnix {

impl fmt::Debug for SocketAddrUnix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "alloc")]
if let Some(path) = self.path() {
path.fmt(f)
} else if let Some(name) = self.abstract_name() {
name.fmt(f)
} else {
"(unnamed)".fmt(f)
return path.fmt(f);
}
if let Some(bytes) = self.path_bytes() {
if let Ok(s) = core::str::from_utf8(bytes) {
return s.fmt(f);
}
return bytes.fmt(f);
}
if let Some(name) = self.abstract_name() {
return name.fmt(f);
}
"(unnamed)".fmt(f)
}
}

Expand Down
8 changes: 4 additions & 4 deletions tests/net/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,19 @@ fn test_unix_addr() {

assert_eq!(
SocketAddrUnix::new("/").unwrap().path().unwrap(),
cstr!("/")
cstr!("/").into()
);
assert_eq!(
SocketAddrUnix::new("//").unwrap().path().unwrap(),
cstr!("//")
cstr!("//").into()
);
assert_eq!(
SocketAddrUnix::new("/foo/bar").unwrap().path().unwrap(),
cstr!("/foo/bar")
cstr!("/foo/bar").into()
);
assert_eq!(
SocketAddrUnix::new("foo").unwrap().path().unwrap(),
cstr!("foo")
cstr!("foo").into()
);
SocketAddrUnix::new("/foo\0/bar").unwrap_err();
assert!(SocketAddrUnix::new("").unwrap().path().is_none());
Expand Down
56 changes: 56 additions & 0 deletions tests/net/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,22 @@ fn do_test_unix_msg_unconnected(addr: SocketAddrUnix) {
#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
#[test]
fn test_unix_msg() {
use rustix::ffi::CString;
use std::os::unix::ffi::OsStrExt as _;

crate::init();

let tmpdir = tempfile::tempdir().unwrap();
let path = tmpdir.path().join("scp_4804");

let name = SocketAddrUnix::new(&path).unwrap();
assert_eq!(
name.path(),
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
);
assert_eq!(name.path_bytes(), Some(path.as_os_str().as_bytes()));
#[cfg(linux_kernel)]
assert!(!name.is_unnamed());
do_test_unix_msg(name);

unlinkat(CWD, path, AtFlags::empty()).unwrap();
Expand All @@ -379,12 +389,22 @@ fn test_unix_msg() {
#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
#[test]
fn test_unix_msg_unconnected() {
use rustix::ffi::CString;
use std::os::unix::ffi::OsStrExt as _;

crate::init();

let tmpdir = tempfile::tempdir().unwrap();
let path = tmpdir.path().join("scp_4804");

let name = SocketAddrUnix::new(&path).unwrap();
assert_eq!(
name.path(),
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
);
assert_eq!(name.path_bytes(), Some(path.as_os_str().as_bytes()));
#[cfg(linux_kernel)]
assert!(!name.is_unnamed());
do_test_unix_msg_unconnected(name);

unlinkat(CWD, path, AtFlags::empty()).unwrap();
Expand All @@ -401,6 +421,8 @@ fn test_abstract_unix_msg() {
let path = tmpdir.path().join("scp_4804");

let name = SocketAddrUnix::new_abstract_name(path.as_os_str().as_bytes()).unwrap();
assert_eq!(name.abstract_name(), Some(path.as_os_str().as_bytes()));
assert!(!name.is_unnamed());
do_test_unix_msg(name);
}

Expand All @@ -416,6 +438,8 @@ fn test_abstract_unix_msg_unconnected() {
let path = tmpdir.path().join("scp_4804");

let name = SocketAddrUnix::new_abstract_name(path.as_os_str().as_bytes()).unwrap();
assert_eq!(name.abstract_name(), Some(path.as_os_str().as_bytes()));
assert!(!name.is_unnamed());
do_test_unix_msg_unconnected(name);
}

Expand Down Expand Up @@ -948,3 +972,35 @@ fn test_bind_unnamed_address() {
assert_ne!(address.abstract_name(), None);
assert_eq!(address.path(), None);
}

/// Test that names long enough to not have room for the NUL terminator are
/// handled properly.
#[test]
fn test_long_named_address() {
use memoffset::span_of;
use rustix::ffi::CString;
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;

let lens = [
span_of!(libc::sockaddr_un, sun_path).len(),
#[cfg(linux_kernel)]
span_of!(linux_raw_sys::net::sockaddr_un, sun_path).len(),
];

for len in lens {
let path = PathBuf::from("a".repeat(len));
let name = SocketAddrUnix::new(&path).unwrap();
assert_eq!(
name.path(),
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
);
assert_eq!(
name.path(),
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
);
assert_eq!(name.path_bytes(), Some(path.as_os_str().as_bytes()));
#[cfg(linux_kernel)]
assert!(!name.is_unnamed());
}
}
Loading