Skip to content

Commit c941888

Browse files
authored
Audit copy_nonoverlapping intrinsic (#1201)
* Add new tests and split old ones * Handle `CopyNonOverlapping` statements from intrinsics code * Add missed changes on one test * Rename and add comments in `codegen_copy` * Slight rewording for alignment checks * Fix format + suggestion
1 parent 849b4e6 commit c941888

File tree

20 files changed

+245
-117
lines changed

20 files changed

+245
-117
lines changed

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

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ impl<'tcx> GotocCtx<'tcx> {
467467
"ceilf64" => codegen_unimplemented_intrinsic!(
468468
"https://github.com/model-checking/kani/issues/1025"
469469
),
470-
"copy" => self.codegen_copy_intrinsic(intrinsic, fargs, farg_types, p, loc),
470+
"copy" => self.codegen_copy(intrinsic, false, fargs, farg_types, Some(p), loc),
471471
"copy_nonoverlapping" => unreachable!(
472472
"Expected `core::intrinsics::unreachable` to be handled by `StatementKind::CopyNonOverlapping`"
473473
),
@@ -897,21 +897,31 @@ impl<'tcx> GotocCtx<'tcx> {
897897
Stmt::atomic_block(vec![skip_stmt], loc)
898898
}
899899

900-
/// An intrinsic that translates directly into `memmove`
901-
/// https://doc.rust-lang.org/core/intrinsics/fn.copy.html
900+
/// Copies `count * size_of::<T>()` bytes from `src` to `dst`.
901+
///
902+
/// Note that this function handles code generation for:
903+
/// 1. The `copy` intrinsic.
904+
/// https://doc.rust-lang.org/core/intrinsics/fn.copy.html
905+
/// 2. The `CopyNonOverlapping` statement.
906+
/// https://doc.rust-lang.org/core/intrinsics/fn.copy_nonoverlapping.html
902907
///
903908
/// Undefined behavior if any of these conditions are violated:
904909
/// * Both `src`/`dst` must be properly aligned (done by alignment checks)
905910
/// * Both `src`/`dst` must be valid for reads/writes of `count *
906911
/// size_of::<T>()` bytes (done by calls to `memmove`)
912+
/// * (Exclusive to nonoverlapping copy) The region of memory beginning
913+
/// at `src` with a size of `count * size_of::<T>()` bytes must *not*
914+
/// overlap with the region of memory beginning at `dst` with the same
915+
/// size.
907916
/// 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(
917+
/// argument of the copy built-in call) would not overflow.
918+
pub fn codegen_copy(
910919
&mut self,
911920
intrinsic: &str,
921+
is_non_overlapping: bool,
912922
mut fargs: Vec<Expr>,
913923
farg_types: &[Ty<'tcx>],
914-
p: &Place<'tcx>,
924+
p: Option<&Place<'tcx>>,
915925
loc: Location,
916926
) -> Stmt {
917927
// The two first arguments are pointers. It's safe to cast them to void
@@ -924,14 +934,14 @@ impl<'tcx> GotocCtx<'tcx> {
924934
let src_align_check = self.codegen_assert(
925935
src_align,
926936
PropertyClass::DefaultAssertion,
927-
"`src` is properly aligned",
937+
"`src` must be properly aligned",
928938
loc,
929939
);
930940
let dst_align = self.is_ptr_aligned(farg_types[1], dst.clone());
931941
let dst_align_check = self.codegen_assert(
932942
dst_align,
933943
PropertyClass::DefaultAssertion,
934-
"`dst` is properly aligned",
944+
"`dst` must be properly aligned",
935945
loc,
936946
);
937947

@@ -941,18 +951,23 @@ impl<'tcx> GotocCtx<'tcx> {
941951
let (count_bytes, overflow_check) =
942952
self.count_in_bytes(count, pointee_type, Type::size_t(), intrinsic, loc);
943953

944-
// Build the call to `memmove`
945-
let call_memmove =
946-
BuiltinFn::Memmove.call(vec![dst.clone(), src, count_bytes.clone()], loc);
954+
// Build the call to the copy built-in (`memmove` or `memcpy`)
955+
let copy_builtin = if is_non_overlapping { BuiltinFn::Memcpy } else { BuiltinFn::Memmove };
956+
let copy_call = copy_builtin.call(vec![dst.clone(), src, count_bytes.clone()], loc);
947957

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.
958+
// The C implementations of `memmove` and `memcpy` do not allow an
959+
// invalid pointer for `src` nor `dst`, but the LLVM implementations
960+
// specify that a zero-length copy is a no-op:
953961
// 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);
962+
// https://llvm.org/docs/LangRef.html#llvm-memcpy-intrinsic
963+
// This comes up specifically when handling the empty string; CBMC will
964+
// fail on passing a reference to it unless we codegen this zero check.
965+
let copy_if_nontrivial = count_bytes.is_zero().ternary(dst, copy_call);
966+
let copy_expr = if p.is_some() {
967+
self.codegen_expr_to_place(p.unwrap(), copy_if_nontrivial)
968+
} else {
969+
copy_if_nontrivial.as_stmt(loc)
970+
};
956971
Stmt::block(vec![src_align_check, dst_align_check, overflow_check, copy_expr], loc)
957972
}
958973

@@ -1274,7 +1289,7 @@ impl<'tcx> GotocCtx<'tcx> {
12741289
let align_check = self.codegen_assert(
12751290
align,
12761291
PropertyClass::DefaultAssertion,
1277-
"`dst` is properly aligned",
1292+
"`dst` must be properly aligned",
12781293
loc,
12791294
);
12801295
let expr = dst.dereference().assign(src, loc);
@@ -1299,13 +1314,13 @@ impl<'tcx> GotocCtx<'tcx> {
12991314
let val = fargs.remove(0).cast_to(Type::c_int());
13001315
let count = fargs.remove(0);
13011316

1302-
// Check that `dst` is properly aligned
1317+
// Check that `dst` must be properly aligned
13031318
let dst_typ = farg_types[0];
13041319
let align = self.is_ptr_aligned(dst_typ, dst.clone());
13051320
let align_check = self.codegen_assert(
13061321
align,
13071322
PropertyClass::DefaultAssertion,
1308-
"`dst` is properly aligned",
1323+
"`dst` must be properly aligned",
13091324
loc,
13101325
);
13111326

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

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -731,22 +731,15 @@ impl<'tcx> GotocCtx<'tcx> {
731731
ref dst,
732732
ref count,
733733
}) => {
734-
let src = self.codegen_operand(src).cast_to(Type::void_pointer());
735-
let dst = self.codegen_operand(dst);
736-
let count = self.codegen_operand(count);
737-
let sz = dst.typ().base_type().unwrap().sizeof(&self.symbol_table);
738-
let sz = Expr::int_constant(sz, Type::size_t());
739-
let n = sz.mul(count);
740-
let dst = dst.cast_to(Type::void_pointer());
741-
let e = BuiltinFn::Memcpy.call(vec![dst, src, n.clone()], location.clone());
742-
743-
// The C implementation of memcpy does not allow an invalid pointer for
744-
// the src/dst, but the LLVM implementation specifies that a copy with
745-
// length zero is a no-op. This comes up specifically when handling
746-
// the empty string; CBMC will fail on passing a reference to empty
747-
// string unless we codegen this zero check.
748-
// https://llvm.org/docs/LangRef.html#llvm-memcpy-intrinsic
749-
Stmt::if_then_else(n.is_zero().not(), e.as_stmt(location.clone()), None, location)
734+
// Pack the operands and their types, then call `codegen_copy`
735+
let fargs = vec![
736+
self.codegen_operand(src),
737+
self.codegen_operand(dst),
738+
self.codegen_operand(count),
739+
];
740+
let farg_types =
741+
&[self.operand_ty(src), self.operand_ty(dst), self.operand_ty(count)];
742+
self.codegen_copy("copy_nonoverlapping", true, fargs, farg_types, None, location)
750743
}
751744
StatementKind::FakeRead(_)
752745
| StatementKind::Retag(_, _)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
copy_nonoverlapping: 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_nonoverlapping` triggers an overflow failure if the `count`
5+
// argument can overflow a `usize`
6+
#[kani::proof]
7+
fn test_copy_nonoverlapping_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_nonoverlapping(src, dst, max_count);
17+
}
18+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
memcpy src/dst overlap
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+
// Check that `copy_nonoverlapping` fails if the `src`/`dst` regions overlap.
5+
#[kani::proof]
6+
fn test_copy_nonoverlapping_with_overlap() {
7+
let arr: [i32; 3] = [0, 1, 0];
8+
let src: *const i32 = arr.as_ptr();
9+
10+
unsafe {
11+
// The call to `copy_nonoverlapping` is expected to fail because
12+
// the `src` region and the `dst` region overlap in `arr[1]`
13+
let dst = src.add(1) as *mut i32;
14+
core::intrinsics::copy_nonoverlapping(src, dst, 2);
15+
}
16+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
`dst` must be 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_nonoverlapping` fails when `dst` is not aligned.
5+
#[kani::proof]
6+
fn test_copy_nonoverlapping_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_nonoverlapping(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` must be 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_nonoverlapping` fails when `src` is not aligned.
5+
#[kani::proof]
6+
fn test_copy_nonoverlapping_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_nonoverlapping(src_unaligned, dst, 1);
16+
}
17+
}

0 commit comments

Comments
 (0)