Skip to content

Refactor UnixAddr #1618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 31, 2021
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
121 changes: 92 additions & 29 deletions src/sys/socket/addr.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use super::sa_family_t;
use cfg_if::cfg_if;
use crate::{Result, NixPath};
use crate::errno::Errno;
use memoffset::offset_of;
use std::{fmt, mem, net, ptr, slice};
use std::convert::TryInto;
use std::ffi::OsStr;
use std::hash::{Hash, Hasher};
use std::path::Path;
Expand Down Expand Up @@ -575,9 +577,17 @@ impl fmt::Display for Ipv6Addr {
/// A wrapper around `sockaddr_un`.
#[derive(Clone, Copy, Debug)]
pub struct UnixAddr {
// INVARIANT: sun & path_len are valid as defined by docs for from_raw_parts
// INVARIANT: sun & sun_len are valid as defined by docs for from_raw_parts
sun: libc::sockaddr_un,
path_len: usize,
/// The length of the valid part of `sun`, including the sun_family field
/// but excluding any trailing nul.
// On the BSDs, this field is built into sun
#[cfg(any(target_os = "android",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux"
))]
sun_len: u8
}

// linux man page unix(7) says there are 3 kinds of unix socket:
Expand All @@ -594,8 +604,10 @@ enum UnixAddrKind<'a> {
Abstract(&'a [u8]),
}
impl<'a> UnixAddrKind<'a> {
/// Safety: sun & path_len must be valid
unsafe fn get(sun: &'a libc::sockaddr_un, path_len: usize) -> Self {
/// Safety: sun & sun_len must be valid
unsafe fn get(sun: &'a libc::sockaddr_un, sun_len: u8) -> Self {
assert!(sun_len as usize >= offset_of!(libc::sockaddr_un, sun_path));
let path_len = sun_len as usize - offset_of!(libc::sockaddr_un, sun_path);
if path_len == 0 {
return Self::Unnamed;
}
Expand All @@ -605,8 +617,19 @@ impl<'a> UnixAddrKind<'a> {
slice::from_raw_parts(sun.sun_path.as_ptr().add(1) as *const u8, path_len - 1);
return Self::Abstract(name);
}
let pathname = slice::from_raw_parts(sun.sun_path.as_ptr() as *const u8, path_len - 1);
Self::Pathname(Path::new(OsStr::from_bytes(pathname)))
let pathname = slice::from_raw_parts(sun.sun_path.as_ptr() as *const u8, path_len);
if pathname.last() == Some(&0) {
// A trailing NUL is not considered part of the path, and it does
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you're using both NUL and nul for b'\0'. Pick one.

// not need to be included in the addrlen passed to functions like
// bind(). However, Linux adds a trailing NUL, even if one was not
// originally present, when returning addrs from functions like
// getsockname() (the BSDs do not do that). So we need to filter
// out any trailing NUL here, so sockaddrs can round-trip through
// the kernel and still compare equal.
Self::Pathname(Path::new(OsStr::from_bytes(&pathname[0..pathname.len() - 1])))
} else {
Self::Pathname(Path::new(OsStr::from_bytes(pathname)))
}
}
}

Expand All @@ -626,19 +649,32 @@ impl UnixAddr {
return Err(Errno::ENAMETOOLONG);
}

let sun_len = (bytes.len() +
offset_of!(libc::sockaddr_un, sun_path)).try_into()
.unwrap();

#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
{
ret.sun_len = sun_len;
}
ptr::copy_nonoverlapping(bytes.as_ptr(),
ret.sun_path.as_mut_ptr() as *mut u8,
bytes.len());

Ok(UnixAddr::from_raw_parts(ret, bytes.len() + 1))
Ok(UnixAddr::from_raw_parts(ret, sun_len))
}
})?
}

/// Create a new `sockaddr_un` representing an address in the "abstract namespace".
///
/// The leading null byte for the abstract namespace is automatically added;
/// thus the input `path` is expected to be the bare name, not null-prefixed.
/// The leading nul byte for the abstract namespace is automatically added;
/// thus the input `path` is expected to be the bare name, not NUL-prefixed.
/// This is a Linux-specific extension, primarily used to allow chrooted
/// processes to communicate with processes having a different filesystem view.
#[cfg(any(target_os = "android", target_os = "linux"))]
Expand All @@ -653,39 +689,51 @@ impl UnixAddr {
if path.len() >= ret.sun_path.len() {
return Err(Errno::ENAMETOOLONG);
}
let sun_len = (path.len() +
1 +
offset_of!(libc::sockaddr_un, sun_path)).try_into()
.unwrap();

// Abstract addresses are represented by sun_path[0] ==
// b'\0', so copy starting one byte in.
ptr::copy_nonoverlapping(path.as_ptr(),
ret.sun_path.as_mut_ptr().offset(1) as *mut u8,
path.len());

Ok(UnixAddr::from_raw_parts(ret, path.len() + 1))
Ok(UnixAddr::from_raw_parts(ret, sun_len))
}
}

