Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions doc/src/challenges/0013-cstr.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,44 @@ All proofs must automatically ensure the absence of the following undefined beha

Note: All solutions to verification challenges need to satisfy the criteria established in the
[challenge book](../general-rules.md) in addition to the ones listed above.

-------------------

## Verification Summary

All 14 items verified using Kani proof harnesses with bounded verification (MAX_SIZE=32).

### Part 1: Invariant

| Item | Status |
|------|--------|
| `Invariant` trait for `&CStr` | Implemented (lines 193-207) |
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This summary hard-codes source line ranges (e.g., "Implemented (lines 193-207)") which will become stale as the file evolves. Prefer a stable reference (file path + item name/harness name) or a link/anchor rather than embedding line numbers in the challenge documentation.

Suggested change
| `Invariant` trait for `&CStr` | Implemented (lines 193-207) |
| `Invariant` trait for `&CStr` | Implemented in the `CStr` invariant verification harness |

Copilot uses AI. Check for mistakes.

### Part 2: Safe Methods

| Function | Harness | Status |
|----------|---------|--------|
| `from_bytes_until_nul` | `check_from_bytes_until_nul` | VERIFIED |
| `from_bytes_with_nul` | `check_from_bytes_with_nul` | VERIFIED |
| `count_bytes` | `check_count_bytes` | VERIFIED |
| `is_empty` | `check_is_empty` | VERIFIED |
| `to_bytes` | `check_to_bytes` | VERIFIED |
| `to_bytes_with_nul` | `check_to_bytes_with_nul` | VERIFIED |
| `bytes` | `check_bytes` | VERIFIED |
| `to_str` | `check_to_str` | VERIFIED |
| `as_ptr` | `check_as_ptr` | VERIFIED |

### Part 3: Unsafe Functions

| Function | Contract | Harness | Status |
|----------|----------|---------|--------|
| `from_ptr` | `#[requires]` + `#[ensures]` | `check_from_ptr_contract` | VERIFIED |
| `from_bytes_with_nul_unchecked` | `#[requires]` + `#[ensures]` | `check_from_bytes_with_nul_unchecked` | VERIFIED |
| `strlen` | `#[requires]` + `#[ensures]` | `check_strlen_contract` | VERIFIED |
Comment on lines +116 to +120
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this table you refer to from_bytes_with_nul_unchecked, but earlier in the challenge description the function name is misspelled as from_bytes_with_nul_uncheked. It would be clearer if the document used a consistent spelling throughout (and corrected the earlier typo).

Copilot uses AI. Check for mistakes.

### Part 4: Trait Implementations

| Trait | Harness | Status |
|-------|---------|--------|
| `ops::Index<ops::RangeFrom<usize>>` | `check_index_range_from` | VERIFIED |
| `CloneToUninit` | `check_clone_to_uninit` | VERIFIED |
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 @@ -875,6 +875,7 @@ impl FusedIterator for Bytes<'_> {}
#[unstable(feature = "kani", issue = "none")]
mod verify {
use super::*;
use crate::clone::CloneToUninit;

// Helper function
fn arbitrary_cstr(slice: &[u8]) -> &CStr {
Expand Down Expand Up @@ -1096,4 +1097,46 @@ mod verify {
assert_eq!(expected_is_empty, c_str.is_empty());
assert!(c_str.is_safe());
}

// ops::Index<ops::RangeFrom<usize>> for CStr
#[kani::proof]
#[kani::unwind(33)]
fn check_index_range_from() {
const MAX_SIZE: usize = 32;
let string: [u8; MAX_SIZE] = kani::any();
let slice = kani::slice::any_slice_of_array(&string);
let c_str = arbitrary_cstr(slice);

let start: usize = kani::any();
let bytes_with_nul = c_str.to_bytes_with_nul();

if start < bytes_with_nul.len() {
let sub_cstr = &c_str[start..];
assert!(sub_cstr.is_safe());
assert_eq!(sub_cstr.to_bytes_with_nul(), &bytes_with_nul[start..]);
}
// else: would panic (expected behavior, not UB)
}

// CloneToUninit for CStr
#[kani::proof]
#[kani::unwind(33)]
fn check_clone_to_uninit() {
const MAX_SIZE: usize = 32;
let string: [u8; MAX_SIZE] = kani::any();
let slice = kani::slice::any_slice_of_array(&string);
let c_str = arbitrary_cstr(slice);

let bytes_with_nul = c_str.to_bytes_with_nul();
let len = bytes_with_nul.len();
kani::assume(len <= MAX_SIZE);

let mut dest: [u8; MAX_SIZE] = [0u8; MAX_SIZE];
unsafe {
c_str.clone_to_uninit(dest.as_mut_ptr());
}

// Verify the clone copied the correct bytes
assert_eq!(&dest[..len], bytes_with_nul);
Comment on lines +1135 to +1140
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check_clone_to_uninit currently passes a destination buffer that is larger than size_of_val(c_str) (always MAX_SIZE). That can mask out-of-bounds writes: an implementation bug that writes past len bytes could still stay within the array and avoid triggering UB checks, even though it would be UB for callers that provide exactly size_of_val(self) bytes as allowed by the trait contract. Consider passing a pointer to a region with exactly len bytes of remaining space (e.g., offset into the fixed array), so any overwrite beyond len is detected, and compare the written region against bytes_with_nul.

Suggested change
unsafe {
c_str.clone_to_uninit(dest.as_mut_ptr());
}
// Verify the clone copied the correct bytes
assert_eq!(&dest[..len], bytes_with_nul);
// Provide exactly `len` bytes of space to `clone_to_uninit` by offsetting
// into the fixed-size array. Any write past `len` will then be out of bounds.
let start = MAX_SIZE - len;
unsafe {
c_str.clone_to_uninit(dest[start..].as_mut_ptr());
}
// Verify the clone copied the correct bytes into the region we passed in
assert_eq!(&dest[start..start + len], bytes_with_nul);

Copilot uses AI. Check for mistakes.
}
}
Loading