Skip to content

Commit 4d56b59

Browse files
committed
Handle non-NUL-terminated strings in SocketAddrUnix.
Unix-domain socket address can be long enough that the NUL terminator does not fit. Handle this case by making `path()` return a `Cow<CStr>` and adding a NUL terminator as needed. Also add a `path_bytes()` function for returning the raw bytes. Fixes #1316.
1 parent 6cd4a06 commit 4d56b59

File tree

5 files changed

+165
-16
lines changed

5 files changed

+165
-16
lines changed

src/backend/libc/net/addr.rs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use {
1313
core::hash::{Hash, Hasher},
1414
core::slice,
1515
};
16+
#[cfg(all(unix, feature = "alloc"))]
17+
use {crate::ffi::CString, alloc::borrow::Cow};
1618

1719
/// `struct sockaddr_un`
1820
#[cfg(unix)]
@@ -35,9 +37,12 @@ impl SocketAddrUnix {
3537
#[inline]
3638
fn _new(path: &CStr) -> io::Result<Self> {
3739
let mut unix = Self::init();
38-
let bytes = path.to_bytes_with_nul();
40+
let mut bytes = path.to_bytes_with_nul();
3941
if bytes.len() > unix.sun_path.len() {
40-
return Err(io::Errno::NAMETOOLONG);
42+
bytes = path.to_bytes(); // without NUL
43+
if bytes.len() > unix.sun_path.len() {
44+
return Err(io::Errno::NAMETOOLONG);
45+
}
4146
}
4247
for (i, b) in bytes.iter().enumerate() {
4348
unix.sun_path[i] = *b as c::c_char;
@@ -129,12 +134,44 @@ impl SocketAddrUnix {
129134

130135
/// For a filesystem path address, return the path.
131136
#[inline]
132-
pub fn path(&self) -> Option<&CStr> {
137+
#[cfg(feature = "alloc")]
138+
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
139+
pub fn path(&self) -> Option<Cow<CStr>> {
140+
let bytes = self.bytes()?;
141+
if !bytes.is_empty() && bytes[0] != 0 {
142+
if self.unix.sun_path.len() == bytes.len() {
143+
self.path_with_termination(bytes)
144+
} else {
145+
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
146+
// NUL-terminated.
147+
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }.into())
148+
}
149+
} else {
150+
None
151+
}
152+
}
153+
154+
/// If the `sun_path` field is not NUL-terminated, terminate it.
155+
#[cfg(feature = "alloc")]
156+
fn path_with_termination(&self, bytes: &[u8]) -> Option<Cow<CStr>> {
157+
let mut owned = bytes.to_owned();
158+
owned.push(b'\0');
159+
Some(CString::from_vec_with_nul(owned).unwrap().into())
160+
}
161+
162+
/// For a filesystem path address, return the path as a byte sequence,
163+
/// excluding the NUL terminator.
164+
#[inline]
165+
pub fn path_bytes(&self) -> Option<&[u8]> {
133166
let bytes = self.bytes()?;
134167
if !bytes.is_empty() && bytes[0] != 0 {
135-
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
136-
// NUL-terminated.
137-
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) })
168+
if self.unix.sun_path.len() == self.len() - offsetof_sun_path() {
169+
// There is no NUL terminator.
170+
Some(bytes)
171+
} else {
172+
// Remove the NUL terminator.
173+
Some(&bytes[..bytes.len() - 1])
174+
}
138175
} else {
139176
None
140177
}

src/backend/linux_raw/net/addr.rs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use crate::{io, path};
1414
use core::cmp::Ordering;
1515
use core::hash::{Hash, Hasher};
1616
use core::{fmt, slice};
17+
#[cfg(feature = "alloc")]
18+
use {crate::ffi::CString, alloc::borrow::Cow};
1719

1820
/// `struct sockaddr_un`
1921
#[derive(Clone)]
@@ -33,9 +35,12 @@ impl SocketAddrUnix {
3335
#[inline]
3436
fn _new(path: &CStr) -> io::Result<Self> {
3537
let mut unix = Self::init();
36-
let bytes = path.to_bytes_with_nul();
38+
let mut bytes = path.to_bytes_with_nul();
3739
if bytes.len() > unix.sun_path.len() {
38-
return Err(io::Errno::NAMETOOLONG);
40+
bytes = path.to_bytes(); // without NUL
41+
if bytes.len() > unix.sun_path.len() {
42+
return Err(io::Errno::NAMETOOLONG);
43+
}
3944
}
4045
for (i, b) in bytes.iter().enumerate() {
4146
unix.sun_path[i] = bitcast!(*b);
@@ -91,12 +96,44 @@ impl SocketAddrUnix {
9196

9297
/// For a filesystem path address, return the path.
9398
#[inline]
94-
pub fn path(&self) -> Option<&CStr> {
99+
#[cfg(feature = "alloc")]
100+
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
101+
pub fn path(&self) -> Option<Cow<CStr>> {
95102
let bytes = self.bytes()?;
96103
if !bytes.is_empty() && bytes[0] != 0 {
97-
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
98-
// NUL-terminated.
99-
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) })
104+
if self.unix.sun_path.len() == bytes.len() {
105+
self.path_with_termination(bytes)
106+
} else {
107+
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
108+
// NUL-terminated.
109+
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }.into())
110+
}
111+
} else {
112+
None
113+
}
114+
}
115+
116+
/// If the `sun_path` field is not NUL-terminated, terminate it.
117+
#[cfg(feature = "alloc")]
118+
fn path_with_termination(&self, bytes: &[u8]) -> Option<Cow<CStr>> {
119+
let mut owned = bytes.to_owned();
120+
owned.push(b'\0');
121+
Some(CString::from_vec_with_nul(owned).unwrap().into())
122+
}
123+
124+
/// For a filesystem path address, return the path as a byte sequence,
125+
/// excluding the NUL terminator.
126+
#[inline]
127+
pub fn path_bytes(&self) -> Option<&[u8]> {
128+
let bytes = self.bytes()?;
129+
if !bytes.is_empty() && bytes[0] != 0 {
130+
if self.unix.sun_path.len() == self.len() - offsetof_sun_path() {
131+
// There is no NUL terminator.
132+
Some(bytes)
133+
} else {
134+
// Remove the NUL terminator.
135+
Some(&bytes[..bytes.len() - 1])
136+
}
100137
} else {
101138
None
102139
}

tests/net/addr.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,19 @@ fn test_unix_addr() {
3131

3232
assert_eq!(
3333
SocketAddrUnix::new("/").unwrap().path().unwrap(),
34-
cstr!("/")
34+
cstr!("/").into()
3535
);
3636
assert_eq!(
3737
SocketAddrUnix::new("//").unwrap().path().unwrap(),
38-
cstr!("//")
38+
cstr!("//").into()
3939
);
4040
assert_eq!(
4141
SocketAddrUnix::new("/foo/bar").unwrap().path().unwrap(),
42-
cstr!("/foo/bar")
42+
cstr!("/foo/bar").into()
4343
);
4444
assert_eq!(
4545
SocketAddrUnix::new("foo").unwrap().path().unwrap(),
46-
cstr!("foo")
46+
cstr!("foo").into()
4747
);
4848
SocketAddrUnix::new("/foo\0/bar").unwrap_err();
4949
assert!(SocketAddrUnix::new("").unwrap().path().is_none());

tests/net/unix.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,21 @@ fn do_test_unix_msg_unconnected(addr: SocketAddrUnix) {
364364
#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
365365
#[test]
366366
fn test_unix_msg() {
367+
use rustix::ffi::CString;
368+
use std::os::unix::ffi::OsStrExt as _;
369+
367370
crate::init();
368371

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

372375
let name = SocketAddrUnix::new(&path).unwrap();
376+
assert_eq!(
377+
name.path(),
378+
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
379+
);
380+
assert_eq!(name.path_bytes(), Some(path.as_os_str().as_bytes()));
381+
assert!(!name.is_unnamed());
373382
do_test_unix_msg(name);
374383

375384
unlinkat(CWD, path, AtFlags::empty()).unwrap();
@@ -379,12 +388,21 @@ fn test_unix_msg() {
379388
#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
380389
#[test]
381390
fn test_unix_msg_unconnected() {
391+
use rustix::ffi::CString;
392+
use std::os::unix::ffi::OsStrExt as _;
393+
382394
crate::init();
383395

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

387399
let name = SocketAddrUnix::new(&path).unwrap();
400+
assert_eq!(
401+
name.path(),
402+
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
403+
);
404+
assert_eq!(name.path_bytes(), Some(path.as_os_str().as_bytes()));
405+
assert!(!name.is_unnamed());
388406
do_test_unix_msg_unconnected(name);
389407

390408
unlinkat(CWD, path, AtFlags::empty()).unwrap();
@@ -401,6 +419,8 @@ fn test_abstract_unix_msg() {
401419
let path = tmpdir.path().join("scp_4804");
402420

403421
let name = SocketAddrUnix::new_abstract_name(path.as_os_str().as_bytes()).unwrap();
422+
assert_eq!(name.abstract_name(), Some(path.as_os_str().as_bytes()));
423+
assert!(!name.is_unnamed());
404424
do_test_unix_msg(name);
405425
}
406426

@@ -416,6 +436,8 @@ fn test_abstract_unix_msg_unconnected() {
416436
let path = tmpdir.path().join("scp_4804");
417437

418438
let name = SocketAddrUnix::new_abstract_name(path.as_os_str().as_bytes()).unwrap();
439+
assert_eq!(name.abstract_name(), Some(path.as_os_str().as_bytes()));
440+
assert!(!name.is_unnamed());
419441
do_test_unix_msg_unconnected(name);
420442
}
421443

@@ -948,3 +970,34 @@ fn test_bind_unnamed_address() {
948970
assert_ne!(address.abstract_name(), None);
949971
assert_eq!(address.path(), None);
950972
}
973+
974+
/// Test that names long enough to not have room for the NUL terminator are
975+
/// handled properly.
976+
#[test]
977+
fn test_long_named_address() {
978+
use memoffset::span_of;
979+
use rustix::ffi::CString;
980+
use std::os::unix::ffi::OsStrExt;
981+
use std::path::PathBuf;
982+
983+
let lens = [
984+
span_of!(libc::sockaddr_un, sun_path).len(),
985+
#[cfg(linux_kernel)]
986+
span_of!(linux_raw_sys::net::sockaddr_un, sun_path).len(),
987+
];
988+
989+
for len in lens {
990+
let path = PathBuf::from("a".repeat(len));
991+
let name = SocketAddrUnix::new(&path).unwrap();
992+
assert_eq!(
993+
name.path(),
994+
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
995+
);
996+
assert_eq!(
997+
name.path(),
998+
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
999+
);
1000+
assert_eq!(name.path_bytes(), Some(path.as_os_str().as_bytes()));
1001+
assert!(!name.is_unnamed());
1002+
}
1003+
}

tests/net/unix_alloc.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,12 +361,21 @@ fn do_test_unix_msg_unconnected(addr: SocketAddrUnix) {
361361
#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
362362
#[test]
363363
fn test_unix_msg() {
364+
use rustix::ffi::CString;
365+
use std::os::unix::ffi::OsStrExt as _;
366+
364367
crate::init();
365368

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

369372
let name = SocketAddrUnix::new(&path).unwrap();
373+
assert_eq!(
374+
name.path(),
375+
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
376+
);
377+
assert_eq!(name.path_bytes(), Some(path.as_os_str().as_bytes()));
378+
assert!(!name.is_unnamed());
370379
do_test_unix_msg(name);
371380

372381
unlinkat(CWD, path, AtFlags::empty()).unwrap();
@@ -376,12 +385,21 @@ fn test_unix_msg() {
376385
#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
377386
#[test]
378387
fn test_unix_msg_unconnected() {
388+
use rustix::ffi::CString;
389+
use std::os::unix::ffi::OsStrExt as _;
390+
379391
crate::init();
380392

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

384396
let name = SocketAddrUnix::new(&path).unwrap();
397+
assert_eq!(
398+
name.path(),
399+
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
400+
);
401+
assert_eq!(name.path_bytes(), Some(path.as_os_str().as_bytes()));
402+
assert!(!name.is_unnamed());
385403
do_test_unix_msg_unconnected(name);
386404

387405
unlinkat(CWD, path, AtFlags::empty()).unwrap();
@@ -398,6 +416,8 @@ fn test_abstract_unix_msg() {
398416
let path = tmpdir.path().join("scp_4804");
399417

400418
let name = SocketAddrUnix::new_abstract_name(path.as_os_str().as_bytes()).unwrap();
419+
assert_eq!(name.abstract_name(), Some(path.as_os_str().as_bytes()));
420+
assert!(!name.is_unnamed());
401421
do_test_unix_msg(name);
402422
}
403423

@@ -413,6 +433,8 @@ fn test_abstract_unix_msg_unconnected() {
413433
let path = tmpdir.path().join("scp_4804");
414434

415435
let name = SocketAddrUnix::new_abstract_name(path.as_os_str().as_bytes()).unwrap();
436+
assert_eq!(name.abstract_name(), Some(path.as_os_str().as_bytes()));
437+
assert!(!name.is_unnamed());
416438
do_test_unix_msg_unconnected(name);
417439
}
418440

0 commit comments

Comments
 (0)