Skip to content

Commit b8369e5

Browse files
adpaco-awstedinski
authored andcommitted
Audit for offset intrinsic (rust-lang#1094)
1 parent f8e9b99 commit b8369e5

File tree

13 files changed

+176
-2
lines changed

13 files changed

+176
-2
lines changed

docs/src/rust-feature-support.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ nearbyintf32 | No | |
371371
nearbyintf64 | No | |
372372
needs_drop | Yes | |
373373
nontemporal_store | No | |
374-
offset | Partial | Missing undefined behavior checks |
374+
offset | Yes | |
375375
powf32 | No | |
376376
powf64 | No | |
377377
powif32 | No | |

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ impl<'tcx> GotocCtx<'tcx> {
513513
"https://github.com/model-checking/kani/issues/1025"
514514
),
515515
"needs_drop" => codegen_intrinsic_const!(),
516-
"offset" => codegen_op_with_overflow_check!(add_overflow),
516+
"offset" => self.codegen_offset(instance, fargs, p, loc),
517517
"powf32" => unstable_codegen!(codegen_simple_intrinsic!(Powf)),
518518
"powf64" => unstable_codegen!(codegen_simple_intrinsic!(Pow)),
519519
"powif32" => unstable_codegen!(codegen_simple_intrinsic!(Powif)),
@@ -868,6 +868,44 @@ impl<'tcx> GotocCtx<'tcx> {
868868
Stmt::atomic_block(vec![skip_stmt], loc)
869869
}
870870

871+
/// Computes the offset from a pointer
872+
/// https://doc.rust-lang.org/std/intrinsics/fn.offset.html
873+
fn codegen_offset(
874+
&mut self,
875+
instance: Instance<'tcx>,
876+
mut fargs: Vec<Expr>,
877+
p: &Place<'tcx>,
878+
loc: Location,
879+
) -> Stmt {
880+
let src_ptr = fargs.remove(0);
881+
let offset = fargs.remove(0);
882+
883+
// Check that computing `bytes` would not overflow
884+
let ty = self.monomorphize(instance.substs.type_at(0));
885+
let layout = self.layout_of(ty);
886+
let size = Expr::int_constant(layout.size.bytes(), Type::ssize_t());
887+
let bytes = offset.clone().mul_overflow(size);
888+
let bytes_overflow_check = self.codegen_assert(
889+
bytes.overflowed.not(),
890+
PropertyClass::ArithmeticOverflow,
891+
"attempt to compute offset in bytes which would overflow",
892+
loc,
893+
);
894+
895+
// Check that the computation would not overflow an `isize`
896+
let dst_ptr_of = src_ptr.clone().cast_to(Type::ssize_t()).add_overflow(bytes.result);
897+
let overflow_check = self.codegen_assert(
898+
dst_ptr_of.overflowed.not(),
899+
PropertyClass::ArithmeticOverflow,
900+
"attempt to compute offset which would overflow",
901+
loc,
902+
);
903+
// Re-compute `dst_ptr` with standard addition to avoid conversion
904+
let dst_ptr = src_ptr.plus(offset);
905+
let expr_place = self.codegen_expr_to_place(p, dst_ptr);
906+
Stmt::block(vec![bytes_overflow_check, overflow_check, expr_place], loc)
907+
}
908+
871909
/// ptr_offset_from returns the offset between two pointers
872910
/// https://doc.rust-lang.org/std/intrinsics/fn.ptr_offset_from.html
873911
fn codegen_ptr_offset_from(
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
attempt to compute offset in bytes which would overflow
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Check that an offset that overflows an `isize::MAX` triggers a verification failure.
5+
use std::convert::TryInto;
6+
7+
#[kani::proof]
8+
unsafe fn check_wrap_offset() {
9+
let v: &[u128] = &[0; 200];
10+
let v_100: *const u128 = &v[100];
11+
let max_offset = usize::MAX / std::mem::size_of::<u128>();
12+
let v_wrap: *const u128 = v_100.offset((max_offset + 1).try_into().unwrap());
13+
assert_eq!(v_100, v_wrap);
14+
assert_eq!(*v_100, *v_wrap);
15+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
dereference failure: pointer outside object bounds
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that the pointer computed with `offset` causes a failure if it's
5+
// dereferenced outside the object bounds
6+
#![feature(core_intrinsics)]
7+
use std::intrinsics::offset;
8+
9+
#[kani::proof]
10+
fn test_offset() {
11+
let arr: [i32; 3] = [1, 2, 3];
12+
let ptr: *const i32 = arr.as_ptr();
13+
14+
unsafe {
15+
let x = *offset(ptr, 3);
16+
}
17+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
attempt to compute offset which would overflow
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that `offset` fails if the offset computation would
5+
// result in an arithmetic overflow
6+
#![feature(core_intrinsics)]
7+
use std::intrinsics::offset;
8+
9+
#[kani::proof]
10+
fn test_offset_overflow() {
11+
let s: &str = "123";
12+
let ptr: *const u8 = s.as_ptr();
13+
14+
unsafe {
15+
let _ = offset(ptr, isize::MAX);
16+
}
17+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
FAILURE\
2+
dereference failure: pointer outside object bounds

tests/expected/offset-u8-fail/main.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that the pointer computed with `offset` causes a failure if it's
5+
// dereferenced outside the object bounds
6+
#![feature(core_intrinsics)]
7+
use std::intrinsics::offset;
8+
9+
#[kani::proof]
10+
fn test_offset() {
11+
let s: &str = "123";
12+
let ptr: *const u8 = s.as_ptr();
13+
14+
unsafe {
15+
let x = *offset(ptr, 4) as char;
16+
}
17+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that `offset` returns the expected addresses
5+
#![feature(core_intrinsics)]
6+
use std::intrinsics::offset;
7+
8+
#[kani::proof]
9+
fn test_offset() {
10+
let arr: [i32; 3] = [1, 2, 3];
11+
let ptr: *const i32 = arr.as_ptr();
12+
13+
unsafe {
14+
assert_eq!(*offset(ptr, 0), 1);
15+
assert_eq!(*offset(ptr, 1), 2);
16+
assert_eq!(*offset(ptr, 2), 3);
17+
assert_eq!(*offset(ptr, 2).sub(1), 2);
18+
19+
// This wouldn't be okay because it's
20+
// more than one byte past the object
21+
// let x = *offset(ptr, 3);
22+
23+
// Check that the results are the same with a pointer
24+
// that goes 1 element behind the original one
25+
let other_ptr: *const i32 = ptr.add(1);
26+
27+
assert_eq!(*offset(other_ptr, 0), 2);
28+
assert_eq!(*offset(other_ptr, 1), 3);
29+
assert_eq!(*offset(other_ptr, 1).sub(1), 2);
30+
}
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that `offset` returns the expected addresses
5+
#![feature(core_intrinsics)]
6+
use std::intrinsics::offset;
7+
8+
#[kani::proof]
9+
fn test_offset() {
10+
let s: &str = "123";
11+
let ptr: *const u8 = s.as_ptr();
12+
13+
unsafe {
14+
assert_eq!(*offset(ptr, 0) as char, '1');
15+
assert_eq!(*offset(ptr, 1) as char, '2');
16+
assert_eq!(*offset(ptr, 2) as char, '3');
17+
assert_eq!(*offset(ptr, 2).sub(1) as char, '2');
18+
19+
// This is okay because it's one byte past the object,
20+
// but there is nothing we can assert about it
21+
let x = *offset(ptr, 3);
22+
23+
// Check that the results are the same with a pointer
24+
// that goes 1 element behind the original one
25+
let other_ptr: *const u8 = ptr.add(1);
26+
27+
assert_eq!(*offset(other_ptr, 0) as char, '2');
28+
assert_eq!(*offset(other_ptr, 1) as char, '3');
29+
assert_eq!(*offset(other_ptr, 1).sub(1) as char, '2');
30+
}
31+
}

0 commit comments

Comments
 (0)