/// Create a UnixAddr from a raw `sockaddr_un` struct and a size. `path_len` is the "addrlen"
/// of this address, but minus `offsetof(struct sockaddr_un, sun_path)`. Basically the length
/// of the data in `sun_path`.
/// Create a UnixAddr from a raw `sockaddr_un` struct and a size. `sun_len`
/// is the size of the valid portion of the struct, excluding any trailing
/// NUL.
///
/// # Safety
/// This pair of sockaddr_un & path_len must be a valid unix addr, which means:
/// - path_len <= sockaddr_un.sun_path.len()
/// - if this is a unix addr with a pathname, sun.sun_path is a nul-terminated fs path and
/// sun.sun_path[path_len - 1] == 0 || sun.sun_path[path_len] == 0
pub(crate) unsafe fn from_raw_parts(sun: libc::sockaddr_un, mut path_len: usize) -> UnixAddr {
if let UnixAddrKind::Pathname(_) = UnixAddrKind::get(&sun, path_len) {
if sun.sun_path[path_len - 1] != 0 {
assert_eq!(sun.sun_path[path_len], 0);
path_len += 1
/// This pair of sockaddr_un & sun_len must be a valid unix addr, which
/// means:
/// - sun_len >= offset_of(sockaddr_un, sun_path)
/// - sun_len <= sockaddr_un.sun_path.len() - offset_of(sockaddr_un, sun_path)
/// - if this is a unix addr with a pathname, sun.sun_path is a
/// fs path, not necessarily nul-terminated.
pub(crate) unsafe fn from_raw_parts(sun: libc::sockaddr_un, sun_len: u8) -> UnixAddr {
cfg_if!{
if #[cfg(any(target_os = "android",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux"
))]
{
UnixAddr { sun, sun_len }
} else {
assert_eq!(sun_len, sun.sun_len);
UnixAddr {sun}
}
}
UnixAddr { sun, path_len }
}

fn kind(&self) -> UnixAddrKind<'_> {
// SAFETY: our sockaddr is always valid because of the invariant on the struct
unsafe { UnixAddrKind::get(&self.sun, self.path_len) }
unsafe { UnixAddrKind::get(&self.sun, self.sun_len()) }
}

/// If this address represents a filesystem path, return that path.
Expand All @@ -699,7 +747,7 @@ impl UnixAddr {
/// If this address represents an abstract socket, return its name.
///
/// For abstract sockets only the bare name is returned, without the
/// leading null byte. `None` is returned for unnamed or path-backed sockets.
/// leading NUL byte. `None` is returned for unnamed or path-backed sockets.
#[cfg(any(target_os = "android", target_os = "linux"))]
#[cfg_attr(docsrs, doc(cfg(all())))]
pub fn as_abstract(&self) -> Option<&[u8]> {
Expand All @@ -712,7 +760,7 @@ impl UnixAddr {
/// Returns the addrlen of this socket - `offsetof(struct sockaddr_un, sun_path)`
#[inline]
pub fn path_len(&self) -> usize {
self.path_len
self.sun_len() as usize - offset_of!(libc::sockaddr_un, sun_path)
}
/// Returns a pointer to the raw `sockaddr_un` struct
#[inline]
Expand All @@ -724,6 +772,21 @@ impl UnixAddr {
pub fn as_mut_ptr(&mut self) -> *mut libc::sockaddr_un {
&mut self.sun
}

fn sun_len(&self)-> u8 {
cfg_if!{
if #[cfg(any(target_os = "android",
target_os = "fuchsia",
target_os = "illumos",
target_os = "linux"
))]
{
self.sun_len
} else {
self.sun.sun_len
}
}
}
}

#[cfg(any(target_os = "android", target_os = "linux"))]
Expand Down Expand Up @@ -957,12 +1020,12 @@ impl SockAddr {
},
mem::size_of_val(addr) as libc::socklen_t
),
SockAddr::Unix(UnixAddr { ref sun, path_len }) => (
SockAddr::Unix(ref unix_addr) => (
// This cast is always allowed in C
unsafe {
&*(sun as *const libc::sockaddr_un as *const libc::sockaddr)
&*(&unix_addr.sun as *const libc::sockaddr_un as *const libc::sockaddr)
},
(path_len + offset_of!(libc::sockaddr_un, sun_path)) as libc::socklen_t
unix_addr.sun_len() as libc::socklen_t
),
#[cfg(any(target_os = "android", target_os = "linux"))]
SockAddr::Netlink(NetlinkAddr(ref sa)) => (
Expand Down
6 changes: 3 additions & 3 deletions src/sys/socket/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use cfg_if::cfg_if;
use crate::{Result, errno::Errno};
use libc::{self, c_void, c_int, iovec, socklen_t, size_t,
CMSG_FIRSTHDR, CMSG_NXTHDR, CMSG_DATA, CMSG_LEN};
use memoffset::offset_of;
use std::convert::TryInto;
use std::{mem, ptr, slice};
use std::os::unix::io::RawFd;
#[cfg(target_os = "linux")]
Expand Down Expand Up @@ -1934,10 +1934,10 @@ pub fn sockaddr_storage_to_addr(
Ok(SockAddr::Inet(InetAddr::V6(sin6)))
}
libc::AF_UNIX => {
let pathlen = len - offset_of!(sockaddr_un, sun_path);
unsafe {
let sun = *(addr as *const _ as *const sockaddr_un);
Ok(SockAddr::Unix(UnixAddr::from_raw_parts(sun, pathlen)))
let sun_len = len.try_into().unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unwrapping here makes me a little concerned, but I'm not familiar with the previous behavior. Should we return an Err instead of panicking?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unwrap is guaranteed to pass, because of the assertion on line 1914.

Ok(SockAddr::Unix(UnixAddr::from_raw_parts(sun, sun_len)))
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
Expand Down