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

Implement proper function pointer handling for validity checks #3606

Merged
merged 2 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 44 additions & 50 deletions kani-compiler/src/kani_middle/transform/check_values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use rustc_middle::ty::{Const, TyCtxt};
use rustc_smir::rustc_internal;
use stable_mir::CrateDef;
use stable_mir::abi::{FieldsShape, Scalar, TagEncoding, ValueAbi, VariantsShape, WrappingRange};
use stable_mir::mir::mono::{Instance, InstanceKind};
use stable_mir::mir::mono::Instance;
use stable_mir::mir::visit::{Location, PlaceContext, PlaceRef};
use stable_mir::mir::{
AggregateKind, BasicBlockIdx, BinOp, Body, CastKind, ConstOperand, FieldIdx, Local, LocalDecl,
Expand Down Expand Up @@ -385,50 +385,47 @@ impl<'a, 'b> MirVisitor for CheckValueVisitor<'a, 'b> {
// Note: For transmute, both Src and Dst must be valid type.
// In this case, we need to save the Dst, and invoke super_terminator.
self.super_terminator(term, location);
celinval marked this conversation as resolved.
Show resolved Hide resolved
let instance = expect_instance(self.locals, func);
if instance.kind == InstanceKind::Intrinsic {
match instance.intrinsic_name().unwrap().as_str() {
"write_bytes" => {
// The write bytes intrinsic may trigger UB in safe code.
// pub unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize)
// <https://doc.rust-lang.org/stable/core/intrinsics/fn.write_bytes.html>
// This is an over-approximation since writing an invalid value is
// not UB, only reading it will be.
assert_eq!(
args.len(),
3,
"Unexpected number of arguments for `write_bytes`"
);
let TyKind::RigidTy(RigidTy::RawPtr(target_ty, Mutability::Mut)) =
args[0].ty(self.locals).unwrap().kind()
else {
unreachable!()
};
let validity = ty_validity_per_offset(&self.machine, target_ty, 0);
match validity {
Ok(ranges) if ranges.is_empty() => {}
Ok(ranges) => {
let sz = rustc_internal::stable(Const::from_target_usize(
self.tcx,
target_ty.layout().unwrap().shape().size.bytes() as u64,
));
self.push_target(SourceOp::BytesValidity {
target_ty,
rvalue: Rvalue::Repeat(args[1].clone(), sz),
ranges,
})
}
_ => self.push_target(SourceOp::UnsupportedCheck {
check: "write_bytes".to_string(),
ty: target_ty,
}),
match intrinsic_name(self.locals, func).as_deref() {
Some("write_bytes") => {
// The write bytes intrinsic may trigger UB in safe code.
// pub unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize)
// <https://doc.rust-lang.org/stable/core/intrinsics/fn.write_bytes.html>
// This is an over-approximation since writing an invalid value is
// not UB, only reading it will be.
assert_eq!(
args.len(),
3,
"Unexpected number of arguments for `write_bytes`"
);
let TyKind::RigidTy(RigidTy::RawPtr(target_ty, Mutability::Mut)) =
args[0].ty(self.locals).unwrap().kind()
else {
unreachable!()
};
let validity = ty_validity_per_offset(&self.machine, target_ty, 0);
match validity {
Ok(ranges) if ranges.is_empty() => {}
Ok(ranges) => {
let sz = rustc_internal::stable(Const::from_target_usize(
self.tcx,
target_ty.layout().unwrap().shape().size.bytes() as u64,
));
self.push_target(SourceOp::BytesValidity {
target_ty,
rvalue: Rvalue::Repeat(args[1].clone(), sz),
ranges,
})
}
_ => self.push_target(SourceOp::UnsupportedCheck {
check: "write_bytes".to_string(),
ty: target_ty,
}),
}
"transmute" | "transmute_copy" => {
unreachable!("Should've been lowered")
}
_ => {}
}
Some("transmute") | Some("transmute_copy") => {
unreachable!("Should've been lowered")
}
_ => {}
}
}
TerminatorKind::Goto { .. }
Expand Down Expand Up @@ -741,16 +738,13 @@ fn assignment_check_points(
invalid_ranges
}

/// Retrieve instance for the given function operand.
/// Retrieve the name of the intrinsic if this operand is an intrinsic.
///
/// This will panic if the operand is not a function or if it cannot be resolved.
fn expect_instance(locals: &[LocalDecl], func: &Operand) -> Instance {
/// Intrinsics can only be invoked directly, so we can safely ignore other operand types.
fn intrinsic_name(locals: &[LocalDecl], func: &Operand) -> Option<String> {
let ty = func.ty(locals).unwrap();
match ty.kind() {
TyKind::RigidTy(RigidTy::FnDef(def, args)) => Instance::resolve(def, &args).unwrap(),
TyKind::RigidTy(RigidTy::FnPtr(sig)) => todo!("Add support to FnPtr: {sig:?}"),
_ => unreachable!("Found: {func:?}"),
}
let TyKind::RigidTy(RigidTy::FnDef(def, args)) = ty.kind() else { return None };
Instance::resolve(def, &args).unwrap().intrinsic_name()
}

/// Instrument MIR to check the value pointed by `rvalue_ptr` satisfies requirement `req`.
Expand Down
3 changes: 3 additions & 0 deletions tests/expected/valid-value-checks/can_dereference.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
2 of 2 cover properties satisfied

Complete - 3 successfully verified harnesses, 0 failures, 3 total.
24 changes: 24 additions & 0 deletions tests/expected/valid-value-checks/can_dereference.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
// kani-flags: -Z valid-value-checks -Z mem-predicates
//! Check that Kani can identify invalid value when using `can_dereference` API.

#[kani::proof]
fn check_can_dereference_char() {
let val: [u32; 2] = kani::any();
kani::cover!(kani::mem::can_dereference(&val as *const _ as *const [char; 2]));
kani::cover!(!kani::mem::can_dereference(&val as *const _ as *const [char; 2]));
}

#[kani::proof]
fn check_can_dereference_always_valid() {
let val: [char; 2] = [kani::any(), kani::any()];
assert!(kani::mem::can_dereference(&val as *const _ as *const [u32; 2]));
}

#[kani::proof]
fn check_can_dereference_always_invalid() {
let val: [u8; 2] = kani::any();
kani::assume(val[0] > 1 || val[1] > 1);
assert!(!kani::mem::can_dereference(&val as *const _ as *const [bool; 2]));
}
Loading