Skip to content

Commit ff91762

Browse files
Mitigate invalid transmute when checking memory initialization (rust-lang#3338)
This PR addresses another aspect of rust-lang#3324, where delayed UB could be caused by transmuting a mutable pointer into the one of incompatible padding. It also adds a check to error whenever transmuting between two types of incompatible padding. 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 398729c commit ff91762

File tree

6 files changed

+85
-10
lines changed

6 files changed

+85
-10
lines changed

kani-compiler/src/kani_middle/transform/check_uninit/mod.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,11 @@ impl UninitPass {
142142
operation: MemoryInitOp,
143143
skip_first: &mut HashSet<usize>,
144144
) {
145-
if let MemoryInitOp::Unsupported { reason } = &operation {
145+
if let MemoryInitOp::Unsupported { reason } | MemoryInitOp::TriviallyUnsafe { reason } =
146+
&operation
147+
{
146148
collect_skipped(&operation, body, skip_first);
147-
self.unsupported_check(tcx, body, source, operation.position(), reason);
149+
self.inject_assert_false(tcx, body, source, operation.position(), reason);
148150
return;
149151
};
150152

@@ -166,7 +168,7 @@ impl UninitPass {
166168
"Kani currently doesn't support checking memory initialization for pointers to `{pointee_ty}.",
167169
);
168170
collect_skipped(&operation, body, skip_first);
169-
self.unsupported_check(tcx, body, source, operation.position(), &reason);
171+
self.inject_assert_false(tcx, body, source, operation.position(), &reason);
170172
return;
171173
}
172174
}
@@ -181,7 +183,7 @@ impl UninitPass {
181183
| MemoryInitOp::SetRef { .. } => {
182184
self.build_set(tcx, body, source, operation, pointee_ty_info, skip_first)
183185
}
184-
MemoryInitOp::Unsupported { .. } => {
186+
MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => {
185187
unreachable!()
186188
}
187189
}
@@ -266,7 +268,7 @@ impl UninitPass {
266268
PointeeLayout::TraitObject => {
267269
collect_skipped(&operation, body, skip_first);
268270
let reason = "Kani does not support reasoning about memory initialization of pointers to trait objects.";
269-
self.unsupported_check(tcx, body, source, operation.position(), reason);
271+
self.inject_assert_false(tcx, body, source, operation.position(), reason);
270272
return;
271273
}
272274
};
@@ -392,7 +394,7 @@ impl UninitPass {
392394
};
393395
}
394396

395-
fn unsupported_check(
397+
fn inject_assert_false(
396398
&self,
397399
tcx: TyCtxt,
398400
body: &mut MutableBody,

kani-compiler/src/kani_middle/transform/check_uninit/uninit_visitor.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ pub enum MemoryInitOp {
3737
SetRef { operand: Operand, value: bool, position: InsertPosition },
3838
/// Unsupported memory initialization operation.
3939
Unsupported { reason: String },
40+
/// Operation that trivially accesses uninitialized memory, results in injecting `assert!(false)`.
41+
TriviallyUnsafe { reason: String },
4042
}
4143

4244
impl MemoryInitOp {
@@ -63,7 +65,9 @@ impl MemoryInitOp {
6365
},
6466
projection: vec![],
6567
}),
66-
MemoryInitOp::Unsupported { .. } => unreachable!(),
68+
MemoryInitOp::Unsupported { .. } | MemoryInitOp::TriviallyUnsafe { .. } => {
69+
unreachable!()
70+
}
6771
}
6872
}
6973

@@ -74,7 +78,8 @@ impl MemoryInitOp {
7478
MemoryInitOp::Check { .. }
7579
| MemoryInitOp::Set { .. }
7680
| MemoryInitOp::SetRef { .. }
77-
| MemoryInitOp::Unsupported { .. } => unreachable!(),
81+
| MemoryInitOp::Unsupported { .. }
82+
| MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(),
7883
}
7984
}
8085

