Skip to content

Commit 5024b63

Browse files
authored
Fix transmute codegen when sizes are different (#3861)
Instead of crashing, add a safety check that ensures the transmute is not reachable. Resolves #3839 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 53013f3 commit 5024b63

5 files changed

Lines changed: 127 additions & 2 deletions

File tree

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -801,8 +801,30 @@ impl GotocCtx<'_> {
801801
self.codegen_pointer_cast(k, e, *t, loc)
802802
}
803803
Rvalue::Cast(CastKind::Transmute, operand, ty) => {
804-
let goto_typ = self.codegen_ty_stable(*ty);
805-
self.codegen_operand_stable(operand).transmute_to(goto_typ, &self.symbol_table)
804+
let src_ty = operand.ty(self.current_fn().locals()).unwrap();
805+
// Transmute requires sized types.
806+
let src_sz = LayoutOf::new(src_ty).size_of().unwrap();
807+
let dst_sz = LayoutOf::new(*ty).size_of().unwrap();
808+
let dst_type = self.codegen_ty_stable(*ty);
809+
if src_sz != dst_sz {
810+
Expr::statement_expression(
811+
vec![
812+
self.codegen_assert_assume_false(
813+
PropertyClass::SafetyCheck,
814+
&format!(
815+
"Cannot transmute between types of different sizes. \
816+
Transmuting from `{src_sz}` to `{dst_sz}` bytes"
817+
),
818+
loc,
819+
),
820+
dst_type.nondet().as_stmt(loc),
821+
],
822+
dst_type,
823+
loc,
824+
)
825+
} else {
826+
self.codegen_operand_stable(operand).transmute_to(dst_type, &self.symbol_table)
827+
}
806828
}
807829
Rvalue::BinaryOp(op, e1, e2) => self.codegen_rvalue_binary_op(res_ty, op, e1, e2, loc),
808830
Rvalue::CheckedBinaryOp(op, e1, e2) => {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
2+
error: aborting due to 3 previous errors
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+
//! Checks that compilation fails when trying to transmute with different src and target sizes.
5+
6+
#![feature(core_intrinsics)]
7+
use std::intrinsics::transmute;
8+
9+
/// This should fail due to UB detection.
10+
#[kani::proof]
11+
pub fn transmute_diff_size() {
12+
let a: u32 = kani::any();
13+
if kani::any() {
14+
let smaller: u16 = unsafe { transmute(a) };
15+
std::hint::black_box(smaller);
16+
} else {
17+
let bigger: (u64, isize) = unsafe { transmute(a) };
18+
std::hint::black_box(bigger);
19+
}
20+
}
21+
22+
/// Generic transmute wrapper.
23+
pub unsafe fn generic_transmute<S, D>(src: S) -> D {
24+
transmute(src)
25+
}
26+
27+
/// This should also fail due to UB detection.
28+
#[kani::proof]
29+
pub fn transmute_wrapper_diff_size() {
30+
let a: (u32, char) = kani::any();
31+
let b: u128 = unsafe { generic_transmute(a) };
32+
std::hint::black_box(b);
33+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Checking harness transmute_wrapper_diff_size...
2+
Status: UNREACHABLE\
3+
Description: ""Unreachable expected""
4+
5+
Failed Checks: Cannot transmute between types of different sizes. Transmuting from `8` to `16` bytes
6+
7+
VERIFICATION:- FAILED
8+
9+
Checking harness transmute_diff_size...
10+
11+
Status: UNREACHABLE\
12+
Description: ""This should never be reached""
13+
14+
Status: UNREACHABLE\
15+
Description: ""Neither this one""
16+
17+
Failed Checks: Cannot transmute between types of different sizes. Transmuting from `4` to `2` bytes
18+
Failed Checks: Cannot transmute between types of different sizes. Transmuting from `4` to `16` bytes
19+
20+
VERIFICATION:- FAILED
21+
22+
Verification failed for - transmute_wrapper_diff_size
23+
Verification failed for - transmute_diff_size
24+
0 successfully verified harnesses, 2 failures, 2 total
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Checks that Kani correctly identify UB when invoking `transmute_unchecked` with different sizes.
5+
//! See <https://github.com/model-checking/kani/issues/3839> for more details.
6+
7+
#![feature(core_intrinsics)]
8+
use std::intrinsics::transmute_unchecked;
9+
10+
/// Kani reachability checks are not currently applied to `unreachable` statements.
11+
macro_rules! unreachable {
12+
($msg:literal) => {
13+
assert!(false, $msg)
14+
};
15+
}
16+
17+
/// This should fail due to UB detection.
18+
#[kani::proof]
19+
pub fn transmute_diff_size() {
20+
let a: u32 = kani::any();
21+
if kani::any() {
22+
let smaller: u16 = unsafe { transmute_unchecked(a) };
23+
std::hint::black_box(smaller);
24+
unreachable!("This should never be reached");
25+
} else {
26+
let bigger: (u64, isize) = unsafe { transmute_unchecked(a) };
27+
std::hint::black_box(bigger);
28+
unreachable!("Neither this one");
29+
}
30+
}
31+
32+
/// Generic transmute wrapper.
33+
pub unsafe fn generic_transmute<S, D>(src: S) -> D {
34+
transmute_unchecked(src)
35+
}
36+
37+
/// This should also fail due to UB detection.
38+
#[kani::proof]
39+
pub fn transmute_wrapper_diff_size() {
40+
let a: (u32, char) = kani::any();
41+
let b: u128 = unsafe { generic_transmute(a) };
42+
std::hint::black_box(b);
43+
unreachable!("Unreachable expected");
44+
}

0 commit comments

Comments
 (0)