Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Cstr verify safety contracts of unsafe functions strlen, from_bytes_with_nul_unchecked #193

Merged
merged 39 commits into from
Dec 12, 2024
Merged
Changes from 11 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
64ddbff
Add to_bytes and to_bytes_with_nul harnesses
Yenyun035 Nov 27, 2024
1edbe31
Merge branch 'main' into c-0013-yenyunw-cstr-to-bytes
Yenyun035 Nov 27, 2024
4059184
Merge branch 'main' into c-0013-yenyunw-cstr-to-bytes
Yenyun035 Nov 28, 2024
8b15d35
add bytes, to_str and as_ptr proofs
rajathkotyal Nov 28, 2024
ec0e94c
Update c_str.rs
rajathkotyal Nov 28, 2024
e70a1ef
add harness for `from_ptr`, `from_bytes_with_nul_unchecked`, `strlen`…
rajathkotyal Nov 28, 2024
7ddf9de
Fix contracts and harnesses
Yenyun035 Nov 28, 2024
937058a
Merge branch 'main' into c-0013-rajathm-part3
rajathkotyal Nov 30, 2024
0a372c1
update stubbing and const proof
rajathkotyal Dec 1, 2024
e323bf3
update stub and cstr compile time proof
rajathkotyal Dec 1, 2024
e054f35
comment
rajathkotyal Dec 1, 2024
8b95cb1
update cstr strlen
rajathkotyal Dec 2, 2024
eaf60eb
Merge branch 'main' into c-0013-rajathm-part3
rajathkotyal Dec 2, 2024
c54a51e
Update c_str.rs
rajathkotyal Dec 2, 2024
11c3f8d
Merge branch 'main' into c-0013-rajathm-part3
rajathkotyal Dec 3, 2024
00365ed
update pre and post conditions
rajathkotyal Dec 3, 2024
549613d
Merge branch 'main' into c-0013-rajathm-part3
rajathkotyal Dec 5, 2024
b21eb00
delimiter typo
rajathkotyal Dec 5, 2024
3f4be6d
retained only necessary checks pertaining to the function, cleaned up…
rajathkotyal Dec 5, 2024
076e972
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 5, 2024
2e3528b
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 6, 2024
407fe08
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 6, 2024
600c859
Merge branch 'main' into c-0013-rajathm-part3
rajathkotyal Dec 6, 2024
53588b4
removed redundant check
rajathkotyal Dec 6, 2024
689c9d0
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 7, 2024
dbf278b
Fix import
Yenyun035 Dec 7, 2024
4301961
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 8, 2024
f330bf4
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 10, 2024
8c47af3
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 10, 2024
e0ef0dc
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 10, 2024
c5d8da3
Fix is_null_terminated not use issue
Yenyun035 Dec 10, 2024
0f075fc
Fix unused import error
carolynzech Dec 10, 2024
e675a37
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 11, 2024
6d0a1f0
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 11, 2024
6d19642
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 11, 2024
2789d95
Fix format && comments
Yenyun035 Dec 11, 2024
23c66a3
Formatting
Yenyun035 Dec 11, 2024
51462ff
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 11, 2024
0521c6b
Merge branch 'main' into c-0013-rajathm-part3
Yenyun035 Dec 11, 2024
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
126 changes: 124 additions & 2 deletions library/core/src/ffi/c_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::slice::memchr;
use crate::{fmt, ops, slice, str};

use crate::ub_checks::Invariant;
use safety::{requires, ensures};

