Skip to content

Commit 1a61de2

Browse files
adpaco-awscelinval
andauthored
Refactor overflow checks in intrinsics code (rust-lang#1131)
* Modify expected tests * Final changes * Change expectation and add comment * Use `count` instead of `number` * Add comment above `v_wrap` assignment * Review overflow check in `ptr_offset_from` * Update test that triggers failure, add a new one for wrap-around * Remove instance argument Co-authored-by: Celina G. Val <[email protected]>
1 parent 0ccd0a0 commit 1a61de2

File tree

7 files changed

+94
-40
lines changed

7 files changed

+94
-40
lines changed

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

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ impl<'tcx> GotocCtx<'tcx> {
528528
"pref_align_of" => codegen_intrinsic_const!(),
529529
"ptr_guaranteed_eq" => codegen_ptr_guaranteed_cmp!(eq),
530530
"ptr_guaranteed_ne" => codegen_ptr_guaranteed_cmp!(neq),
531-
"ptr_offset_from" => self.codegen_ptr_offset_from(instance, fargs, p, loc),
531+
"ptr_offset_from" => self.codegen_ptr_offset_from(fargs, p, loc),
532532
"raw_eq" => self.codegen_intrinsic_raw_eq(instance, fargs, p, loc),
533533
"rintf32" => codegen_unimplemented_intrinsic!(
534534
"https://github.com/model-checking/kani/issues/1025"
@@ -637,7 +637,7 @@ impl<'tcx> GotocCtx<'tcx> {
637637
"wrapping_sub" => codegen_wrapping_op!(sub),
638638
"write_bytes" => {
639639
assert!(self.place_ty(p).is_unit());
640-
self.codegen_write_bytes(instance, intrinsic, fargs, loc)
640+
self.codegen_write_bytes(instance, fargs, loc)
641641
}
642642
// Unimplemented
643643
_ => codegen_unimplemented_intrinsic!(
@@ -887,26 +887,22 @@ impl<'tcx> GotocCtx<'tcx> {
887887
let src_ptr = fargs.remove(0);
888888
let offset = fargs.remove(0);
889889

890-
// Check that computing `bytes` would not overflow
890+
// Check that computing `offset` in bytes would not overflow
891891
let ty = self.monomorphize(instance.substs.type_at(0));
892-
let layout = self.layout_of(ty);
893-
let size = Expr::int_constant(layout.size.bytes(), Type::ssize_t());
894-
let bytes = offset.clone().mul_overflow(size);
895-
let bytes_overflow_check = self.codegen_assert(
896-
bytes.overflowed.not(),
897-
PropertyClass::ArithmeticOverflow,
898-
"attempt to compute offset in bytes which would overflow",
899-
loc,
900-
);
892+
let (offset_bytes, bytes_overflow_check) =
893+
self.count_in_bytes(offset.clone(), ty, Type::ssize_t(), "offset", loc);
901894

902895
// Check that the computation would not overflow an `isize`
903-
let dst_ptr_of = src_ptr.clone().cast_to(Type::ssize_t()).add_overflow(bytes.result);
896+
// These checks may allow a wrapping-around behavior in CBMC:
897+
// https://github.com/model-checking/kani/issues/1150
898+
let dst_ptr_of = src_ptr.clone().cast_to(Type::ssize_t()).add_overflow(offset_bytes);
904899
let overflow_check = self.codegen_assert(
905900
dst_ptr_of.overflowed.not(),
906901
PropertyClass::ArithmeticOverflow,
907902
"attempt to compute offset which would overflow",
908903
loc,
909904
);
905+
910906
// Re-compute `dst_ptr` with standard addition to avoid conversion
911907
let dst_ptr = src_ptr.plus(offset);
912908
let expr_place = self.codegen_expr_to_place(p, dst_ptr);
@@ -917,7 +913,6 @@ impl<'tcx> GotocCtx<'tcx> {
917913
/// https://doc.rust-lang.org/std/intrinsics/fn.ptr_offset_from.html
918914
fn codegen_ptr_offset_from(
919915
&mut self,
920-
instance: Instance<'tcx>,
921916
mut fargs: Vec<Expr>,
922917
p: &Place<'tcx>,
923918
loc: Location,
@@ -928,17 +923,15 @@ impl<'tcx> GotocCtx<'tcx> {
928923
// Compute the offset with standard substraction using `isize`
929924
let cast_dst_ptr = dst_ptr.clone().cast_to(Type::ssize_t());
930925
let cast_src_ptr = src_ptr.clone().cast_to(Type::ssize_t());
931-
let offset = cast_dst_ptr.sub(cast_src_ptr);
926+
let offset = cast_dst_ptr.sub_overflow(cast_src_ptr);
932927

933-
// Check that computing `offset_bytes` would not overflow an `isize`
934-
let ty = self.monomorphize(instance.substs.type_at(0));
935-
let layout = self.layout_of(ty);
936-
let size = Expr::int_constant(layout.size.bytes(), Type::ssize_t());
937-
let offset_bytes = offset.clone().mul_overflow(size);
928+
// Check that computing `offset` in bytes would not overflow an `isize`
929+
// These checks may allow a wrapping-around behavior in CBMC:
930+
// https://github.com/model-checking/kani/issues/1150
938931
let overflow_check = self.codegen_assert(
939-
offset_bytes.overflowed.not(),
932+
offset.overflowed.not(),
940933
PropertyClass::ArithmeticOverflow,
941-
"attempt to compute offset in bytes which would overflow",
934+
"attempt to compute offset in bytes which would overflow an `isize`",
942935
loc,
943936
);
944937

@@ -1218,7 +1211,6 @@ impl<'tcx> GotocCtx<'tcx> {
12181211
fn codegen_write_bytes(
12191212
&mut self,
12201213
instance: Instance<'tcx>,
1221-
intrinsic: &str,
12221214
mut fargs: Vec<Expr>,
12231215
loc: Location,
12241216
) -> Stmt {
@@ -1236,18 +1228,41 @@ impl<'tcx> GotocCtx<'tcx> {
12361228
loc,
12371229
);
12381230

1239-
// Check that computing `bytes` would not overflow
1231+
// Check that computing `count` in bytes would not overflow
1232+
let (count_bytes, overflow_check) =
1233+
self.count_in_bytes(count, ty, Type::size_t(), "write_bytes", loc);
1234+
1235+
let memset_call = BuiltinFn::Memset.call(vec![dst, val, count_bytes], loc);
1236+
Stmt::block(vec![align_check, overflow_check, memset_call.as_stmt(loc)], loc)
1237+
}
1238+
1239+
/// Computes (multiplies) the equivalent of a memory-related number (e.g., an offset) in bytes.
1240+
/// Because this operation may result in an arithmetic overflow, it includes an overflow check.
1241+
/// Returns a tuple with:
1242+
/// * The result expression of the computation.
1243+
/// * An assertion statement to ensure the operation has not overflowed.
1244+
fn count_in_bytes(
1245+
&self,
1246+
count: Expr,
1247+
ty: Ty<'tcx>,
1248+
res_ty: Type,
1249+
intrinsic: &str,
1250+
loc: Location,
1251+
) -> (Expr, Stmt) {
1252+
assert!(res_ty.is_integer());
12401253
let layout = self.layout_of(ty);
1241-
let size = Expr::int_constant(layout.size.bytes(), Type::size_t());
1242-
let bytes = count.mul_overflow(size);
1243-
let overflow_check = self.codegen_assert(
1244-
bytes.overflowed.not(),
1254+
let size_of_elem = Expr::int_constant(layout.size.bytes(), res_ty);
1255+
let size_of_count_elems = count.mul_overflow(size_of_elem);
1256+
let message = format!(
1257+
"{}: attempt to compute number in bytes which would overflow",
1258+
intrinsic.to_string()
1259+
);
1260+
let assert_stmt = self.codegen_assert(
1261+
size_of_count_elems.overflowed.not(),
12451262
PropertyClass::ArithmeticOverflow,
1246-
format!("{}: attempt to compute `bytes` which would overflow", intrinsic).as_str(),
1263+
message.as_str(),
12471264
loc,
12481265
);
1249-
1250-
let memset_call = BuiltinFn::Memset.call(vec![dst, val, bytes.result], loc);
1251-
Stmt::block(vec![align_check, overflow_check, memset_call.as_stmt(loc)], loc)
1266+
(size_of_count_elems.result, assert_stmt)
12521267
}
12531268
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
FAILURE\
2-
write_bytes: attempt to compute `bytes` which would overflow
2+
write_bytes: attempt to compute number in bytes which would overflow
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
FAILURE\
2-
attempt to compute offset in bytes which would overflow
2+
offset: attempt to compute number in bytes which would overflow
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
FAILURE\
2-
attempt to compute offset in bytes which would overflow
2+
attempt to compute offset which would overflow
3+
FAILURE\
4+
attempt to compute offset in bytes which would overflow an `isize`

tests/expected/offset-from-bytes-overflow/main.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ use std::convert::TryInto;
77

88
#[kani::proof]
99
fn main() {
10-
let v: &[u128] = &[0; 200];
11-
let v_100: *const u128 = &v[100];
12-
let max_offset = usize::MAX / std::mem::size_of::<u128>();
10+
let v: &[u128] = &[0; 10];
11+
let v_0: *const u128 = &v[0];
12+
let high_offset = usize::MAX / (std::mem::size_of::<u128>() * 2);
1313
unsafe {
14-
let v_wrap: *const u128 = v_100.add((max_offset + 1).try_into().unwrap());
15-
let _ = v_wrap.offset_from(v_100) == 2;
14+
let v_wrap: *const u128 = v_0.add(high_offset.try_into().unwrap());
15+
let _ = v_wrap.offset_from(v_0);
1616
}
1717
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
assertion failed: high_offset == wrapped_offset.try_into().unwrap()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Check that a high offset causes a "wrapping around" behavior in CBMC.
5+
6+
// This example can be really confusing. This program works fine in Rust and
7+
// it's okay to assert that the value coming from `offset_from` is equal to
8+
// `high_offset`. But CBMC's memory model is going to cause a "wrapping around"
9+
// behavior in `v_wrap`, so any values that depend on it are going to show a
10+
// strange behavior as well.
11+
use std::convert::TryInto;
12+
13+
#[kani::proof]
14+
fn main() {
15+
let v: &[u128] = &[0; 10];
16+
let v_0: *const u128 = &v[0];
17+
let high_offset = usize::MAX / (std::mem::size_of::<u128>() * 4);
18+
unsafe {
19+
// Adding `high offset` to `v_0` is undefined behavior, but Kani's
20+
// default behavior does not report it. This kind of operations
21+
// are quite common in the standard library, and we disabled such
22+
// checks in order to avoid spurious verification failures.
23+
//
24+
// Note that this instance of undefined behavior will be reported
25+
// by `miri` and also by Kani with `--extra-pointer-checks`.
26+
// Also, dereferencing the pointer will also be reported by Kani's
27+
// default behavior.
28+
let v_wrap: *const u128 = v_0.add(high_offset.try_into().unwrap());
29+
let wrapped_offset = v_wrap.offset_from(v_0);
30+
// Both offsets should be the same, but because of the "wrapping around"
31+
// behavior in CBMC, `wrapped_offset` does not equal `high_offset`
32+
// https://github.com/model-checking/kani/issues/1150
33+
assert!(high_offset == wrapped_offset.try_into().unwrap());
34+
}
35+
}

0 commit comments

Comments
 (0)