Skip to content

Commit 4ad9705

Browse files
Handle non-NUL-terminated strings in SocketAddrUnix. (#1371)
* 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. * Allocated `SocketAddrUnix::path()` with correct length (#1372) Using `.to_owned()` + `.push()` will cause a reallocation, because the initially allocated array with be one byte too short. We can use `CString::from_vec_with_nul_unchecked()` because it is a known invariant that the input does not contain any `NUL`s, not even the terminating `NUL`. --------- Co-authored-by: René Kijewski <[email protected]>
1 parent 9ca75f5 commit 4ad9705

File tree

5 files changed

+210
-28
lines changed

5 files changed

+210
-28
lines changed

src/backend/libc/net/addr.rs

Lines changed: 63 additions & 13 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, alloc::vec::Vec};
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,52 @@ 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>> {
133140
let bytes = self.bytes()?;
134141
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) })
142+
if self.unix.sun_path.len() == bytes.len() {
143+
// SAFETY: no NULs are contained in bytes
144+
unsafe { self.path_with_termination(bytes) }
145+
} else {
146+
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
147+
// NUL-terminated.
148+
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }.into())
149+
}
150+
} else {
151+
None
152+
}
153+
}
154+
155+
/// If the `sun_path` field is not NUL-terminated, terminate it.
156+
///
157+
/// SAFETY: the input `bytes` must not contain any NULs
158+
#[cfg(feature = "alloc")]
159+
unsafe fn path_with_termination(&self, bytes: &[u8]) -> Option<Cow<'_, CStr>> {
160+
let mut owned = Vec::with_capacity(bytes.len() + 1);
161+
owned.extend_from_slice(bytes);
162+
owned.push(b'\0');
163+
// SAFETY: `from_vec_with_nul_unchecked` since the string is
164+
// NUL-terminated and `bytes` does not conain any NULs.
165+
Some(Cow::Owned(
166+
CString::from_vec_with_nul_unchecked(owned).into(),
167+
))
168+
}
169+
170+
/// For a filesystem path address, return the path as a byte sequence,
171+
/// excluding the NUL terminator.
172+
#[inline]
173+
pub fn path_bytes(&self) -> Option<&[u8]> {
174+
let bytes = self.bytes()?;
175+
if !bytes.is_empty() && bytes[0] != 0 {
176+
if self.unix.sun_path.len() == self.len() - offsetof_sun_path() {
177+
// There is no NUL terminator.
178+
Some(bytes)
179+
} else {
180+
// Remove the NUL terminator.
181+
Some(&bytes[..bytes.len() - 1])
182+
}
138183
} else {
139184
None
140185
}
@@ -231,16 +276,21 @@ impl Hash for SocketAddrUnix {
231276
#[cfg(unix)]
232277
impl fmt::Debug for SocketAddrUnix {
233278
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279+
#[cfg(feature = "alloc")]
234280
if let Some(path) = self.path() {
235-
path.fmt(f)
236-
} else {
237-
#[cfg(linux_kernel)]
238-
if let Some(name) = self.abstract_name() {
239-
return name.fmt(f);
281+
return path.fmt(f);
282+
}
283+
if let Some(bytes) = self.path_bytes() {
284+
if let Ok(s) = core::str::from_utf8(bytes) {
285+
return s.fmt(f);
240286
}
241-
242-
"(unnamed)".fmt(f)
287+
return bytes.fmt(f);
288+
}
289+
#[cfg(linux_kernel)]
290+
if let Some(name) = self.abstract_name() {
291+
return name.fmt(f);
243292
}
293+
"(unnamed)".fmt(f)
244294
}
245295
}
246296

src/backend/linux_raw/net/addr.rs

Lines changed: 63 additions & 11 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, alloc::vec::Vec};
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,52 @@ 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+
// SAFETY: no NULs are contained in bytes
106+
unsafe { self.path_with_termination(bytes) }
107+
} else {
108+
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
109+
// NUL-terminated.
110+
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }.into())
111+
}
112+
} else {
113+
None
114+
}
115+
}
116+
117+
/// If the `sun_path` field is not NUL-terminated, terminate it.
118+
///
119+
/// SAFETY: the input `bytes` must not contain any NULs
120+
#[cfg(feature = "alloc")]
121+
unsafe fn path_with_termination(&self, bytes: &[u8]) -> Option<Cow<'_, CStr>> {
122+
let mut owned = Vec::with_capacity(bytes.len() + 1);
123+
owned.extend_from_slice(bytes);
124+
owned.push(b'\0');
125+
// SAFETY: `from_vec_with_nul_unchecked` since the string is
126+
// NUL-terminated and `bytes` does not conain any NULs.
127+
Some(Cow::Owned(
128+
CString::from_vec_with_nul_unchecked(owned).into(),
129+
))
130+
}
131+
132+
/// For a filesystem path address, return the path as a byte sequence,
133+
/// excluding the NUL terminator.
134+
#[inline]
135+
pub fn path_bytes(&self) -> Option<&[u8]> {
136+
let bytes = self.bytes()?;
137+
if !bytes.is_empty() && bytes[0] != 0 {
138+
if self.unix.sun_path.len() == self.len() - offsetof_sun_path() {
139+
// There is no NUL terminator.
140+
Some(bytes)
141+
} else {
142+
// Remove the NUL terminator.
143+
Some(&bytes[..bytes.len() - 1])
144+
}
100145
} else {
101146
None
102147
}
@@ -178,13 +223,20 @@ impl Hash for SocketAddrUnix {
178223

179224
impl fmt::Debug for SocketAddrUnix {
180225
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226+
#[cfg(feature = "alloc")]
181227
if let Some(path) = self.path() {
182-
path.fmt(f)
183-
} else if let Some(name) = self.abstract_name() {
184-
name.fmt(f)
185-
} else {
186-
"(unnamed)".fmt(f)
228+
return path.fmt(f);
229+
}
230+
if let Some(bytes) = self.path_bytes() {
231+
if let Ok(s) = core::str::from_utf8(bytes) {
232+
return s.fmt(f);
233+
}
234+
return bytes.fmt(f);
235+
}
236+
if let Some(name) = self.abstract_name() {
237+
return name.fmt(f);
187238
}
239+
"(unnamed)".fmt(f)
188240
}
189241
}
190242

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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,22 @@ 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+
#[cfg(linux_kernel)]
382+
assert!(!name.is_unnamed());
373383
do_test_unix_msg(name);
374384

375385
unlinkat(CWD, path, AtFlags::empty()).unwrap();
@@ -379,12 +389,22 @@ fn test_unix_msg() {
379389
#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "wasi")))]
380390
#[test]
381391
fn test_unix_msg_unconnected() {
392+
use rustix::ffi::CString;
393+
use std::os::unix::ffi::OsStrExt as _;
394+
382395
crate::init();
383396

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

387400
let name = SocketAddrUnix::new(&path).unwrap();
401+
assert_eq!(
402+
name.path(),
403+
Some(CString::new(path.as_os_str().as_bytes()).unwrap().into())
404+
);
405+
assert_eq!(name.path_bytes(), Some(path.as_os_str().as_bytes()));
406+
#[cfg(linux_kernel)]
407+
assert!(!name.is_unnamed());
388408
do_test_unix_msg_unconnected(name);
389409

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

403423
let name = SocketAddrUnix::new_abstract_name(path.as_os_str().as_bytes()).unwrap();
424+
assert_eq!(name.abstract_name(), Some(path.as_os_str().as_bytes()));
425+
assert!(!name.is_unnamed());
404426
do_test_unix_msg(name);
405427
}
406428

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

418440
let name = SocketAddrUnix::new_abstract_name(path.as_os_str().as_bytes()).unwrap();
441+
assert_eq!(name.abstract_name(), Some(path.as_os_str().as_bytes()));
442+
assert!(!name.is_unnamed());
419443
do_test_unix_msg_unconnected(name);
420444
}
421445

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

0 commit comments

Comments
 (0)