Skip to content

Commit 897c8d0

Browse files
committed
Add debug asserts to validate NUL terminator in c strings
Signed-off-by: Alex Saveau <[email protected]>
1 parent 1e12aef commit 897c8d0

File tree

2 files changed

+29
-20
lines changed

2 files changed

+29
-20
lines changed

library/std/src/ffi/c_str.rs

+29-12
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ impl CString {
382382
let bytes: Vec<u8> = self.into();
383383
match memchr::memchr(0, &bytes) {
384384
Some(i) => Err(NulError(i, bytes)),
385-
None => Ok(unsafe { CString::from_vec_unchecked(bytes) }),
385+
None => Ok(unsafe { CString::_from_vec_unchecked(bytes) }),
386386
}
387387
}
388388
}
@@ -405,7 +405,7 @@ impl CString {
405405
// This allows better optimizations if lto enabled.
406406
match memchr::memchr(0, bytes) {
407407
Some(i) => Err(NulError(i, buffer)),
408-
None => Ok(unsafe { CString::from_vec_unchecked(buffer) }),
408+
None => Ok(unsafe { CString::_from_vec_unchecked(buffer) }),
409409
}
410410
}
411411

@@ -451,10 +451,15 @@ impl CString {
451451
/// ```
452452
#[must_use]
453453
#[stable(feature = "rust1", since = "1.0.0")]
454-
pub unsafe fn from_vec_unchecked(mut v: Vec<u8>) -> CString {
454+
pub unsafe fn from_vec_unchecked(v: Vec<u8>) -> Self {
455+
debug_assert!(memchr::memchr(0, &v).is_none());
456+
unsafe { Self::_from_vec_unchecked(v) }
457+
}
458+
459+
unsafe fn _from_vec_unchecked(mut v: Vec<u8>) -> Self {
455460
v.reserve_exact(1);
456461
v.push(0);
457-
CString { inner: v.into_boxed_slice() }
462+
Self { inner: v.into_boxed_slice() }
458463
}
459464

460465
/// Retakes ownership of a `CString` that was transferred to C via
@@ -578,7 +583,7 @@ impl CString {
578583
pub fn into_string(self) -> Result<String, IntoStringError> {
579584
String::from_utf8(self.into_bytes()).map_err(|e| IntoStringError {
580585
error: e.utf8_error(),
581-
inner: unsafe { CString::from_vec_unchecked(e.into_bytes()) },
586+
inner: unsafe { Self::_from_vec_unchecked(e.into_bytes()) },
582587
})
583588
}
584589

@@ -735,6 +740,11 @@ impl CString {
735740
#[must_use]
736741
#[stable(feature = "cstring_from_vec_with_nul", since = "1.58.0")]
737742
pub unsafe fn from_vec_with_nul_unchecked(v: Vec<u8>) -> Self {
743+
debug_assert!(memchr::memchr(0, &v).unwrap() + 1 == v.len());
744+
unsafe { Self::_from_vec_with_nul_unchecked(v) }
745+
}
746+
747+
unsafe fn _from_vec_with_nul_unchecked(v: Vec<u8>) -> Self {
738748
Self { inner: v.into_boxed_slice() }
739749
}
740750

@@ -778,7 +788,7 @@ impl CString {
778788
Some(nul_pos) if nul_pos + 1 == v.len() => {
779789
// SAFETY: We know there is only one nul byte, at the end
780790
// of the vec.
781-
Ok(unsafe { Self::from_vec_with_nul_unchecked(v) })
791+
Ok(unsafe { Self::_from_vec_with_nul_unchecked(v) })
782792
}
783793
Some(nul_pos) => Err(FromVecWithNulError {
784794
error_kind: FromBytesWithNulErrorKind::InteriorNul(nul_pos),
@@ -811,7 +821,7 @@ impl ops::Deref for CString {
811821

812822
#[inline]
813823
fn deref(&self) -> &CStr {
814-
unsafe { CStr::from_bytes_with_nul_unchecked(self.as_bytes_with_nul()) }
824+
unsafe { CStr::_from_bytes_with_nul_unchecked(self.as_bytes_with_nul()) }
815825
}
816826
}
817827

@@ -922,7 +932,7 @@ impl From<Vec<NonZeroU8>> for CString {
922932
};
923933
// SAFETY: `v` cannot contain null bytes, given the type-level
924934
// invariant of `NonZeroU8`.
925-
CString::from_vec_unchecked(v)
935+
Self::_from_vec_unchecked(v)
926936
}
927937
}
928938
}
@@ -1215,7 +1225,7 @@ impl CStr {
12151225
unsafe {
12161226
let len = sys::strlen(ptr);
12171227
let ptr = ptr as *const u8;
1218-
CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr, len as usize + 1))
1228+
Self::_from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr, len as usize + 1))
12191229
}
12201230
}
12211231