#[cfg(kani)]
use crate::kani;
Expand Down Expand Up @@ -432,6 +433,10 @@ impl CStr {
#[stable(feature = "cstr_from_bytes", since = "1.10.0")]
#[rustc_const_stable(feature = "const_cstr_unchecked", since = "1.59.0")]
#[rustc_allow_const_fn_unstable(const_eval_select)]
// Preconditions: Null-terminated and no intermediate null bytes
#[requires(!bytes.is_empty() && bytes[bytes.len() - 1] == 0 && !bytes[..bytes.len()-1].contains(&0))]
// Postcondition: The resulting CStr satisfies the same conditions as preconditions
#[ensures(|result| result.is_safe())]
pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr {
const_eval_select!(
@capture { bytes: &[u8] } -> &CStr:
Expand Down Expand Up @@ -755,6 +760,33 @@ impl AsRef<CStr> for CStr {
#[unstable(feature = "cstr_internals", issue = "none")]
#[cfg_attr(bootstrap, rustc_const_stable(feature = "const_cstr_from_ptr", since = "1.81.0"))]
#[rustc_allow_const_fn_unstable(const_eval_select)]
// Preconditions:
// - ptr must be non-null
// - ptr must point to a valid null-terminated string
// - null terminator must be within isize::MAX bytes from ptr
#[requires(!ptr.is_null() && {
rajathkotyal marked this conversation as resolved.
Show resolved Hide resolved
let mut found_null = false;
for i in 0..isize::MAX as usize {
if unsafe { *ptr.add(i) } == 0 {
found_null = true;
break;
}
}
found_null
})]
// Postconditions:
// - Returns length before null terminator
// - Length must be less than isize::MAX
#[ensures(|&result| result < isize::MAX as usize && {
let mut valid = true;
for i in 0..result {
if unsafe { *ptr.add(i) } == 0 {
valid = false;
break;
}
}
valid && unsafe { *ptr.add(result) } == 0
})]
rajathkotyal marked this conversation as resolved.
Show resolved Hide resolved
const unsafe fn strlen(ptr: *const c_char) -> usize {
const_eval_select!(
@capture { s: *const c_char = ptr } -> usize:
Expand Down Expand Up @@ -867,7 +899,7 @@ mod verify {
c_str
}

// pub const fn from_bytes_until_nul(bytes: &[u8]) -> Result<&CStr, FromBytesUntilNulError>
// Proof harness for 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() {
Expand All @@ -882,6 +914,25 @@ mod verify {
assert!(c_str.is_safe());
}
}

rajathkotyal marked this conversation as resolved.
Show resolved Hide resolved
// pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr
#[kani::proof_for_contract(CStr::from_bytes_with_nul_unchecked)]
#[kani::unwind(33)]
fn check_from_bytes_with_nul_unchecked() {
const MAX_SIZE: usize = 32;
let string: [u8; MAX_SIZE] = kani::any();
let slice = kani::slice::any_slice_of_array(&string);

// Kani assumes that the input slice is null-terminated and contains
// no intermediate null bytes
let c_str = unsafe { CStr::from_bytes_with_nul_unchecked(slice) };
// Kani ensures that the output CStr holds the CStr safety invariant

// Correctness check
let bytes = c_str.to_bytes();
let len = bytes.len();
assert_eq!(bytes, &slice[..len]);
}

// pub const fn count_bytes(&self) -> usize
#[kani::proof]
Expand Down Expand Up @@ -939,4 +990,75 @@ mod verify {
assert_eq!(bytes, &slice[..end_idx]);
assert!(c_str.is_safe());
}
}

// Proof harness that uses the const-evaluated length
// #[kani::proof_for_contract(strlen)]
#[kani::proof]
#[kani::unwind(32)]
rajathkotyal marked this conversation as resolved.
Show resolved Hide resolved
fn check_strlen_const_path() {
// Get the compile-time computed length
const BYTES: &[u8] = b"test\0";
const PTR: *const c_char = BYTES.as_ptr() as *const c_char;

// This must be evaluated at compile time
const COMPUTED_LEN: usize = unsafe { strlen(PTR) };

// Verify the const-computed length
assert_eq!(COMPUTED_LEN, 4);

// Additional compile-time verifications
const BYTES1: &[u8] = b"test\0";
const PTR1: *const c_char = BYTES1.as_ptr() as *const c_char;

// Verify the string properties using the const-computed length
unsafe {
// These assertions will use the const-computed values
assert_eq!(*PTR1.add(COMPUTED_LEN), 0);

let mut i = 0;
while i < COMPUTED_LEN {
assert_ne!(*PTR1.add(i), 0);
i += 1;
}
}
}

// Stub for external C strlen
#[allow(dead_code)]
mod stubs {
use super::*;
celinval marked this conversation as resolved.
Show resolved Hide resolved

#[no_mangle]
pub unsafe extern "C" fn strlen(s: *const c_char) -> usize {
4
}
}

// **Proof Harness for External `strlen` Function with Stubbing**
//
// This harness should verify that the `else` block of the cstr `strlen` function
// correctly interacts with the external `strlen` function, which is stubbed.
#[kani::proof]
#[kani::stub(strlen, stubs::strlen)]
fn check_external_strlen() {
const MAX_SIZE: usize = 32;
let mut string: [u8; MAX_SIZE] = kani::any();

// Generate position for null terminator
let nul_position: usize = kani::any_where(|x| *x > 0 && *x < MAX_SIZE);
string[nul_position] = 0;
rajathkotyal marked this conversation as resolved.
Show resolved Hide resolved

rajathkotyal marked this conversation as resolved.
Show resolved Hide resolved
// Ensure no null bytes before nul_position
for i in 0..nul_position {
kani::assume(string[i] != 0);
}

let ptr = string.as_ptr() as *const c_char;
unsafe {
let len = strlen(ptr);
// Verify stub returns fixed value 4
assert_eq!(len, 4);
}
}
}