Unverified Commit 8fb5f559 by Enkelmann Committed by GitHub

Refactor DataDomain (#209)

parent 9a0ae7a3
FROM rust:1.49 AS builder
FROM rust:1.53 AS builder
WORKDIR /cwe_checker
......
......@@ -44,7 +44,7 @@ If you want to build the docker image yourself, just run `docker build -t cwe_ch
### Local installation ###
The following dependencies must be installed in order to build and install the *cwe_checker* locally:
- [Rust](https://www.rust-lang.org) >= 1.49
- [Rust](https://www.rust-lang.org) >= 1.53
- [Ghidra](https://ghidra-sre.org/) >= 9.2
Run `make all GHIDRA_PATH=/path/to/ghidra_folder` (with the correct path to the local Ghidra installation inserted) to compile and install the cwe_checker.
......
......@@ -137,7 +137,7 @@ fn run_with_ghidra(args: &CmdlineArgs) {
// Generate the representation of the runtime memory image of the binary
let mut runtime_memory_image = if let Some(bare_metal_config) = bare_metal_config_opt.as_ref() {
RuntimeMemoryImage::new_from_bare_metal(&binary, &bare_metal_config).unwrap_or_else(|err| {
RuntimeMemoryImage::new_from_bare_metal(&binary, bare_metal_config).unwrap_or_else(|err| {
panic!("Error while generating runtime memory image: {}", err);
})
} else {
......
use super::{
AbstractDomain, AbstractIdentifier, HasTop, Interval, PointerDomain, RegisterDomain,
SizedDomain, SpecializeByConditional, TryToBitvec, TryToInterval,
AbstractDomain, AbstractIdentifier, HasTop, Interval, RegisterDomain, SizedDomain,
SpecializeByConditional, TryToBitvec, TryToInterval,
};
use crate::intermediate_representation::*;
use crate::prelude::*;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Display;
/// An abstract domain representing either a pointer or a non-pointer value.
/// Both non-pointer values and offsets of pointers are represented by the same abstract domain `T`.
use std::iter::FromIterator;
mod arithmetics;
mod conditional_specialization;
mod trait_impl;
/// An abstract domain representing a set of base values plus offsets or an absolute value (or both).
///
/// The base values are represented as abstract IDs,
/// i.e. they are treated as variables with unknown absolute value.
/// For each base value the offset is given by an abstract domain `T`,
/// which should specialize in representing absolute values (e.g. an interval domain).
/// Note that the domain assumes pointer semantics for these values.
/// That means if one applies operations to the domain that are not used in pointer arithmetics,
/// the abstract ID of the base value might be removed from the domain.
///
/// If the domain also represents absolute values,
/// then the values are given by a single instance of the abstract domain `T`.
///
/// The domain also contains a flag to indicate that it includes `Top` values,
/// i.e. values of fully unknown origin and offset.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum DataDomain<T: RegisterDomain> {
/// The `Top` element of the domain.
/// Describes a value for which nothing is known.
Top(ByteSize),
/// The value is a pointer to an abstract memory object.
Pointer(PointerDomain<T>),
/// The value is a non-pointer value or a pointer to global memory.
/// The latter can happen if pointers to global memory are described by their absolute value.
Value(T),
pub struct DataDomain<T: RegisterDomain> {
/// The byte size of the represented values.
size: ByteSize,
/// A map from base values to the corresponding offset.
relative_values: BTreeMap<AbstractIdentifier, T>,
/// An absolute value if the domain may represent an absolute value.
absolute_value: Option<T>,
/// An indicator whether the domain also represents values for which both the base and the offset are unknown.
contains_top_values: bool,
}
impl<T: RegisterDomain> DataDomain<T> {
/// Returns true if the domain does not represent any value.
///
/// The meaning of an empty value depends on the usage of the domain.
/// E.g. it may indicate an impossible runtime state of a program in one analysis
/// or simply no value of interest in another analysis.
///
/// An empty value represents the bottom value in the partial order of the domain.
pub fn is_empty(&self) -> bool {
self.relative_values.is_empty()
&& self.absolute_value.is_none()
&& !self.contains_top_values
}
/// Return a new empty value with the given bytesize.
pub fn new_empty(size: ByteSize) -> Self {
DataDomain {
size,
relative_values: BTreeMap::new(),
absolute_value: None,
contains_top_values: false,
}
}
/// For pointer values replace an abstract identifier with another one and add the offset_adjustment to the pointer offset.
/// This is needed to adjust stack pointer on call and return instructions.
pub fn replace_abstract_id(
......@@ -30,300 +71,149 @@ impl<T: RegisterDomain> DataDomain<T> {
new_id: &AbstractIdentifier,
offset_adjustment: &T,
) {
if let Self::Pointer(pointer) = self {
pointer.replace_abstract_id(old_id, new_id, offset_adjustment);
if let Some(old_offset) = self.relative_values.get(old_id) {
let new_offset = old_offset.bin_op(BinOpType::IntAdd, offset_adjustment);
self.relative_values.remove(old_id);
self.relative_values.insert(new_id.clone(), new_offset);
}
}
/// Return a set of all referenced abstract IDs. The set is empty if `self` is not a pointer.
pub fn referenced_ids(&self) -> BTreeSet<AbstractIdentifier> {
if let Self::Pointer(pointer) = self {
pointer.ids().cloned().collect()
} else {
BTreeSet::new()
}
/// Return an iterator over all referenced abstract IDs.
pub fn referenced_ids(&self) -> impl Iterator<Item = &AbstractIdentifier> {
self.relative_values.keys()
}
/// If *self* is a pointer, remove all provided IDs from the target list of it.
/// If this would leave the pointer without any targets, replace it with *Top*.
pub fn remove_ids(&mut self, ids_to_remove: &BTreeSet<AbstractIdentifier>) {
if let Self::Pointer(pointer) = self {
let remaining_targets: BTreeMap<AbstractIdentifier, T> = pointer
.targets()
.iter()
.filter_map(|(id, offset)| {
if ids_to_remove.get(id).is_none() {
Some((id.clone(), offset.clone()))
} else {
None
}
})
.collect();
if remaining_targets.is_empty() {
*self = Self::new_top(self.bytesize());
} else {
*self = Self::Pointer(PointerDomain::with_targets(remaining_targets));
}
}
/// Return the relative values contained in the domain.
pub fn get_relative_values(&self) -> &BTreeMap<AbstractIdentifier, T> {
&self.relative_values
}
}
impl<T: SpecializeByConditional + RegisterDomain> SpecializeByConditional for DataDomain<T> {
fn add_signed_less_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> {
if let Self::Value(value) = self {
Ok(Self::Value(value.add_signed_less_equal_bound(bound)?))
} else {
Ok(self)
}
/// Replace the map of relative values with the given one.
pub fn set_relative_values(&mut self, relative_values: BTreeMap<AbstractIdentifier, T>) {
self.relative_values = relative_values;
}
fn add_unsigned_less_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> {
if let Self::Value(value) = self {
Ok(Self::Value(value.add_unsigned_less_equal_bound(bound)?))
} else {
Ok(self)
}
/// Return the absolute value contained in the domain if present
pub fn get_absolute_value(&self) -> Option<&T> {
self.absolute_value.as_ref()
}
fn add_signed_greater_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> {
if let Self::Value(value) = self {
Ok(Self::Value(value.add_signed_greater_equal_bound(bound)?))
} else {
Ok(self)
}
/// Replace the absolute value contained in the domain with the given one.
/// A value of `None` means that the domain does not contain an absolute value.
pub fn set_absolute_value(&mut self, value: Option<T>) {
self.absolute_value = value
}
fn add_unsigned_greater_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> {
if let Self::Value(value) = self {
Ok(Self::Value(value.add_unsigned_greater_equal_bound(bound)?))
} else {
Ok(self)
}
/// Returns `true` if the domain contains `Top` values,
/// i.e. values for which neither a value nor an abstract identifier is known.
///
/// Note that the `DataDomain` itself has no maximal value,
/// i.e. this does not indicate a `Top` value of the abstract domain.
pub fn contains_top(&self) -> bool {
self.contains_top_values
}
fn add_not_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> {
if let Self::Value(value) = self {
Ok(Self::Value(value.add_not_equal_bound(bound)?))
} else {
Ok(self)
}
/// Indicate that the domain may contain `Top` values
/// in addition to the contained absolute and relative values.
///
/// This does not remove absolute or relative value information from the domain.
pub fn set_contains_top_flag(&mut self) {
self.contains_top_values = true;
}
}
impl<T: RegisterDomain> SizedDomain for DataDomain<T> {
// Return the bitsize of `self`.
fn bytesize(&self) -> ByteSize {
use DataDomain::*;
match self {
Top(size) => *size,
Pointer(pointer) => pointer.bytesize(),
Value(bitvec) => bitvec.bytesize(),
/// Return a new value representing a variable plus an offset,
/// where the variable is represented by the given abstract ID.
pub fn from_target(id: AbstractIdentifier, offset: T) -> Self {
DataDomain {
size: offset.bytesize(),
relative_values: BTreeMap::from_iter([(id, offset)]),
absolute_value: None,
contains_top_values: false,
}
}
// Return a new *Top* element with the given bytesize
fn new_top(bytesize: ByteSize) -> Self {
Self::Top(bytesize)
}
}
impl<T: RegisterDomain> HasTop for DataDomain<T> {
// Generate a new *Top* element with the same bitsize as `self`.
fn top(&self) -> Self {
DataDomain::new_top(self.bytesize())
}
}
impl<T: RegisterDomain> RegisterDomain for DataDomain<T> {
/// Compute the (abstract) result of a binary operation
fn bin_op(&self, op: BinOpType, rhs: &Self) -> Self {
use BinOpType::*;
use DataDomain::*;
match (self, op, rhs) {
(Value(left), _, Value(right)) => Value(left.bin_op(op, right)),
(Pointer(pointer), IntAdd, Value(value)) | (Value(value), IntAdd, Pointer(pointer)) => {
Pointer(pointer.add_to_offset(value))
}
(Pointer(pointer), IntSub, Value(value)) => Pointer(pointer.sub_from_offset(value)),
(Pointer(pointer_lhs), IntSub, Pointer(pointer_rhs)) => {
if pointer_lhs.ids().len() == 1 && pointer_rhs.ids().len() == 1 {
let (id_lhs, offset_lhs) = pointer_lhs.targets().iter().next().unwrap();
let (id_rhs, offset_rhs) = pointer_rhs.targets().iter().next().unwrap();
if id_lhs == id_rhs {
Self::Value(offset_lhs.bin_op(IntSub, offset_rhs))
} else {
Self::Top(self.bytesize())
}
/// Remove all provided IDs from the list of relative values.
pub fn remove_ids(&mut self, ids_to_remove: &BTreeSet<AbstractIdentifier>) {
self.relative_values = self
.relative_values
.iter()
.filter_map(|(id, offset)| {
if ids_to_remove.get(id).is_none() {
Some((id.clone(), offset.clone()))
} else {
// We cannot be sure that both pointers point to the same target
Self::Top(self.bytesize())
None
}
}
(_, IntEqual, _)
| (_, IntNotEqual, _)
| (_, IntLess, _)
| (_, IntLessEqual, _)
| (_, IntSLess, _)
| (_, IntSLessEqual, _)
| (_, IntCarry, _)
| (_, IntSCarry, _)
| (_, IntSBorrow, _)
| (_, BoolXOr, _)
| (_, BoolOr, _)
| (_, BoolAnd, _)
| (_, FloatEqual, _)
| (_, FloatNotEqual, _)
| (_, FloatLess, _)
| (_, FloatLessEqual, _) => T::new_top(ByteSize::new(1)).into(),
(_, IntAdd, _)
| (_, IntSub, _)
| (_, IntMult, _)
| (_, IntDiv, _)
| (_, IntSDiv, _)
| (_, IntRem, _)
| (_, IntSRem, _)
| (_, IntLeft, _)
| (_, IntRight, _)
| (_, IntSRight, _)
| (_, IntAnd, _)
| (_, IntOr, _)
| (_, IntXOr, _)
| (_, FloatAdd, _)
| (_, FloatSub, _)
| (_, FloatMult, _)
| (_, FloatDiv, _) => Self::new_top(self.bytesize()),
(_, Piece, _) => Self::new_top(self.bytesize() + rhs.bytesize()),
}
})
.collect();
}
/// Compute the (abstract) result of a unary operation
fn un_op(&self, op: UnOpType) -> Self {
if let Self::Value(value) = self {
Self::Value(value.un_op(op))
/// Return the contained absolute value
/// only if `self` contains no other (relative or `Top`) values.
pub fn get_if_absolute_value(&self) -> Option<&T> {
if self.relative_values.is_empty() && !self.contains_top_values {
self.absolute_value.as_ref()
} else {
match op {
UnOpType::BoolNegate | UnOpType::FloatNaN => Self::new_top(ByteSize::new(1)),
_ => Self::new_top(self.bytesize()),
}
None
}
}
/// extract a sub-bitvector
fn subpiece(&self, low_byte: ByteSize, size: ByteSize) -> Self {
if let Self::Value(value) = self {
Self::Value(value.subpiece(low_byte, size))
} else if low_byte == ByteSize::new(0) && size == self.bytesize() {
// The operation is a no-op
self.clone()
/// Return the contained absolute value
/// if `self` only contains absolute or `Top` values.
fn get_if_absolute_value_or_top(&self) -> Option<&T> {
if self.relative_values.is_empty() {
self.absolute_value.as_ref()
} else {
Self::new_top(size)
None
}
}
/// Cast a bitvector using the given cast type
fn cast(&self, kind: CastOpType, width: ByteSize) -> Self {
if let Self::Value(value) = self {
Self::Value(value.cast(kind, width))
/// Return the target ID and offset of the contained relative value
/// if `self` contains exactly one relative value and no absolute or `Top` values.
pub fn get_if_unique_target(&self) -> Option<(&AbstractIdentifier, &T)> {
if self.relative_values.len() == 1
&& self.absolute_value.is_none()
&& !self.contains_top_values
{
Some(self.relative_values.iter().next().unwrap())
} else {
// The result of casting pointers is undefined.
Self::new_top(width)
}
}
}
impl<T: RegisterDomain> AbstractDomain for DataDomain<T> {
// Merge `self` with `other`.
fn merge(&self, other: &Self) -> Self {
use DataDomain::*;
match (self, other) {
(Top(bytesize), _) | (_, Top(bytesize)) => Top(*bytesize),
(Pointer(pointer1), Pointer(pointer2)) => Pointer(pointer1.merge(pointer2)),
(Value(val1), Value(val2)) => Value(val1.merge(val2)),
(Pointer(_), Value(_)) | (Value(_), Pointer(_)) => Top(self.bytesize()),
None
}
}
/// Return whether the element represents a top element or not.
fn is_top(&self) -> bool {
matches!(self, Self::Top(_))
}
}
impl<T: RegisterDomain> From<PointerDomain<T>> for DataDomain<T> {
fn from(val: PointerDomain<T>) -> Self {
Self::Pointer(val)
}
}
impl<T: RegisterDomain> From<T> for DataDomain<T> {
fn from(value: T) -> Self {
Self::Value(value)
}
}
impl<T: RegisterDomain + From<Bitvector>> From<Bitvector> for DataDomain<T> {
fn from(bitvector: Bitvector) -> Self {
Self::Value(bitvector.into())
}
}
impl<T: RegisterDomain + TryToBitvec> TryToBitvec for DataDomain<T> {
/// If the domain represents a single, absolute value, return it.
fn try_to_bitvec(&self) -> Result<Bitvector, Error> {
match self {
DataDomain::Value(value) => value.try_to_bitvec(),
DataDomain::Pointer(_) => Err(anyhow!("Value is a pointer.")),
DataDomain::Top(_) => Err(anyhow!("Value is Top")),
}
}
}
impl<T: RegisterDomain + TryToInterval> TryToInterval for DataDomain<T> {
/// If the domain represents (or can be widened to) an interval of absolute values, return the interval.
fn try_to_interval(&self) -> Result<Interval, Error> {
match self {
DataDomain::Value(value) => value.try_to_interval(),
DataDomain::Pointer(_) => Err(anyhow!("Value is a pointer.")),
DataDomain::Top(_) => Err(anyhow!("Value is Top")),
}
}
}
impl<T: RegisterDomain> std::ops::Add for DataDomain<T> {
type Output = DataDomain<T>;
fn add(self, rhs: Self) -> Self {
self.bin_op(BinOpType::IntAdd, &rhs)
}
}
impl<T: RegisterDomain> std::ops::Sub for DataDomain<T> {
type Output = DataDomain<T>;
fn sub(self, rhs: Self) -> Self {
self.bin_op(BinOpType::IntSub, &rhs)
}
}
impl<T: RegisterDomain + Display> DataDomain<T> {
/// Get a more compact json-representation of the data domain.
/// Intended for pretty printing, not useable for serialization/deserialization.
pub fn to_json_compact(&self) -> serde_json::Value {
match self {
Self::Top(bitsize) => serde_json::Value::String(format!("Top:{}", bitsize)),
Self::Pointer(pointer) => {
let target_iter = pointer.targets().iter().map(|(id, offset)| {
(
format!("{}", id),
serde_json::Value::String(format!("{}", offset)),
)
});
let targets = serde_json::Value::Object(target_iter.collect());
let mut obj_map = serde_json::Map::new();
obj_map.insert("Pointer".to_string(), targets);
serde_json::Value::Object(obj_map)
}
Self::Value(bitvector) => serde_json::Value::String(format!("Value: {}", bitvector)),
let mut values = Vec::new();
if !self.relative_values.is_empty() {
let target_iter = self.relative_values.iter().map(|(id, offset)| {
(
format!("{}", id),
serde_json::Value::String(format!("{}", offset)),
)
});
let targets = serde_json::Value::Object(target_iter.collect());
let mut obj_map = serde_json::Map::new();
obj_map.insert("Pointer".to_string(), targets);
values.push(serde_json::Value::Object(obj_map));
}
if let Some(absolute_value) = &self.absolute_value {
values.push(serde_json::Value::String(format!(
"Value: {}",
absolute_value
)));
}
if self.contains_top_values {
values.push(serde_json::Value::String(format!(
"Top:{}",
self.bytesize()
)));
}
match values.len() {
0 => serde_json::Value::String(format!("Empty:{}", self.bytesize())),
1 => values.pop().unwrap(),
_ => serde_json::Value::Array(values),
}
}
}
......@@ -333,7 +223,18 @@ mod tests {
use super::super::*;
use super::*;
type Data = DataDomain<BitvectorDomain>;
impl<T: RegisterDomain> DataDomain<T> {
/// Return a new domain representing a set of relative values.
/// Note that this function will panic if given an empty set as input.
pub fn mock_from_target_map(targets: BTreeMap<AbstractIdentifier, T>) -> Self {
DataDomain {
size: targets.values().next().unwrap().bytesize(),
relative_values: targets,
absolute_value: None,
contains_top_values: false,
}
}
}
fn bv(value: i64) -> BitvectorDomain {
BitvectorDomain::Value(Bitvector::from_i64(value))
......@@ -346,63 +247,19 @@ mod tests {
)
}
fn new_pointer_domain(location: &str, offset: i64) -> PointerDomain<BitvectorDomain> {
let id = new_id(location);
PointerDomain::new(id, bv(offset))
}
fn new_pointer(location: &str, offset: i64) -> Data {
Data::Pointer(new_pointer_domain(location, offset))
}
fn new_value(value: i64) -> Data {
Data::Value(bv(value))
}
#[test]
fn data_abstract_domain() {
let pointer = new_pointer("Rax".into(), 0);
let data = new_value(42);
assert_eq!(pointer.merge(&pointer), pointer);
assert_eq!(pointer.merge(&data), Data::new_top(ByteSize::new(8)));
assert_eq!(
data.merge(&new_value(41)),
Data::Value(BitvectorDomain::new_top(ByteSize::new(8)))
);
let other_pointer = new_pointer("Rbx".into(), 0);
match pointer.merge(&other_pointer) {
Data::Pointer(_) => (),
_ => panic!(),
}
}
#[test]
fn data_register_domain() {
use BinOpType::*;
let data = new_value(42);
assert_eq!(data.bytesize(), ByteSize::new(8));
let three = new_value(3);
let pointer = new_pointer("Rax".into(), 0);
assert_eq!(data.bin_op(IntAdd, &three), new_value(45));
assert_eq!(pointer.bin_op(IntAdd, &three), new_pointer("Rax".into(), 3));
assert_eq!(three.un_op(UnOpType::Int2Comp), new_value(-3));
assert_eq!(
three.subpiece(ByteSize::new(0), ByteSize::new(4)),
Data::Value(BitvectorDomain::Value(Bitvector::from_i32(3)))
);
fn replace_abstract_ids() {
let mut targets = BTreeMap::new();
targets.insert(new_id("Rax"), bv(1));
targets.insert(new_id("Rbx"), bv(2));
let mut data = DataDomain::mock_from_target_map(targets);
data.replace_abstract_id(&new_id("Rbx"), &new_id("replaced_Rbx"), &bv(10));
assert_eq!(data.relative_values.len(), 2);
assert_eq!(*data.relative_values.get(&new_id("Rax")).unwrap(), bv(1));
assert_eq!(
data.cast(CastOpType::IntSExt, ByteSize::new(16)).bytesize(),
ByteSize::new(16)
*data.relative_values.get(&new_id("replaced_Rbx")).unwrap(),
bv(12)
);
let one = Data::Value(BitvectorDomain::Value(Bitvector::from_i32(1)));
let two = Data::Value(BitvectorDomain::Value(Bitvector::from_i32(2)));
let concat = new_value((1 << 32) + 2);
assert_eq!(one.bin_op(Piece, &two), concat);
}
#[test]
......@@ -410,7 +267,7 @@ mod tests {
let mut targets = BTreeMap::new();
targets.insert(new_id("Rax"), bv(1));
targets.insert(new_id("Rbx"), bv(2));
let mut data: Data = PointerDomain::with_targets(targets).into();
let mut data = DataDomain::mock_from_target_map(targets);
let mut ids_to_remove = BTreeSet::new();
ids_to_remove.insert(new_id("Rbx"));
......@@ -418,20 +275,14 @@ mod tests {
data.remove_ids(&ids_to_remove);
assert_eq!(
data.referenced_ids(),
vec![new_id("Rax")].into_iter().collect()
data.referenced_ids()
.cloned()
.collect::<Vec<AbstractIdentifier>>(),
vec![new_id("Rax")]
);
data = bv(42).into();
data.remove_ids(&ids_to_remove);
assert_eq!(data, bv(42).into());
}
#[test]
fn float_nan_bytesize() {
let top_value: DataDomain<BitvectorDomain> = DataDomain::new_top(ByteSize::new(8));
let result = top_value.un_op(UnOpType::FloatNaN);
assert!(result.is_top());
assert_eq!(result.bytesize(), ByteSize::new(1));
}
}
use super::*;
impl<T: RegisterDomain> DataDomain<T> {
/// Compute `self + rhs`.
fn compute_add(&self, rhs: &Self) -> Self {
if let Some(offset) = self.get_if_absolute_value_or_top() {
let mut result = rhs.add_offset(offset);
result.contains_top_values |= self.contains_top_values;
result
} else if let Some(offset) = rhs.get_if_absolute_value_or_top() {
let mut result = self.add_offset(offset);
result.contains_top_values |= rhs.contains_top_values;
result
} else {
self.preserve_relative_targets_for_binop(rhs)
}
}
/// Add `offset` to all contained absolute and relative values of `self` and return the result.
pub fn add_offset(&self, offset: &T) -> Self {
DataDomain {
size: self.size,
relative_values: self
.relative_values
.iter()
.map(|(id, old_offset)| (id.clone(), old_offset.bin_op(BinOpType::IntAdd, offset)))
.collect(),
absolute_value: self
.absolute_value
.as_ref()
.map(|old_offset| old_offset.bin_op(BinOpType::IntAdd, offset)),
contains_top_values: self.contains_top_values,
}
}
/// Subtract `offset` from all contained absolute and relative values of `self` and return the result.
pub fn subtract_offset(&self, offset: &T) -> Self {
DataDomain {
size: self.size,
relative_values: self
.relative_values
.iter()
.map(|(id, old_offset)| (id.clone(), old_offset.bin_op(BinOpType::IntSub, offset)))
.collect(),
absolute_value: self
.absolute_value
.as_ref()
.map(|old_offset| old_offset.bin_op(BinOpType::IntSub, offset)),
contains_top_values: self.contains_top_values,
}
}
/// If both `self` and `rhs` are pointers to a unique (but not necessarily the same) target,
/// compute `self - rhs`.
/// Return `None` otherwise.
fn compute_sub_if_offset_through_pointer_subtraction(&self, rhs: &Self) -> Option<Self> {
if let (Some((lhs_id, lhs_offset)), Some((rhs_id, rhs_offset))) =
(self.get_if_unique_target(), rhs.get_if_unique_target())
{
// Compute an offset by subtracting pointers
if lhs_id == rhs_id {
Some(DataDomain {
size: self.bytesize(),
relative_values: BTreeMap::new(),
absolute_value: Some(lhs_offset.bin_op(BinOpType::IntSub, rhs_offset)),
contains_top_values: false,
})
} else {
// `self` and `rhs` are relative different abstract IDs.
Some(DataDomain {
size: self.bytesize(),
relative_values: BTreeMap::from_iter([
(lhs_id.clone(), T::new_top(self.bytesize())),
(rhs_id.clone(), T::new_top(self.bytesize())),
]),
absolute_value: Some(T::new_top(self.bytesize())),
contains_top_values: false,
})
}
} else {
None
}
}
/// Compute `self - rhs`.
fn compute_sub(&self, rhs: &Self) -> Self {
if self.is_empty() || rhs.is_empty() {
// The result is again empty.
Self::new_empty(self.bytesize())
} else if rhs.relative_values.is_empty() {
// Subtract a (possibly unknown) offset
let offset = rhs
.absolute_value
.as_ref()
.cloned()
.unwrap_or_else(|| T::new_top(self.size));
let mut result = self.subtract_offset(&offset);
result.contains_top_values = result.contains_top_values || rhs.contains_top_values;
result
} else if let Some(result) = self.compute_sub_if_offset_through_pointer_subtraction(rhs) {
result
} else {
// We do not know whether the result is a relative or absolute value.
self.preserve_relative_targets_for_binop(rhs)
}
}
/// Compute the result of a byte size preserving binary operation
/// where it is unknown whether the result is an absolute or relative value.
///
/// This function conservately approximates all offsets with `Top`.
fn preserve_relative_targets_for_binop(&self, rhs: &Self) -> Self {
if self.is_empty() || rhs.is_empty() {
// The result is again empty.
return Self::new_empty(self.bytesize());
}
let mut relative_values = BTreeMap::new();
for id in self.relative_values.keys() {
relative_values.insert(id.clone(), T::new_top(self.bytesize()));
}
for id in rhs.relative_values.keys() {
relative_values.insert(id.clone(), T::new_top(self.bytesize()));
}
DataDomain {
size: self.bytesize(),
relative_values,
absolute_value: Some(T::new_top(self.bytesize())),
contains_top_values: self.contains_top_values || rhs.contains_top_values,
}
}
}
impl<T: RegisterDomain> RegisterDomain for DataDomain<T> {
/// Compute the (abstract) result of a binary operation
fn bin_op(&self, op: BinOpType, rhs: &Self) -> Self {
use BinOpType::*;
if let (Some(left), Some(right)) =
(self.get_if_absolute_value(), rhs.get_if_absolute_value())
{
// Case 1: A binary operation of absolute values.
left.bin_op(op, right).into()
} else {
match op {
// Case 2: Addition
IntAdd => self.compute_add(rhs),
// Case 3: Subtraction
IntSub => self.compute_sub(rhs),
// Case 4: An operation where the result may be a pointer.
IntAnd | IntOr | IntXOr => self.preserve_relative_targets_for_binop(rhs),
// Case 5: An operation with result being a boolean.
IntEqual | IntNotEqual | IntLess | IntLessEqual | IntSLess | IntSLessEqual
| IntCarry | IntSCarry | IntSBorrow | BoolXOr | BoolOr | BoolAnd | FloatEqual
| FloatNotEqual | FloatLess | FloatLessEqual => {
if self.is_empty() || rhs.is_empty() {
Self::new_empty(ByteSize::new(1))
} else {
T::new_top(ByteSize::new(1)).into()
}
}
// Case 6: An operation that does not change the byte size.
IntMult | IntDiv | IntSDiv | IntRem | IntSRem | IntLeft | IntRight | IntSRight
| FloatAdd | FloatSub | FloatMult | FloatDiv => {
if self.is_empty() || rhs.is_empty() {
Self::new_empty(self.bytesize())
} else {
Self::new_top(self.bytesize())
}
}
// Case 7: Concatenating two bitvectors
Piece => {
if self.is_empty() || rhs.is_empty() {
Self::new_empty(self.bytesize() + rhs.bytesize())
} else {
Self::new_top(self.bytesize() + rhs.bytesize())
}
}
}
}
}
/// Compute the (abstract) result of a unary operation
fn un_op(&self, op: UnOpType) -> Self {
let size = match op {
UnOpType::BoolNegate | UnOpType::FloatNaN => ByteSize::new(1),
_ => self.bytesize(),
};
DataDomain {
size,
relative_values: BTreeMap::new(),
absolute_value: self.absolute_value.as_ref().map(|val| val.un_op(op)),
contains_top_values: self.contains_top_values || !self.relative_values.is_empty(),
}
}
/// extract a sub-bitvector
fn subpiece(&self, low_byte: ByteSize, size: ByteSize) -> Self {
if low_byte == ByteSize::new(0) && size == self.bytesize() {
// The operation is a no-op
self.clone()
} else {
DataDomain {
size,
relative_values: BTreeMap::new(),
absolute_value: self
.absolute_value
.as_ref()
.map(|val| val.subpiece(low_byte, size)),
contains_top_values: self.contains_top_values || !self.relative_values.is_empty(),
}
}
}
/// Cast a bitvector using the given cast type
fn cast(&self, kind: CastOpType, width: ByteSize) -> Self {
DataDomain {
size: width,
relative_values: BTreeMap::new(),
absolute_value: self
.absolute_value
.as_ref()
.map(|val| val.cast(kind, width)),
contains_top_values: self.contains_top_values || !self.relative_values.is_empty(),
}
}
}
impl<T: RegisterDomain> std::ops::Add for DataDomain<T> {
type Output = DataDomain<T>;
fn add(self, rhs: Self) -> Self {
self.bin_op(BinOpType::IntAdd, &rhs)
}
}
impl<T: RegisterDomain> std::ops::Sub for DataDomain<T> {
type Output = DataDomain<T>;
fn sub(self, rhs: Self) -> Self {
self.bin_op(BinOpType::IntSub, &rhs)
}
}
#[cfg(test)]
mod tests {
use super::super::*;
use super::*;
use crate::abstract_domain::*;
type Data = DataDomain<BitvectorDomain>;
fn bv(value: i64) -> BitvectorDomain {
BitvectorDomain::Value(Bitvector::from_i64(value))
}
fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("time0"),
AbstractLocation::Register(name.into(), ByteSize::new(8)),
)
}
fn new_pointer(location: &str, offset: i64) -> Data {
DataDomain {
size: ByteSize::new(8),
relative_values: BTreeMap::from_iter([(new_id(location), bv(offset))]),
absolute_value: None,
contains_top_values: false,
}
}
fn new_value(value: i64) -> Data {
Data::from(bv(value))
}
#[test]
fn pointer_sub() {
use BinOpType::*;
let pointer1 = new_pointer("Rax".into(), 10);
let pointer2 = new_pointer("Rax".into(), 4);
// Pointer difference computes offset (i.e. an absolute value)
assert_eq!(pointer1.bin_op(IntSub, &pointer2), bv(6).into());
// It is unknown whether the difference is an offset or not.
let other_pointer = new_pointer("Rbx".into(), 4);
let diff = pointer1.bin_op(IntSub, &&other_pointer);
assert_eq!(diff.relative_values.len(), 2);
assert_eq!(
*diff.relative_values.get(&new_id("Rax")).unwrap(),
BitvectorDomain::new_top(ByteSize::new(8))
);
assert_eq!(
*diff.relative_values.get(&new_id("Rbx")).unwrap(),
BitvectorDomain::new_top(ByteSize::new(8))
);
assert_eq!(
diff.absolute_value,
Some(BitvectorDomain::new_top(ByteSize::new(8)))
);
assert_eq!(diff.contains_top_values, false);
}
#[test]
fn data_register_domain() {
use BinOpType::*;
let data = new_value(42);
assert_eq!(data.bytesize(), ByteSize::new(8));
let three = new_value(3);
let pointer = new_pointer("Rax".into(), 0);
assert_eq!(data.bin_op(IntAdd, &three), new_value(45));
assert_eq!(pointer.bin_op(IntAdd, &three), new_pointer("Rax".into(), 3));
assert_eq!(three.un_op(UnOpType::Int2Comp), new_value(-3));
assert_eq!(
three.subpiece(ByteSize::new(0), ByteSize::new(4)),
BitvectorDomain::Value(Bitvector::from_i32(3)).into()
);
assert_eq!(
data.cast(CastOpType::IntSExt, ByteSize::new(16)).bytesize(),
ByteSize::new(16)
);
let one: Data = BitvectorDomain::Value(Bitvector::from_i32(1)).into();
let two: Data = BitvectorDomain::Value(Bitvector::from_i32(2)).into();
let concat = new_value((1 << 32) + 2);
assert_eq!(one.bin_op(Piece, &two), concat);
}
#[test]
fn float_nan_bytesize() {
let top_value: DataDomain<BitvectorDomain> = DataDomain::new_top(ByteSize::new(8));
let result = top_value.un_op(UnOpType::FloatNaN);
assert!(result.is_top());
assert_eq!(result.bytesize(), ByteSize::new(1));
}
}
use super::*;
/// Compute the intersection of relative targets for two `DataDomain` instances.
fn intersect_relative_values<T: SpecializeByConditional + RegisterDomain>(
values_left: &BTreeMap<AbstractIdentifier, T>,
values_right: &BTreeMap<AbstractIdentifier, T>,
) -> BTreeMap<AbstractIdentifier, T> {
values_left
.iter()
.filter_map(|(id, offset)| {
values_right
.get(id)
.map(|other_offset| {
if let Ok(intersected_offset) = offset.clone().intersect(other_offset) {
Some((id.clone(), intersected_offset))
} else {
None
}
})
.flatten()
})
.collect()
}
impl<T: SpecializeByConditional + RegisterDomain> SpecializeByConditional for DataDomain<T> {
fn add_signed_less_equal_bound(mut self, bound: &Bitvector) -> Result<Self, Error> {
self.absolute_value = self
.absolute_value
.map(|value| value.add_signed_less_equal_bound(bound).ok())
.flatten();
if self.is_empty() {
Err(anyhow!("Empty value"))
} else {
Ok(self)
}
}
fn add_unsigned_less_equal_bound(mut self, bound: &Bitvector) -> Result<Self, Error> {
self.absolute_value = self
.absolute_value
.map(|value| value.add_unsigned_less_equal_bound(bound).ok())
.flatten();
if self.is_empty() {
Err(anyhow!("Empty value"))
} else {
Ok(self)
}
}
fn add_signed_greater_equal_bound(mut self, bound: &Bitvector) -> Result<Self, Error> {
self.absolute_value = self
.absolute_value
.map(|value| value.add_signed_greater_equal_bound(bound).ok())
.flatten();
if self.is_empty() {
Err(anyhow!("Empty value"))
} else {
Ok(self)
}
}
fn add_unsigned_greater_equal_bound(mut self, bound: &Bitvector) -> Result<Self, Error> {
self.absolute_value = self
.absolute_value
.map(|value| value.add_unsigned_greater_equal_bound(bound).ok())
.flatten();
if self.is_empty() {
Err(anyhow!("Empty value"))
} else {
Ok(self)
}
}
fn add_not_equal_bound(mut self, bound: &Bitvector) -> Result<Self, Error> {
self.absolute_value = self
.absolute_value
.map(|value| value.add_not_equal_bound(bound).ok())
.flatten();
if self.is_empty() {
Err(anyhow!("Empty value"))
} else {
Ok(self)
}
}
fn intersect(self, other: &Self) -> Result<Self, Error> {
let result = match (self.contains_top_values, other.contains_top_values) {
// If only one input value contains top elements, then the other input is the best approximation for the intersection.
(true, false) => other.clone(),
(false, true) => self,
// Else we can compute the intersection field-wise.
(true, true) | (false, false) => {
let relative_values =
intersect_relative_values(&self.relative_values, &other.relative_values);
let absolute_value = if let (Some(value), Some(other_value)) =
(&self.absolute_value, &other.absolute_value)
{
value.clone().intersect(other_value).ok()
} else {
None
};
DataDomain {
size: self.bytesize(),
relative_values,
absolute_value,
contains_top_values: self.contains_top_values && other.contains_top_values,
}
}
};
if result.is_empty() {
Err(anyhow!("Domain is empty."))
} else {
Ok(result)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::abstract_domain::*;
fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("time0"),
AbstractLocation::Register(name.into(), ByteSize::new(8)),
)
}
#[test]
fn intersect() {
let mut targets = BTreeMap::new();
targets.insert(new_id("Rax"), IntervalDomain::mock(1, 1));
targets.insert(new_id("Rbx"), IntervalDomain::mock(1, 10));
let mut data_left = DataDomain::mock_from_target_map(targets);
data_left.set_absolute_value(Some(IntervalDomain::mock(1, 10)));
let mut targets = BTreeMap::new();
targets.insert(new_id("Rax"), IntervalDomain::mock(3, 3));
targets.insert(new_id("Rbx"), IntervalDomain::mock(5, 15));
targets.insert(new_id("Rcx"), IntervalDomain::mock(1, 1));
let mut data_right = DataDomain::mock_from_target_map(targets);
data_right.set_absolute_value(Some(IntervalDomain::mock(10, 20)));
// Element-wise intersection
let intersection = data_left.intersect(&data_right).unwrap();
assert_eq!(intersection.relative_values.len(), 1);
assert_eq!(
*intersection.relative_values.get(&new_id("Rbx")).unwrap(),
IntervalDomain::mock(5, 10)
);
assert_eq!(
intersection.absolute_value,
Some(IntervalDomain::mock(10, 10))
);
assert_eq!(intersection.contains_top_values, false);
// Intersection where exactly one side contains top elements
let mut data_with_top = DataDomain::new_top(ByteSize::new(8));
data_with_top.set_absolute_value(Some(IntervalDomain::mock(15, 100)));
let intersection = data_right.clone().intersect(&data_with_top).unwrap();
assert_eq!(intersection, data_right);
// Empty intersection
let data_absolute_val = IntervalDomain::mock(100, 100).into();
assert!(data_right.intersect(&data_absolute_val).is_err());
}
}
use super::*;
impl<T: RegisterDomain> SizedDomain for DataDomain<T> {
/// Return the bytesize of `self`.
fn bytesize(&self) -> ByteSize {
self.size
}
/// Return a new *Top* element with the given bytesize.
///
/// Note that `DataDomain` technically does not have a `Top` element with respect to the partial order.
/// Instead a `Top` element here represents a non-empty value
/// for which nothing is known about the contained values.
fn new_top(bytesize: ByteSize) -> Self {
DataDomain {
size: bytesize,
relative_values: BTreeMap::new(),
absolute_value: None,
contains_top_values: true,
}
}
}
impl<T: RegisterDomain> HasTop for DataDomain<T> {
/// Generate a new *Top* element with the same bytesize as `self`.
fn top(&self) -> Self {
DataDomain::new_top(self.bytesize())
}
}
impl<T: RegisterDomain> AbstractDomain for DataDomain<T> {
// Merge `self` with `other`.
fn merge(&self, other: &Self) -> Self {
let mut relative_values = self.relative_values.clone();
for (id, offset_other) in other.relative_values.iter() {
relative_values
.entry(id.clone())
.and_modify(|offset| *offset = offset.merge(offset_other))
.or_insert_with(|| offset_other.clone());
}
let absolute_value = match (&self.absolute_value, &other.absolute_value) {
(Some(left), Some(right)) => Some(left.merge(right)),
(Some(val), None) | (None, Some(val)) => Some(val.clone()),
(None, None) => None,
};
DataDomain {
size: self.bytesize(),
relative_values,
absolute_value,
contains_top_values: self.contains_top_values || other.contains_top_values,
}
}
/// Return whether the element represents a top element or not.
///
/// Note that `DataDomain` technically does not have a `Top` element with respect to the partial order.
/// Instead a `Top` element here represents a non-empty value
/// for which nothing is known about the contained values.
fn is_top(&self) -> bool {
self.relative_values.is_empty() && self.absolute_value.is_none() && self.contains_top_values
}
}
impl<T: RegisterDomain> From<T> for DataDomain<T> {
fn from(value: T) -> Self {
Self {
size: value.bytesize(),
relative_values: BTreeMap::new(),
absolute_value: Some(value),
contains_top_values: false,
}
}
}
impl<T: RegisterDomain + From<Bitvector>> From<Bitvector> for DataDomain<T> {
fn from(bitvector: Bitvector) -> Self {
let val: T = bitvector.into();
val.into()
}
}
impl<T: RegisterDomain + TryToBitvec> TryToBitvec for DataDomain<T> {
/// If the domain represents a single, absolute value, return it.
fn try_to_bitvec(&self) -> Result<Bitvector, Error> {
if !self.relative_values.is_empty() || self.contains_top_values {
Err(anyhow!("May contain non-absolute values."))
} else if let Some(val) = &self.absolute_value {
val.try_to_bitvec()
} else {
Err(anyhow!("Domain is empty."))
}
}
}
impl<T: RegisterDomain + TryToInterval> TryToInterval for DataDomain<T> {
/// If the domain represents (or can be widened to) an interval of absolute values, return the interval.
fn try_to_interval(&self) -> Result<Interval, Error> {
if !self.relative_values.is_empty() || self.contains_top_values {
Err(anyhow!("May contain non-absolute values."))
} else if let Some(val) = &self.absolute_value {
val.try_to_interval()
} else {
Err(anyhow!("Domain is empty."))
}
}
}
#[cfg(test)]
mod tests {
use super::super::*;
use super::*;
use crate::abstract_domain::*;
type Data = DataDomain<BitvectorDomain>;
fn bv(value: i64) -> BitvectorDomain {
BitvectorDomain::Value(Bitvector::from_i64(value))
}
fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("time0"),
AbstractLocation::Register(name.into(), ByteSize::new(8)),
)
}
fn new_pointer(location: &str, offset: i64) -> Data {
DataDomain {
size: ByteSize::new(8),
relative_values: BTreeMap::from_iter([(new_id(location), bv(offset))]),
absolute_value: None,
contains_top_values: false,
}
}
fn new_value(value: i64) -> Data {
Data::from(bv(value))
}
#[test]
fn data_merge() {
let pointer = new_pointer("RAX".into(), 0);
let value = new_value(42);
let merged_data = pointer.merge(&value);
assert_eq!(pointer.merge(&pointer), pointer);
assert_eq!(merged_data.relative_values, pointer.relative_values);
assert_eq!(merged_data.absolute_value, value.absolute_value);
let other_value = new_value(-1);
let merged_data = value.merge(&other_value);
assert!(merged_data.relative_values.is_empty());
assert_eq!(
merged_data.absolute_value,
Some(BitvectorDomain::new_top(ByteSize::new(8)))
);
let other_pointer = new_pointer("RBX".into(), 10);
let merged_data = pointer.merge(&other_pointer);
assert_eq!(
merged_data.relative_values.get(&new_id("RAX")),
Some(&bv(0))
);
assert_eq!(
merged_data.relative_values.get(&new_id("RBX")),
Some(&bv(10))
);
}
}
......@@ -251,29 +251,6 @@ impl IntervalDomain {
}
}
/// Compute the intersection of two intervals.
/// Return an error if the intersection is empty.
pub fn intersect(&self, other: &Self) -> Result<Self, Error> {
let mut intersected_domain: IntervalDomain =
self.interval.signed_intersect(&other.interval)?.into();
intersected_domain.update_widening_lower_bound(&self.widening_lower_bound);
intersected_domain.update_widening_lower_bound(&other.widening_lower_bound);
intersected_domain.update_widening_upper_bound(&self.widening_upper_bound);
intersected_domain.update_widening_upper_bound(&other.widening_upper_bound);
intersected_domain.widening_delay =
std::cmp::max(self.widening_delay, other.widening_delay);
if let Ok(interval_length) = (intersected_domain.interval.end.clone()
- &intersected_domain.interval.start)
.try_to_u64()
{
intersected_domain.widening_delay =
std::cmp::min(intersected_domain.widening_delay, interval_length);
}
Ok(intersected_domain)
}
/// Check whether all values in the interval are representable by bitvectors of the given `size`.
/// Does not check whether this is also true for the widening hints.
pub fn fits_into_size(&self, size: ByteSize) -> bool {
......@@ -503,6 +480,29 @@ impl SpecializeByConditional for IntervalDomain {
Ok(self)
}
}
/// Compute the intersection of two intervals.
/// Return an error if the intersection is empty.
fn intersect(self, other: &Self) -> Result<Self, Error> {
let mut intersected_domain: IntervalDomain =
self.interval.signed_intersect(&other.interval)?.into();
intersected_domain.update_widening_lower_bound(&self.widening_lower_bound);
intersected_domain.update_widening_lower_bound(&other.widening_lower_bound);
intersected_domain.update_widening_upper_bound(&self.widening_upper_bound);
intersected_domain.update_widening_upper_bound(&other.widening_upper_bound);
intersected_domain.widening_delay =
std::cmp::max(self.widening_delay, other.widening_delay);
if let Ok(interval_length) = (intersected_domain.interval.end.clone()
- &intersected_domain.interval.start)
.try_to_u64()
{
intersected_domain.widening_delay =
std::cmp::min(intersected_domain.widening_delay, interval_length);
}
Ok(intersected_domain)
}
}
impl AbstractDomain for IntervalDomain {
......
......@@ -591,7 +591,7 @@ fn add_not_equal_bounds() {
fn intersection() {
let interval1 = IntervalDomain::mock_with_bounds(Some(-100), -10, 10, Some(100));
let interval2 = IntervalDomain::mock_with_bounds(Some(-20), 2, 30, None);
let intersection = interval1.intersect(&interval2).unwrap();
let intersection = interval1.clone().intersect(&interval2).unwrap();
assert_eq!(
intersection,
IntervalDomain::mock_with_bounds(Some(-20), 2, 10, Some(100))
......
......@@ -185,7 +185,7 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T
for (pos_left, elem_left) in self.values.iter() {
if let Some((_pos_right, elem_right)) = other.values.get_key_value(pos_left) {
if elem_left.bytesize() == elem_right.bytesize() {
let merged_val = elem_left.merge(&elem_right);
let merged_val = elem_left.merge(elem_right);
if !merged_val.is_top() {
// we discard top()-values, as they don't contain information
merged_values.insert(*pos_left, merged_val);
......
......@@ -10,9 +10,6 @@ pub use bitvector::*;
mod identifier;
pub use identifier::*;
mod pointer;
pub use pointer::*;
mod data;
pub use data::*;
......@@ -153,4 +150,7 @@ pub trait SpecializeByConditional: Sized {
/// Return the restriction of `self` to values satisfying `self != bound`
/// Returns an error if `self` only represents one value for which `self == bound` holds.
fn add_not_equal_bound(self, bound: &Bitvector) -> Result<Self, Error>;
/// Return the intersection of two values or an error if the intersection is empty.
fn intersect(self, other: &Self) -> Result<Self, Error>;
}
use super::{AbstractDomain, AbstractIdentifier, RegisterDomain, SizedDomain};
use crate::intermediate_representation::{BinOpType, ByteSize};
use crate::prelude::*;
use std::collections::BTreeMap;
use std::fmt::Display;
/// An abstract value representing a pointer given as a map from an abstract identifier
/// to the offset in the pointed to object. The offset itself is also a member of an abstract domain.
///
/// If the map contains more than one key,
/// it indicates that the pointer may point to any of the contained objects.
///
/// A `PointerDomain` value always has at least one target.
/// Trying to create a pointer without targets should always lead to panics.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct PointerDomain<T: RegisterDomain>(BTreeMap<AbstractIdentifier, T>);
impl<T: RegisterDomain> AbstractDomain for PointerDomain<T> {
/// Merge two pointers.
///
/// The merged pointer contains all targets of `self` and `other`.
/// For targets, that are contained in both, the offsets are merged.
fn merge(&self, other: &Self) -> Self {
let mut merged_map = self.0.clone();
for (location, offset) in other.0.iter() {
if merged_map.contains_key(location) {
merged_map.insert(location.clone(), merged_map[location].merge(offset));
} else {
merged_map.insert(location.clone(), offset.clone());
}
}
PointerDomain(merged_map)
}
/// Returns false, as PointerDomain has no *Top* element.
fn is_top(&self) -> bool {
false
}
}
impl<T: RegisterDomain> SizedDomain for PointerDomain<T> {
/// Return the bitsize of the pointer.
/// Should always equal the pointer size of the CPU architecture.
fn bytesize(&self) -> ByteSize {
self.0
.values()
.next()
.expect("Pointer without targets encountered")
.bytesize()
}
/// PointerDomain has no explicit `Top` element, thus calling this function will panic.
fn new_top(_bytesize: ByteSize) -> Self {
panic!()
}
}
impl<T: RegisterDomain> PointerDomain<T> {
/// Create a new pointer with exactly one target.
pub fn new(target: AbstractIdentifier, offset: T) -> PointerDomain<T> {
let mut map = BTreeMap::new();
map.insert(target, offset);
PointerDomain(map)
}
/// Create a new pointer with a set of targets. Panics if no targets are provided.
pub fn with_targets(targets: BTreeMap<AbstractIdentifier, T>) -> PointerDomain<T> {
assert!(!targets.is_empty());
PointerDomain(targets)
}
/// Add a new target to the pointer.
/// If the pointer already contains a target with the same abstract identifier, the offsets of both targets get merged.
pub fn add_target(&mut self, target: AbstractIdentifier, offset: T) {
if let Some(old_offset) = self.0.get(&target) {
let merged_offset = old_offset.merge(&offset);
self.0.insert(target, merged_offset);
} else {
self.0.insert(target, offset);
}
}
/// Replace an abstract identifier with another one and add the offset_adjustment to the pointer offset.
/// This is needed to adjust stack pointer on call and return instructions.
pub fn replace_abstract_id(
&mut self,
old_id: &AbstractIdentifier,
new_id: &AbstractIdentifier,
offset_adjustment: &T,
) {
if let Some(old_offset) = self.0.get(&old_id) {
let new_offset = old_offset.bin_op(BinOpType::IntAdd, offset_adjustment);
self.0.remove(old_id);
self.0.insert(new_id.clone(), new_offset);
}
}
/// add a value to the offset
pub fn add_to_offset(&self, value: &T) -> PointerDomain<T> {
let mut result = self.clone();
for offset in result.0.values_mut() {
*offset = offset.bin_op(BinOpType::IntAdd, value);
}
result
}
/// subtract a value from the offset
pub fn sub_from_offset(&self, value: &T) -> PointerDomain<T> {
let mut result = self.clone();
for offset in result.0.values_mut() {
*offset = offset.bin_op(BinOpType::IntSub, value);
}
result
}
/// Get all possible abstract targets (together with the offset in the target) the pointer may point to.
pub fn targets(&self) -> &BTreeMap<AbstractIdentifier, T> {
&self.0
}
/// Get an iterator over all abstract IDs that the pointer may target.
pub fn ids(&self) -> std::collections::btree_map::Keys<AbstractIdentifier, T> {
self.0.keys()
}
/// Return the target and offset of the pointer if it points to an unique ID.
pub fn unwrap_if_unique_target(&self) -> Option<(&AbstractIdentifier, &T)> {
if self.0.len() == 1 {
return self.0.iter().next();
} else {
None
}
}
}
impl<T: RegisterDomain + Display> PointerDomain<T> {
/// Get a more compact json-representation of the pointer.
/// Intended for pretty printing, not useable for serialization/deserialization.
pub fn to_json_compact(&self) -> serde_json::Value {
serde_json::Value::Object(
self.0
.iter()
.map(|(id, offset)| {
(
format!("{}", id),
serde_json::Value::String(format!("{}", offset)),
)
})
.collect(),
)
}
}
#[cfg(test)]
mod tests {
use super::super::{AbstractLocation, BitvectorDomain};
use super::*;
fn bv(value: i64) -> BitvectorDomain {
BitvectorDomain::Value(Bitvector::from_i64(value))
}
fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("time0"),
AbstractLocation::Register(name.into(), ByteSize::new(8)),
)
}
fn new_pointer_domain(location: &str, offset: i64) -> PointerDomain<BitvectorDomain> {
let id = new_id(location);
PointerDomain::new(id, bv(offset))
}
#[test]
fn pointer_domain() {
let pointer = new_pointer_domain("Rax".into(), 0);
let offset = bv(3);
let pointer_plus = new_pointer_domain("Rax".into(), 3);
let pointer_minus = new_pointer_domain("Rax".into(), -3);
assert_eq!(pointer.add_to_offset(&offset), pointer_plus);
assert_eq!(pointer.sub_from_offset(&offset), pointer_minus);
let other_pointer = new_pointer_domain("Rbx".into(), 5);
let merged = pointer.merge(&other_pointer);
assert_eq!(merged.0.len(), 2);
assert_eq!(merged.0.get(&new_id("Rax".into())), Some(&bv(0)));
assert_eq!(merged.0.get(&new_id("Rbx".into())), Some(&bv(5)));
}
#[test]
fn replace_abstract_id() {
let mut targets = BTreeMap::new();
targets.insert(new_id("Rax"), bv(5));
targets.insert(new_id("Rbx"), bv(7));
let mut pointer = PointerDomain::with_targets(targets);
pointer.replace_abstract_id(&new_id("Rax"), &new_id("replacement"), &bv(5));
let mut new_targets = BTreeMap::new();
new_targets.insert(new_id("replacement"), bv(10));
new_targets.insert(new_id("Rbx"), bv(7));
assert_eq!(pointer.0, new_targets);
}
}
......@@ -132,7 +132,7 @@ impl<T: Context> Computation<T> {
/// Get the value of a node.
pub fn get_node_value(&self, node: NodeIndex) -> Option<&T::NodeValue> {
if let Some(ref value) = self.node_values.get(&node) {
if let Some(value) = self.node_values.get(&node) {
Some(value)
} else {
self.default_value.as_ref()
......
......@@ -334,7 +334,7 @@ impl<'a> GraphBuilder<'a> {
}
} else {
let mut call_source_node: Option<NodeIndex> = None;
if let Some((target_node, _)) = self.call_targets.get(&target) {
if let Some((target_node, _)) = self.call_targets.get(target) {
let (target_block, target_sub) = match self.graph[*target_node] {
Node::BlkStart(target_block, target_sub) => (target_block, target_sub),
_ => panic!(),
......
......@@ -89,27 +89,17 @@ impl<'a> Context<'a> {
}
_ => Bitvector::zero(apint::BitWidth::from(self.project.get_pointer_bytesize())),
};
match state_before_return.get_register(&self.project.stack_pointer_register) {
Data::Pointer(pointer) => {
if pointer.targets().len() == 1 {
let (id, offset) = pointer.targets().iter().next().unwrap();
if *id != state_before_return.stack_id
|| *offset != expected_stack_pointer_offset.into()
{
Err(anyhow!("Unexpected stack register value on return"))
} else {
Ok(())
}
} else {
Err(anyhow!(
"Unexpected number of stack register targets on return"
))
}
match state_before_return
.get_register(&self.project.stack_pointer_register)
.get_if_unique_target()
{
Some((id, offset))
if *id == state_before_return.stack_id
&& *offset == expected_stack_pointer_offset.into() =>
{
Ok(())
}
Data::Top(_) => Err(anyhow!(
"Stack register value lost during function execution"
)),
Data::Value(_) => Err(anyhow!("Unexpected stack register value on return")),
_ => Err(anyhow!("Unexpected stack register value on return")),
}
}
......@@ -166,10 +156,10 @@ impl<'a> Context<'a> {
}
_ => DataDomain::new_top(address_bytesize),
};
match object_size {
Data::Value(val) => val,
_ => ValueDomain::new_top(address_bytesize),
}
object_size
.get_if_absolute_value()
.cloned()
.unwrap_or_else(|| ValueDomain::new_top(address_bytesize))
}
/// Add a new abstract object and a pointer to it in the return register of an extern call.
......@@ -183,7 +173,7 @@ impl<'a> Context<'a> {
extern_symbol: &ExternSymbol,
) -> State {
let address_bytesize = self.project.get_pointer_bytesize();
let object_size = self.get_allocation_size_of_alloc_call(&state, extern_symbol);
let object_size = self.get_allocation_size_of_alloc_call(state, extern_symbol);
match extern_symbol.get_unique_return_register() {
Ok(return_register) => {
......@@ -205,11 +195,11 @@ impl<'a> Context<'a> {
&object_id,
&(object_size - Bitvector::one(address_bytesize.into()).into()),
);
let pointer = PointerDomain::new(
let pointer = Data::from_target(
object_id,
Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(),
);
new_state.set_register(return_register, pointer.into());
new_state.set_register(return_register, pointer);
new_state
}
Err(err) => {
......@@ -235,36 +225,29 @@ impl<'a> Context<'a> {
let parameter_value = state.eval_parameter_arg(
parameter,
&self.project.stack_pointer_register,
&self.runtime_memory_image,
self.runtime_memory_image,
);
match parameter_value {
Ok(memory_object_pointer) => {
if let Data::Pointer(pointer) = memory_object_pointer {
if let Err(possible_double_frees) =
new_state.mark_mem_object_as_freed(&pointer)
{
let warning = CweWarning {
name: "CWE415".to_string(),
version: VERSION.to_string(),
addresses: vec![call.tid.address.clone()],
tids: vec![format!("{}", call.tid)],
symbols: Vec::new(),
other: vec![possible_double_frees
.into_iter()
.map(|(id, err)| format!("{}: {}", id, err))
.collect()],
description: format!(
"(Double Free) Object may have been freed before at {}",
call.tid.address
),
};
let _ = self.log_collector.send(LogThreadMsg::Cwe(warning));
}
} else {
self.log_debug(
Err(anyhow!("Free on a non-pointer value called.")),
Some(&call.tid),
);
if let Err(possible_double_frees) =
new_state.mark_mem_object_as_freed(&memory_object_pointer)
{
let warning = CweWarning {
name: "CWE415".to_string(),
version: VERSION.to_string(),
addresses: vec![call.tid.address.clone()],
tids: vec![format!("{}", call.tid)],
symbols: Vec::new(),
other: vec![possible_double_frees
.into_iter()
.map(|(id, err)| format!("{}: {}", id, err))
.collect()],
description: format!(
"(Double Free) Object may have been freed before at {}",
call.tid.address
),
};
let _ = self.log_collector.send(LogThreadMsg::Cwe(warning));
}
new_state.remove_unreferenced_objects();
new_state
......@@ -294,7 +277,7 @@ impl<'a> Context<'a> {
match state.eval_parameter_arg(
parameter,
&self.project.stack_pointer_register,
&self.runtime_memory_image,
self.runtime_memory_image,
) {
Ok(value) => {
if state.memory.is_dangling_pointer(&value, true) {
......@@ -340,7 +323,7 @@ impl<'a> Context<'a> {
match state.eval_parameter_arg(
parameter,
&self.project.stack_pointer_register,
&self.runtime_memory_image,
self.runtime_memory_image,
) {
Ok(data) => {
if state.pointer_contains_out_of_bounds_target(&data, self.runtime_memory_image)
......@@ -425,7 +408,7 @@ impl<'a> Context<'a> {
),
Some(&call.tid),
);
let calling_conv = extern_symbol.get_calling_convention(&self.project);
let calling_conv = extern_symbol.get_calling_convention(self.project);
let mut possible_referenced_ids = BTreeSet::new();
if extern_symbol.parameters.is_empty() && extern_symbol.return_values.is_empty() {
// We assume here that we do not know the parameters and approximate them by all possible parameter registers.
......@@ -437,7 +420,7 @@ impl<'a> Context<'a> {
.chain(calling_conv.float_parameter_register.iter())
{
if let Some(register_value) = state.get_register_by_name(parameter_register_name) {
possible_referenced_ids.append(&mut register_value.referenced_ids());
possible_referenced_ids.extend(register_value.referenced_ids().cloned());
}
}
} else {
......@@ -445,9 +428,9 @@ impl<'a> Context<'a> {
if let Ok(data) = state.eval_parameter_arg(
parameter,
&self.project.stack_pointer_register,
&self.runtime_memory_image,
self.runtime_memory_image,
) {
possible_referenced_ids.append(&mut data.referenced_ids());
possible_referenced_ids.extend(data.referenced_ids().cloned());
}
}
}
......@@ -484,7 +467,7 @@ impl<'a> Context<'a> {
if let Some(register_value) =
state_before_call.get_register_by_name(parameter_register_name)
{
possible_referenced_ids.append(&mut register_value.referenced_ids());
possible_referenced_ids.extend(register_value.referenced_ids().cloned());
}
}
possible_referenced_ids =
......@@ -503,15 +486,12 @@ impl<'a> Context<'a> {
/// Get the offset of the current stack pointer to the base of the current stack frame.
fn get_current_stack_offset(&self, state: &State) -> ValueDomain {
if let Data::Pointer(ref stack_pointer) =
state.get_register(&self.project.stack_pointer_register)
if let Some((stack_id, stack_offset_domain)) = state
.get_register(&self.project.stack_pointer_register)
.get_if_unique_target()
{
if stack_pointer.targets().len() == 1 {
let (stack_id, stack_offset_domain) =
stack_pointer.targets().iter().next().unwrap();
if *stack_id == state.stack_id {
return stack_offset_domain.clone();
}
if *stack_id == state.stack_id {
return stack_offset_domain.clone();
}
}
ValueDomain::new_top(self.project.stack_pointer_register.size)
......
......@@ -142,7 +142,7 @@ fn context_problem_implementation() {
// test update_def
state = context.update_def(&state, &def).unwrap();
let stack_pointer = Data::Pointer(PointerDomain::new(new_id("main", "RSP"), bv(-16)));
let stack_pointer = Data::from_target(new_id("main", "RSP"), bv(-16));
assert_eq!(state.eval(&Var(register("RSP"))), stack_pointer);
state = context.update_def(&state, &store_term).unwrap();
......@@ -175,8 +175,8 @@ fn context_problem_implementation() {
callee_state
.memory
.set_value(
PointerDomain::new(new_id("func", "RSP"), bv(-30)),
Data::Value(bv(33).into()),
Data::from_target(new_id("func", "RSP"), bv(-30)),
bv(33).into(),
)
.unwrap();
// Emulate removing the return pointer from the stack for x64
......@@ -213,28 +213,25 @@ fn context_problem_implementation() {
.bin_op(BinOpType::IntAdd, &Bitvector::from_i64(8).into())
);
state.set_register(&register("callee_saved_reg"), Data::Value(bv(13)));
state.set_register(&register("other_reg"), Data::Value(bv(14)));
state.set_register(&register("callee_saved_reg"), bv(13).into());
state.set_register(&register("other_reg"), bv(14).into());
let malloc = call_term("extern_malloc");
let mut state_after_malloc = context.update_call_stub(&state, &malloc).unwrap();
assert_eq!(
state_after_malloc.get_register(&register("RDX")),
Data::Pointer(PointerDomain::new(
new_id("call_extern_malloc", "RDX"),
bv(0)
))
Data::from_target(new_id("call_extern_malloc", "RDX"), bv(0))
);
assert_eq!(state_after_malloc.memory.get_num_objects(), 2);
assert_eq!(
state_after_malloc.get_register(&register("RSP")),
state
.get_register(&register("RSP"))
.bin_op(BinOpType::IntAdd, &Data::Value(bv(8)))
.bin_op(BinOpType::IntAdd, &bv(8).into())
);
assert_eq!(
state_after_malloc.get_register(&register("callee_saved_reg")),
Data::Value(bv(13))
bv(13).into()
);
assert!(state_after_malloc
.get_register(&register("other_reg"))
......@@ -242,10 +239,7 @@ fn context_problem_implementation() {
state_after_malloc.set_register(
&register("callee_saved_reg"),
Data::Pointer(PointerDomain::new(
new_id("call_extern_malloc", "RDX"),
bv(0),
)),
Data::from_target(new_id("call_extern_malloc", "RDX"), bv(0)),
);
let free = call_term("extern_free");
let state_after_free = context
......@@ -255,10 +249,7 @@ fn context_problem_implementation() {
assert_eq!(state_after_free.memory.get_num_objects(), 2);
assert_eq!(
state_after_free.get_register(&register("callee_saved_reg")),
Data::Pointer(PointerDomain::new(
new_id("call_extern_malloc", "RDX"),
bv(0)
))
Data::from_target(new_id("call_extern_malloc", "RDX"), bv(0))
);
let other_extern_fn = call_term("extern_other");
......@@ -268,11 +259,11 @@ fn context_problem_implementation() {
state_after_other_fn.get_register(&register("RSP")),
state
.get_register(&register("RSP"))
.bin_op(BinOpType::IntAdd, &Data::Value(bv(8)))
.bin_op(BinOpType::IntAdd, &bv(8).into())
);
assert_eq!(
state_after_other_fn.get_register(&register("callee_saved_reg")),
Data::Value(bv(13))
bv(13).into()
);
assert!(state_after_other_fn
.get_register(&register("other_reg"))
......@@ -326,10 +317,7 @@ fn update_return() {
.insert(other_callsite_id.clone());
state_before_return.set_register(
&register("RDX"),
Data::Pointer(PointerDomain::new(
new_id("call_callee_other", "RSP"),
bv(-32),
)),
Data::from_target(new_id("call_callee_other", "RSP"), bv(-32)),
);
let state_before_call = State::new(&register("RSP"), Tid::new("original_caller_id"));
......@@ -379,10 +367,7 @@ fn update_return() {
.get_all_object_ids()
.get(&new_id("caller_caller", "RSP"))
.is_some());
let expected_rsp = Data::Pointer(PointerDomain::new(
new_id("original_caller_id", "RSP"),
bv(-8),
));
let expected_rsp = Data::from_target(new_id("original_caller_id", "RSP"), bv(-8));
assert_eq!(state.get_register(&register("RSP")), expected_rsp);
}
......
......@@ -5,7 +5,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
/// Get the underlying graph on which the analysis operates.
fn get_graph(&self) -> &Graph<'a> {
&self.graph
self.graph
}
/// Merge two state values.
......@@ -77,7 +77,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
}
Def::Load { var, address } => {
self.log_debug(
new_state.handle_load(var, address, &self.runtime_memory_image),
new_state.handle_load(var, address, self.runtime_memory_image),
Some(&def.tid),
);
Some(new_state)
......@@ -158,11 +158,10 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
// At the beginning of a function this is the only known pointer to the new stack frame.
callee_state.set_register(
&self.project.stack_pointer_register,
PointerDomain::new(
Data::from_target(
callee_stack_id.clone(),
Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(),
)
.into(),
),
);
// set the list of caller stack ids to only this caller id
callee_state.caller_stack_ids = BTreeSet::new();
......@@ -202,7 +201,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
(Some(state_call), None) => {
if self.is_indirect_call_with_top_target(state_call, call_term) {
// We know nothing about the call target.
return self.handle_call_to_generic_unknown_function(&state_call);
return self.handle_call_to_generic_unknown_function(state_call);
} else {
// We know at least something about the call target.
// Since we don't have a return value,
......@@ -294,7 +293,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
Jmp::CallInd { .. } => {
if self.is_indirect_call_with_top_target(state, call) {
// We know nothing about the call target.
return self.handle_call_to_generic_unknown_function(&state);
return self.handle_call_to_generic_unknown_function(state);
} else {
return None;
}
......@@ -318,7 +317,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
);
}
// Clear non-callee-saved registers from the state.
let cconv = extern_symbol.get_calling_convention(&self.project);
let cconv = extern_symbol.get_calling_convention(self.project);
new_state.clear_non_callee_saved_register(&cconv.callee_saved_register[..]);
// Adjust stack register value (for x86 architecture).
self.adjust_stack_register_on_extern_call(state, &mut new_state);
......
......@@ -360,7 +360,7 @@ impl<'a> PointerInference<'a> {
for jmp in block.term.jmps.iter() {
match &jmp.term {
Jmp::BranchInd(target_expr) => {
let address = state.eval(&target_expr);
let address = state.eval(target_expr);
println!(
"{}: Indirect jump to {}",
jmp.tid,
......@@ -368,7 +368,7 @@ impl<'a> PointerInference<'a> {
);
}
Jmp::CallInd { target, return_ } => {
let address = state.eval(&target);
let address = state.eval(target);
println!(
"{}: Indirect call to {}. HasReturn: {}",
jmp.tid,
......
......@@ -131,9 +131,7 @@ impl AbstractObjectInfo {
/// If the abstract object is not unique (i.e. may represent more than one actual object),
/// merge the old value at the given offset with the new value.
pub fn set_value(&mut self, value: Data, offset: &ValueDomain) -> Result<(), Error> {
if let Data::Pointer(ref pointer) = value {
self.pointer_targets.extend(pointer.ids().cloned());
};
self.pointer_targets.extend(value.referenced_ids().cloned());
if let Ok(concrete_offset) = offset.try_to_bitvec() {
if self.is_unique {
self.memory.add(value, concrete_offset);
......@@ -155,9 +153,7 @@ impl AbstractObjectInfo {
/// Merge `value` at position `offset` with the value currently saved at that position.
pub fn merge_value(&mut self, value: Data, offset: &ValueDomain) {
if let Data::Pointer(ref pointer) = value {
self.pointer_targets.extend(pointer.ids().cloned());
};
self.pointer_targets.extend(value.referenced_ids().cloned());
if let Ok(concrete_offset) = offset.try_to_bitvec() {
let merged_value = self
.memory
......@@ -184,7 +180,7 @@ impl AbstractObjectInfo {
pub fn get_referenced_ids_underapproximation(&self) -> BTreeSet<AbstractIdentifier> {
let mut referenced_ids = BTreeSet::new();
for data in self.memory.values() {
referenced_ids.append(&mut data.referenced_ids())
referenced_ids.extend(data.referenced_ids().cloned())
}
referenced_ids
}
......@@ -201,8 +197,8 @@ impl AbstractObjectInfo {
elem.replace_abstract_id(old_id, new_id, offset_adjustment);
}
self.memory.clear_top_values();
if self.pointer_targets.get(&old_id).is_some() {
self.pointer_targets.remove(&old_id);
if self.pointer_targets.get(old_id).is_some() {
self.pointer_targets.remove(old_id);
self.pointer_targets.insert(new_id.clone());
}
}
......@@ -218,6 +214,8 @@ impl AbstractObjectInfo {
/// Remove the provided IDs from the target lists of all pointers in the memory object.
/// Also remove them from the pointer_targets list.
///
/// If this operation would produce an empty value, it replaces it with a `Top` value instead.
pub fn remove_ids(&mut self, ids_to_remove: &BTreeSet<AbstractIdentifier>) {
self.pointer_targets = self
.pointer_targets
......@@ -226,6 +224,9 @@ impl AbstractObjectInfo {
.collect();
for value in self.memory.values_mut() {
value.remove_ids(ids_to_remove);
if value.is_empty() {
*value = value.top();
}
}
self.memory.clear_top_values(); // In case the previous operation left *Top* values in the memory struct.
}
......@@ -425,7 +426,7 @@ mod tests {
}
fn new_data(number: i64) -> Data {
Data::Value(bv(number))
bv(number).into()
}
fn bv(number: i64) -> ValueDomain {
......@@ -447,7 +448,7 @@ mod tests {
object.set_value(three, &offset).unwrap();
assert_eq!(
object.get_value(Bitvector::from_i64(-16), ByteSize::new(8)),
Data::Top(ByteSize::new(8))
Data::new_top(ByteSize::new(8))
);
assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
......@@ -456,12 +457,12 @@ mod tests {
object.set_value(new_data(4), &bv(-12)).unwrap();
assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
Data::Top(ByteSize::new(8))
Data::new_top(ByteSize::new(8))
);
object.merge_value(new_data(23), &bv(-12));
assert_eq!(
object.get_value(Bitvector::from_i64(-12), ByteSize::new(8)),
Data::Value(IntervalDomain::mock(4, 23).with_stride(19))
IntervalDomain::mock(4, 23).with_stride(19).into()
);
let mut other_object = new_abstract_object();
......@@ -470,7 +471,7 @@ mod tests {
let merged_object = object.merge(&other_object);
assert_eq!(
merged_object.get_value(Bitvector::from_i64(-12), ByteSize::new(8)),
Data::Top(ByteSize::new(8))
Data::new_top(ByteSize::new(8))
);
assert_eq!(
merged_object.get_value(Bitvector::from_i64(0), ByteSize::new(8)),
......@@ -486,8 +487,8 @@ mod tests {
target_map.insert(new_id("time_1", "RAX"), bv(20));
target_map.insert(new_id("time_234", "RAX"), bv(30));
target_map.insert(new_id("time_1", "RBX"), bv(40));
let pointer = PointerDomain::with_targets(target_map.clone());
object.set_value(pointer.into(), &bv(-15)).unwrap();
let pointer = DataDomain::mock_from_target_map(target_map.clone());
object.set_value(pointer, &bv(-15)).unwrap();
assert_eq!(object.get_referenced_ids_overapproximation().len(), 3);
object.replace_abstract_id(
......@@ -496,10 +497,10 @@ mod tests {
&bv(10),
);
target_map.remove(&new_id("time_1", "RAX"));
let modified_pointer = PointerDomain::with_targets(target_map);
let modified_pointer = DataDomain::mock_from_target_map(target_map);
assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
modified_pointer.into()
modified_pointer
);
object.replace_abstract_id(
......@@ -510,10 +511,10 @@ mod tests {
let mut target_map = BTreeMap::new();
target_map.insert(new_id("time_234", "RAX"), bv(30));
target_map.insert(new_id("time_234", "RBX"), bv(50));
let modified_pointer = PointerDomain::with_targets(target_map);
let modified_pointer = DataDomain::mock_from_target_map(target_map);
assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
modified_pointer.into()
modified_pointer
);
}
......@@ -525,8 +526,8 @@ mod tests {
target_map.insert(new_id("time_1", "RAX"), bv(20));
target_map.insert(new_id("time_234", "RAX"), bv(30));
target_map.insert(new_id("time_1", "RBX"), bv(40));
let pointer = PointerDomain::with_targets(target_map.clone());
object.set_value(pointer.into(), &bv(-15)).unwrap();
let pointer = DataDomain::mock_from_target_map(target_map.clone());
object.set_value(pointer, &bv(-15)).unwrap();
assert_eq!(object.get_referenced_ids_overapproximation().len(), 3);
let ids_to_remove = vec![new_id("time_1", "RAX"), new_id("time_23", "RBX")]
......
......@@ -50,20 +50,16 @@ impl AbstractObjectList {
/// I.e. objects representing more than one actual object (e.g. an array of object) will not get reported,
/// even if their state is unknown and `report_unknown_states` is `true`.
pub fn is_dangling_pointer(&self, address: &Data, report_unknown_states: bool) -> bool {
match address {
Data::Value(_) | Data::Top(_) => (),
Data::Pointer(pointer) => {
for id in pointer.ids() {
let (object, _offset_id) = self.objects.get(id).unwrap();
match (report_unknown_states, object.get_state()) {
(_, ObjectState::Dangling) => return true,
(true, ObjectState::Unknown) => {
if object.is_unique {
return true;
}
for id in address.referenced_ids() {
if let Some((object, _offset_id)) = self.objects.get(id) {
match (report_unknown_states, object.get_state()) {
(_, ObjectState::Dangling) => return true,
(true, ObjectState::Unknown) => {
if object.is_unique {
return true;
}
_ => (),
}
_ => (),
}
}
}
......@@ -75,15 +71,13 @@ impl AbstractObjectList {
/// whose state is either dangling or unknown,
/// as flagged.
pub fn mark_dangling_pointer_targets_as_flagged(&mut self, address: &Data) {
if let Data::Pointer(pointer) = address {
for id in pointer.ids() {
let (object, _) = self.objects.get_mut(id).unwrap();
if matches!(
object.get_state(),
ObjectState::Unknown | ObjectState::Dangling
) {
object.set_state(ObjectState::Flagged);
}
for id in address.referenced_ids() {
let (object, _) = self.objects.get_mut(id).unwrap();
if matches!(
object.get_state(),
ObjectState::Unknown | ObjectState::Dangling
) {
object.set_state(ObjectState::Flagged);
}
}
}
......@@ -99,27 +93,26 @@ impl AbstractObjectList {
size: ByteSize,
global_data: &RuntimeMemoryImage,
) -> bool {
match address {
Data::Value(value) => {
if let Ok((start, end)) = value.try_to_offset_interval() {
if start < 0 || end < start {
return true;
}
return global_data
.is_interval_readable(start as u64, end as u64 + u64::from(size) - 1)
.is_err();
if let Some(value) = address.get_absolute_value() {
if let Ok((start, end)) = value.try_to_offset_interval() {
if start < 0 || end < start {
return true;
}
if global_data
.is_interval_readable(start as u64, end as u64 + u64::from(size) - 1)
.is_err()
{
return true;
}
}
Data::Top(_) => (),
Data::Pointer(pointer) => {
for (id, offset) in pointer.targets() {
let (object, base_offset) = self.objects.get(id).unwrap();
let adjusted_offset = offset.clone() + base_offset.clone();
if !adjusted_offset.is_top()
&& !object.access_contained_in_bounds(&adjusted_offset, size)
{
return true;
}
}
for (id, offset) in address.get_relative_values() {
if let Some((object, base_offset)) = self.objects.get(id) {
let adjusted_offset = offset.clone() + base_offset.clone();
if !adjusted_offset.is_top()
&& !object.access_contained_in_bounds(&adjusted_offset, size)
{
return true;
}
}
}
......@@ -155,57 +148,53 @@ impl AbstractObjectList {
/// Get the value at a given address.
/// If the address is not unique, merge the value of all possible addresses.
///
/// Returns an error if the address is a `Data::Value`, i.e. not a pointer.
pub fn get_value(&self, address: &Data, size: ByteSize) -> Result<Data, Error> {
match address {
Data::Value(_) => Err(anyhow!("Load from non-pointer value")),
Data::Top(_) => Ok(Data::new_top(size)),
Data::Pointer(pointer) => {
let mut merged_value: Option<Data> = None;
for (id, offset_pointer_domain) in pointer.targets() {
let (object, offset_identifier) = self.objects.get(id).unwrap();
let offset = offset_pointer_domain.clone() + offset_identifier.clone();
if let Ok(concrete_offset) = offset.try_to_bitvec() {
let value = object.get_value(concrete_offset, size);
merged_value = match merged_value {
Some(accum) => Some(accum.merge(&value)),
None => Some(value),
};
} else {
merged_value = Some(Data::new_top(size));
break;
}
/// This function only checks for relative targets and not for absolute addresses.
/// If the address does not contain any relative targets an empty value is returned.
pub fn get_value(&self, address: &Data, size: ByteSize) -> Data {
let mut merged_value = Data::new_empty(size);
for (id, offset_pointer) in address.get_relative_values() {
if let Some((object, offset_identifier)) = self.objects.get(id) {
let offset = offset_pointer.clone() + offset_identifier.clone();
if let Ok(concrete_offset) = offset.try_to_bitvec() {
let value = object.get_value(concrete_offset, size);
merged_value = merged_value.merge(&value);
} else {
merged_value.set_contains_top_flag();
}
merged_value.ok_or_else(|| panic!("Pointer without targets encountered."))
} else {
merged_value.set_contains_top_flag();
}
}
if address.contains_top() {
merged_value.set_contains_top_flag();
}
merged_value
}
/// Set the value at a given address.
///
/// If the address has more than one target,
/// we merge-write the value to all targets.
pub fn set_value(
&mut self,
pointer: PointerDomain<ValueDomain>,
value: Data,
) -> Result<(), Error> {
let targets = pointer.targets();
assert!(!targets.is_empty());
if targets.len() == 1 {
let (id, pointer_offset) = targets.iter().next().unwrap();
let (object, id_offset) = self.objects.get_mut(id).unwrap();
let adjusted_offset = pointer_offset.clone() + id_offset.clone();
object.set_value(value, &adjusted_offset)
} else {
// There is more than one object that the pointer may write to.
// We merge-write to all possible targets
for (id, offset) in targets {
let (object, object_offset) = self.objects.get_mut(id).unwrap();
let adjusted_offset = offset.clone() + object_offset.clone();
object.merge_value(value.clone(), &adjusted_offset);
pub fn set_value(&mut self, pointer: Data, value: Data) -> Result<(), Error> {
let targets = pointer.get_relative_values();
match targets.len() {
0 => Ok(()),
1 => {
let (id, pointer_offset) = targets.iter().next().unwrap();
let (object, id_offset) = self.objects.get_mut(id).unwrap();
let adjusted_offset = pointer_offset.clone() + id_offset.clone();
object.set_value(value, &adjusted_offset)
}
_ => {
// There is more than one object that the pointer may write to.
// We merge-write to all possible targets
for (id, offset) in targets {
let (object, object_offset) = self.objects.get_mut(id).unwrap();
let adjusted_offset = offset.clone() + object_offset.clone();
object.merge_value(value.clone(), &adjusted_offset);
}
Ok(())
}
Ok(())
}
}
......@@ -309,9 +298,9 @@ impl AbstractObjectList {
/// Returns either a non-empty list of detected errors (like possible double frees) or `OK(())` if no errors were found.
pub fn mark_mem_object_as_freed(
&mut self,
object_pointer: &PointerDomain<ValueDomain>,
object_pointer: &Data,
) -> Result<(), Vec<(AbstractIdentifier, Error)>> {
let ids: Vec<AbstractIdentifier> = object_pointer.ids().cloned().collect();
let ids: Vec<AbstractIdentifier> = object_pointer.referenced_ids().cloned().collect();
let mut possible_double_free_ids = Vec::new();
if ids.len() > 1 {
for id in ids {
......@@ -320,11 +309,9 @@ impl AbstractObjectList {
}
}
} else if let Some(id) = ids.get(0) {
if let Err(error) = self.objects.get_mut(&id).unwrap().0.mark_as_freed() {
if let Err(error) = self.objects.get_mut(id).unwrap().0.mark_as_freed() {
possible_double_free_ids.push((id.clone(), error));
}
} else {
panic!("Pointer without targets encountered")
}
if possible_double_free_ids.is_empty() {
Ok(())
......@@ -470,31 +457,25 @@ mod tests {
assert_eq!(obj_list.objects.len(), 1);
assert_eq!(obj_list.objects.values().next().unwrap().1, bv(0));
let pointer = PointerDomain::new(new_id("RSP".into()), bv(8));
obj_list
.set_value(pointer.clone(), Data::Value(bv(42)))
.unwrap();
let pointer = DataDomain::from_target(new_id("RSP".into()), bv(8));
obj_list.set_value(pointer.clone(), bv(42).into()).unwrap();
assert_eq!(
obj_list
.get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8))
.unwrap(),
Data::Value(bv(42))
obj_list.get_value(&pointer, ByteSize::new(8)),
bv(42).into()
);
let mut other_obj_list =
AbstractObjectList::from_stack_id(new_id("RSP".into()), ByteSize::new(8));
let second_pointer = PointerDomain::new(new_id("RSP".into()), bv(-8));
let second_pointer = DataDomain::from_target(new_id("RSP".into()), bv(-8));
other_obj_list
.set_value(pointer.clone(), Data::Value(bv(42)))
.set_value(pointer.clone(), bv(42).into())
.unwrap();
other_obj_list
.set_value(second_pointer.clone(), Data::Value(bv(35)))
.set_value(second_pointer.clone(), bv(35).into())
.unwrap();
assert_eq!(
other_obj_list
.get_value(&Data::Pointer(second_pointer.clone()), ByteSize::new(8))
.unwrap(),
Data::Value(bv(35))
other_obj_list.get_value(&second_pointer, ByteSize::new(8)),
bv(35).into()
);
other_obj_list.add_abstract_object(
......@@ -503,51 +484,38 @@ mod tests {
ObjectType::Heap,
ByteSize::new(8),
);
let heap_pointer = PointerDomain::new(new_id("RAX".into()), bv(8));
let heap_pointer = DataDomain::from_target(new_id("RAX".into()), bv(8));
other_obj_list
.set_value(heap_pointer.clone(), Data::Value(bv(3)))
.set_value(heap_pointer.clone(), bv(3).into())
.unwrap();
let mut merged = obj_list.merge(&other_obj_list);
assert_eq!(merged.get_value(&pointer, ByteSize::new(8)), bv(42).into());
assert_eq!(
merged
.get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8))
.unwrap(),
Data::Value(bv(42))
);
assert_eq!(
merged
.get_value(&Data::Pointer(second_pointer.clone()), ByteSize::new(8))
.unwrap(),
merged.get_value(&second_pointer, ByteSize::new(8)),
Data::new_top(ByteSize::new(8))
);
assert_eq!(
merged
.get_value(&Data::Pointer(heap_pointer.clone()), ByteSize::new(8))
.unwrap(),
Data::Value(bv(3))
merged.get_value(&heap_pointer, ByteSize::new(8)),
bv(3).into()
);
assert_eq!(merged.objects.len(), 2);
merged
.set_value(pointer.merge(&heap_pointer), Data::Value(bv(3)))
.set_value(pointer.merge(&heap_pointer), bv(3).into())
.unwrap();
assert_eq!(
merged
.get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8))
.unwrap(),
Data::Value(IntervalDomain::mock(3, 42).with_stride(39))
merged.get_value(&pointer, ByteSize::new(8)),
IntervalDomain::mock(3, 42).with_stride(39).into()
);
assert_eq!(
merged
.get_value(&Data::Pointer(heap_pointer.clone()), ByteSize::new(8))
.unwrap(),
Data::Value(bv(3))
merged.get_value(&heap_pointer, ByteSize::new(8)),
bv(3).into()
);
assert_eq!(merged.objects.len(), 2);
other_obj_list
.set_value(pointer.clone(), Data::Pointer(heap_pointer.clone()))
.set_value(pointer.clone(), heap_pointer.clone())
.unwrap();
assert_eq!(
other_obj_list
......@@ -564,13 +532,11 @@ mod tests {
new_id("RAX".into())
);
let modified_heap_pointer = PointerDomain::new(new_id("ID2".into()), bv(8));
let modified_heap_pointer = DataDomain::from_target(new_id("ID2".into()), bv(8));
other_obj_list.replace_abstract_id(&new_id("RAX".into()), &new_id("ID2".into()), &bv(0));
assert_eq!(
other_obj_list
.get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8))
.unwrap(),
Data::Pointer(modified_heap_pointer.clone())
other_obj_list.get_value(&pointer, ByteSize::new(8)),
modified_heap_pointer.clone()
);
assert_eq!(other_obj_list.objects.get(&new_id("RAX".into())), None);
assert!(matches!(
......
......@@ -51,9 +51,7 @@ impl State {
let caller_addresses: Vec<_> = self
.caller_stack_ids
.iter()
.map(|caller_stack_id| {
PointerDomain::new(caller_stack_id.clone(), offset.clone()).into()
})
.map(|caller_stack_id| Data::from_target(caller_stack_id.clone(), offset.clone()))
.collect();
let mut result = Ok(());
for address in caller_addresses {
......@@ -64,30 +62,27 @@ impl State {
// Note that this only returns the last error that was detected.
result
} else {
match self.adjust_pointer_for_read(address) {
Data::Pointer(pointer) => {
self.memory.set_value(pointer, value.clone())?;
Ok(())
}
Data::Value(absolute_address) => {
if let Ok(address_to_global_data) = absolute_address.try_to_bitvec() {
match global_memory.is_address_writeable(&address_to_global_data) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow!("Write to read-only global data")),
Err(err) => Err(err),
}
} else if let Ok((start, end)) = absolute_address.try_to_offset_interval() {
match global_memory.is_interval_writeable(start as u64, end as u64) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow!("Write to read-only global data")),
Err(err) => Err(err),
}
} else {
// We assume inexactness of the algorithm instead of a possible CWE here.
Ok(())
let pointer = self.adjust_pointer_for_read(address);
self.memory.set_value(pointer.clone(), value.clone())?;
if let Some(absolute_address) = pointer.get_absolute_value() {
if let Ok(address_to_global_data) = absolute_address.try_to_bitvec() {
match global_memory.is_address_writeable(&address_to_global_data) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow!("Write to read-only global data")),
Err(err) => Err(err),
}
} else if let Ok((start, end)) = absolute_address.try_to_offset_interval() {
match global_memory.is_interval_writeable(start as u64, end as u64) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow!("Write to read-only global data")),
Err(err) => Err(err),
}
} else {
// We assume inexactness of the algorithm instead of a possible CWE here.
Ok(())
}
Data::Top(_) => Ok(()),
} else {
Ok(())
}
}
}
......@@ -122,28 +117,38 @@ impl State {
global_memory: &RuntimeMemoryImage,
) -> Result<Data, Error> {
let address = self.adjust_pointer_for_read(&self.eval(address));
match address {
Data::Value(global_address) => {
if let Ok(address_bitvector) = global_address.try_to_bitvec() {
if let Some(loaded_value) = global_memory.read(&address_bitvector, size)? {
Ok(Data::Value(loaded_value.into()))
} else {
Ok(Data::Top(size))
}
} else if let Ok((start, end)) = global_address.try_to_offset_interval() {
if global_memory
.is_interval_readable(start as u64, end as u64 + u64::from(size))?
{
Ok(Data::new_top(size))
} else {
Err(anyhow!("Target address is not readable."))
}
let mut result = if let Some(global_address) = address.get_absolute_value() {
if let Ok(address_bitvector) = global_address.try_to_bitvec() {
match global_memory.read(&address_bitvector, size) {
Ok(Some(loaded_value)) => loaded_value.into(),
Ok(None) => Data::new_top(size),
Err(_) => Data::new_empty(size),
}
} else if let Ok((start, end)) = global_address.try_to_offset_interval() {
if global_memory
.is_interval_readable(start as u64, end as u64 + u64::from(size))
.ok()
== Some(true)
{
Data::new_top(size)
} else {
Ok(Data::new_top(size))
Data::new_empty(size)
}
} else {
Data::new_top(size)
}
Data::Top(_) => Ok(Data::new_top(size)),
Data::Pointer(_) => Ok(self.memory.get_value(&address, size)?),
} else {
Data::new_empty(size)
};
result = result.merge(&self.memory.get_value(&address, size));
if address.contains_top() {
result.set_contains_top_flag()
}
if result.is_empty() {
Err(anyhow!("Could not read from address"))
} else {
Ok(result)
}
}
......@@ -169,44 +174,40 @@ impl State {
/// If the pointer contains a reference to the stack with offset >= 0, replace it with a pointer
/// pointing to all possible caller IDs.
fn adjust_pointer_for_read(&self, address: &Data) -> Data {
if let Data::Pointer(pointer) = address {
let mut new_targets = BTreeMap::new();
for (id, offset) in pointer.targets() {
if *id == self.stack_id {
if let Ok((interval_start, interval_end)) = offset.try_to_offset_interval() {
if interval_start >= 0
&& interval_end >= 0
&& !self.caller_stack_ids.is_empty()
{
for caller_id in self.caller_stack_ids.iter() {
new_targets.insert(caller_id.clone(), offset.clone());
}
// Note that the id of the current stack frame was *not* added.
} else {
new_targets.insert(id.clone(), offset.clone());
}
} else {
let mut adjusted_address = address.clone();
let mut new_targets = BTreeMap::new();
for (id, offset) in address.get_relative_values() {
if *id == self.stack_id {
if let Ok((interval_start, interval_end)) = offset.try_to_offset_interval() {
if interval_start >= 0 && interval_end >= 0 && !self.caller_stack_ids.is_empty()
{
for caller_id in self.caller_stack_ids.iter() {
new_targets.insert(caller_id.clone(), offset.clone());
}
// Note that we also add the id of the current stack frame
// Note that the id of the current stack frame was *not* added.
} else {
new_targets.insert(id.clone(), offset.clone());
}
} else {
for caller_id in self.caller_stack_ids.iter() {
new_targets.insert(caller_id.clone(), offset.clone());
}
// Note that we also add the id of the current stack frame
new_targets.insert(id.clone(), offset.clone());
}
} else {
new_targets.insert(id.clone(), offset.clone());
}
Data::Pointer(PointerDomain::with_targets(new_targets))
} else {
address.clone()
}
adjusted_address.set_relative_values(new_targets);
adjusted_address
}
/// Evaluate the value of an expression in the current state
pub fn eval(&self, expression: &Expression) -> Data {
use Expression::*;
match expression {
Var(variable) => self.get_register(&variable),
Var(variable) => self.get_register(variable),
Const(bitvector) => bitvector.clone().into(),
BinOp { op, lhs, rhs } => {
if *op == BinOpType::IntXOr && lhs == rhs {
......@@ -294,24 +295,20 @@ impl State {
data: &Data,
global_data: &RuntimeMemoryImage,
) -> bool {
let data = self.adjust_pointer_for_read(data);
matches!(data, Data::Pointer(_))
&& self
.memory
.is_out_of_bounds_mem_access(&data, ByteSize::new(1), global_data)
let mut data = self.adjust_pointer_for_read(data);
data.set_absolute_value(None); // Do not check absolute_values
self.memory
.is_out_of_bounds_mem_access(&data, ByteSize::new(1), global_data)
}
/// Return `true` if `data` is a pointer to the current stack frame with a constant positive address,
/// i.e. if it accesses a stack parameter (or the return-to address for x86) of the current function.
pub fn is_stack_pointer_with_nonnegative_offset(&self, data: &Data) -> bool {
if let Data::Pointer(pointer) = data {
if pointer.targets().len() == 1 {
let (target, offset) = pointer.targets().iter().next().unwrap();
if *target == self.stack_id {
if let Ok(offset_val) = offset.try_to_offset() {
if offset_val >= 0 {
return true;
}
if let Some((target, offset)) = data.get_if_unique_target() {
if *target == self.stack_id {
if let Ok(offset_val) = offset.try_to_offset() {
if offset_val >= 0 {
return true;
}
}
}
......@@ -327,16 +324,13 @@ impl State {
if self.caller_stack_ids.is_empty() {
return None;
}
if let Data::Pointer(pointer) = address {
match (pointer.targets().len(), pointer.targets().iter().next()) {
(1, Some((id, offset))) if self.stack_id == *id => {
if let Ok((interval_start, _interval_end)) = offset.try_to_offset_interval() {
if interval_start >= 0 {
return Some(offset.clone());
}
if let Some((id, offset)) = address.get_if_unique_target() {
if self.stack_id == *id {
if let Ok((interval_start, _interval_end)) = offset.try_to_offset_interval() {
if interval_start >= 0 {
return Some(offset.clone());
}
}
_ => (),
}
}
None
......
......@@ -48,11 +48,10 @@ impl State {
let mut register: BTreeMap<Variable, Data> = BTreeMap::new();
register.insert(
stack_register.clone(),
PointerDomain::new(
Data::from_target(
stack_id.clone(),
Bitvector::zero(apint::BitWidth::from(stack_register.size)).into(),
)
.into(),
),
);
State {
register,
......@@ -153,7 +152,7 @@ impl State {
// get all referenced IDs
let mut referenced_ids = BTreeSet::new();
for (_reg_name, data) in self.register.iter() {
referenced_ids.append(&mut data.referenced_ids());
referenced_ids.extend(data.referenced_ids().cloned());
}
referenced_ids.insert(self.stack_id.clone());
referenced_ids.append(&mut self.caller_stack_ids.clone());
......@@ -239,7 +238,7 @@ impl State {
/// an error with the list of possibly already freed objects is returned.
pub fn mark_mem_object_as_freed(
&mut self,
object_pointer: &PointerDomain<ValueDomain>,
object_pointer: &Data,
) -> Result<(), Vec<(AbstractIdentifier, Error)>> {
self.memory.mark_mem_object_as_freed(object_pointer)
}
......@@ -263,6 +262,9 @@ impl State {
ids_to_remove.remove(caller_id);
for register_value in self.register.values_mut() {
register_value.remove_ids(&ids_to_remove);
if register_value.is_empty() {
*register_value = register_value.top();
}
}
self.memory.remove_ids(&ids_to_remove);
self.caller_stack_ids = BTreeSet::new();
......@@ -341,31 +343,7 @@ impl State {
result: Data,
) -> Result<(), Error> {
if let Expression::Var(var) = expression {
match (self.eval(expression), result) {
(Data::Value(old_value), Data::Value(result_value)) => {
self.set_register(var, old_value.intersect(&result_value)?.into())
}
(Data::Pointer(old_pointer), Data::Pointer(result_pointer)) => {
let mut specialized_targets = BTreeMap::new();
for (id, offset) in result_pointer.targets() {
if let Some(old_offset) = old_pointer.targets().get(id) {
if let Ok(specialized_offset) = old_offset.intersect(offset) {
specialized_targets.insert(id.clone(), specialized_offset);
}
}
}
if !specialized_targets.is_empty() {
self.set_register(
var,
PointerDomain::with_targets(specialized_targets).into(),
);
} else {
return Err(anyhow!("Pointer with no targets is unsatisfiable"));
}
}
(Data::Top(_), result) => self.set_register(var, result),
_ => (),
}
self.set_register(var, self.eval(expression).intersect(&result)?);
Ok(())
} else if let Expression::BinOp { op, lhs, rhs } = expression {
self.specialize_by_binop_expression_result(op, lhs, rhs, result)
......@@ -413,7 +391,7 @@ impl State {
arg,
} => {
if *low_byte == ByteSize::new(0) {
if let Data::Value(arg_value) = self.eval(expression) {
if let Some(arg_value) = self.eval(expression).get_if_absolute_value() {
if arg_value.fits_into_size(*size) {
let intermediate_result =
result.cast(CastOpType::IntSExt, arg.bytesize());
......@@ -598,45 +576,42 @@ impl State {
lhs: &Expression,
rhs: &Expression,
) -> Result<(), Error> {
if let (Data::Pointer(lhs_pointer), Data::Pointer(rhs_pointer)) =
(self.eval(lhs), self.eval(rhs))
{
match (
lhs_pointer.unwrap_if_unique_target(),
rhs_pointer.unwrap_if_unique_target(),
) {
(Some((lhs_id, lhs_offset)), Some((rhs_id, rhs_offset))) if lhs_id == rhs_id => {
if !(self.memory.is_unique_object(lhs_id)?) {
// Since the pointers may or may not point to different instances referenced by the same ID we cannot compare them.
return Ok(());
let (lhs_pointer, rhs_pointer) = (self.eval(lhs), self.eval(rhs));
match (
lhs_pointer.get_if_unique_target(),
rhs_pointer.get_if_unique_target(),
) {
(Some((lhs_id, lhs_offset)), Some((rhs_id, rhs_offset))) if lhs_id == rhs_id => {
if !(self.memory.is_unique_object(lhs_id)?) {
// Since the pointers may or may not point to different instances referenced by the same ID we cannot compare them.
return Ok(());
}
if *op == BinOpType::IntEqual {
let specialized_offset = lhs_offset.clone().intersect(rhs_offset)?;
let specialized_domain: Data =
Data::from_target(lhs_id.clone(), specialized_offset);
self.specialize_by_expression_result(lhs, specialized_domain.clone())?;
self.specialize_by_expression_result(rhs, specialized_domain)?;
} else if *op == BinOpType::IntNotEqual {
if let Ok(rhs_offset_bitvec) = rhs_offset.try_to_bitvec() {
let new_lhs_offset =
lhs_offset.clone().add_not_equal_bound(&rhs_offset_bitvec)?;
self.specialize_by_expression_result(
lhs,
Data::from_target(lhs_id.clone(), new_lhs_offset),
)?;
}
if *op == BinOpType::IntEqual {
let specialized_offset = lhs_offset.intersect(rhs_offset)?;
let specialized_domain: Data =
PointerDomain::new(lhs_id.clone(), specialized_offset).into();
self.specialize_by_expression_result(lhs, specialized_domain.clone())?;
self.specialize_by_expression_result(rhs, specialized_domain)?;
} else if *op == BinOpType::IntNotEqual {
if let Ok(rhs_offset_bitvec) = rhs_offset.try_to_bitvec() {
let new_lhs_offset =
lhs_offset.clone().add_not_equal_bound(&rhs_offset_bitvec)?;
self.specialize_by_expression_result(
lhs,
PointerDomain::new(lhs_id.clone(), new_lhs_offset).into(),
)?;
}
if let Ok(lhs_offset_bitvec) = lhs_offset.try_to_bitvec() {
let new_rhs_offset =
rhs_offset.clone().add_not_equal_bound(&lhs_offset_bitvec)?;
self.specialize_by_expression_result(
rhs,
PointerDomain::new(rhs_id.clone(), new_rhs_offset).into(),
)?;
}
if let Ok(lhs_offset_bitvec) = lhs_offset.try_to_bitvec() {
let new_rhs_offset =
rhs_offset.clone().add_not_equal_bound(&lhs_offset_bitvec)?;
self.specialize_by_expression_result(
rhs,
Data::from_target(rhs_id.clone(), new_rhs_offset),
)?;
}
}
_ => (), // Other cases not handled, since it depends on the meaning of pointer IDs, which may change in the future.
}
_ => (), // Other cases not handled, since it depends on the meaning of pointer IDs, which may change in the future.
}
Ok(())
}
......
......@@ -39,31 +39,27 @@ fn state() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(&register("RSP"), Tid::new("time0"));
let stack_id = new_id("time0", "RSP");
let stack_addr = Data::Pointer(PointerDomain::new(stack_id.clone(), bv(8)));
let stack_addr = Data::from_target(stack_id.clone(), bv(8));
state
.store_value(&stack_addr, &Data::Value(bv(42)), &global_memory)
.store_value(&stack_addr, &bv(42).into(), &global_memory)
.unwrap();
state.register.insert(register("RSP"), stack_addr.clone());
assert_eq!(
state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap(),
Data::Value(bv(42))
bv(42).into()
);
let mut other_state = State::new(&register("RSP"), Tid::new("time0"));
state.register.insert(register("RAX"), Data::Value(bv(42)));
state.register.insert(register("RAX"), bv(42).into());
other_state
.register
.insert(register("RSP"), stack_addr.clone());
other_state
.register
.insert(register("RAX"), Data::Value(bv(42)));
other_state
.register
.insert(register("RBX"), Data::Value(bv(35)));
other_state.register.insert(register("RAX"), bv(42).into());
other_state.register.insert(register("RBX"), bv(35).into());
let merged_state = state.merge(&other_state);
assert_eq!(merged_state.register[&register("RAX")], Data::Value(bv(42)));
assert_eq!(merged_state.register[&register("RAX")], bv(42).into());
assert_eq!(merged_state.register.get(&register("RBX")), None);
assert_eq!(
merged_state
......@@ -81,62 +77,53 @@ fn state() {
);
state.caller_stack_ids.insert(new_id("time0", "caller"));
state
.store_value(&stack_addr, &Data::Value(bv(15)), &global_memory)
.store_value(&stack_addr, &bv(15).into(), &global_memory)
.unwrap();
assert_eq!(
state
.memory
.get_value(
&Data::Pointer(PointerDomain::new(new_id("time0", "caller"), bv(8))),
ByteSize::new(8)
)
.unwrap(),
Data::Value(bv(15))
state.memory.get_value(
&Data::from_target(new_id("time0", "caller"), bv(8)),
ByteSize::new(8)
),
bv(15).into()
);
assert_eq!(
state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap(),
Data::Value(bv(15))
bv(15).into()
);
// Test replace_abstract_id
let pointer = Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-16)));
let pointer = Data::from_target(stack_id.clone(), bv(-16));
state.register.insert(register("RSP"), pointer.clone());
state
.store_value(&pointer, &Data::Value(bv(7)), &global_memory)
.store_value(&pointer, &bv(7).into(), &global_memory)
.unwrap();
assert_eq!(
state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap(),
Data::Value(bv(7))
bv(7).into()
);
state.replace_abstract_id(&stack_id, &new_id("time0", "callee"), &bv(-8));
assert_eq!(
state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap(),
Data::Value(bv(7))
bv(7).into()
);
assert_eq!(
state
.memory
.get_value(
&Data::Pointer(PointerDomain::new(new_id("time0", "callee"), bv(-8))),
ByteSize::new(8)
)
.unwrap(),
Data::Value(bv(7))
state.memory.get_value(
&Data::from_target(new_id("time0", "callee"), bv(-8)),
ByteSize::new(8)
),
bv(7).into()
);
assert_eq!(
state
.memory
.get_value(
&Data::Pointer(PointerDomain::new(new_id("time0", "callee"), bv(-16))),
ByteSize::new(8)
)
.unwrap(),
state.memory.get_value(
&Data::from_target(new_id("time0", "callee"), bv(-16)),
ByteSize::new(8)
),
Data::new_top(ByteSize::new(8))
);
......@@ -159,18 +146,18 @@ fn handle_store() {
let stack_id = new_id("time0", "RSP");
assert_eq!(
state.eval(&Var(register("RSP"))),
Data::Pointer(PointerDomain::new(stack_id.clone(), bv(0)))
Data::from_target(stack_id.clone(), bv(0))
);
state.handle_register_assign(&register("RSP"), &reg_sub("RSP", 32));
assert_eq!(
state.eval(&Var(register("RSP"))),
Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-32)))
Data::from_target(stack_id.clone(), bv(-32))
);
state.handle_register_assign(&register("RSP"), &reg_add("RSP", -8));
assert_eq!(
state.eval(&Var(register("RSP"))),
Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-40)))
Data::from_target(stack_id.clone(), bv(-40))
);
state
......@@ -245,19 +232,19 @@ fn handle_caller_stack_stores() {
)
.unwrap();
// check that it was saved in all caller objects and not on the callee stack object
let pointer = PointerDomain::new(new_id("time0", "RSP"), bv(8)).into();
let pointer = Data::from_target(new_id("time0", "RSP"), bv(8));
assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(),
state.memory.get_value(&pointer, ByteSize::new(8)),
Data::new_top(ByteSize::new(8))
);
let pointer = PointerDomain::new(new_id("caller1", "RSP"), bv(8)).into();
let pointer = Data::from_target(new_id("caller1", "RSP"), bv(8));
assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(),
state.memory.get_value(&pointer, ByteSize::new(8)),
bv(42).into()
);
let pointer = PointerDomain::new(new_id("caller2", "RSP"), bv(8)).into();
let pointer = Data::from_target(new_id("caller2", "RSP"), bv(8));
assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(),
state.memory.get_value(&pointer, ByteSize::new(8)),
bv(42).into()
);
// accessing through a positive stack register offset should yield the value of the caller stacks
......@@ -276,7 +263,7 @@ fn clear_parameters_on_the_stack_on_extern_calls() {
let mut state = State::new(&register("RSP"), Tid::new("time0"));
state.register.insert(
register("RSP"),
PointerDomain::new(new_id("time0", "RSP"), bv(-20)).into(),
Data::from_target(new_id("time0", "RSP"), bv(-20)),
);
// write something onto the stack
state
......@@ -303,9 +290,9 @@ fn clear_parameters_on_the_stack_on_extern_calls() {
has_var_args: false,
};
// check the value before
let pointer = PointerDomain::new(new_id("time0", "RSP"), bv(-12)).into();
let pointer = Data::from_target(new_id("time0", "RSP"), bv(-12));
assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(),
state.memory.get_value(&pointer, ByteSize::new(8)),
bv(42).into()
);
// clear stack parameter
......@@ -314,7 +301,7 @@ fn clear_parameters_on_the_stack_on_extern_calls() {
.unwrap();
// check the value after
assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(),
state.memory.get_value(&pointer, ByteSize::new(8)),
Data::new_top(ByteSize::new(8))
);
}
......@@ -333,7 +320,7 @@ fn merge_callee_stack_to_caller_stack() {
// check the state before merging to the caller stack
assert_eq!(
state.register.get(&register("RSP")).unwrap(),
&PointerDomain::new(new_id("callee", "RSP"), bv(0)).into()
&Data::from_target(new_id("callee", "RSP"), bv(0))
);
assert_eq!(state.memory.get_all_object_ids().len(), 2);
// check state after merging to the caller stack
......@@ -344,7 +331,7 @@ fn merge_callee_stack_to_caller_stack() {
);
assert_eq!(
state.register.get(&register("RSP")).unwrap(),
&PointerDomain::new(new_id("callsite", "RSP"), bv(52)).into()
&Data::from_target(new_id("callsite", "RSP"), bv(52))
);
assert_eq!(state.memory.get_all_object_ids().len(), 1);
}
......@@ -378,10 +365,8 @@ fn reachable_ids_under_and_overapproximation() {
let mut state = State::new(&register("RSP"), Tid::new("func_tid"));
let stack_id = new_id("func_tid", "RSP");
let heap_id = new_id("heap_obj", "RAX");
let stack_address: Data =
PointerDomain::new(stack_id.clone(), Bitvector::from_i64(-8).into()).into();
let heap_address: Data =
PointerDomain::new(heap_id.clone(), Bitvector::from_i64(0).into()).into();
let stack_address: Data = Data::from_target(stack_id.clone(), Bitvector::from_i64(-8).into());
let heap_address: Data = Data::from_target(heap_id.clone(), Bitvector::from_i64(0).into());
// Add the heap object to the state, so that it can be recursively searched.
state.memory.add_abstract_object(
heap_id.clone(),
......@@ -408,8 +393,8 @@ fn reachable_ids_under_and_overapproximation() {
);
let _ = state.store_value(
&PointerDomain::new(stack_id.clone(), ValueDomain::new_top(ByteSize::new(8))).into(),
&Data::Value(Bitvector::from_i64(42).into()),
&Data::from_target(stack_id.clone(), ValueDomain::new_top(ByteSize::new(8))),
&Bitvector::from_i64(42).into(),
&global_memory,
);
assert_eq!(
......@@ -435,12 +420,12 @@ fn global_mem_access() {
state
.load_value(&address_expr, ByteSize::new(4), &global_memory)
.unwrap(),
DataDomain::Value(Bitvector::from_u32(0xb3b2b1b0).into()) // note that we read in little-endian byte order
Bitvector::from_u32(0xb3b2b1b0).into() // note that we read in little-endian byte order
);
assert!(state
.write_to_address(
&address_expr,
&DataDomain::Top(ByteSize::new(4)),
&DataDomain::new_top(ByteSize::new(4)),
&global_memory
)
.is_err());
......@@ -451,12 +436,12 @@ fn global_mem_access() {
state
.load_value(&address_expr, ByteSize::new(4), &global_memory)
.unwrap(),
DataDomain::Top(ByteSize::new(4))
DataDomain::new_top(ByteSize::new(4))
);
assert!(state
.write_to_address(
&address_expr,
&DataDomain::Top(ByteSize::new(4)),
&DataDomain::new_top(ByteSize::new(4)),
&global_memory
)
.is_ok());
......@@ -469,7 +454,7 @@ fn global_mem_access() {
assert!(state
.write_to_address(
&address_expr,
&DataDomain::Top(ByteSize::new(4)),
&DataDomain::new_top(ByteSize::new(4)),
&global_memory
)
.is_err());
......@@ -507,16 +492,16 @@ fn specialize_by_expression_results() {
);
state.set_register(
&register("RAX"),
PointerDomain::new(abstract_id.clone(), IntervalDomain::mock(0, 50)).into(),
Data::from_target(abstract_id.clone(), IntervalDomain::mock(0, 50)),
);
let x = state.specialize_by_expression_result(
&Expression::var("RAX", 8),
PointerDomain::new(abstract_id.clone(), IntervalDomain::mock(20, 70)).into(),
Data::from_target(abstract_id.clone(), IntervalDomain::mock(20, 70)),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
PointerDomain::new(abstract_id, IntervalDomain::mock(20, 50)).into()
Data::from_target(abstract_id, IntervalDomain::mock(20, 50))
);
// Expr = Const
......@@ -1015,11 +1000,11 @@ fn specialize_by_unsigned_comparison_op() {
#[test]
fn stack_pointer_with_nonnegative_offset() {
let state = State::new(&register("RSP"), Tid::new("func_tid"));
let pointer = PointerDomain::new(state.stack_id.clone(), Bitvector::from_i64(-1).into()).into();
let pointer = Data::from_target(state.stack_id.clone(), Bitvector::from_i64(-1).into());
assert!(!state.is_stack_pointer_with_nonnegative_offset(&pointer));
let pointer = PointerDomain::new(state.stack_id.clone(), Bitvector::from_i64(5).into()).into();
let pointer = Data::from_target(state.stack_id.clone(), Bitvector::from_i64(5).into());
assert!(state.is_stack_pointer_with_nonnegative_offset(&pointer));
let pointer = PointerDomain::new(state.stack_id.clone(), IntervalDomain::mock(2, 3)).into();
let pointer = Data::from_target(state.stack_id.clone(), IntervalDomain::mock(2, 3));
assert!(!state.is_stack_pointer_with_nonnegative_offset(&pointer)); // The offset is not a constant
}
......@@ -1041,16 +1026,16 @@ fn out_of_bounds_access_recognition() {
.memory
.set_upper_index_bound(&heap_obj_id, &Bitvector::from_u64(7).into());
let pointer = PointerDomain::new(heap_obj_id.clone(), Bitvector::from_i64(-1).into()).into();
let pointer = Data::from_target(heap_obj_id.clone(), Bitvector::from_i64(-1).into());
assert!(state.pointer_contains_out_of_bounds_target(&pointer, &global_data));
let pointer = PointerDomain::new(heap_obj_id.clone(), Bitvector::from_u64(0).into()).into();
let pointer = Data::from_target(heap_obj_id.clone(), Bitvector::from_u64(0).into());
assert!(!state.pointer_contains_out_of_bounds_target(&pointer, &global_data));
let pointer = PointerDomain::new(heap_obj_id.clone(), Bitvector::from_u64(7).into()).into();
let pointer = Data::from_target(heap_obj_id.clone(), Bitvector::from_u64(7).into());
assert!(!state.pointer_contains_out_of_bounds_target(&pointer, &global_data));
let pointer = PointerDomain::new(heap_obj_id.clone(), Bitvector::from_u64(8).into()).into();
let pointer = Data::from_target(heap_obj_id.clone(), Bitvector::from_u64(8).into());
assert!(state.pointer_contains_out_of_bounds_target(&pointer, &global_data));
let address = PointerDomain::new(heap_obj_id.clone(), Bitvector::from_u64(0).into()).into();
let address = Data::from_target(heap_obj_id.clone(), Bitvector::from_u64(0).into());
state.set_register(&Variable::mock("RAX", 8), address);
let load_def = Def::load(
"tid",
......@@ -1059,13 +1044,13 @@ fn out_of_bounds_access_recognition() {
);
assert!(!state.contains_out_of_bounds_mem_access(&load_def.term, &global_data));
let address = PointerDomain::new(heap_obj_id.clone(), Bitvector::from_u64(0).into()).into();
let address = Data::from_target(heap_obj_id.clone(), Bitvector::from_u64(0).into());
state.set_register(&Variable::mock("RAX", 8), address);
assert!(!state.contains_out_of_bounds_mem_access(&load_def.term, &global_data));
let address = PointerDomain::new(heap_obj_id.clone(), Bitvector::from_u64(1).into()).into();
let address = Data::from_target(heap_obj_id.clone(), Bitvector::from_u64(1).into());
state.set_register(&Variable::mock("RAX", 8), address);
assert!(state.contains_out_of_bounds_mem_access(&load_def.term, &global_data));
let address = PointerDomain::new(state.stack_id.clone(), Bitvector::from_u64(8).into()).into();
let address = Data::from_target(state.stack_id.clone(), Bitvector::from_u64(8).into());
state.set_register(&Variable::mock("RAX", 8), address);
assert!(!state.contains_out_of_bounds_mem_access(&load_def.term, &global_data));
}
......@@ -1076,12 +1061,12 @@ fn specialize_pointer_comparison() {
let interval = IntervalDomain::mock(-5, 10);
state.set_register(
&register("RAX"),
PointerDomain::new(new_id("func_tid", "RSP"), interval.into()).into(),
Data::from_target(new_id("func_tid", "RSP"), interval.into()),
);
let interval = IntervalDomain::mock(20, 20);
state.set_register(
&register("RBX"),
PointerDomain::new(new_id("func_tid", "RSP"), interval.into()).into(),
Data::from_target(new_id("func_tid", "RSP"), interval.into()),
);
let expression = Expression::BinOp {
op: BinOpType::IntEqual,
......@@ -1094,7 +1079,7 @@ fn specialize_pointer_comparison() {
.is_err());
let specialized_interval = IntervalDomain::mock_with_bounds(None, -5, 10, Some(19));
let specialized_pointer =
PointerDomain::new(new_id("func_tid", "RSP"), specialized_interval.into()).into();
Data::from_target(new_id("func_tid", "RSP"), specialized_interval.into());
assert!(state
.specialize_by_expression_result(&expression, Bitvector::from_i8(0).into())
.is_ok());
......
......@@ -88,7 +88,7 @@ pub fn check_cwe(
let general_context = Context::new(
project,
analysis_results.runtime_memory_image,
&pointer_inference_results,
pointer_inference_results,
cwe_sender,
);
......
......@@ -216,7 +216,7 @@ impl<'a> Context<'a> {
if let Ok(stack_param) = pi_state.eval_parameter_arg(
parameter,
&self.project.stack_pointer_register,
&self.runtime_memory_image,
self.runtime_memory_image,
) {
if state.check_if_address_points_to_taint(stack_param, pi_state) {
return true;
......
......@@ -150,13 +150,11 @@ impl State {
/// Return whether the value at the given address (with the given size) is tainted.
pub fn load_taint_from_memory(&self, address: &Data, size: ByteSize) -> Taint {
let mut taint = Taint::Top(size);
if let Data::Pointer(pointer) = address {
for (mem_id, offset) in pointer.targets().iter() {
if let (Some(mem_region), Ok(position)) =
(self.memory_taint.get(mem_id), offset.try_to_bitvec())
{
taint = taint.merge(&mem_region.get(position.clone(), size));
}
for (mem_id, offset) in address.get_relative_values() {
if let (Some(mem_region), Ok(position)) =
(self.memory_taint.get(mem_id), offset.try_to_bitvec())
{
taint = taint.merge(&mem_region.get(position.clone(), size));
}
}
taint
......@@ -168,30 +166,26 @@ impl State {
/// we merge the taint object with the object at the targets,
/// possibly tainting all possible targets.
pub fn save_taint_to_memory(&mut self, address: &Data, taint: Taint) {
if let Data::Pointer(pointer) = address {
if pointer.targets().len() == 1 {
for (mem_id, offset) in pointer.targets().iter() {
if let Ok(position) = offset.try_to_bitvec() {
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
mem_region.add(taint, position.clone());
} else {
let mut mem_region = MemRegion::new(address.bytesize());
mem_region.add(taint, position.clone());
self.memory_taint.insert(mem_id.clone(), mem_region);
}
}
if let Some((mem_id, offset)) = address.get_if_unique_target() {
if let Ok(position) = offset.try_to_bitvec() {
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
mem_region.add(taint, position);
} else {
let mut mem_region = MemRegion::new(address.bytesize());
mem_region.add(taint, position);
self.memory_taint.insert(mem_id.clone(), mem_region);
}
} else {
for (mem_id, offset) in pointer.targets().iter() {
if let Ok(position) = offset.try_to_bitvec() {
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
let old_taint = mem_region.get(position.clone(), taint.bytesize());
mem_region.add(old_taint.merge(&taint), position.clone());
} else {
let mut mem_region = MemRegion::new(address.bytesize());
mem_region.add(taint, position.clone());
self.memory_taint.insert(mem_id.clone(), mem_region);
}
}
} else {
for (mem_id, offset) in address.get_relative_values() {
if let Ok(position) = offset.try_to_bitvec() {
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
let old_taint = mem_region.get(position.clone(), taint.bytesize());
mem_region.add(old_taint.merge(&taint), position.clone());
} else {
let mut mem_region = MemRegion::new(address.bytesize());
mem_region.add(taint, position.clone());
self.memory_taint.insert(mem_id.clone(), mem_region);
}
}
}
......@@ -214,7 +208,7 @@ impl State {
/// Return true if the memory object with the given ID contains a tainted value.
pub fn check_mem_id_for_taint(&self, id: &AbstractIdentifier) -> bool {
if let Some(mem_object) = self.memory_taint.get(&id) {
if let Some(mem_object) = self.memory_taint.get(id) {
for elem in mem_object.values() {
if elem.is_tainted() {
return true;
......@@ -234,27 +228,26 @@ impl State {
pi_state: &PointerInferenceState,
) -> bool {
use crate::analysis::pointer_inference::object::ObjectType;
if let Data::Pointer(pointer) = address {
for (target, offset) in pointer.targets() {
if let Ok(Some(ObjectType::Stack)) = pi_state.memory.get_object_type(target) {
// Only check if the value at the address is tainted
if let (Some(mem_object), Ok(target_offset)) =
(self.memory_taint.get(target), offset.try_to_bitvec())
{
if let Some(taint) = mem_object.get_unsized(target_offset.clone()) {
if taint.is_tainted() {
return true;
}
for (target, offset) in address.get_relative_values() {
if let Ok(Some(ObjectType::Stack)) = pi_state.memory.get_object_type(target) {
// Only check if the value at the address is tainted
if let (Some(mem_object), Ok(target_offset)) =
(self.memory_taint.get(target), offset.try_to_bitvec())
{
if let Some(taint) = mem_object.get_unsized(target_offset.clone()) {
if taint.is_tainted() {
return true;
}
}
} else {
// Check whether the memory object contains any taint.
if self.check_mem_id_for_taint(target) {
return true;
}
}
} else {
// Check whether the memory object contains any taint.
if self.check_mem_id_for_taint(target) {
return true;
}
}
}
false
}
......@@ -439,9 +432,9 @@ mod tests {
)
}
fn new_pointer_domain(location: &str, offset: i64) -> PointerDomain<ValueDomain> {
fn new_pointer(location: &str, offset: i64) -> DataDomain<ValueDomain> {
let id = new_id(location);
PointerDomain::new(id, bv(offset))
DataDomain::from_target(id, bv(offset))
}
#[test]
......@@ -453,7 +446,7 @@ mod tests {
state.set_register_taint(&register("RAX"), taint.clone());
let mut other_state = State::mock();
let address = Data::Pointer(new_pointer_domain("mem", 10));
let address = new_pointer("mem", 10);
other_state.save_taint_to_memory(&address, taint);
let merged_state = state.merge(&other_state);
......@@ -466,7 +459,7 @@ mod tests {
merged_state.load_taint_from_memory(&address, ByteSize::new(8)),
taint.clone()
);
let other_address = Data::Pointer(new_pointer_domain("mem", 18));
let other_address = new_pointer("mem", 18);
assert_eq!(
merged_state.load_taint_from_memory(&other_address, ByteSize::new(8)),
top.clone()
......
......@@ -53,7 +53,7 @@ pub fn get_calls<'a>(
let mut calls: Vec<(&str, &Tid, &str)> = Vec::new();
let mut symbol_map: HashMap<&Tid, &str> = HashMap::with_capacity(dangerous_symbols.len());
for symbol in dangerous_symbols.iter() {
symbol_map.insert(&symbol.tid, &symbol.name.as_str());
symbol_map.insert(&symbol.tid, symbol.name.as_str());
}
for sub in subfunctions.iter() {
calls.append(&mut get_calls_to_symbols(sub, &symbol_map));
......
use petgraph::graph::NodeIndex;
use crate::abstract_domain::{DataDomain, IntervalDomain, PointerDomain};
use crate::abstract_domain::IntervalDomain;
use crate::analysis::pointer_inference::{Data, PointerInference as PointerInferenceComputation};
use crate::intermediate_representation::{
Arg, BinOpType, Bitvector, ByteSize, CallingConvention, Expression, ExternSymbol, Tid, Variable,
......@@ -193,8 +193,7 @@ fn tainting_user_input_symbol_parameters() {
format_string_index.insert("scanf".to_string(), 0);
let global_address = Bitvector::from_str_radix(16, "500c").unwrap();
let string_address =
DataDomain::Value(IntervalDomain::new(global_address.clone(), global_address));
let string_address = IntervalDomain::new(global_address.clone(), global_address).into();
let mut pi_result_state = pi_results
.get_node_value(call_source_node)
......@@ -217,7 +216,7 @@ fn tainting_user_input_symbol_parameters() {
})),
rhs: Box::new(Expression::Const(Bitvector::from_u64(0))),
},
&Data::Pointer(PointerDomain::new(setup.pi_state.stack_id.clone(), bv(-8))),
&Data::from_target(setup.pi_state.stack_id.clone(), bv(-8)),
&mem_image,
)
.expect("Failed to write to address.");
......@@ -294,7 +293,7 @@ fn processing_scanf() {
})),
rhs: Box::new(Expression::Const(Bitvector::from_u64(0))),
},
&Data::Pointer(PointerDomain::new(setup.pi_state.stack_id.clone(), bv(-8))),
&Data::from_target(setup.pi_state.stack_id.clone(), bv(-8)),
context.runtime_memory_image,
)
.expect("Failed to write to address.");
......@@ -349,7 +348,7 @@ fn processing_sscanf() {
})),
rhs: Box::new(Expression::Const(Bitvector::from_u64(0))),
},
&Data::Pointer(PointerDomain::new(setup.pi_state.stack_id.clone(), bv(-8))),
&Data::from_target(setup.pi_state.stack_id.clone(), bv(-8)),
context.runtime_memory_image,
)
.expect("Failed to write to address.");
......@@ -418,7 +417,7 @@ fn tainting_function_arguments() {
})),
rhs: Box::new(Expression::Const(Bitvector::from_u64(24))),
},
&Data::Pointer(PointerDomain::new(setup.pi_state.stack_id.clone(), bv(32))),
&Data::from_target(setup.pi_state.stack_id.clone(), bv(32)),
context.runtime_memory_image,
)
.expect("Failed to write to address.");
......@@ -431,7 +430,7 @@ fn tainting_function_arguments() {
);
assert!(setup.state.address_points_to_taint(
Data::Pointer(PointerDomain::new(setup.pi_state.stack_id.clone(), bv(32))),
Data::from_target(setup.pi_state.stack_id.clone(), bv(32)),
&setup.pi_state
));
}
......
......@@ -4,7 +4,7 @@ use super::*;
use crate::analysis::{backward_interprocedural_fixpoint::Context as BackwardContext, graph::Node};
use crate::{
abstract_domain::{DataDomain, PointerDomain, SizedDomain},
abstract_domain::{DataDomain, SizedDomain},
analysis::pointer_inference::{Data, State as PointerInferenceState, ValueDomain},
intermediate_representation::{Expression, Variable},
};
......@@ -118,8 +118,8 @@ impl Setup {
state,
pi_state,
taint_source,
base_eight_offset: Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-8))),
base_sixteen_offset: Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-16))),
base_eight_offset: Data::from_target(stack_id.clone(), bv(-8)),
base_sixteen_offset: Data::from_target(stack_id.clone(), bv(-16)),
}
}
}
......@@ -339,7 +339,7 @@ fn creating_pi_def_map() {
} else if *def_tid == def2 {
assert_eq!(
pi_state.get_register(&rdi_reg),
Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-8))),
Data::from_target(stack_id.clone(), bv(-8)),
);
}
}
......@@ -568,10 +568,8 @@ fn handling_assign_and_load() {
new_state = context.update_def(&new_state, &mock_assign_stack).unwrap();
assert_eq!(new_state.get_register_taint(&r9_reg), None);
assert_eq!(
new_state.address_points_to_taint(
Data::Pointer(PointerDomain::new(stack_id.clone(), bv(0))),
&setup.pi_state
),
new_state
.address_points_to_taint(Data::from_target(stack_id.clone(), bv(0)), &setup.pi_state),
true
);
......@@ -653,10 +651,8 @@ fn updating_def() {
new_state = context.update_def(&new_state, &mock_assign_stack).unwrap();
assert_eq!(new_state.get_register_taint(&r9_reg), None);
assert_eq!(
new_state.address_points_to_taint(
Data::Pointer(PointerDomain::new(stack_id.clone(), bv(0))),
&setup.pi_state
),
new_state
.address_points_to_taint(Data::from_target(stack_id.clone(), bv(0)), &setup.pi_state),
true
);
......
......@@ -132,30 +132,26 @@ impl State {
/// we merge the taint object with the object at the targets,
/// possibly tainting all possible targets.
pub fn save_taint_to_memory(&mut self, address: &Data, taint: Taint) {
if let Data::Pointer(pointer) = address {
if pointer.targets().len() == 1 {
for (mem_id, offset) in pointer.targets().iter() {
if let Ok(position) = offset.try_to_bitvec() {
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
mem_region.add(taint, position.clone());
} else {
let mut mem_region = MemRegion::new(address.bytesize());
mem_region.add(taint, position.clone());
self.memory_taint.insert(mem_id.clone(), mem_region);
}
}
if let Some((mem_id, offset)) = address.get_if_unique_target() {
if let Ok(position) = offset.try_to_bitvec() {
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
mem_region.add(taint, position);
} else {
let mut mem_region = MemRegion::new(address.bytesize());
mem_region.add(taint, position);
self.memory_taint.insert(mem_id.clone(), mem_region);
}
} else {
for (mem_id, offset) in pointer.targets().iter() {
if let Ok(position) = offset.try_to_bitvec() {
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
let old_taint = mem_region.get(position.clone(), taint.bytesize());
mem_region.add(old_taint.merge(&taint), position.clone());
} else {
let mut mem_region = MemRegion::new(address.bytesize());
mem_region.add(taint, position.clone());
self.memory_taint.insert(mem_id.clone(), mem_region);
}
}
} else {
for (mem_id, offset) in address.get_relative_values() {
if let Ok(position) = offset.try_to_bitvec() {
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
let old_taint = mem_region.get(position.clone(), taint.bytesize());
mem_region.add(old_taint.merge(&taint), position.clone());
} else {
let mut mem_region = MemRegion::new(address.bytesize());
mem_region.add(taint, position.clone());
self.memory_taint.insert(mem_id.clone(), mem_region);
}
}
}
......@@ -285,7 +281,7 @@ impl State {
if let Some(pid_map) = self.pi_def_map.as_ref() {
if let Some(pi_state) = pid_map.get(def_tid) {
let address = pi_state.eval(target);
if self.address_points_to_taint(address.clone(), &pi_state) {
if self.address_points_to_taint(address.clone(), pi_state) {
self.taint_def_input_register(
value,
stack_pointer_register,
......@@ -361,15 +357,12 @@ impl State {
/// Remove the taint in the specified memory regions at the specified offsets.
pub fn remove_mem_taint_at_target(&mut self, address: &Data) {
if let Data::Pointer(pointer) = address {
for (mem_id, offset) in pointer.targets().iter() {
if let (Some(mem_region), Ok(position)) =
(self.memory_taint.get_mut(mem_id), offset.try_to_bitvec())
{
if let Some(taint) = mem_region.get_unsized(position.clone()) {
mem_region
.remove(position, Bitvector::from_u64(u64::from(taint.bytesize())));
}
for (mem_id, offset) in address.get_relative_values() {
if let (Some(mem_region), Ok(position)) =
(self.memory_taint.get_mut(mem_id), offset.try_to_bitvec())
{
if let Some(taint) = mem_region.get_unsized(position.clone()) {
mem_region.remove(position, Bitvector::from_u64(u64::from(taint.bytesize())));
}
}
}
......@@ -391,7 +384,7 @@ impl State {
/// Return true if the memory object with the given ID contains a tainted value.
pub fn check_mem_id_for_taint(&self, id: &AbstractIdentifier) -> bool {
if let Some(mem_object) = self.memory_taint.get(&id) {
if let Some(mem_object) = self.memory_taint.get(id) {
for elem in mem_object.values() {
if elem.is_tainted() {
return true;
......@@ -407,24 +400,22 @@ impl State {
/// return true if the memory object contains any tainted value (at any position).
pub fn address_points_to_taint(&self, address: Data, pi_state: &PointerInferenceState) -> bool {
use crate::analysis::pointer_inference::object::ObjectType;
if let Data::Pointer(pointer) = address {
for (target, offset) in pointer.targets() {
if let Ok(Some(ObjectType::Stack)) = pi_state.memory.get_object_type(target) {
// Only check if the value at the address is tainted
if let (Some(mem_object), Ok(target_offset)) =
(self.memory_taint.get(target), offset.try_to_bitvec())
{
if let Some(taint) = mem_object.get_unsized(target_offset.clone()) {
if taint.is_tainted() {
return true;
}
for (target, offset) in address.get_relative_values() {
if let Ok(Some(ObjectType::Stack)) = pi_state.memory.get_object_type(target) {
// Only check if the value at the address is tainted
if let (Some(mem_object), Ok(target_offset)) =
(self.memory_taint.get(target), offset.try_to_bitvec())
{
if let Some(taint) = mem_object.get_unsized(target_offset.clone()) {
if taint.is_tainted() {
return true;
}
}
} else {
// Check whether the memory object contains any taint.
if self.check_mem_id_for_taint(target) {
return true;
}
}
} else {
// Check whether the memory object contains any taint.
if self.check_mem_id_for_taint(target) {
return true;
}
}
}
......@@ -445,7 +436,7 @@ impl State {
let taints = self.register_taint.clone();
for (register, _) in taints.iter() {
if register_names.get(&register.name).is_none() {
self.register_taint.remove(&register);
self.register_taint.remove(register);
}
}
}
......
use crate::analysis::pointer_inference::ValueDomain;
use crate::{
abstract_domain::{DataDomain, PointerDomain},
intermediate_representation::CastOpType,
};
use crate::{abstract_domain::DataDomain, intermediate_representation::CastOpType};
use super::*;
......@@ -98,9 +95,9 @@ impl Setup {
constant: String::from("Hello World"),
constant_address: Bitvector::from_u32(12290),
def_tid: Tid::new("def"),
stack_pointer: Data::Pointer(PointerDomain::new(stack_id.clone(), bv(0))),
base_eight_offset: Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-8))),
base_sixteen_offset: Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-16))),
stack_pointer: Data::from_target(stack_id.clone(), bv(0)),
base_eight_offset: Data::from_target(stack_id.clone(), bv(-8)),
base_sixteen_offset: Data::from_target(stack_id.clone(), bv(-16)),
}
}
}
......
......@@ -37,7 +37,7 @@ pub static CWE_MODULE: crate::CweModule = crate::CweModule {
/// check whether the ioctl symbol is called by any subroutine. If so, generate the cwe warning.
pub fn handle_sub(sub: &Term<Sub>, symbol: &HashMap<&Tid, &str>) -> Vec<CweWarning> {
let calls: Vec<(&str, &Tid, &str)> = get_calls_to_symbols(sub, &symbol);
let calls: Vec<(&str, &Tid, &str)> = get_calls_to_symbols(sub, symbol);
if !calls.is_empty() {
return generate_cwe_warning(&calls);
}
......
......@@ -52,7 +52,7 @@ impl Term<Blk> {
.indirect_jmp_targets
.iter()
.filter_map(|target| {
if known_block_tids.get(&target).is_some() {
if known_block_tids.get(target).is_some() {
Some(target.clone())
} else {
let error_msg =
......
......@@ -131,7 +131,7 @@ impl Variable {
match (&self.name, &self.value) {
(None, Some(hex_value)) => {
assert!(u64::from(self.size) <= 8);
let val: u64 = u64::from_str_radix(&hex_value, 16).unwrap();
let val: u64 = u64::from_str_radix(hex_value, 16).unwrap();
val.into()
}
_ => panic!(),
......
......@@ -514,12 +514,11 @@ impl ExternSymbol {
let mut symbol = self.clone();
let mut parameters = Vec::new();
let mut return_values = Vec::new();
let input_args: Vec<&Arg> = symbol
let symbol_has_input_args = symbol
.arguments
.iter()
.filter(|arg| matches!(arg.intent, ArgIntent::INPUT))
.collect();
if symbol.is_scanf_or_sscanf() && input_args.is_empty() {
.any(|arg| matches!(arg.intent, ArgIntent::INPUT));
if symbol.is_scanf_or_sscanf() && !symbol_has_input_args {
symbol.create_format_string_args_for_scanf_and_sscanf(
conventions,
stack_pointer,
......
......@@ -7,7 +7,7 @@ use crate::{intermediate_representation::Datatype, prelude::*};
use regex::Regex;
use crate::{
abstract_domain::{DataDomain, IntervalDomain, TryToBitvec},
abstract_domain::{IntervalDomain, TryToBitvec},
analysis::pointer_inference::State as PointerInferenceState,
intermediate_representation::{
Arg, ByteSize, CallingConvention, DatatypeProperties, ExternSymbol, Project, Variable,
......@@ -37,13 +37,13 @@ pub fn get_input_format_string(
runtime_memory_image: &RuntimeMemoryImage,
) -> Result<String, Error> {
if let Some(format_string) = extern_symbol.parameters.get(format_string_index) {
if let Ok(DataDomain::Value(address)) = pi_state.eval_parameter_arg(
format_string,
&stack_pointer_register,
runtime_memory_image,
) {
if let Ok(Some(address)) = pi_state
.eval_parameter_arg(format_string, stack_pointer_register, runtime_memory_image)
.as_ref()
.map(|param| param.get_if_absolute_value())
{
return parse_format_string_destination_and_return_content(
address,
address.clone(),
runtime_memory_image,
);
}
......
......@@ -24,7 +24,7 @@ fn test_get_variable_parameters() {
let global_address = Bitvector::from_str_radix(16, "5000").unwrap();
pi_state.set_register(
&Variable::mock("RDI", 8 as u64),
DataDomain::Value(IntervalDomain::new(global_address.clone(), global_address)),
IntervalDomain::new(global_address.clone(), global_address).into(),
);
let mut project = Project::mock_empty();
let cconv = CallingConvention::mock_with_parameter_registers(
......@@ -66,7 +66,7 @@ fn test_get_variable_parameters() {
let global_address = Bitvector::from_str_radix(16, "500c").unwrap();
pi_state.set_register(
&Variable::mock("RDI", 8 as u64),
DataDomain::Value(IntervalDomain::new(global_address.clone(), global_address)),
IntervalDomain::new(global_address.clone(), global_address).into(),
);
assert_eq!(
......@@ -91,7 +91,7 @@ fn test_get_input_format_string() {
let global_address = Bitvector::from_str_radix(16, "3002").unwrap();
pi_state.set_register(
&Variable::mock("RSI", 8 as u64),
DataDomain::Value(IntervalDomain::new(global_address.clone(), global_address)),
IntervalDomain::new(global_address.clone(), global_address).into(),
);
assert_eq!(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment