Skip to content

Commit deaa485

Browse files
authored
Audit for copy intrinsic (rust-lang#1199)
* Add/restore tests for copy intrinsics * Audit for `copy` intrinsic * More expected tests (readable/writable)
1 parent 2aef548 commit deaa485

File tree

13 files changed

+190
-48
lines changed

13 files changed

+190
-48
lines changed

kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -146,39 +146,6 @@ impl<'tcx> GotocCtx<'tcx> {
146146
let farg_types = sig.inputs();
147147
let cbmc_ret_ty = self.codegen_ty(ret_ty);
148148

149-
/// https://doc.rust-lang.org/core/intrinsics/fn.copy.html
150-
/// https://doc.rust-lang.org/core/intrinsics/fn.copy_nonoverlapping.html
151-
/// An intrinsic that translates directly into either memmove (for copy) or memcpy (copy_nonoverlapping)
152-
macro_rules! _codegen_intrinsic_copy {
153-
($f:ident) => {{
154-
let src = fargs.remove(0).cast_to(Type::void_pointer());
155-
let dst = fargs.remove(0).cast_to(Type::void_pointer());
156-
let count = fargs.remove(0);
157-
let sz = {
158-
match self.fn_sig_of_instance(instance).unwrap().skip_binder().inputs()[0]
159-
.kind()
160-
{
161-
ty::RawPtr(t) => {
162-
let layout = self.layout_of(t.ty);
163-
Expr::int_constant(layout.size.bytes(), Type::size_t())
164-
}
165-
_ => unreachable!(),
166-
}
167-
};
168-
let n = sz.mul(count);
169-
let call_memcopy = BuiltinFn::$f.call(vec![dst.clone(), src, n.clone()], loc);
170-
171-
// The C implementation of memcpy does not allow an invalid pointer for
172-
// the src/dst, but the LLVM implementation specifies that a copy with
173-
// length zero is a no-op. This comes up specifically when handling
174-
// the empty string; CBMC will fail on passing a reference to empty
175-
// string unless we codegen this zero check.
176-
// https://llvm.org/docs/LangRef.html#llvm-memcpy-intrinsic
177-
let copy_if_nontrivial = n.is_zero().ternary(dst, call_memcopy);
178-
self.codegen_expr_to_place(p, copy_if_nontrivial)
179-
}};
180-
}
181-
182149
// Codegens a simple intrinsic: ie. one which maps directly to a matching goto construct
183150
// We need to use this macro form because of a known limitation in rust
184151
// `codegen_simple_intrinsic!(self.get_sqrt(), Type::float())` gives the error message:
@@ -500,8 +467,10 @@ impl<'tcx> GotocCtx<'tcx> {
500467
"ceilf64" => codegen_unimplemented_intrinsic!(
501468
"https://github.com/model-checking/kani/issues/1025"
502469
),
503-
"copy" => unstable_codegen!(_codegen_intrinsic_copy!(Memmove)),
504-
"copy_nonoverlapping" => unstable_codegen!(_codegen_intrinsic_copy!(Memcpy)),
470+
"copy" => self.codegen_copy_intrinsic(intrinsic, fargs, farg_types, p, loc),
471+
"copy_nonoverlapping" => unreachable!(
472+
"Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`"
473+
),
505474
"copysignf32" => unstable_codegen!(codegen_simple_intrinsic!(Copysignf)),
506475
"copysignf64" => unstable_codegen!(codegen_simple_intrinsic!(Copysign)),
507476
"cosf32" => codegen_simple_intrinsic!(Cosf),
@@ -928,6 +897,65 @@ impl<'tcx> GotocCtx<'tcx> {
928897
Stmt::atomic_block(vec![skip_stmt], loc)
929898
}
930899

900+
/// An intrinsic that translates directly into `memmove`
901+
/// https://doc.rust-lang.org/core/intrinsics/fn.copy.html
902+
///
903+
/// Undefined behavior if any of these conditions are violated:
904+
/// * Both `src`/`dst` must be properly aligned (done by alignment checks)
905+
/// * Both `src`/`dst` must be valid for reads/writes of `count *
906+
/// size_of::<T>()` bytes (done by calls to `memmove`)
907+
/// In addition, we check that computing `count` in bytes (i.e., the third
908+
/// argument for the `memmove` call) would not overflow.
909+
fn codegen_copy_intrinsic(
910+
&mut self,
911+
intrinsic: &str,
912+
mut fargs: Vec<Expr>,
913+
farg_types: &[Ty<'tcx>],
914+
p: &Place<'tcx>,
915+
loc: Location,
916+
) -> Stmt {
917+
// The two first arguments are pointers. It's safe to cast them to void
918+
// pointers or directly unwrap the `pointee_type` result as seen later.
919+
let src = fargs.remove(0).cast_to(Type::void_pointer());
920+
let dst = fargs.remove(0).cast_to(Type::void_pointer());
921+
922+
// Generate alignment checks for both pointers
923+
let src_align = self.is_ptr_aligned(farg_types[0], src.clone());
924+
let src_align_check = self.codegen_assert(
925+
src_align,
926+
PropertyClass::DefaultAssertion,
927+
"`src` is properly aligned",
928+
loc,
929+
);
930+
let dst_align = self.is_ptr_aligned(farg_types[1], dst.clone());
931+
let dst_align_check = self.codegen_assert(
932+
dst_align,
933+
PropertyClass::DefaultAssertion,
934+
"`dst` is properly aligned",
935+
loc,
936+
);
937+
938+
// Compute the number of bytes to be copied
939+
let count = fargs.remove(0);
940+
let pointee_type = pointee_type(farg_types[0]).unwrap();
941+
let (count_bytes, overflow_check) =
942+
self.count_in_bytes(count, pointee_type, Type::size_t(), intrinsic, loc);
943+
944+
// Build the call to `memmove`
945+
let call_memmove =
946+
BuiltinFn::Memmove.call(vec![dst.clone(), src, count_bytes.clone()], loc);
947+
948+
// The C implementation of `memmove` does not allow an invalid pointer for
949+
// `src` nor `dst`, but the LLVM implementation specifies that a copy with
950+
// length zero is a no-op. This comes up specifically when handling
951+
// the empty string; CBMC will fail on passing a reference to empty
952+
// string unless we codegen this zero check.
953+
// https://llvm.org/docs/LangRef.html#llvm-memmove-intrinsic
954+
let copy_if_nontrivial = count_bytes.is_zero().ternary(dst, call_memmove);
955+
let copy_expr = self.codegen_expr_to_place(p, copy_if_nontrivial);
956+
Stmt::block(vec![src_align_check, dst_align_check, overflow_check, copy_expr], loc)
957+
}
958+
931959
/// Computes the offset from a pointer
932960
/// https://doc.rust-lang.org/std/intrinsics/fn.offset.html
933961
fn codegen_offset(
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
copy: attempt to compute number in bytes which would overflow
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that `copy` triggers an overflow failure if the `count` argument can
5+
// overflow a `usize`
6+
#[kani::proof]
7+
fn test_copy_unaligned() {
8+
let arr: [i32; 3] = [0, 1, 0];
9+
let src: *const i32 = arr.as_ptr();
10+
// Passing `max_count` is guaranteed to overflow
11+
// the count in bytes for `i32` pointers
12+
let max_count = usize::MAX / 4 + 1;
13+
14+
unsafe {
15+
let dst = src.add(1) as *mut i32;
16+
core::intrinsics::copy(src, dst, max_count);
17+
}
18+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
`dst` is properly aligned
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that `copy` fails when `dst` is not aligned.
5+
#[kani::proof]
6+
fn test_copy_unaligned() {
7+
let arr: [i32; 3] = [0, 1, 0];
8+
let src: *const i32 = arr.as_ptr();
9+
10+
unsafe {
11+
// Get an unaligned pointer with a single-byte offset
12+
let dst_i8: *const i8 = src.add(1) as *mut i8;
13+
let dst_unaligned = unsafe { dst_i8.add(1) as *mut i32 };
14+
core::intrinsics::copy(src, dst_unaligned, 1);
15+
}
16+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
`src` is properly aligned
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that `copy` fails when `src` is not aligned.
5+
#[kani::proof]
6+
fn test_copy_unaligned() {
7+
let arr: [i32; 3] = [0, 1, 0];
8+
let src: *const i32 = arr.as_ptr();
9+
10+
unsafe {
11+
// Get an unaligned pointer with a single-byte offset
12+
let src_i8: *const i8 = src as *const i8;
13+
let src_unaligned = unsafe { src_i8.add(1) as *const i32 };
14+
let dst = src.add(1) as *mut i32;
15+
core::intrinsics::copy(src_unaligned, dst, 1);
16+
}
17+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
memmove source region readable
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that `copy` fails when `src` is not valid for reads.
5+
#[kani::proof]
6+
fn test_copy_invalid() {
7+
let arr: [i32; 3] = [0, 1, 0];
8+
let src: *const i32 = arr.as_ptr();
9+
10+
unsafe {
11+
// Get an invalid pointer with a negative offset
12+
let src_invalid = unsafe { src.sub(1) as *const i32 };
13+
let dst = src.add(1) as *mut i32;
14+
core::intrinsics::copy(src_invalid, dst, 1);
15+
}
16+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
memmove destination region writeable
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that `copy` fails when `dst` is not valid for writes.
5+
#[kani::proof]
6+
fn test_copy_invalid() {
7+
let arr: [i32; 3] = [0, 1, 0];
8+
let src: *const i32 = arr.as_ptr();
9+
10+
unsafe {
11+
// Get an invalid pointer with an out-of-bounds offset
12+
let dst_invalid = src.add(3) as *mut i32;
13+
core::intrinsics::copy(src, dst_invalid, 1);
14+
}
15+
}

tests/kani/Intrinsics/Copy/copy.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Check that `copy` works as expected: Copies a number `n` of elements from
5+
// pointer `src` to pointer `dst`, even if their regions overlap.
6+
7+
#[kani::proof]
8+
fn test_copy_simple() {
9+
let mut expected_val = 42;
10+
let src: *mut i32 = &mut expected_val as *mut i32;
11+
let mut old_val = 99;
12+
let dst: *mut i32 = &mut old_val;
13+
unsafe {
14+
core::intrinsics::copy(src, dst, 1);
15+
assert!(*dst == expected_val);
16+
}
17+
}
18+
19+
#[kani::proof]
20+
fn test_copy_with_overlap() {
21+
let arr: [i32; 3] = [0, 1, 0];
22+
let src: *const i32 = arr.as_ptr();
23+
24+
unsafe {
25+
let dst = src.add(1) as *mut i32;
26+
core::intrinsics::copy(src, dst, 2);
27+
// The first value does not change
28+
assert!(arr[0] == 0);
29+
// The next values are copied from `arr[0..=1]`
30+
assert!(arr[1] == 0);
31+
assert!(arr[2] == 1);
32+
}
33+
}

tests/kani/CopyIntrinsics/main_fixme.rs renamed to tests/kani/Intrinsics/Copy/copy_nonoverlapping.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,8 @@
55
use std::mem;
66
use std::ptr;
77

8-
fn test_copy() {
9-
// TODO: make an overlapping set of locations, and check that it does the right thing for the overlapping region too.
10-
// https://github.com/model-checking/kani/issues/12
11-
let mut expected_val = 42;
12-
let src: *mut i32 = &mut expected_val as *mut i32;
13-
let mut old_val = 99;
14-
let dst: *mut i32 = &mut old_val;
15-
unsafe {
16-
ptr::copy(src, dst, 1);
17-
assert!(*dst == expected_val);
18-
}
19-
}
8+
// Note: Calls to `copy_nonoverlapping` are handled by `codegen_statement`
9+
// and not `codegen_intrinsic`: https://github.com/model-checking/kani/issues/1198
2010

2111
/// https://doc.rust-lang.org/std/ptr/fn.copy_nonoverlapping.html
2212
/// Moves all the elements of `src` into `dst`, leaving `src` empty.
@@ -89,7 +79,6 @@ fn test_swap() {
8979

9080
#[kani::proof]
9181
fn main() {
92-
test_copy();
9382
test_swap();
9483
test_append();
9584
}

0 commit comments

Comments
 (0)