Skip to content

Commit 0446b60

Browse files
committed
Challenge 13: Verify safety of CStr
Verify all 14 items listed in the challenge specification. 14 Kani proof harnesses, 0 failures. Bounded verification with MAX_SIZE=32. Part 1: Invariant trait for CStr (pre-existing). Part 2: Harnesses for all 9 safe methods — from_bytes_until_nul, from_bytes_with_nul, count_bytes, is_empty, to_bytes, to_bytes_with_nul, bytes, to_str, as_ptr (pre-existing). Part 3: Contracts and harnesses for all 3 unsafe functions — from_ptr, from_bytes_with_nul_unchecked, strlen (pre-existing). Part 4: New harnesses for trait implementations: - check_index_range_from: verifies Index<RangeFrom<usize>> preserves the CStr invariant when slicing from any valid start index. - check_clone_to_uninit: verifies CloneToUninit copies correct bytes to the destination with no undefined behavior. Note: A formal #[requires] contract on CloneToUninit::clone_to_uninit could not be added because the safety crate's proc macro does not currently support methods inside unsafe impl Trait blocks. The harness verifies safety via CBMC's built-in memory model checks. Resolves #150
1 parent 2840898 commit 0446b60

File tree

2 files changed

+97
-3
lines changed

2 files changed

+97
-3
lines changed

doc/src/challenges/0013-cstr.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,44 @@ All proofs must automatically ensure the absence of the following undefined beha
8484

8585
Note: All solutions to verification challenges need to satisfy the criteria established in the
8686
[challenge book](../general-rules.md) in addition to the ones listed above.
87+
88+
-------------------
89+
90+
## Verification Summary
91+
92+
All 14 items verified using Kani proof harnesses with bounded verification (MAX_SIZE=32).
93+
94+
### Part 1: Invariant
95+
96+
| Item | Status |
97+
|------|--------|
98+
| `Invariant` trait for `&CStr` | Implemented (lines 193-207) |
99+
100+
### Part 2: Safe Methods
101+
102+
| Function | Harness | Status |
103+
|----------|---------|--------|
104+
| `from_bytes_until_nul` | `check_from_bytes_until_nul` | VERIFIED |
105+
| `from_bytes_with_nul` | `check_from_bytes_with_nul` | VERIFIED |
106+
| `count_bytes` | `check_count_bytes` | VERIFIED |
107+
| `is_empty` | `check_is_empty` | VERIFIED |
108+
| `to_bytes` | `check_to_bytes` | VERIFIED |
109+
| `to_bytes_with_nul` | `check_to_bytes_with_nul` | VERIFIED |
110+
| `bytes` | `check_bytes` | VERIFIED |
111+
| `to_str` | `check_to_str` | VERIFIED |
112+
| `as_ptr` | `check_as_ptr` | VERIFIED |
113+
114+
### Part 3: Unsafe Functions
115+
116+
| Function | Contract | Harness | Status |
117+
|----------|----------|---------|--------|
118+
| `from_ptr` | `#[requires]` + `#[ensures]` | `check_from_ptr_contract` | VERIFIED |
119+
| `from_bytes_with_nul_unchecked` | `#[requires]` + `#[ensures]` | `check_from_bytes_with_nul_unchecked` | VERIFIED |
120+
| `strlen` | `#[requires]` + `#[ensures]` | `check_strlen_contract` | VERIFIED |
121+
122+
### Part 4: Trait Implementations
123+
124+
| Trait | Harness | Status |
125+
|-------|---------|--------|
126+
| `ops::Index<ops::RangeFrom<usize>>` | `check_index_range_from` | VERIFIED |
127+
| `CloneToUninit` | `check_clone_to_uninit` | VERIFIED |

library/core/src/ffi/c_str.rs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,10 @@ impl fmt::Display for FromBytesWithNulError {
146146
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147147
match self {
148148
Self::InteriorNul { position } => {
149-
write!(f, "data provided contains an interior nul byte at byte position {position}")
149+
write!(
150+
f,
151+
"data provided contains an interior nul byte at byte position {position}"
152+
)
150153
}
151154
Self::NotNulTerminated => write!(f, "data provided is not nul terminated"),
152155
}
@@ -821,7 +824,10 @@ unsafe impl Sync for Bytes<'_> {}
821824
impl<'a> Bytes<'a> {
822825
#[inline]
823826
fn new(s: &'a CStr) -> Self {
824-
Self { ptr: s.as_non_null_ptr().cast(), phantom: PhantomData }
827+
Self {
828+
ptr: s.as_non_null_ptr().cast(),
829+
phantom: PhantomData,
830+
}
825831
}
826832

827833
#[inline]
@@ -858,7 +864,11 @@ impl Iterator for Bytes<'_> {
858864

859865
#[inline]
860866
fn size_hint(&self) -> (usize, Option<usize>) {
861-
if self.is_empty() { (0, Some(0)) } else { (1, None) }
867+
if self.is_empty() {
868+
(0, Some(0))
869+
} else {
870+
(1, None)
871+
}
862872
}
863873

864874
#[inline]
@@ -875,6 +885,7 @@ impl FusedIterator for Bytes<'_> {}
875885
#[unstable(feature = "kani", issue = "none")]
876886
mod verify {
877887
use super::*;
888+
use crate::clone::CloneToUninit;
878889

879890
// Helper function
880891
fn arbitrary_cstr(slice: &[u8]) -> &CStr {
@@ -1096,4 +1107,46 @@ mod verify {
10961107
assert_eq!(expected_is_empty, c_str.is_empty());
10971108
assert!(c_str.is_safe());
10981109
}
1110+
1111+
// ops::Index<ops::RangeFrom<usize>> for CStr
1112+
#[kani::proof]
1113+
#[kani::unwind(33)]
1114+
fn check_index_range_from() {
1115+
const MAX_SIZE: usize = 32;
1116+
let string: [u8; MAX_SIZE] = kani::any();
1117+
let slice = kani::slice::any_slice_of_array(&string);
1118+
let c_str = arbitrary_cstr(slice);
1119+
1120+
let start: usize = kani::any();
1121+
let bytes_with_nul = c_str.to_bytes_with_nul();
1122+
1123+
if start < bytes_with_nul.len() {
1124+
let sub_cstr = &c_str[start..];
1125+
assert!(sub_cstr.is_safe());
1126+
assert_eq!(sub_cstr.to_bytes_with_nul(), &bytes_with_nul[start..]);
1127+
}
1128+
// else: would panic (expected behavior, not UB)
1129+
}
1130+
1131+
// CloneToUninit for CStr
1132+
#[kani::proof]
1133+
#[kani::unwind(33)]
1134+
fn check_clone_to_uninit() {
1135+
const MAX_SIZE: usize = 32;
1136+
let string: [u8; MAX_SIZE] = kani::any();
1137+
let slice = kani::slice::any_slice_of_array(&string);
1138+
let c_str = arbitrary_cstr(slice);
1139+
1140+
let bytes_with_nul = c_str.to_bytes_with_nul();
1141+
let len = bytes_with_nul.len();
1142+
kani::assume(len <= MAX_SIZE);
1143+
1144+
let mut dest: [u8; MAX_SIZE] = [0u8; MAX_SIZE];
1145+
unsafe {
1146+
c_str.clone_to_uninit(dest.as_mut_ptr());
1147+
}
1148+
1149+
// Verify the clone copied the correct bytes
1150+
assert_eq!(&dest[..len], bytes_with_nul);
1151+
}
10991152
}

0 commit comments

Comments
 (0)