Skip to content

Commit a986fdd

Browse files
committed
Rework UnixAddr to fix soundness issues
1 parent 426b09a commit a986fdd

File tree

4 files changed

+106
-60
lines changed

4 files changed

+106
-60
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ This project adheres to [Semantic Versioning](https://semver.org/).
5858
- Minimum supported Rust version is now 1.46.0.
5959
([#1492](https://github.com/nix-rust/nix/pull/1492))
6060

61+
- Rework `UnixAddr` to encapsulate internals better in order to fix soundness
62+
issues.
63+
([#1496](https://github.com/nix-rust/nix/pull/1496))
64+
6165
### Fixed
6266

6367
- Added more errno definitions for better backwards compatibility with

src/sys/socket/addr.rs

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -515,15 +515,44 @@ impl fmt::Display for Ipv6Addr {
515515
}
516516

517517
/// A wrapper around `sockaddr_un`.
518-
///
519-
/// This also tracks the length of `sun_path` address (excluding
520-
/// a terminating null), because it may not be null-terminated. For example,
521-
/// unconnected and Linux abstract sockets are never null-terminated, and POSIX
522-
/// does not require that `sun_len` include the terminating null even for normal
523-
/// sockets. Note that the actual sockaddr length is greater by
524-
/// `offset_of!(libc::sockaddr_un, sun_path)`
525518
#[derive(Clone, Copy, Debug)]
526-
pub struct UnixAddr(pub libc::sockaddr_un, pub usize);
519+
pub struct UnixAddr {
520+
// INVARIANT: sun+sun_len is valid as defined by docs for from_raw_parts
521+
sun: libc::sockaddr_un,
522+
sun_len: usize
523+
}
524+
525+
// linux man page unix(7) says there are 3 kinds of unix socket:
526+
// pathname: addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1
527+
// unnamed: addrlen = sizeof(sa_family_t)
528+
// abstract: addren > sizeof(sa_family_t), name = sun_path[..(addrlen - sizeof(sa_family_t))]
529+
//
530+
// what we call "len"/"sun_len" = addrlen - offsetof(struct sockaddr_un, sun_path)
531+
#[derive(PartialEq, Eq, Hash)]
532+
enum UnixAddrKind<'a> {
533+
Pathname(&'a Path),
534+
Unnamed,
535+
#[cfg(any(target_os = "android", target_os = "linux"))]
536+
Abstract(&'a [u8]),
537+
}
538+
impl<'a> UnixAddrKind<'a> {
539+
/// Safety: sockaddr+size must be valid
540+
#[deny(unsafe_op_in_unsafe_fn)]
541+
unsafe fn get(sun: &'a libc::sockaddr_un, len: usize) -> Self {
542+
if len == 0 {
543+
return Self::Unnamed;
544+
}
545+
#[cfg(any(target_os = "android", target_os = "linux"))]
546+
if sun.sun_path[0] == 0 {
547+
return Self::Abstract(unsafe {
548+
slice::from_raw_parts(sun.sun_path.as_ptr().add(1) as *const u8, len - 1)
549+
});
550+
}
551+
let pathname = unsafe { slice::from_raw_parts(sun.sun_path.as_ptr() as *const u8, len - 1) };
552+
Self::Pathname(Path::new(OsStr::from_bytes(pathname)))
553+
}
554+
}
555+
527556

528557
impl UnixAddr {
529558
/// Create a new sockaddr_un representing a filesystem path.
@@ -537,15 +566,15 @@ impl UnixAddr {
537566

538567
let bytes = cstr.to_bytes();
539568

540-
if bytes.len() > ret.sun_path.len() {
569+
if bytes.len() >= ret.sun_path.len() {
541570
return Err(Error::from(Errno::ENAMETOOLONG));
542571
}
543572

544573
ptr::copy_nonoverlapping(bytes.as_ptr(),
545574
ret.sun_path.as_mut_ptr() as *mut u8,
546575
bytes.len());
547576

548-
Ok(UnixAddr(ret, bytes.len()))
577+
Ok(UnixAddr::from_raw_parts(ret, bytes.len() + 1))
549578
}
550579
})?
551580
}
@@ -564,7 +593,7 @@ impl UnixAddr {
564593
.. mem::zeroed()
565594
};
566595

567-
if path.len() + 1 > ret.sun_path.len() {
596+
if path.len() >= ret.sun_path.len() {
568597
return Err(Error::from(Errno::ENAMETOOLONG));
569598
}
570599

@@ -574,28 +603,31 @@ impl UnixAddr {
574603
ret.sun_path.as_mut_ptr().offset(1) as *mut u8,
575604
path.len());
576605

577-
Ok(UnixAddr(ret, path.len() + 1))
606+
Ok(UnixAddr::from_raw_parts(ret, path.len() + 1))
578607
}
579608
}
580609

581-
fn sun_path(&self) -> &[u8] {
582-
unsafe { slice::from_raw_parts(self.0.sun_path.as_ptr() as *const u8, self.1) }
610+
/// Create a UnixAddr from a raw `sockaddr_un` struct and a size.
611+
///
612+
/// # Safety
613+
/// This pair of sockaddr_un+size must be a valid unix addr, which means:
614+
/// - sun_len <= sockaddr_un.sun_path.len()
615+
/// - if this is a unix addr with a fs path, sun.sun_path is a nul-terminated fs path and
616+
/// sun.sun_path[sun_len - 1] == 0
617+
pub unsafe fn from_raw_parts(sun: libc::sockaddr_un, sun_len: usize) -> UnixAddr {
618+
UnixAddr { sun, sun_len }
619+
}
620+
621+
fn kind(&self) -> UnixAddrKind<'_> {
622+
// SAFETY: our sockaddr is always valid
623+
unsafe { UnixAddrKind::get(&self.sun, self.sun_len) }
583624
}
584625

585626
/// If this address represents a filesystem path, return that path.
586627
pub fn path(&self) -> Option<&Path> {
587-
if self.1 == 0 || self.0.sun_path[0] == 0 {
588-
// unnamed or abstract
589-
None
590-
} else {
591-
let p = self.sun_path();
592-
// POSIX only requires that `sun_len` be at least long enough to
593-
// contain the pathname, and it need not be null-terminated. So we
594-
// need to create a string that is the shorter of the
595-
// null-terminated length or the full length.
596-
let ptr = &self.0.sun_path as *const libc::c_char;
597-
let reallen = unsafe { libc::strnlen(ptr, p.len()) };
598-
Some(Path::new(<OsStr as OsStrExt>::from_bytes(&p[..reallen])))
628+
match self.kind() {
629+
UnixAddrKind::Pathname(path) => Some(path),
630+
_ => None
599631
}
600632
}
601633

@@ -605,39 +637,53 @@ impl UnixAddr {
605637
/// leading null byte. `None` is returned for unnamed or path-backed sockets.
606638
#[cfg(any(target_os = "android", target_os = "linux"))]
607639
pub fn as_abstract(&self) -> Option<&[u8]> {
608-
if self.1 >= 1 && self.0.sun_path[0] == 0 {
609-
Some(&self.sun_path()[1..])
610-
} else {
611-
// unnamed or filesystem path
612-
None
640+
match self.kind() {
641+
UnixAddrKind::Abstract(name) => Some(name),
642+
_ => None
613643
}
614644
}
645+
646+
/// Returns the addrlen of this socket - `offsetof(struct sockaddr_un, sun_path)`
647+
#[inline]
648+
pub fn sun_len(&self) -> usize {
649+
self.sun_len
650+
}
651+
/// Returns a pointer to the raw `sockaddr_un` struct
652+
#[inline]
653+
pub fn as_ptr(&self) -> *const libc::sockaddr_un {
654+
&self.sun
655+
}
656+
/// Returns a mutable pointer to the raw `sockaddr_un` struct
657+
#[inline]
658+
pub fn as_mut_ptr(&mut self) -> *mut libc::sockaddr_un {
659+
&mut self.sun
660+
}
615661
}
616662

617663
impl fmt::Display for UnixAddr {
618664
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
619-
if self.1 == 0 {
620-
f.write_str("<unbound UNIX socket>")
621-
} else if let Some(path) = self.path() {
622-
path.display().fmt(f)
623-
} else {
624-
let display = String::from_utf8_lossy(&self.sun_path()[1..]);
625-
write!(f, "@{}", display)
665+
match self.kind() {
666+
UnixAddrKind::Pathname(path) => path.display().fmt(f),
667+
UnixAddrKind::Unnamed => f.pad("<unbound UNIX socket>"),
668+
#[cfg(any(target_os = "android", target_os = "linux"))]
669+
UnixAddrKind::Abstract(name) => {
670+
write!(f, "@{}", String::from_utf8_lossy(name))
671+
}
626672
}
627673
}
628674
}
629675

630676
impl PartialEq for UnixAddr {
631677
fn eq(&self, other: &UnixAddr) -> bool {
632-
self.sun_path() == other.sun_path()
678+
self.kind() == other.kind()
633679
}
634680
}
635681

636682
impl Eq for UnixAddr {}
637683

638684
impl Hash for UnixAddr {
639685
fn hash<H: Hasher>(&self, s: &mut H) {
640-
( self.0.sun_family, self.sun_path() ).hash(s)
686+
self.kind().hash(s)
641687
}
642688
}
643689

@@ -805,12 +851,12 @@ impl SockAddr {
805851
},
806852
mem::size_of_val(addr) as libc::socklen_t
807853
),
808-
SockAddr::Unix(UnixAddr(ref addr, len)) => (
854+
SockAddr::Unix(UnixAddr { ref sun, sun_len }) => (
809855
// This cast is always allowed in C
810856
unsafe {
811-
&*(addr as *const libc::sockaddr_un as *const libc::sockaddr)
857+
&*(sun as *const libc::sockaddr_un as *const libc::sockaddr)
812858
},
813-
(len + offset_of!(libc::sockaddr_un, sun_path)) as libc::socklen_t
859+
(sun_len + offset_of!(libc::sockaddr_un, sun_path)) as libc::socklen_t
814860
),
815861
#[cfg(any(target_os = "android", target_os = "linux"))]
816862
SockAddr::Netlink(NetlinkAddr(ref sa)) => (
@@ -1376,11 +1422,8 @@ mod tests {
13761422
let name = String::from("nix\0abstract\0test");
13771423
let addr = UnixAddr::new_abstract(name.as_bytes()).unwrap();
13781424

1379-
let sun_path1 = addr.sun_path();
1380-
let sun_path2 = [0u8, 110, 105, 120, 0, 97, 98, 115, 116, 114, 97, 99, 116, 0, 116, 101, 115, 116];
1381-
assert_eq!(sun_path1.len(), sun_path2.len());
1382-
for i in 0..sun_path1.len() {
1383-
assert_eq!(sun_path1[i], sun_path2[i]);
1384-
}
1425+
let sun_path1 = unsafe { &(*addr.as_ptr()).sun_path[..addr.sun_len()] };
1426+
let sun_path2 = [0, 110, 105, 120, 0, 97, 98, 115, 116, 114, 97, 99, 116, 0, 116, 101, 115, 116];
1427+
assert_eq!(sun_path1, sun_path2);
13851428
}
13861429
}

src/sys/socket/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,10 +1802,10 @@ pub fn sockaddr_storage_to_addr(
18021802
}
18031803
libc::AF_UNIX => {
18041804
let pathlen = len - offset_of!(sockaddr_un, sun_path);
1805-
let sun = unsafe {
1806-
*(addr as *const _ as *const sockaddr_un)
1807-
};
1808-
Ok(SockAddr::Unix(UnixAddr(sun, pathlen)))
1805+
unsafe {
1806+
let sun = *(addr as *const _ as *const sockaddr_un);
1807+
Ok(SockAddr::Unix(UnixAddr::from_raw_parts(sun, pathlen)))
1808+
}
18091809
}
18101810
#[cfg(any(target_os = "android", target_os = "linux"))]
18111811
libc::AF_PACKET => {

test/sys/test_socket.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ pub fn test_path_to_sock_addr() {
113113
let addr = UnixAddr::new(actual).unwrap();
114114

115115
let expect: &[c_char] = unsafe {
116-
slice::from_raw_parts(path.as_bytes().as_ptr() as *const c_char, path.len())
116+
slice::from_raw_parts(path.as_ptr() as *const c_char, path.len())
117117
};
118-
assert_eq!(&addr.0.sun_path[..8], expect);
118+
assert_eq!(unsafe { &(*addr.as_ptr()).sun_path[..8] }, expect);
119119

120120
assert_eq!(addr.path(), Some(actual));
121121
}
@@ -133,7 +133,7 @@ pub fn test_addr_equality_path() {
133133
let addr1 = UnixAddr::new(actual).unwrap();
134134
let mut addr2 = addr1;
135135

136-
addr2.0.sun_path[10] = 127;
136+
unsafe { (*addr2.as_mut_ptr()).sun_path[10] = 127 };
137137

138138
assert_eq!(addr1, addr2);
139139
assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2));
@@ -157,7 +157,7 @@ pub fn test_addr_equality_abstract() {
157157
assert_eq!(addr1, addr2);
158158
assert_eq!(calculate_hash(&addr1), calculate_hash(&addr2));
159159

160-
addr2.0.sun_path[17] = 127;
160+
unsafe { (*addr2.as_mut_ptr()).sun_path[17] = 127 };
161161
assert_ne!(addr1, addr2);
162162
assert_ne!(calculate_hash(&addr1), calculate_hash(&addr2));
163163
}
@@ -180,7 +180,7 @@ pub fn test_abstract_uds_addr() {
180180
assert_eq!(addr.path(), None);
181181

182182
// Internally, name is null-prefixed (abstract namespace)
183-
assert_eq!(addr.0.sun_path[0], 0);
183+
assert_eq!(unsafe { (*addr.as_ptr()).sun_path[0] }, 0);
184184
}
185185

186186
#[test]
@@ -194,8 +194,7 @@ pub fn test_getsockname() {
194194
.expect("socket failed");
195195
let sockaddr = SockAddr::new_unix(&sockname).unwrap();
196196
bind(sock, &sockaddr).expect("bind failed");
197-
assert_eq!(sockaddr.to_string(),
198-
getsockname(sock).expect("getsockname failed").to_string());
197+
assert_eq!(sockaddr, getsockname(sock).expect("getsockname failed"));
199198
}
200199

201200
#[test]

0 commit comments

Comments
 (0)