Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Kani reject mutable pointer casts if padding is incompatible and memory initialization is checked #3332

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ use stable_mir::mir::{
NonDivergingIntrinsic, Operand, Place, PointerCoercion, ProjectionElem, Rvalue, Statement,
StatementKind, Terminator, TerminatorKind,
};
use stable_mir::ty::{ConstantKind, MirConst, RigidTy, Span, TyKind, UintTy};
use stable_mir::ty::{ConstantKind, MirConst, RigidTy, Span, Ty, TyKind, UintTy};
use strum_macros::AsRefStr;

use super::{PointeeInfo, PointeeLayout};

/// Memory initialization operations: set or get memory initialization state for a given pointer.
#[derive(AsRefStr, Clone, Debug)]
pub enum MemoryInitOp {
Expand Down Expand Up @@ -540,13 +542,34 @@ impl<'a> MirVisitor for CheckUninitVisitor<'a> {
}

fn visit_rvalue(&mut self, rvalue: &Rvalue, location: Location) {
if let Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, ty) = rvalue {
if let TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) = ty.kind() {
if pointee_ty.kind().is_trait() {
self.push_target(MemoryInitOp::Unsupported {
reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(),
});
if let Rvalue::Cast(cast_kind, operand, ty) = rvalue {
match cast_kind {
CastKind::PointerCoercion(PointerCoercion::Unsize) => {
if let TyKind::RigidTy(RigidTy::RawPtr(pointee_ty, _)) = ty.kind() {
if pointee_ty.kind().is_trait() {
self.push_target(MemoryInitOp::Unsupported {
reason: "Kani does not support reasoning about memory initialization of unsized pointers.".to_string(),
});
}
}
}
CastKind::PtrToPtr => {
let operand_ty = operand.ty(&self.locals).unwrap();
if let (
RigidTy::RawPtr(from_ty, Mutability::Mut),
RigidTy::RawPtr(to_ty, Mutability::Mut),
) = (operand_ty.kind().rigid().unwrap(), ty.kind().rigid().unwrap())
{
if !tys_layout_compatible(from_ty, to_ty) {
// If casting from a mutable pointer to a mutable pointer with
// different layouts, delayed UB could occur.
self.push_target(MemoryInitOp::Unsupported {
reason: "Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB.".to_string(),
});
}
}
}
_ => {}
}
};
self.super_rvalue(rvalue, location);
Expand Down Expand Up @@ -711,3 +734,36 @@ fn try_resolve_instance(locals: &[LocalDecl], func: &Operand) -> Result<Instance
)),
}
}

/// Returns true if `to_ty` has a smaller or equal size and the same padding bytes as `from_ty` up until
/// its size.
fn tys_layout_compatible(from_ty: &Ty, to_ty: &Ty) -> bool {
// Retrieve layouts to assess compatibility.
let from_ty_info = PointeeInfo::from_ty(*from_ty);
let to_ty_info = PointeeInfo::from_ty(*to_ty);
if let (Ok(from_ty_info), Ok(to_ty_info)) = (from_ty_info, to_ty_info) {
let from_ty_layout = match from_ty_info.layout() {
PointeeLayout::Sized { layout } => layout,
PointeeLayout::Slice { element_layout } => element_layout,
PointeeLayout::TraitObject => return false,
};
let to_ty_layout = match to_ty_info.layout() {
PointeeLayout::Sized { layout } => layout,
PointeeLayout::Slice { element_layout } => element_layout,
PointeeLayout::TraitObject => return false,
};
// Ensure `to_ty_layout` does not have a larger size.
if to_ty_layout.len() <= from_ty_layout.len() {
// Check data and padding bytes pair-wise.
if from_ty_layout.iter().zip(to_ty_layout.iter()).all(
|(from_ty_layout_byte, to_ty_layout_byte)| {
// Make sure all data and padding bytes match.
from_ty_layout_byte == to_ty_layout_byte
},
) {
return true;
}
}
};
false
}
2 changes: 1 addition & 1 deletion tests/expected/uninit/access-padding-via-cast/expected
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Failed Checks: Undefined Behavior: Reading from an uninitialized pointer of type `*mut [u8; 4]`
Failed Checks: Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB.

VERIFICATION:- FAILED

Expand Down
14 changes: 14 additions & 0 deletions tests/expected/uninit/delayed-ub/delayed-ub.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Z ghost-state -Z uninit-checks

/// Checks that Kani rejects mutable pointer casts between types of different padding.
#[kani::proof]
fn invalid_value() {
unsafe {
let mut value: u128 = 0;
let ptr = &mut value as *mut _ as *mut (u8, u32, u64);
*ptr = (4, 4, 4); // This assignment itself does not cause UB...
let c: u128 = value; // ...but this reads a padding value!
}
}
5 changes: 5 additions & 0 deletions tests/expected/uninit/delayed-ub/expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Failed Checks: Kani does not support reasoning about memory initialization in presence of mutable raw pointer casts that could cause delayed UB.

VERIFICATION:- FAILED

Complete - 0 successfully verified harnesses, 1 failures, 1 total.
Loading