Skip to content

Commit 923346c

Browse files
Make Kani reject mutable pointer casts if padding is incompatible and memory initialization is checked (#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 #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)