Skip to content

Commit 90ae7a5

Browse files
committed
Add CStr safety invariant && from_bytes_until_null harnesses
1 parent 63d53fb commit 90ae7a5

File tree

2 files changed

+64
-29
lines changed

2 files changed

+64
-29
lines changed

library/core/src/ffi/c_str.rs

+64-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::slice::memchr;
1010
use crate::{fmt, intrinsics, ops, slice, str};
1111

1212
// use safety::{requires, ensures};
13+
use crate::ub_checks::Invariant;
1314

1415
#[cfg(kani)]
1516
use crate::kani;
@@ -105,9 +106,6 @@ use crate::kani;
105106
// want `repr(transparent)` but we don't want it to show up in rustdoc, so we hide it under
106107
// `cfg(doc)`. This is an ad-hoc implementation of attribute privacy.
107108
#[repr(transparent)]
108-
#[derive(kani::Arbitrary)]
109-
#[derive(kani::Invariant)]
110-
#[safety_constraint(inner.len() > 0 && inner[inner.len() - 1] == 0 && !inner[..inner.len() - 1].contains(&0))]
111109
pub struct CStr {
112110
// FIXME: this should not be represented with a DST slice but rather with
113111
// just a raw `c_char` along with some form of marker to make
@@ -215,6 +213,23 @@ impl fmt::Display for FromBytesWithNulError {
215213
}
216214
}
217215

216+
#[unstable(feature = "ub_checks", issue = "none")]
217+
impl Invariant for &CStr {
218+
fn is_safe(&self) -> bool {
219+
let bytes: &[c_char] = &self.inner;
220+
let len = bytes.len();
221+
222+
// An empty CStr should has a null byte.
223+
// A valid CStr should end with a null-terminator and contains
224+
// no intermediate null bytes.
225+
if bytes.is_empty() || bytes[len - 1] != 0 || bytes[..len-1].contains(&0) {
226+
return false;
227+
}
228+
229+
true
230+
}
231+
}
232+
218233
impl CStr {
219234
/// Wraps a raw C string with a safe C string wrapper.
220235
///
@@ -841,3 +856,49 @@ impl Iterator for Bytes<'_> {
841856

842857
#[unstable(feature = "cstr_bytes", issue = "112115")]
843858
impl FusedIterator for Bytes<'_> {}
859+
860+
#[cfg(kani)]
861+
#[unstable(feature = "kani", issue = "none")]
862+
mod verify {
863+
use super::*;
864+
865+
// pub const fn from_bytes_until_nul(bytes: &[u8]) -> Result<&CStr, FromBytesUntilNulError>
866+
#[kani::proof]
867+
#[kani::unwind(32)] // Proof bounded by array length
868+
fn check_from_bytes_until_nul_random_nul_byte() {
869+
const ARR_LEN: usize = 1000;
870+
let mut string: [u8; ARR_LEN] = kani::any();
871+
872+
// ensure that there is at least one null byte
873+
let idx: usize = kani::any_where(|x: &usize| *x >= 0 && *x < ARR_LEN);
874+
string[idx] = 0;
875+
876+
let c_str = CStr::from_bytes_until_nul(&string).unwrap();
877+
assert!(c_str.is_safe());
878+
}
879+
880+
#[kani::proof]
881+
#[kani::unwind(32)] // Proof bounded by array length
882+
fn check_from_bytes_until_nul_single_nul_byte_end() {
883+
const ARR_LEN: usize = 1000;
884+
// ensure that the string does not have intermediate null bytes
885+
let mut string: [u8; ARR_LEN] = kani::any_where(|x: &[u8; ARR_LEN]| !x[..ARR_LEN-1].contains(&0));
886+
// ensure that the string is properly null-terminated
887+
string[ARR_LEN - 1] = 0;
888+
889+
let c_str = CStr::from_bytes_until_nul(&string).unwrap();
890+
assert!(c_str.is_safe());
891+
}
892+
893+
#[kani::proof]
894+
#[kani::unwind(8)] // Proof bounded by array length
895+
fn check_from_bytes_until_nul_single_nul_byte_head() {
896+
const ARR_LEN: usize = 8;
897+
let mut string: [u8; ARR_LEN] = kani::any();
898+
// The first byte is a null byte should result in an empty CStr.
899+
string[0] = 0;
900+
901+
let c_str = CStr::from_bytes_until_nul(&string).unwrap();
902+
assert!(c_str.is_safe());
903+
}
904+
}

library/core/src/ffi/mod.rs

-26
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ pub use self::c_str::FromBytesUntilNulError;
2020
pub use self::c_str::FromBytesWithNulError;
2121
use crate::fmt;
2222

23-
use safety::{requires, ensures};
24-
25-
#[cfg(kani)]
26-
use crate::kani;
27-
2823
#[unstable(feature = "c_str_module", issue = "112134")]
2924
pub mod c_str;
3025

@@ -228,24 +223,3 @@ impl fmt::Debug for c_void {
228223
)]
229224
#[link(name = "/defaultlib:libcmt", modifiers = "+verbatim", cfg(target_feature = "crt-static"))]
230225
extern "C" {}
231-
232-
#[cfg(kani)]
233-
#[unstable(feature = "kani", issue = "none")]
234-
mod verify {
235-
use super::*;
236-
237-
// pub const fn from_bytes_until_nul(bytes: &[u8]) -> Result<&CStr, FromBytesUntilNulError>
238-
#[kani::proof]
239-
#[kani::unwind(16)] // Proof bounded by array length
240-
fn check_from_bytes_until_nul() {
241-
const ARR_LEN: usize = 16;
242-
let mut string: [u8; ARR_LEN] = kani::any();
243-
// ensure that there is at least one null byte
244-
let idx: usize = kani::any_where(|x| *x >= 0 && *x < ARR_LEN);
245-
string[idx] = 0;
246-
247-
let c_str = CStr::from_bytes_until_nul(&string).unwrap();
248-
assert!(c_str.is_safe());
249-
}
250-
251-
}

0 commit comments

Comments
 (0)