Skip to content

Commit

Permalink
CStr Safety invariant & Harnesses for from_bytes_until_nul (#180)
Browse files Browse the repository at this point in the history
Towards #150 

### Changes
* Added a `CStr` Safety Invariant
* Added a harness for `from_bytes_until_nul`, the harness covers:
    * The input slice contains a single null byte at the end;
    * The input slice contains no null bytes;
    * The input slice contains intermediate null bytes

### Discussion
* [Safety invariant
implementation](#150 (comment))
* [Input array
generation](#181)

### Verification Result
`./scripts/run-kani.sh --kani-args --harness ffi::c_str::verify`

```
// array size 16
Checking harness ffi::c_str::verify::check_from_bytes_until_nul...

VERIFICATION RESULT:
 ** 0 of 140 failed (5 unreachable)

VERIFICATION:- SUCCESSFUL
Verification Time: 7.3023376s

Complete - 1 successfully verified harnesses, 0 failures, 1 total.
```

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 and MIT licenses.
  • Loading branch information
Yenyun035 authored Nov 26, 2024
1 parent 07318df commit 82da845
Showing 1 changed file with 43 additions and 0 deletions.
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());
}
}
}

0 comments on commit 82da845

Please sign in to comment.