Skip to content

Commit 923346c

Browse files
Make Kani reject mutable pointer casts if padding is incompatible and memory initialization is checked (rust-lang#3332)
This PR introduces layout checks for types to instrument mutable pointer casts. If two types have incompatible padding (e.g. a padding byte in one is a data byte in the other or vice-versa), an "unsupported check" assertion is inserted. This overapproximates for soundness, since the casts do not cause UB themselves, but an alternative solution involves tracking every MIR place, which is costly. Resolves rust-lang#3324 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 34820bd commit 923346c

File tree

4 files changed

+83
-8
lines changed

4 files changed

+83
-8
lines changed

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

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ use stable_mir::mir::{
1212
NonDivergingIntrinsic, Operand, Place, PointerCoercion, ProjectionElem, Rvalue, Statement,
1313
StatementKind, Terminator, TerminatorKind,
1414
};
15-
use stable_mir::ty::{ConstantKind, MirConst, RigidTy, Span, TyKind, UintTy};
15+
use stable_mir::ty::{ConstantKind, MirConst, RigidTy, Span, Ty, TyKind, UintTy};
1616
use strum_macros::AsRefStr;
1717

18+
use super::{PointeeInfo, PointeeLayout};
19+
1820
/// Memory initialization operations: set or get memory initialization state for a given pointer.
1921
#[derive(AsRefStr, Clone, Debug)]
2022
pub enum MemoryInitOp {
@@ -540,13 +542,34 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> {
540542
}
541543

542544
fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) {
543-
if let Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, ty) = rvalue {
544-
if let TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) = ty.kind() {
545-
if pointee_ty.kind().is_trait() {
546-
self.push_target(MemoryInitOp::Unsupported {
547-
reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(),
548-
});
545+
if let Rvalue::Cast(cast_kind, operand, ty) = rvalue {
546+
match cast_kind {
547+
CastKind::PointerCoercion(PointerCoercion::Unsize) => {
548+
if let TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) = ty.kind() {
549+
if pointee_ty.kind().is_trait() {
550+
self.push_target(MemoryInitOp::Unsupported {
551+
reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(),
552+
});
553+
}
554+
}
549555
}
556+
CastKind::PtrToPtr => {
557+
let operand_ty = operand.ty(&self.locals).unwrap();
558+
if let (
559+
RigidTy::RawPtr(from_ty, Mutability::Mut),
560+
RigidTy::RawPtr(to_ty, Mutability::Mut),
561+
) = (operand_ty.kind().rigid().unwrap(), ty.kind().rigid().unwrap())
562+
{
563+
if !tys_layout_compatible(from_ty, to_ty) {
564+
// If casting from a mutable pointer to a mutable pointer with
565+
// different layouts, delayed UB could occur.
566+
self.push_target(MemoryInitOp::Unsupported {
567+
reason: "Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB.".to_string(),
568+
});
569+
}
570+
}
571+
}
572+
_ => {}
550573
}
551574
};
552575
self.super_rvalue(rvalue, location);
@@ -711,3 +734,36 @@ fn try_resolve_instance(locals: &[LocalDecl], func: &Operand) -> Result<Instance
711734
)),
712735
}
713736
}
737+
738+
/// Returns true if `to_ty` has a smaller or equal size and the same padding bytes as `from_ty` up until
739+
/// its size.
740+
fn tys_layout_compatible(from_ty: &Ty, to_ty: &Ty) -> bool {
741+
// Retrieve layouts to assess compatibility.
742+
let from_ty_info = PointeeInfo::from_ty(*from_ty);
743+
let to_ty_info = PointeeInfo::from_ty(*to_ty);
744+
if let (Ok(from_ty_info), Ok(to_ty_info)) = (from_ty_info, to_ty_info) {
745+
let from_ty_layout = match from_ty_info.layout() {
746+
PointeeLayout::Sized { layout } => layout,
747+
PointeeLayout::Slice { element_layout } => element_layout,
748+
PointeeLayout::TraitObject => return false,
749+
};
750+
let to_ty_layout = match to_ty_info.layout() {
751+
PointeeLayout::Sized { layout } => layout,
752+
PointeeLayout::Slice { element_layout } => element_layout,
753+
PointeeLayout::TraitObject => return false,
754+
};
755+
// Ensure `to_ty_layout` does not have a larger size.
756+
if to_ty_layout.len() <= from_ty_layout.len() {
757+
// Check data and padding bytes pair-wise.
758+
if from_ty_layout.iter().zip(to_ty_layout.iter()).all(
759+
|(from_ty_layout_byte, to_ty_layout_byte)| {
760+
// Make sure all data and padding bytes match.
761+
from_ty_layout_byte == to_ty_layout_byte
762+
},
763+
) {
764+
return true;
765+
}
766+
}
767+
};
768+
false
769+
}

tests/expected/uninit/access-padding-via-cast/expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*mut [u8; 4]`
1+
Failed Checks: Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB.
22

33
VERIFICATION:- FAILED
44

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 ghost-state -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 value as *mut _ as *mut (u8, u32, u64);
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.

0 commit comments

Comments
 (0)