Skip to content

Commit bc1216d

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 bc1216d

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
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: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,7 @@ impl FusedIterator for Bytes<'_> {}
875875
#[unstable(feature = "kani", issue = "none")]
876876
mod verify {
877877
use super::*;
878+
use crate::clone::CloneToUninit;
878879

879880
// Helper function
880881
fn arbitrary_cstr(slice: &[u8]) -> &CStr {
@@ -1096,4 +1097,46 @@ mod verify {
10961097
assert_eq!(expected_is_empty, c_str.is_empty());
10971098
assert!(c_str.is_safe());
10981099
}
1100+
1101+
// ops::Index<ops::RangeFrom<usize>> for CStr
1102+
#[kani::proof]
1103+
#[kani::unwind(33)]
1104+
fn check_index_range_from() {
1105+
const MAX_SIZE: usize = 32;
1106+
let string: [u8; MAX_SIZE] = kani::any();
1107+
let slice = kani::slice::any_slice_of_array(&string);
1108+
let c_str = arbitrary_cstr(slice);
1109+
1110+
let start: usize = kani::any();
1111+
let bytes_with_nul = c_str.to_bytes_with_nul();
1112+
1113+
if start < bytes_with_nul.len() {
1114+
let sub_cstr = &c_str[start..];
1115+
assert!(sub_cstr.is_safe());
1116+
assert_eq!(sub_cstr.to_bytes_with_nul(), &bytes_with_nul[start..]);
1117+
}
1118+
// else: would panic (expected behavior, not UB)
1119+
}
1120+
1121+
// CloneToUninit for CStr
1122+
#[kani::proof]
1123+
#[kani::unwind(33)]
1124+
fn check_clone_to_uninit() {
1125+
const MAX_SIZE: usize = 32;
1126+
let string: [u8; MAX_SIZE] = kani::any();
1127+
let slice = kani::slice::any_slice_of_array(&string);
1128+
let c_str = arbitrary_cstr(slice);
1129+
1130+
let bytes_with_nul = c_str.to_bytes_with_nul();
1131+
let len = bytes_with_nul.len();
1132+
kani::assume(len <= MAX_SIZE);
1133+
1134+
let mut dest: [u8; MAX_SIZE] = [0u8; MAX_SIZE];
1135+
unsafe {
1136+
c_str.clone_to_uninit(dest.as_mut_ptr());
1137+
}
1138+
1139+
// Verify the clone copied the correct bytes
1140+
assert_eq!(&dest[..len], bytes_with_nul);
1141+
}
10991142
}

0 commit comments

Comments
 (0)