Skip to content

CStr Safety invariant & Harnesses for from_bytes_until_nul #180

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 12 commits into from
Nov 26, 2024
43 changes: 43 additions & 0 deletions library/core/src/ffi/c_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ use crate::ptr::NonNull;
use crate::slice::memchr;
use crate::{fmt, intrinsics, ops, slice, str};

use crate::ub_checks::Invariant;

#[cfg(kani)]
use crate::kani;

// FIXME: because this is doc(inline)d, we *have* to use intra-doc links because the actual link
// depends on where the item is being documented. however, since this is libcore, we can't
// actually reference libstd or liballoc in intra-doc links. so, the best we can do is remove the
Expand Down Expand Up @@ -207,6 +212,22 @@ impl fmt::Display for FromBytesWithNulError {
}
}

#[unstable(feature = "ub_checks", issue = "none")]
impl Invariant for &CStr {
/**
* Safety invariant of a valid CStr:
* 1. An empty CStr should have a null byte.
* 2. A valid CStr should end with a null-terminator and contains
* no intermediate null bytes.
*/
fn is_safe(&self) -> bool {
let bytes: &[c_char] = &self.inner;
let len = bytes.len();

!bytes.is_empty() && bytes[len - 1] == 0 && !bytes[..len-1].contains(&0)
}
}

impl CStr {
/// Wraps a raw C string with a safe C string wrapper.
///
Expand Down Expand Up @@ -833,3 +854,25 @@ impl Iterator for Bytes<'_> {

#[unstable(feature = "cstr_bytes", issue = "112115")]
impl FusedIterator for Bytes<'_> {}

#[cfg(kani)]
#[unstable(feature = "kani", issue = "none")]
mod verify {
use super::*;

// pub const fn from_bytes_until_nul(bytes: &[u8]) -> Result<&CStr, FromBytesUntilNulError>
#[kani::proof]
#[kani::unwind(32)] // 7.3 seconds when 16; 33.1 seconds when 32
fn check_from_bytes_until_nul() {
const MAX_SIZE: usize = 32;
let string: [u8; MAX_SIZE] = kani::any();
// Covers the case of a single null byte at the end, no null bytes, as
// well as intermediate null bytes
let slice = kani::slice::any_slice_of_array(&string);

let result = CStr::from_bytes_until_nul(slice);
if let Ok(c_str) = result {
assert!(c_str.is_safe());
}
}
}
Loading