Skip to content

Commit 7bf23a2

Browse files
authored
Add an experimental API for shadow memory (#3200)
Introduces a data structure, `ShadowMem<T>`, with two methods: ```rust pub fn set<U>(&mut self, ptr: *const U, val: T) pub fn get<U>(&self, ptr: *const U) -> T ``` for setting and getting values of type `T` associated with the memory location that `ptr` points to. Towards #3184 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent 0c40b6f commit 7bf23a2

File tree

11 files changed

+308
-0
lines changed

11 files changed

+308
-0
lines changed

kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,74 @@ impl GotocHook for IsAllocated {
242242
}
243243
}
244244

245+
/// Encodes __CPROVER_pointer_object(ptr)
246+
struct PointerObject;
247+
impl GotocHook for PointerObject {
248+
fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool {
249+
matches_function(tcx, instance.def, "KaniPointerObject")
250+
}
251+
252+
fn handle(
253+
&self,
254+
gcx: &mut GotocCtx,
255+
_instance: Instance,
256+
mut fargs: Vec<Expr>,
257+
assign_to: &Place,
258+
target: Option<BasicBlockIdx>,
259+
span: Span,
260+
) -> Stmt {
261+
assert_eq!(fargs.len(), 1);
262+
let ptr = fargs.pop().unwrap().cast_to(Type::void_pointer());
263+
let target = target.unwrap();
264+
let loc = gcx.codegen_caller_span_stable(span);
265+
let ret_place =
266+
unwrap_or_return_codegen_unimplemented_stmt!(gcx, gcx.codegen_place_stable(assign_to));
267+
let ret_type = ret_place.goto_expr.typ().clone();
268+
269+
Stmt::block(
270+
vec![
271+
ret_place.goto_expr.assign(Expr::pointer_object(ptr).cast_to(ret_type), loc),
272+
Stmt::goto(bb_label(target), loc),
273+
],
274+
loc,
275+
)
276+
}
277+
}
278+
279+
/// Encodes __CPROVER_pointer_offset(ptr)
280+
struct PointerOffset;
281+
impl GotocHook for PointerOffset {
282+
fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool {
283+
matches_function(tcx, instance.def, "KaniPointerOffset")
284+
}
285+
286+
fn handle(
287+
&self,
288+
gcx: &mut GotocCtx,
289+
_instance: Instance,
290+
mut fargs: Vec<Expr>,
291+
assign_to: &Place,
292+
target: Option<BasicBlockIdx>,
293+
span: Span,
294+
) -> Stmt {
295+
assert_eq!(fargs.len(), 1);
296+
let ptr = fargs.pop().unwrap().cast_to(Type::void_pointer());
297+
let target = target.unwrap();
298+
let loc = gcx.codegen_caller_span_stable(span);
299+
let ret_place =
300+
unwrap_or_return_codegen_unimplemented_stmt!(gcx, gcx.codegen_place_stable(assign_to));
301+
let ret_type = ret_place.goto_expr.typ().clone();
302+
303+
Stmt::block(
304+
vec![
305+
ret_place.goto_expr.assign(Expr::pointer_offset(ptr).cast_to(ret_type), loc),
306+
Stmt::goto(bb_label(target), loc),
307+
],
308+
loc,
309+
)
310+
}
311+
}
312+
245313
struct RustAlloc;
246314
// Removing this hook causes regression failures.
247315
// https://github.com/model-checking/kani/issues/1170
@@ -399,6 +467,8 @@ pub fn fn_hooks() -> GotocHooks {
399467
Rc::new(Cover),
400468
Rc::new(Nondet),
401469
Rc::new(IsAllocated),
470+
Rc::new(PointerObject),
471+
Rc::new(PointerOffset),
402472
Rc::new(RustAlloc),
403473
Rc::new(MemCmp),
404474
Rc::new(UntrackedDeref),

kani_metadata/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ pub enum UnstableFeature {
8787
/// Automatically check that no invalid value is produced which is considered UB in Rust.
8888
/// Note that this does not include checking uninitialized value.
8989
ValidValueChecks,
90+
/// Ghost state and shadow memory APIs
91+
GhostState,
9092
}
9193

9294
impl UnstableFeature {

library/kani/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub mod arbitrary;
2424
mod concrete_playback;
2525
pub mod futures;
2626
pub mod mem;
27+
pub mod shadow;
2728
pub mod slice;
2829
pub mod tuple;
2930
pub mod vec;

library/kani/src/mem.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,20 @@ unsafe fn has_valid_value<T: ?Sized>(_ptr: *const T) -> bool {
290290
kani_intrinsic()
291291
}
292292

293+
/// Get the object ID of the given pointer.
294+
#[rustc_diagnostic_item = "KaniPointerObject"]
295+
#[inline(never)]
296+
pub fn pointer_object<T: ?Sized>(_ptr: *const T) -> usize {
297+
kani_intrinsic()
298+
}
299+
300+
/// Get the object offset of the given pointer.
301+
#[rustc_diagnostic_item = "KaniPointerOffset"]
302+
#[inline(never)]
303+
pub fn pointer_offset<T: ?Sized>(_ptr: *const T) -> usize {
304+
kani_intrinsic()
305+
}
306+
293307
#[cfg(test)]
294308
mod tests {
295309
use super::{can_dereference, can_write, PtrProperties};

library/kani/src/shadow.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! This module contains an API for shadow memory.
5+
//! Shadow memory is a mechanism by which we can store metadata on memory
6+
//! locations, e.g. whether a memory location is initialized.
7+
//!
8+
//! The main data structure provided by this module is the `ShadowMem` struct,
9+
//! which allows us to store metadata on a given memory location.
10+
//!
11+
//! # Example
12+
//!
13+
//! ```
14+
//! use kani::shadow::ShadowMem;
15+
//! use std::alloc::{alloc, Layout};
16+
//!
17+
//! let mut sm = ShadowMem::new(false);
18+
//!
19+
//! unsafe {
20+
//! let ptr = alloc(Layout::new::<u8>());
21+
//! // assert the memory location is not initialized
22+
//! assert!(!sm.get(ptr));
23+
//! // write to the memory location
24+
//! *ptr = 42;
25+
//! // update the shadow memory to indicate that this location is now initialized
26+
//! sm.set(ptr, true);
27+
//! }
28+
//! ```
29+
30+
const MAX_NUM_OBJECTS: usize = 1024;
31+
const MAX_OBJECT_SIZE: usize = 64;
32+
33+
const MAX_NUM_OBJECTS_ASSERT_MSG: &str = "The number of objects exceeds the maximum number supported by Kani's shadow memory model (1024)";
34+
const MAX_OBJECT_SIZE_ASSERT_MSG: &str =
35+
"The object size exceeds the maximum size supported by Kani's shadow memory model (64)";
36+
37+
/// A shadow memory data structure that contains a two-dimensional array of a
38+
/// generic type `T`.
39+
/// Each element of the outer array represents an object, and each element of
40+
/// the inner array represents a byte in the object.
41+
pub struct ShadowMem<T: Copy> {
42+
mem: [[T; MAX_OBJECT_SIZE]; MAX_NUM_OBJECTS],
43+
}
44+
45+
impl<T: Copy> ShadowMem<T> {
46+
/// Create a new shadow memory instance initialized with the given value
47+
#[crate::unstable(
48+
feature = "ghost-state",
49+
issue = 3184,
50+
reason = "experimental ghost state/shadow memory API"
51+
)]
52+
pub const fn new(val: T) -> Self {
53+
Self { mem: [[val; MAX_OBJECT_SIZE]; MAX_NUM_OBJECTS] }
54+
}
55+
56+
/// Get the shadow memory value of the given pointer
57+
#[crate::unstable(
58+
feature = "ghost-state",
59+
issue = 3184,
60+
reason = "experimental ghost state/shadow memory API"
61+
)]
62+
pub fn get<U>(&self, ptr: *const U) -> T {
63+
let obj = crate::mem::pointer_object(ptr);
64+
let offset = crate::mem::pointer_offset(ptr);
65+
crate::assert(obj < MAX_NUM_OBJECTS, MAX_NUM_OBJECTS_ASSERT_MSG);
66+
crate::assert(offset < MAX_OBJECT_SIZE, MAX_OBJECT_SIZE_ASSERT_MSG);
67+
self.mem[obj][offset]
68+
}
69+
70+
/// Set the shadow memory value of the given pointer
71+
#[crate::unstable(
72+
feature = "ghost-state",
73+
issue = 3184,
74+
reason = "experimental ghost state/shadow memory API"
75+
)]
76+
pub fn set<U>(&mut self, ptr: *const U, val: T) {
77+
let obj = crate::mem::pointer_object(ptr);
78+
let offset = crate::mem::pointer_offset(ptr);
79+
crate::assert(obj < MAX_NUM_OBJECTS, MAX_NUM_OBJECTS_ASSERT_MSG);
80+
crate::assert(offset < MAX_OBJECT_SIZE, MAX_OBJECT_SIZE_ASSERT_MSG);
81+
self.mem[obj][offset] = val;
82+
}
83+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Failed Checks: assertion failed: SM.get(p)
2+
Verification failed for - check_init_any
3+
Complete - 1 successfully verified harnesses, 1 failures, 2 total.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Zghost-state
4+
5+
// This is a basic test for the shadow memory implementation.
6+
// It checks that shadow memory can be used to track whether a memory location
7+
// is initialized.
8+
9+
use std::alloc::{alloc, dealloc, Layout};
10+
11+
static mut SM: kani::shadow::ShadowMem<bool> = kani::shadow::ShadowMem::new(false);
12+
13+
fn write(ptr: *mut i8, offset: usize, x: i8) {
14+
unsafe {
15+
let p = ptr.offset(offset as isize);
16+
p.write(x);
17+
SM.set(p as *const i8, true);
18+
};
19+
}
20+
21+
fn check_init(b: bool) {
22+
// allocate an array of 5 i8's
23+
let layout = Layout::array::<i8>(5).unwrap();
24+
let ptr = unsafe { alloc(layout) as *mut i8 };
25+
26+
// unconditionally write to all 5 locations except for the middle element
27+
write(ptr, 0, 0);
28+
write(ptr, 1, 1);
29+
if b {
30+
write(ptr, 2, 2)
31+
};
32+
write(ptr, 3, 3);
33+
write(ptr, 4, 4);
34+
35+
// non-deterministically read from any of the elements and assert that:
36+
// 1. The memory location is initialized
37+
// 2. It has the expected value
38+
// This would fail if `b` is false and `index == 2`
39+
let index: usize = kani::any();
40+
if index < 5 {
41+
unsafe {
42+
let p = ptr.offset(index as isize);
43+
let x = p.read();
44+
assert!(SM.get(p));
45+
assert_eq!(x, index as i8);
46+
}
47+
}
48+
unsafe { dealloc(ptr as *mut u8, layout) };
49+
}
50+
51+
#[kani::proof]
52+
fn check_init_true() {
53+
check_init(true);
54+
}
55+
56+
#[kani::proof]
57+
fn check_init_any() {
58+
check_init(kani::any());
59+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Failed Checks: The number of objects exceeds the maximum number supported by Kani's shadow memory model (1024)
2+
Verification failed for - check_max_objects_fail
3+
Complete - 1 successfully verified harnesses, 1 failures, 2 total.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Zghost-state
4+
5+
// This test checks the maximum number of objects supported by Kani's shadow
6+
// memory model (currently 1024)
7+
8+
static mut SM: kani::shadow::ShadowMem<bool> = kani::shadow::ShadowMem::new(false);
9+
10+
fn check_max_objects<const N: usize>() {
11+
let mut i = 0;
12+
// A dummy loop that creates `N`` objects.
13+
// After the loop, CBMC's object ID counter should be at `N` + 2:
14+
// - `N` created in the loop +
15+
// - the NULL pointer whose object ID is 0, and
16+
// - the object ID for `i`
17+
while i < N {
18+
let x = i;
19+
assert_eq!(kani::mem::pointer_object(&x as *const usize), i + 2);
20+
i += 1;
21+
}
22+
23+
// create a new object whose ID is `N` + 2
24+
let x = 42;
25+
assert_eq!(kani::mem::pointer_object(&x as *const i32), N + 2);
26+
// the following call to `set` would fail if the object ID for `x` exceeds
27+
// the maximum allowed by Kani's shadow memory model
28+
unsafe {
29+
SM.set(&x as *const i32, true);
30+
}
31+
}
32+
33+
#[kani::proof]
34+
fn check_max_objects_pass() {
35+
check_max_objects::<1021>();
36+
}
37+
38+
#[kani::proof]
39+
fn check_max_objects_fail() {
40+
check_max_objects::<1022>();
41+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Failed Checks: The object size exceeds the maximum size supported by Kani's shadow memory model (64)
2+
Verification failed for - check_max_object_size_fail
3+
Complete - 1 successfully verified harnesses, 1 failures, 2 total.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Zghost-state
4+
5+
// This test checks the maximum object size supported by Kani's shadow
6+
// memory model (currently 64)
7+
8+
static mut SM: kani::shadow::ShadowMem<bool> = kani::shadow::ShadowMem::new(false);
9+
10+
fn check_max_objects<const N: usize>() {
11+
let arr: [u8; N] = [0; N];
12+
let last = &arr[N - 1];
13+
assert_eq!(kani::mem::pointer_offset(last as *const u8), N - 1);
14+
// the following call to `set_init` would fail if the object offset for
15+
// `last` exceeds the maximum allowed by Kani's shadow memory model
16+
unsafe {
17+
SM.set(last as *const u8, true);
18+
}
19+
}
20+
21+
#[kani::proof]
22+
fn check_max_object_size_pass() {
23+
check_max_objects::<64>();
24+
}
25+
26+
#[kani::proof]
27+
fn check_max_object_size_fail() {
28+
check_max_objects::<65>();
29+
}

0 commit comments

Comments
 (0)