@@ -85,7 +90,8 @@ impl MemoryInitOp {
8590
| MemoryInitOp::SetRef { value, .. } => *value,
8691
MemoryInitOp::Check { .. }
8792
| MemoryInitOp::CheckSliceChunk { .. }
88-
| MemoryInitOp::Unsupported { .. } => unreachable!(),
93+
| MemoryInitOp::Unsupported { .. }
94+
| MemoryInitOp::TriviallyUnsafe { .. } => unreachable!(),
8995
}
9096
}
9197

@@ -96,7 +102,8 @@ impl MemoryInitOp {
96102
| MemoryInitOp::SetRef { position, .. } => *position,
97103
MemoryInitOp::Check { .. }
98104
| MemoryInitOp::CheckSliceChunk { .. }
99-
| MemoryInitOp::Unsupported { .. } => InsertPosition::Before,
105+
| MemoryInitOp::Unsupported { .. }
106+
| MemoryInitOp::TriviallyUnsafe { .. } => InsertPosition::Before,
100107
}
101108
}
102109
}
@@ -581,6 +588,29 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> {
581588
}
582589
}
583590
}
591+
CastKind::Transmute => {
592+
let operand_ty = operand.ty(&self.locals).unwrap();
593+
if let (
594+
RigidTy::RawPtr(from_ty, Mutability::Mut),
595+
RigidTy::RawPtr(to_ty, Mutability::Mut),
596+
) = (operand_ty.kind().rigid().unwrap(), ty.kind().rigid().unwrap())
597+
{
598+
if !tys_layout_compatible(from_ty, to_ty) {
599+
// If casting from a mutable pointer to a mutable pointer with different
600+
// layouts, delayed UB could occur.
601+
self.push_target(MemoryInitOp::Unsupported {
602+
reason: "Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB.".to_string(),
603+
});
604+
}
605+
} else if !tys_layout_compatible(&operand_ty, &ty) {
606+
// If transmuting between two types of incompatible layouts, padding
607+
// bytes are exposed, which is UB.
608+
self.push_target(MemoryInitOp::TriviallyUnsafe {
609+
reason: "Transmuting between types of incompatible layouts."
610+
.to_string(),
611+
});
612+
}
613+
}
584614
_ => {}
585615
}
586616
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Z uninit-checks
4+
5+
/// Checks that Kani rejects mutable pointer casts between types of different padding.
6+
#[kani::proof]
7+
fn invalid_value() {
8+
unsafe {
9+
let mut value: u128 = 0;
10+
let ptr: *mut (u8, u32, u64) = std::mem::transmute(&mut value as *mut _);
11+
*ptr = (4, 4, 4); // This assignment itself does not cause UB...
12+
let c: u128 = value; // ...but this reads a padding value!
13+
}
14+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Failed Checks: Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB.
2+
3+
VERIFICATION:- FAILED
4+
5+
Complete - 0 successfully verified harnesses, 1 failures, 1 total.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Failed Checks: Transmuting between types of incompatible layouts.
2+
3+
VERIFICATION:- FAILED
4+
5+
Complete - 0 successfully verified harnesses, 1 failures, 1 total.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
// kani-flags: -Z uninit-checks
4+
5+
// 5 bytes of data + 3 bytes of padding.
6+
#[repr(C)]
7+
#[derive(kani::Arbitrary)]
8+
struct S(u32, u8);
9+
10+
/// Checks that Kani catches an attempt to access padding of a struct using transmute.
11+
#[kani::proof]
12+
fn check_uninit_padding() {
13+
let s = kani::any();
14+
access_padding(s);
15+
}
16+
17+
fn access_padding(s: S) {
18+
let _padding: u64 = unsafe { std::mem::transmute(s) }; // ~ERROR: padding bytes are uninitialized, so reading them is UB.
19+
}

0 commit comments

Comments
 (0)