@@ -1258,7 +1268,7 @@ impl CStr {
12581268
Some(nul_pos) if nul_pos + 1 == bytes.len() => {
12591269
// SAFETY: We know there is only one nul byte, at the end
12601270
// of the byte slice.
1261-
Ok(unsafe { Self::from_bytes_with_nul_unchecked(bytes) })
1271+
Ok(unsafe { Self::_from_bytes_with_nul_unchecked(bytes) })
12621272
}
12631273
Some(nul_pos) => Err(FromBytesWithNulError::interior_nul(nul_pos)),
12641274
None => Err(FromBytesWithNulError::not_nul_terminated()),
@@ -1287,12 +1297,19 @@ impl CStr {
12871297
#[stable(feature = "cstr_from_bytes", since = "1.10.0")]
12881298
#[rustc_const_stable(feature = "const_cstr_unchecked", since = "1.59.0")]
12891299
pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr {
1300+
// We're in a const fn, so this is the best we can do
1301+
debug_assert!(!bytes.is_empty() && bytes[bytes.len() - 1] == 0);
1302+
unsafe { Self::_from_bytes_with_nul_unchecked(bytes) }
1303+
}
1304+
1305+
#[inline]
1306+
const unsafe fn _from_bytes_with_nul_unchecked(bytes: &[u8]) -> &Self {
12901307
// SAFETY: Casting to CStr is safe because its internal representation
12911308
// is a [u8] too (safe only inside std).
12921309
// Dereferencing the obtained pointer is safe because it comes from a
12931310
// reference. Making a reference is then safe because its lifetime
12941311
// is bound by the lifetime of the given `bytes`.
1295-
unsafe { &*(bytes as *const [u8] as *const CStr) }
1312+
unsafe { &*(bytes as *const [u8] as *const Self) }
12961313
}
12971314

12981315
/// Returns the inner pointer to this C string.
@@ -1555,7 +1572,7 @@ impl ops::Index<ops::RangeFrom<usize>> for CStr {
15551572
// byte, since otherwise we could get an empty string that doesn't end
15561573
// in a null.
15571574
if index.start < bytes.len() {
1558-
unsafe { CStr::from_bytes_with_nul_unchecked(&bytes[index.start..]) }
1575+
unsafe { CStr::_from_bytes_with_nul_unchecked(&bytes[index.start..]) }
15591576
} else {
15601577
panic!(
15611578
"index out of bounds: the len is {} but the index is {}",

library/std/src/ffi/c_str/tests.rs

-8
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,6 @@ fn build_with_zero2() {
3232
assert!(CString::new(vec![0]).is_err());
3333
}
3434

35-
#[test]
36-
fn build_with_zero3() {
37-
unsafe {
38-
let s = CString::from_vec_unchecked(vec![0]);
39-
assert_eq!(s.as_bytes(), b"\0");
40-
}
41-
}
42-
4335
#[test]
4436
fn formatted() {
4537
let s = CString::new(&b"abc\x01\x02\n\xE2\x80\xA6\xFF"[..]).unwrap();

0 commit comments

Comments
 (0)