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 WORKDIR /cwe_checker
......
...@@ -44,7 +44,7 @@ If you want to build the docker image yourself, just run `docker build -t cwe_ch ...@@ -44,7 +44,7 @@ If you want to build the docker image yourself, just run `docker build -t cwe_ch
### Local installation ### ### Local installation ###
The following dependencies must be installed in order to build and install the *cwe_checker* locally: 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 - [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. 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) { ...@@ -137,7 +137,7 @@ fn run_with_ghidra(args: &CmdlineArgs) {
// Generate the representation of the runtime memory image of the binary // 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() { 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); panic!("Error while generating runtime memory image: {}", err);
}) })
} else { } else {
......
use super::{ use super::{
AbstractDomain, AbstractIdentifier, HasTop, Interval, PointerDomain, RegisterDomain, AbstractDomain, AbstractIdentifier, HasTop, Interval, RegisterDomain, SizedDomain,
SizedDomain, SpecializeByConditional, TryToBitvec, TryToInterval, SpecializeByConditional, TryToBitvec, TryToInterval,
}; };
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Display; use std::fmt::Display;
use std::iter::FromIterator;
/// 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`. 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)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum DataDomain<T: RegisterDomain> { pub struct DataDomain<T: RegisterDomain> {
/// The `Top` element of the domain. /// The byte size of the represented values.
/// Describes a value for which nothing is known. size: ByteSize,
Top(ByteSize), /// A map from base values to the corresponding offset.
/// The value is a pointer to an abstract memory object. relative_values: BTreeMap<AbstractIdentifier, T>,
Pointer(PointerDomain<T>), /// An absolute value if the domain may represent an absolute value.
/// The value is a non-pointer value or a pointer to global memory. absolute_value: Option<T>,
/// The latter can happen if pointers to global memory are described by their absolute value. /// An indicator whether the domain also represents values for which both the base and the offset are unknown.
Value(T), contains_top_values: bool,
} }
impl<T: RegisterDomain> DataDomain<T> { 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. /// 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. /// This is needed to adjust stack pointer on call and return instructions.
pub fn replace_abstract_id( pub fn replace_abstract_id(
...@@ -30,300 +71,149 @@ impl<T: RegisterDomain> DataDomain<T> { ...@@ -30,300 +71,149 @@ impl<T: RegisterDomain> DataDomain<T> {
new_id: &AbstractIdentifier, new_id: &AbstractIdentifier,
offset_adjustment: &T, offset_adjustment: &T,
) { ) {
if let Self::Pointer(pointer) = self { if let Some(old_offset) = self.relative_values.get(old_id) {
pointer.replace_abstract_id(old_id, new_id, offset_adjustment); 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. /// Return an iterator over all referenced abstract IDs.
pub fn referenced_ids(&self) -> BTreeSet<AbstractIdentifier> { pub fn referenced_ids(&self) -> impl Iterator<Item = &AbstractIdentifier> {
if let Self::Pointer(pointer) = self { self.relative_values.keys()
pointer.ids().cloned().collect()
} else {
BTreeSet::new()
}
} }
/// If *self* is a pointer, remove all provided IDs from the target list of it. /// Return the relative values contained in the domain.
/// If this would leave the pointer without any targets, replace it with *Top*. pub fn get_relative_values(&self) -> &BTreeMap<AbstractIdentifier, T> {
pub fn remove_ids(&mut self, ids_to_remove: &BTreeSet<AbstractIdentifier>) { &self.relative_values
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));
}
}
} }
}
impl<T: SpecializeByConditional + RegisterDomain> SpecializeByConditional for DataDomain<T> { /// Replace the map of relative values with the given one.
fn add_signed_less_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> { pub fn set_relative_values(&mut self, relative_values: BTreeMap<AbstractIdentifier, T>) {
if let Self::Value(value) = self { self.relative_values = relative_values;
Ok(Self::Value(value.add_signed_less_equal_bound(bound)?))
} else {
Ok(self)
}
} }
fn add_unsigned_less_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> { /// Return the absolute value contained in the domain if present
if let Self::Value(value) = self { pub fn get_absolute_value(&self) -> Option<&T> {
Ok(Self::Value(value.add_unsigned_less_equal_bound(bound)?)) self.absolute_value.as_ref()
} else {
Ok(self)
}
} }
fn add_signed_greater_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> { /// Replace the absolute value contained in the domain with the given one.
if let Self::Value(value) = self { /// A value of `None` means that the domain does not contain an absolute value.
Ok(Self::Value(value.add_signed_greater_equal_bound(bound)?)) pub fn set_absolute_value(&mut self, value: Option<T>) {
} else { self.absolute_value = value
Ok(self)
}
} }
fn add_unsigned_greater_equal_bound(self, bound: &Bitvector) -> Result<Self, Error> { /// Returns `true` if the domain contains `Top` values,
if let Self::Value(value) = self { /// i.e. values for which neither a value nor an abstract identifier is known.
Ok(Self::Value(value.add_unsigned_greater_equal_bound(bound)?)) ///
} else { /// Note that the `DataDomain` itself has no maximal value,
Ok(self) /// 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> { /// Indicate that the domain may contain `Top` values
if let Self::Value(value) = self { /// in addition to the contained absolute and relative values.
Ok(Self::Value(value.add_not_equal_bound(bound)?)) ///
} else { /// This does not remove absolute or relative value information from the domain.
Ok(self) pub fn set_contains_top_flag(&mut self) {
} self.contains_top_values = true;
} }
}
impl<T: RegisterDomain> SizedDomain for DataDomain<T> { /// Return a new value representing a variable plus an offset,
// Return the bitsize of `self`. /// where the variable is represented by the given abstract ID.
fn bytesize(&self) -> ByteSize { pub fn from_target(id: AbstractIdentifier, offset: T) -> Self {
use DataDomain::*; DataDomain {
match self { size: offset.bytesize(),
Top(size) => *size, relative_values: BTreeMap::from_iter([(id, offset)]),
Pointer(pointer) => pointer.bytesize(), absolute_value: None,
Value(bitvec) => bitvec.bytesize(), contains_top_values: false,
} }
} }
// Return a new *Top* element with the given bytesize /// Remove all provided IDs from the list of relative values.
fn new_top(bytesize: ByteSize) -> Self { pub fn remove_ids(&mut self, ids_to_remove: &BTreeSet<AbstractIdentifier>) {
Self::Top(bytesize) self.relative_values = self
} .relative_values
} .iter()
.filter_map(|(id, offset)| {
impl<T: RegisterDomain> HasTop for DataDomain<T> { if ids_to_remove.get(id).is_none() {
// Generate a new *Top* element with the same bitsize as `self`. Some((id.clone(), offset.clone()))
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())
}
} else { } else {
// We cannot be sure that both pointers point to the same target None
Self::Top(self.bytesize())
} }
} })
(_, IntEqual, _) .collect();
| (_, 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()),
}
} }
/// Compute the (abstract) result of a unary operation /// Return the contained absolute value
fn un_op(&self, op: UnOpType) -> Self { /// only if `self` contains no other (relative or `Top`) values.
if let Self::Value(value) = self { pub fn get_if_absolute_value(&self) -> Option<&T> {
Self::Value(value.un_op(op)) if self.relative_values.is_empty() && !self.contains_top_values {
self.absolute_value.as_ref()
} else { } else {
match op { None
UnOpType::BoolNegate | UnOpType::FloatNaN => Self::new_top(ByteSize::new(1)),
_ => Self::new_top(self.bytesize()),
}
} }
} }
/// extract a sub-bitvector /// Return the contained absolute value
fn subpiece(&self, low_byte: ByteSize, size: ByteSize) -> Self { /// if `self` only contains absolute or `Top` values.
if let Self::Value(value) = self { fn get_if_absolute_value_or_top(&self) -> Option<&T> {
Self::Value(value.subpiece(low_byte, size)) if self.relative_values.is_empty() {
} else if low_byte == ByteSize::new(0) && size == self.bytesize() { self.absolute_value.as_ref()
// The operation is a no-op
self.clone()
} else { } else {
Self::new_top(size) None
} }
} }
/// Cast a bitvector using the given cast type /// Return the target ID and offset of the contained relative value
fn cast(&self, kind: CastOpType, width: ByteSize) -> Self { /// if `self` contains exactly one relative value and no absolute or `Top` values.
if let Self::Value(value) = self { pub fn get_if_unique_target(&self) -> Option<(&AbstractIdentifier, &T)> {
Self::Value(value.cast(kind, width)) if self.relative_values.len() == 1
&& self.absolute_value.is_none()
&& !self.contains_top_values
{
Some(self.relative_values.iter().next().unwrap())
} else { } else {
// The result of casting pointers is undefined. None
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()),
} }
} }
/// 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> { impl<T: RegisterDomain + Display> DataDomain<T> {
/// Get a more compact json-representation of the data domain. /// Get a more compact json-representation of the data domain.
/// Intended for pretty printing, not useable for serialization/deserialization. /// Intended for pretty printing, not useable for serialization/deserialization.
pub fn to_json_compact(&self) -> serde_json::Value { pub fn to_json_compact(&self) -> serde_json::Value {
match self { let mut values = Vec::new();
Self::Top(bitsize) => serde_json::Value::String(format!("Top:{}", bitsize)), if !self.relative_values.is_empty() {
Self::Pointer(pointer) => { let target_iter = self.relative_values.iter().map(|(id, offset)| {
let target_iter = pointer.targets().iter().map(|(id, offset)| { (
( format!("{}", id),
format!("{}", id), serde_json::Value::String(format!("{}", offset)),
serde_json::Value::String(format!("{}", offset)), )
) });
}); let targets = serde_json::Value::Object(target_iter.collect());
let targets = serde_json::Value::Object(target_iter.collect()); let mut obj_map = serde_json::Map::new();
let mut obj_map = serde_json::Map::new(); obj_map.insert("Pointer".to_string(), targets);
obj_map.insert("Pointer".to_string(), targets); values.push(serde_json::Value::Object(obj_map));
serde_json::Value::Object(obj_map) }
} if let Some(absolute_value) = &self.absolute_value {
Self::Value(bitvector) => serde_json::Value::String(format!("Value: {}", bitvector)), 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 { ...@@ -333,7 +223,18 @@ mod tests {
use super::super::*; use super::super::*;
use 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 { fn bv(value: i64) -> BitvectorDomain {
BitvectorDomain::Value(Bitvector::from_i64(value)) BitvectorDomain::Value(Bitvector::from_i64(value))
...@@ -346,63 +247,19 @@ mod tests { ...@@ -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] #[test]
fn data_register_domain() { fn replace_abstract_ids() {
use BinOpType::*; let mut targets = BTreeMap::new();
let data = new_value(42); targets.insert(new_id("Rax"), bv(1));
assert_eq!(data.bytesize(), ByteSize::new(8)); targets.insert(new_id("Rbx"), bv(2));
let mut data = DataDomain::mock_from_target_map(targets);
let three = new_value(3); data.replace_abstract_id(&new_id("Rbx"), &new_id("replaced_Rbx"), &bv(10));
let pointer = new_pointer("Rax".into(), 0); assert_eq!(data.relative_values.len(), 2);
assert_eq!(data.bin_op(IntAdd, &three), new_value(45)); assert_eq!(*data.relative_values.get(&new_id("Rax")).unwrap(), bv(1));
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)))
);
assert_eq!( assert_eq!(
data.cast(CastOpType::IntSExt, ByteSize::new(16)).bytesize(), *data.relative_values.get(&new_id("replaced_Rbx")).unwrap(),
ByteSize::new(16) 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] #[test]
...@@ -410,7 +267,7 @@ mod tests { ...@@ -410,7 +267,7 @@ mod tests {
let mut targets = BTreeMap::new(); let mut targets = BTreeMap::new();
targets.insert(new_id("Rax"), bv(1)); targets.insert(new_id("Rax"), bv(1));
targets.insert(new_id("Rbx"), bv(2)); 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(); let mut ids_to_remove = BTreeSet::new();
ids_to_remove.insert(new_id("Rbx")); ids_to_remove.insert(new_id("Rbx"));
...@@ -418,20 +275,14 @@ mod tests { ...@@ -418,20 +275,14 @@ mod tests {
data.remove_ids(&ids_to_remove); data.remove_ids(&ids_to_remove);
assert_eq!( assert_eq!(
data.referenced_ids(), data.referenced_ids()
vec![new_id("Rax")].into_iter().collect() .cloned()
.collect::<Vec<AbstractIdentifier>>(),
vec![new_id("Rax")]
); );
data = bv(42).into(); data = bv(42).into();
data.remove_ids(&ids_to_remove); data.remove_ids(&ids_to_remove);
assert_eq!(data, bv(42).into()); 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 { ...@@ -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`. /// 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. /// Does not check whether this is also true for the widening hints.
pub fn fits_into_size(&self, size: ByteSize) -> bool { pub fn fits_into_size(&self, size: ByteSize) -> bool {
...@@ -503,6 +480,29 @@ impl SpecializeByConditional for IntervalDomain { ...@@ -503,6 +480,29 @@ impl SpecializeByConditional for IntervalDomain {
Ok(self) 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 { impl AbstractDomain for IntervalDomain {
......
...@@ -591,7 +591,7 @@ fn add_not_equal_bounds() { ...@@ -591,7 +591,7 @@ fn add_not_equal_bounds() {
fn intersection() { fn intersection() {
let interval1 = IntervalDomain::mock_with_bounds(Some(-100), -10, 10, Some(100)); let interval1 = IntervalDomain::mock_with_bounds(Some(-100), -10, 10, Some(100));
let interval2 = IntervalDomain::mock_with_bounds(Some(-20), 2, 30, None); 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!( assert_eq!(
intersection, intersection,
IntervalDomain::mock_with_bounds(Some(-20), 2, 10, Some(100)) IntervalDomain::mock_with_bounds(Some(-20), 2, 10, Some(100))
......
...@@ -185,7 +185,7 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T ...@@ -185,7 +185,7 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T
for (pos_left, elem_left) in self.values.iter() { for (pos_left, elem_left) in self.values.iter() {
if let Some((_pos_right, elem_right)) = other.values.get_key_value(pos_left) { if let Some((_pos_right, elem_right)) = other.values.get_key_value(pos_left) {
if elem_left.bytesize() == elem_right.bytesize() { 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() { if !merged_val.is_top() {
// we discard top()-values, as they don't contain information // we discard top()-values, as they don't contain information
merged_values.insert(*pos_left, merged_val); merged_values.insert(*pos_left, merged_val);
......
...@@ -10,9 +10,6 @@ pub use bitvector::*; ...@@ -10,9 +10,6 @@ pub use bitvector::*;
mod identifier; mod identifier;
pub use identifier::*; pub use identifier::*;
mod pointer;
pub use pointer::*;
mod data; mod data;
pub use data::*; pub use data::*;
...@@ -153,4 +150,7 @@ pub trait SpecializeByConditional: Sized { ...@@ -153,4 +150,7 @@ pub trait SpecializeByConditional: Sized {
/// Return the restriction of `self` to values satisfying `self != bound` /// Return the restriction of `self` to values satisfying `self != bound`
/// Returns an error if `self` only represents one value for which `self == bound` holds. /// 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>; 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> { ...@@ -132,7 +132,7 @@ impl<T: Context> Computation<T> {
/// Get the value of a node. /// Get the value of a node.
pub fn get_node_value(&self, node: NodeIndex) -> Option<&T::NodeValue> { 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) Some(value)
} else { } else {
self.default_value.as_ref() self.default_value.as_ref()
......
...@@ -334,7 +334,7 @@ impl<'a> GraphBuilder<'a> { ...@@ -334,7 +334,7 @@ impl<'a> GraphBuilder<'a> {
} }
} else { } else {
let mut call_source_node: Option<NodeIndex> = None; 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] { let (target_block, target_sub) = match self.graph[*target_node] {
Node::BlkStart(target_block, target_sub) => (target_block, target_sub), Node::BlkStart(target_block, target_sub) => (target_block, target_sub),
_ => panic!(), _ => panic!(),
......
...@@ -89,27 +89,17 @@ impl<'a> Context<'a> { ...@@ -89,27 +89,17 @@ impl<'a> Context<'a> {
} }
_ => Bitvector::zero(apint::BitWidth::from(self.project.get_pointer_bytesize())), _ => Bitvector::zero(apint::BitWidth::from(self.project.get_pointer_bytesize())),
}; };
match state_before_return.get_register(&self.project.stack_pointer_register) { match state_before_return
Data::Pointer(pointer) => { .get_register(&self.project.stack_pointer_register)
if pointer.targets().len() == 1 { .get_if_unique_target()
let (id, offset) = pointer.targets().iter().next().unwrap(); {
if *id != state_before_return.stack_id Some((id, offset))
|| *offset != expected_stack_pointer_offset.into() if *id == state_before_return.stack_id
{ && *offset == expected_stack_pointer_offset.into() =>
Err(anyhow!("Unexpected stack register value on return")) {
} else { Ok(())
Ok(())
}
} else {
Err(anyhow!(
"Unexpected number of stack register targets on return"
))
}
} }
Data::Top(_) => Err(anyhow!( _ => Err(anyhow!("Unexpected stack register value on return")),
"Stack register value lost during function execution"
)),
Data::Value(_) => Err(anyhow!("Unexpected stack register value on return")),
} }
} }
...@@ -166,10 +156,10 @@ impl<'a> Context<'a> { ...@@ -166,10 +156,10 @@ impl<'a> Context<'a> {
} }
_ => DataDomain::new_top(address_bytesize), _ => DataDomain::new_top(address_bytesize),
}; };
match object_size { object_size
Data::Value(val) => val, .get_if_absolute_value()
_ => ValueDomain::new_top(address_bytesize), .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. /// 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> { ...@@ -183,7 +173,7 @@ impl<'a> Context<'a> {
extern_symbol: &ExternSymbol, extern_symbol: &ExternSymbol,
) -> State { ) -> State {
let address_bytesize = self.project.get_pointer_bytesize(); 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() { match extern_symbol.get_unique_return_register() {
Ok(return_register) => { Ok(return_register) => {
...@@ -205,11 +195,11 @@ impl<'a> Context<'a> { ...@@ -205,11 +195,11 @@ impl<'a> Context<'a> {
&object_id, &object_id,
&(object_size - Bitvector::one(address_bytesize.into()).into()), &(object_size - Bitvector::one(address_bytesize.into()).into()),
); );
let pointer = PointerDomain::new( let pointer = Data::from_target(
object_id, object_id,
Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(), 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 new_state
} }
Err(err) => { Err(err) => {
...@@ -235,36 +225,29 @@ impl<'a> Context<'a> { ...@@ -235,36 +225,29 @@ impl<'a> Context<'a> {
let parameter_value = state.eval_parameter_arg( let parameter_value = state.eval_parameter_arg(
parameter, parameter,
&self.project.stack_pointer_register, &self.project.stack_pointer_register,
&self.runtime_memory_image, self.runtime_memory_image,
); );
match parameter_value { match parameter_value {
Ok(memory_object_pointer) => { Ok(memory_object_pointer) => {
if let Data::Pointer(pointer) = memory_object_pointer { if let Err(possible_double_frees) =
if let Err(possible_double_frees) = new_state.mark_mem_object_as_freed(&memory_object_pointer)
new_state.mark_mem_object_as_freed(&pointer) {
{ let warning = CweWarning {
let warning = CweWarning { name: "CWE415".to_string(),
name: "CWE415".to_string(), version: VERSION.to_string(),
version: VERSION.to_string(), addresses: vec![call.tid.address.clone()],
addresses: vec![call.tid.address.clone()], tids: vec![format!("{}", call.tid)],
tids: vec![format!("{}", call.tid)], symbols: Vec::new(),
symbols: Vec::new(), other: vec![possible_double_frees
other: vec![possible_double_frees .into_iter()
.into_iter() .map(|(id, err)| format!("{}: {}", id, err))
.map(|(id, err)| format!("{}: {}", id, err)) .collect()],
.collect()], description: format!(
description: format!( "(Double Free) Object may have been freed before at {}",
"(Double Free) Object may have been freed before at {}", call.tid.address
call.tid.address ),
), };
}; let _ = self.log_collector.send(LogThreadMsg::Cwe(warning));
let _ = self.log_collector.send(LogThreadMsg::Cwe(warning));
}
} else {
self.log_debug(
Err(anyhow!("Free on a non-pointer value called.")),
Some(&call.tid),
);
} }
new_state.remove_unreferenced_objects(); new_state.remove_unreferenced_objects();
new_state new_state
...@@ -294,7 +277,7 @@ impl<'a> Context<'a> { ...@@ -294,7 +277,7 @@ impl<'a> Context<'a> {
match state.eval_parameter_arg( match state.eval_parameter_arg(
parameter, parameter,
&self.project.stack_pointer_register, &self.project.stack_pointer_register,
&self.runtime_memory_image, self.runtime_memory_image,
) { ) {
Ok(value) => { Ok(value) => {
if state.memory.is_dangling_pointer(&value, true) { if state.memory.is_dangling_pointer(&value, true) {
...@@ -340,7 +323,7 @@ impl<'a> Context<'a> { ...@@ -340,7 +323,7 @@ impl<'a> Context<'a> {
match state.eval_parameter_arg( match state.eval_parameter_arg(
parameter, parameter,
&self.project.stack_pointer_register, &self.project.stack_pointer_register,
&self.runtime_memory_image, self.runtime_memory_image,
) { ) {
Ok(data) => { Ok(data) => {
if state.pointer_contains_out_of_bounds_target(&data, self.runtime_memory_image) if state.pointer_contains_out_of_bounds_target(&data, self.runtime_memory_image)
...@@ -425,7 +408,7 @@ impl<'a> Context<'a> { ...@@ -425,7 +408,7 @@ impl<'a> Context<'a> {
), ),
Some(&call.tid), 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(); let mut possible_referenced_ids = BTreeSet::new();
if extern_symbol.parameters.is_empty() && extern_symbol.return_values.is_empty() { 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. // 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> { ...@@ -437,7 +420,7 @@ impl<'a> Context<'a> {
.chain(calling_conv.float_parameter_register.iter()) .chain(calling_conv.float_parameter_register.iter())
{ {
if let Some(register_value) = state.get_register_by_name(parameter_register_name) { 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 { } else {
...@@ -445,9 +428,9 @@ impl<'a> Context<'a> { ...@@ -445,9 +428,9 @@ impl<'a> Context<'a> {
if let Ok(data) = state.eval_parameter_arg( if let Ok(data) = state.eval_parameter_arg(
parameter, parameter,
&self.project.stack_pointer_register, &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> { ...@@ -484,7 +467,7 @@ impl<'a> Context<'a> {
if let Some(register_value) = if let Some(register_value) =
state_before_call.get_register_by_name(parameter_register_name) 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 = possible_referenced_ids =
...@@ -503,15 +486,12 @@ impl<'a> Context<'a> { ...@@ -503,15 +486,12 @@ impl<'a> Context<'a> {
/// Get the offset of the current stack pointer to the base of the current stack frame. /// 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 { fn get_current_stack_offset(&self, state: &State) -> ValueDomain {
if let Data::Pointer(ref stack_pointer) = if let Some((stack_id, stack_offset_domain)) = state
state.get_register(&self.project.stack_pointer_register) .get_register(&self.project.stack_pointer_register)
.get_if_unique_target()
{ {
if stack_pointer.targets().len() == 1 { if *stack_id == state.stack_id {
let (stack_id, stack_offset_domain) = return stack_offset_domain.clone();
stack_pointer.targets().iter().next().unwrap();
if *stack_id == state.stack_id {
return stack_offset_domain.clone();
}
} }
} }
ValueDomain::new_top(self.project.stack_pointer_register.size) ValueDomain::new_top(self.project.stack_pointer_register.size)
......
...@@ -142,7 +142,7 @@ fn context_problem_implementation() { ...@@ -142,7 +142,7 @@ fn context_problem_implementation() {
// test update_def // test update_def
state = context.update_def(&state, &def).unwrap(); 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); assert_eq!(state.eval(&Var(register("RSP"))), stack_pointer);
state = context.update_def(&state, &store_term).unwrap(); state = context.update_def(&state, &store_term).unwrap();
...@@ -175,8 +175,8 @@ fn context_problem_implementation() { ...@@ -175,8 +175,8 @@ fn context_problem_implementation() {
callee_state callee_state
.memory .memory
.set_value( .set_value(
PointerDomain::new(new_id("func", "RSP"), bv(-30)), Data::from_target(new_id("func", "RSP"), bv(-30)),
Data::Value(bv(33).into()), bv(33).into(),
) )
.unwrap(); .unwrap();
// Emulate removing the return pointer from the stack for x64 // Emulate removing the return pointer from the stack for x64
...@@ -213,28 +213,25 @@ fn context_problem_implementation() { ...@@ -213,28 +213,25 @@ fn context_problem_implementation() {
.bin_op(BinOpType::IntAdd, &Bitvector::from_i64(8).into()) .bin_op(BinOpType::IntAdd, &Bitvector::from_i64(8).into())
); );
state.set_register(&register("callee_saved_reg"), Data::Value(bv(13))); state.set_register(&register("callee_saved_reg"), bv(13).into());
state.set_register(&register("other_reg"), Data::Value(bv(14))); state.set_register(&register("other_reg"), bv(14).into());
let malloc = call_term("extern_malloc"); let malloc = call_term("extern_malloc");
let mut state_after_malloc = context.update_call_stub(&state, &malloc).unwrap(); let mut state_after_malloc = context.update_call_stub(&state, &malloc).unwrap();
assert_eq!( assert_eq!(
state_after_malloc.get_register(&register("RDX")), state_after_malloc.get_register(&register("RDX")),
Data::Pointer(PointerDomain::new( Data::from_target(new_id("call_extern_malloc", "RDX"), bv(0))
new_id("call_extern_malloc", "RDX"),
bv(0)
))
); );
assert_eq!(state_after_malloc.memory.get_num_objects(), 2); assert_eq!(state_after_malloc.memory.get_num_objects(), 2);
assert_eq!( assert_eq!(
state_after_malloc.get_register(&register("RSP")), state_after_malloc.get_register(&register("RSP")),
state state
.get_register(&register("RSP")) .get_register(&register("RSP"))
.bin_op(BinOpType::IntAdd, &Data::Value(bv(8))) .bin_op(BinOpType::IntAdd, &bv(8).into())
); );
assert_eq!( assert_eq!(
state_after_malloc.get_register(&register("callee_saved_reg")), state_after_malloc.get_register(&register("callee_saved_reg")),
Data::Value(bv(13)) bv(13).into()
); );
assert!(state_after_malloc assert!(state_after_malloc
.get_register(&register("other_reg")) .get_register(&register("other_reg"))
...@@ -242,10 +239,7 @@ fn context_problem_implementation() { ...@@ -242,10 +239,7 @@ fn context_problem_implementation() {
state_after_malloc.set_register( state_after_malloc.set_register(
&register("callee_saved_reg"), &register("callee_saved_reg"),
Data::Pointer(PointerDomain::new( Data::from_target(new_id("call_extern_malloc", "RDX"), bv(0)),
new_id("call_extern_malloc", "RDX"),
bv(0),
)),
); );
let free = call_term("extern_free"); let free = call_term("extern_free");
let state_after_free = context let state_after_free = context
...@@ -255,10 +249,7 @@ fn context_problem_implementation() { ...@@ -255,10 +249,7 @@ fn context_problem_implementation() {
assert_eq!(state_after_free.memory.get_num_objects(), 2); assert_eq!(state_after_free.memory.get_num_objects(), 2);
assert_eq!( assert_eq!(
state_after_free.get_register(&register("callee_saved_reg")), state_after_free.get_register(&register("callee_saved_reg")),
Data::Pointer(PointerDomain::new( Data::from_target(new_id("call_extern_malloc", "RDX"), bv(0))
new_id("call_extern_malloc", "RDX"),
bv(0)
))
); );
let other_extern_fn = call_term("extern_other"); let other_extern_fn = call_term("extern_other");
...@@ -268,11 +259,11 @@ fn context_problem_implementation() { ...@@ -268,11 +259,11 @@ fn context_problem_implementation() {
state_after_other_fn.get_register(&register("RSP")), state_after_other_fn.get_register(&register("RSP")),
state state
.get_register(&register("RSP")) .get_register(&register("RSP"))
.bin_op(BinOpType::IntAdd, &Data::Value(bv(8))) .bin_op(BinOpType::IntAdd, &bv(8).into())
); );
assert_eq!( assert_eq!(
state_after_other_fn.get_register(&register("callee_saved_reg")), state_after_other_fn.get_register(&register("callee_saved_reg")),
Data::Value(bv(13)) bv(13).into()
); );
assert!(state_after_other_fn assert!(state_after_other_fn
.get_register(&register("other_reg")) .get_register(&register("other_reg"))
...@@ -326,10 +317,7 @@ fn update_return() { ...@@ -326,10 +317,7 @@ fn update_return() {
.insert(other_callsite_id.clone()); .insert(other_callsite_id.clone());
state_before_return.set_register( state_before_return.set_register(
&register("RDX"), &register("RDX"),
Data::Pointer(PointerDomain::new( Data::from_target(new_id("call_callee_other", "RSP"), bv(-32)),
new_id("call_callee_other", "RSP"),
bv(-32),
)),
); );
let state_before_call = State::new(&register("RSP"), Tid::new("original_caller_id")); let state_before_call = State::new(&register("RSP"), Tid::new("original_caller_id"));
...@@ -379,10 +367,7 @@ fn update_return() { ...@@ -379,10 +367,7 @@ fn update_return() {
.get_all_object_ids() .get_all_object_ids()
.get(&new_id("caller_caller", "RSP")) .get(&new_id("caller_caller", "RSP"))
.is_some()); .is_some());
let expected_rsp = Data::Pointer(PointerDomain::new( let expected_rsp = Data::from_target(new_id("original_caller_id", "RSP"), bv(-8));
new_id("original_caller_id", "RSP"),
bv(-8),
));
assert_eq!(state.get_register(&register("RSP")), expected_rsp); assert_eq!(state.get_register(&register("RSP")), expected_rsp);
} }
......
...@@ -5,7 +5,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont ...@@ -5,7 +5,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
/// Get the underlying graph on which the analysis operates. /// Get the underlying graph on which the analysis operates.
fn get_graph(&self) -> &Graph<'a> { fn get_graph(&self) -> &Graph<'a> {
&self.graph self.graph
} }
/// Merge two state values. /// Merge two state values.
...@@ -77,7 +77,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont ...@@ -77,7 +77,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
} }
Def::Load { var, address } => { Def::Load { var, address } => {
self.log_debug( 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(&def.tid),
); );
Some(new_state) Some(new_state)
...@@ -158,11 +158,10 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont ...@@ -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. // At the beginning of a function this is the only known pointer to the new stack frame.
callee_state.set_register( callee_state.set_register(
&self.project.stack_pointer_register, &self.project.stack_pointer_register,
PointerDomain::new( Data::from_target(
callee_stack_id.clone(), callee_stack_id.clone(),
Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(), Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(),
) ),
.into(),
); );
// set the list of caller stack ids to only this caller id // set the list of caller stack ids to only this caller id
callee_state.caller_stack_ids = BTreeSet::new(); callee_state.caller_stack_ids = BTreeSet::new();
...@@ -202,7 +201,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont ...@@ -202,7 +201,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
(Some(state_call), None) => { (Some(state_call), None) => {
if self.is_indirect_call_with_top_target(state_call, call_term) { if self.is_indirect_call_with_top_target(state_call, call_term) {
// We know nothing about the call target. // 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 { } else {
// We know at least something about the call target. // We know at least something about the call target.
// Since we don't have a return value, // Since we don't have a return value,
...@@ -294,7 +293,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont ...@@ -294,7 +293,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
Jmp::CallInd { .. } => { Jmp::CallInd { .. } => {
if self.is_indirect_call_with_top_target(state, call) { if self.is_indirect_call_with_top_target(state, call) {
// We know nothing about the call target. // 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 { } else {
return None; return None;
} }
...@@ -318,7 +317,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont ...@@ -318,7 +317,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
); );
} }
// Clear non-callee-saved registers from the state. // 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[..]); new_state.clear_non_callee_saved_register(&cconv.callee_saved_register[..]);
// Adjust stack register value (for x86 architecture). // Adjust stack register value (for x86 architecture).
self.adjust_stack_register_on_extern_call(state, &mut new_state); self.adjust_stack_register_on_extern_call(state, &mut new_state);
......
...@@ -360,7 +360,7 @@ impl<'a> PointerInference<'a> { ...@@ -360,7 +360,7 @@ impl<'a> PointerInference<'a> {
for jmp in block.term.jmps.iter() { for jmp in block.term.jmps.iter() {
match &jmp.term { match &jmp.term {
Jmp::BranchInd(target_expr) => { Jmp::BranchInd(target_expr) => {
let address = state.eval(&target_expr); let address = state.eval(target_expr);
println!( println!(
"{}: Indirect jump to {}", "{}: Indirect jump to {}",
jmp.tid, jmp.tid,
...@@ -368,7 +368,7 @@ impl<'a> PointerInference<'a> { ...@@ -368,7 +368,7 @@ impl<'a> PointerInference<'a> {
); );
} }
Jmp::CallInd { target, return_ } => { Jmp::CallInd { target, return_ } => {
let address = state.eval(&target); let address = state.eval(target);
println!( println!(
"{}: Indirect call to {}. HasReturn: {}", "{}: Indirect call to {}. HasReturn: {}",
jmp.tid, jmp.tid,
......
...@@ -131,9 +131,7 @@ impl AbstractObjectInfo { ...@@ -131,9 +131,7 @@ impl AbstractObjectInfo {
/// If the abstract object is not unique (i.e. may represent more than one actual object), /// 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. /// merge the old value at the given offset with the new value.
pub fn set_value(&mut self, value: Data, offset: &ValueDomain) -> Result<(), Error> { pub fn set_value(&mut self, value: Data, offset: &ValueDomain) -> Result<(), Error> {
if let Data::Pointer(ref pointer) = value { self.pointer_targets.extend(value.referenced_ids().cloned());
self.pointer_targets.extend(pointer.ids().cloned());
};
if let Ok(concrete_offset) = offset.try_to_bitvec() { if let Ok(concrete_offset) = offset.try_to_bitvec() {
if self.is_unique { if self.is_unique {
self.memory.add(value, concrete_offset); self.memory.add(value, concrete_offset);
...@@ -155,9 +153,7 @@ impl AbstractObjectInfo { ...@@ -155,9 +153,7 @@ impl AbstractObjectInfo {
/// Merge `value` at position `offset` with the value currently saved at that position. /// Merge `value` at position `offset` with the value currently saved at that position.
pub fn merge_value(&mut self, value: Data, offset: &ValueDomain) { pub fn merge_value(&mut self, value: Data, offset: &ValueDomain) {
if let Data::Pointer(ref pointer) = value { self.pointer_targets.extend(value.referenced_ids().cloned());
self.pointer_targets.extend(pointer.ids().cloned());
};
if let Ok(concrete_offset) = offset.try_to_bitvec() { if let Ok(concrete_offset) = offset.try_to_bitvec() {
let merged_value = self let merged_value = self
.memory .memory
...@@ -184,7 +180,7 @@ impl AbstractObjectInfo { ...@@ -184,7 +180,7 @@ impl AbstractObjectInfo {
pub fn get_referenced_ids_underapproximation(&self) -> BTreeSet<AbstractIdentifier> { pub fn get_referenced_ids_underapproximation(&self) -> BTreeSet<AbstractIdentifier> {
let mut referenced_ids = BTreeSet::new(); let mut referenced_ids = BTreeSet::new();
for data in self.memory.values() { for data in self.memory.values() {
referenced_ids.append(&mut data.referenced_ids()) referenced_ids.extend(data.referenced_ids().cloned())
} }
referenced_ids referenced_ids
} }
...@@ -201,8 +197,8 @@ impl AbstractObjectInfo { ...@@ -201,8 +197,8 @@ impl AbstractObjectInfo {
elem.replace_abstract_id(old_id, new_id, offset_adjustment); elem.replace_abstract_id(old_id, new_id, offset_adjustment);
} }
self.memory.clear_top_values(); self.memory.clear_top_values();
if self.pointer_targets.get(&old_id).is_some() { if self.pointer_targets.get(old_id).is_some() {
self.pointer_targets.remove(&old_id); self.pointer_targets.remove(old_id);
self.pointer_targets.insert(new_id.clone()); self.pointer_targets.insert(new_id.clone());
} }
} }
...@@ -218,6 +214,8 @@ impl AbstractObjectInfo { ...@@ -218,6 +214,8 @@ impl AbstractObjectInfo {
/// Remove the provided IDs from the target lists of all pointers in the memory object. /// Remove the provided IDs from the target lists of all pointers in the memory object.
/// Also remove them from the pointer_targets list. /// 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>) { pub fn remove_ids(&mut self, ids_to_remove: &BTreeSet<AbstractIdentifier>) {
self.pointer_targets = self self.pointer_targets = self
.pointer_targets .pointer_targets
...@@ -226,6 +224,9 @@ impl AbstractObjectInfo { ...@@ -226,6 +224,9 @@ impl AbstractObjectInfo {
.collect(); .collect();
for value in self.memory.values_mut() { for value in self.memory.values_mut() {
value.remove_ids(ids_to_remove); 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. self.memory.clear_top_values(); // In case the previous operation left *Top* values in the memory struct.
} }
...@@ -425,7 +426,7 @@ mod tests { ...@@ -425,7 +426,7 @@ mod tests {
} }
fn new_data(number: i64) -> Data { fn new_data(number: i64) -> Data {
Data::Value(bv(number)) bv(number).into()
} }
fn bv(number: i64) -> ValueDomain { fn bv(number: i64) -> ValueDomain {
...@@ -447,7 +448,7 @@ mod tests { ...@@ -447,7 +448,7 @@ mod tests {
object.set_value(three, &offset).unwrap(); object.set_value(three, &offset).unwrap();
assert_eq!( assert_eq!(
object.get_value(Bitvector::from_i64(-16), ByteSize::new(8)), object.get_value(Bitvector::from_i64(-16), ByteSize::new(8)),
Data::Top(ByteSize::new(8)) Data::new_top(ByteSize::new(8))
); );
assert_eq!( assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)), object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
...@@ -456,12 +457,12 @@ mod tests { ...@@ -456,12 +457,12 @@ mod tests {
object.set_value(new_data(4), &bv(-12)).unwrap(); object.set_value(new_data(4), &bv(-12)).unwrap();
assert_eq!( assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)), 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)); object.merge_value(new_data(23), &bv(-12));
assert_eq!( assert_eq!(
object.get_value(Bitvector::from_i64(-12), ByteSize::new(8)), 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(); let mut other_object = new_abstract_object();
...@@ -470,7 +471,7 @@ mod tests { ...@@ -470,7 +471,7 @@ mod tests {
let merged_object = object.merge(&other_object); let merged_object = object.merge(&other_object);
assert_eq!( assert_eq!(
merged_object.get_value(Bitvector::from_i64(-12), ByteSize::new(8)), merged_object.get_value(Bitvector::from_i64(-12), ByteSize::new(8)),
Data::Top(ByteSize::new(8)) Data::new_top(ByteSize::new(8))
); );
assert_eq!( assert_eq!(
merged_object.get_value(Bitvector::from_i64(0), ByteSize::new(8)), merged_object.get_value(Bitvector::from_i64(0), ByteSize::new(8)),
...@@ -486,8 +487,8 @@ mod tests { ...@@ -486,8 +487,8 @@ mod tests {
target_map.insert(new_id("time_1", "RAX"), bv(20)); 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_234", "RAX"), bv(30));
target_map.insert(new_id("time_1", "RBX"), bv(40)); target_map.insert(new_id("time_1", "RBX"), bv(40));
let pointer = PointerDomain::with_targets(target_map.clone()); let pointer = DataDomain::mock_from_target_map(target_map.clone());
object.set_value(pointer.into(), &bv(-15)).unwrap(); object.set_value(pointer, &bv(-15)).unwrap();
assert_eq!(object.get_referenced_ids_overapproximation().len(), 3); assert_eq!(object.get_referenced_ids_overapproximation().len(), 3);
object.replace_abstract_id( object.replace_abstract_id(
...@@ -496,10 +497,10 @@ mod tests { ...@@ -496,10 +497,10 @@ mod tests {
&bv(10), &bv(10),
); );
target_map.remove(&new_id("time_1", "RAX")); 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!( assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)), object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
modified_pointer.into() modified_pointer
); );
object.replace_abstract_id( object.replace_abstract_id(
...@@ -510,10 +511,10 @@ mod tests { ...@@ -510,10 +511,10 @@ mod tests {
let mut target_map = BTreeMap::new(); let mut target_map = BTreeMap::new();
target_map.insert(new_id("time_234", "RAX"), bv(30)); target_map.insert(new_id("time_234", "RAX"), bv(30));
target_map.insert(new_id("time_234", "RBX"), bv(50)); 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!( assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)), object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
modified_pointer.into() modified_pointer
); );
} }
...@@ -525,8 +526,8 @@ mod tests { ...@@ -525,8 +526,8 @@ mod tests {
target_map.insert(new_id("time_1", "RAX"), bv(20)); 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_234", "RAX"), bv(30));
target_map.insert(new_id("time_1", "RBX"), bv(40)); target_map.insert(new_id("time_1", "RBX"), bv(40));
let pointer = PointerDomain::with_targets(target_map.clone()); let pointer = DataDomain::mock_from_target_map(target_map.clone());
object.set_value(pointer.into(), &bv(-15)).unwrap(); object.set_value(pointer, &bv(-15)).unwrap();
assert_eq!(object.get_referenced_ids_overapproximation().len(), 3); assert_eq!(object.get_referenced_ids_overapproximation().len(), 3);
let ids_to_remove = vec![new_id("time_1", "RAX"), new_id("time_23", "RBX")] let ids_to_remove = vec![new_id("time_1", "RAX"), new_id("time_23", "RBX")]
......
...@@ -50,20 +50,16 @@ impl AbstractObjectList { ...@@ -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, /// 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`. /// 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 { pub fn is_dangling_pointer(&self, address: &Data, report_unknown_states: bool) -> bool {
match address { for id in address.referenced_ids() {
Data::Value(_) | Data::Top(_) => (), if let Some((object, _offset_id)) = self.objects.get(id) {
Data::Pointer(pointer) => { match (report_unknown_states, object.get_state()) {
for id in pointer.ids() { (_, ObjectState::Dangling) => return true,
let (object, _offset_id) = self.objects.get(id).unwrap(); (true, ObjectState::Unknown) => {
match (report_unknown_states, object.get_state()) { if object.is_unique {
(_, ObjectState::Dangling) => return true, return true;
(true, ObjectState::Unknown) => {
if object.is_unique {
return true;
}
} }
_ => (),
} }
_ => (),
} }
} }
} }
...@@ -75,15 +71,13 @@ impl AbstractObjectList { ...@@ -75,15 +71,13 @@ impl AbstractObjectList {
/// whose state is either dangling or unknown, /// whose state is either dangling or unknown,
/// as flagged. /// as flagged.
pub fn mark_dangling_pointer_targets_as_flagged(&mut self, address: &Data) { pub fn mark_dangling_pointer_targets_as_flagged(&mut self, address: &Data) {
if let Data::Pointer(pointer) = address { for id in address.referenced_ids() {
for id in pointer.ids() { let (object, _) = self.objects.get_mut(id).unwrap();
let (object, _) = self.objects.get_mut(id).unwrap(); if matches!(
if matches!( object.get_state(),
object.get_state(), ObjectState::Unknown | ObjectState::Dangling
ObjectState::Unknown | ObjectState::Dangling ) {
) { object.set_state(ObjectState::Flagged);
object.set_state(ObjectState::Flagged);
}
} }
} }
} }
...@@ -99,27 +93,26 @@ impl AbstractObjectList { ...@@ -99,27 +93,26 @@ impl AbstractObjectList {
size: ByteSize, size: ByteSize,
global_data: &RuntimeMemoryImage, global_data: &RuntimeMemoryImage,
) -> bool { ) -> bool {
match address { if let Some(value) = address.get_absolute_value() {
Data::Value(value) => { if let Ok((start, end)) = value.try_to_offset_interval() {
if let Ok((start, end)) = value.try_to_offset_interval() { if start < 0 || end < start {
if start < 0 || end < start { return true;
return true; }
} if global_data
return global_data .is_interval_readable(start as u64, end as u64 + u64::from(size) - 1)
.is_interval_readable(start as u64, end as u64 + u64::from(size) - 1) .is_err()
.is_err(); {
return true;
} }
} }
Data::Top(_) => (), }
Data::Pointer(pointer) => { for (id, offset) in address.get_relative_values() {
for (id, offset) in pointer.targets() { if let Some((object, base_offset)) = self.objects.get(id) {
let (object, base_offset) = self.objects.get(id).unwrap(); let adjusted_offset = offset.clone() + base_offset.clone();
let adjusted_offset = offset.clone() + base_offset.clone(); if !adjusted_offset.is_top()
if !adjusted_offset.is_top() && !object.access_contained_in_bounds(&adjusted_offset, size)
&& !object.access_contained_in_bounds(&adjusted_offset, size) {
{ return true;
return true;
}
} }
} }
} }
...@@ -155,57 +148,53 @@ impl AbstractObjectList { ...@@ -155,57 +148,53 @@ impl AbstractObjectList {
/// Get the value at a given address. /// Get the value at a given address.
/// If the address is not unique, merge the value of all possible addresses. /// 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. /// This function only checks for relative targets and not for absolute addresses.
pub fn get_value(&self, address: &Data, size: ByteSize) -> Result<Data, Error> { /// If the address does not contain any relative targets an empty value is returned.
match address { pub fn get_value(&self, address: &Data, size: ByteSize) -> Data {
Data::Value(_) => Err(anyhow!("Load from non-pointer value")), let mut merged_value = Data::new_empty(size);
Data::Top(_) => Ok(Data::new_top(size)), for (id, offset_pointer) in address.get_relative_values() {
Data::Pointer(pointer) => { if let Some((object, offset_identifier)) = self.objects.get(id) {
let mut merged_value: Option<Data> = None; let offset = offset_pointer.clone() + offset_identifier.clone();
for (id, offset_pointer_domain) in pointer.targets() { if let Ok(concrete_offset) = offset.try_to_bitvec() {
let (object, offset_identifier) = self.objects.get(id).unwrap(); let value = object.get_value(concrete_offset, size);
let offset = offset_pointer_domain.clone() + offset_identifier.clone(); merged_value = merged_value.merge(&value);
if let Ok(concrete_offset) = offset.try_to_bitvec() { } else {
let value = object.get_value(concrete_offset, size); merged_value.set_contains_top_flag();
merged_value = match merged_value {
Some(accum) => Some(accum.merge(&value)),
None => Some(value),
};
} else {
merged_value = Some(Data::new_top(size));
break;
}
} }
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. /// Set the value at a given address.
/// ///
/// If the address has more than one target, /// If the address has more than one target,
/// we merge-write the value to all targets. /// we merge-write the value to all targets.
pub fn set_value( pub fn set_value(&mut self, pointer: Data, value: Data) -> Result<(), Error> {
&mut self, let targets = pointer.get_relative_values();
pointer: PointerDomain<ValueDomain>, match targets.len() {
value: Data, 0 => Ok(()),
) -> Result<(), Error> { 1 => {
let targets = pointer.targets(); let (id, pointer_offset) = targets.iter().next().unwrap();
assert!(!targets.is_empty()); let (object, id_offset) = self.objects.get_mut(id).unwrap();
if targets.len() == 1 { let adjusted_offset = pointer_offset.clone() + id_offset.clone();
let (id, pointer_offset) = targets.iter().next().unwrap(); object.set_value(value, &adjusted_offset)
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.
} else { // We merge-write to all possible targets
// There is more than one object that the pointer may write to. for (id, offset) in targets {
// We merge-write to all possible targets let (object, object_offset) = self.objects.get_mut(id).unwrap();
for (id, offset) in targets { let adjusted_offset = offset.clone() + object_offset.clone();
let (object, object_offset) = self.objects.get_mut(id).unwrap(); object.merge_value(value.clone(), &adjusted_offset);
let adjusted_offset = offset.clone() + object_offset.clone(); }
object.merge_value(value.clone(), &adjusted_offset); Ok(())
} }
Ok(())
} }
} }
...@@ -309,9 +298,9 @@ impl AbstractObjectList { ...@@ -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. /// 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( pub fn mark_mem_object_as_freed(
&mut self, &mut self,
object_pointer: &PointerDomain<ValueDomain>, object_pointer: &Data,
) -> Result<(), Vec<(AbstractIdentifier, Error)>> { ) -> 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(); let mut possible_double_free_ids = Vec::new();
if ids.len() > 1 { if ids.len() > 1 {
for id in ids { for id in ids {
...@@ -320,11 +309,9 @@ impl AbstractObjectList { ...@@ -320,11 +309,9 @@ impl AbstractObjectList {
} }
} }
} else if let Some(id) = ids.get(0) { } 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)); possible_double_free_ids.push((id.clone(), error));
} }
} else {
panic!("Pointer without targets encountered")
} }
if possible_double_free_ids.is_empty() { if possible_double_free_ids.is_empty() {
Ok(()) Ok(())
...@@ -470,31 +457,25 @@ mod tests { ...@@ -470,31 +457,25 @@ mod tests {
assert_eq!(obj_list.objects.len(), 1); assert_eq!(obj_list.objects.len(), 1);
assert_eq!(obj_list.objects.values().next().unwrap().1, bv(0)); assert_eq!(obj_list.objects.values().next().unwrap().1, bv(0));
let pointer = PointerDomain::new(new_id("RSP".into()), bv(8)); let pointer = DataDomain::from_target(new_id("RSP".into()), bv(8));
obj_list obj_list.set_value(pointer.clone(), bv(42).into()).unwrap();
.set_value(pointer.clone(), Data::Value(bv(42)))
.unwrap();
assert_eq!( assert_eq!(
obj_list obj_list.get_value(&pointer, ByteSize::new(8)),
.get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8)) bv(42).into()
.unwrap(),
Data::Value(bv(42))
); );
let mut other_obj_list = let mut other_obj_list =
AbstractObjectList::from_stack_id(new_id("RSP".into()), ByteSize::new(8)); 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 other_obj_list
.set_value(pointer.clone(), Data::Value(bv(42))) .set_value(pointer.clone(), bv(42).into())
.unwrap(); .unwrap();
other_obj_list other_obj_list
.set_value(second_pointer.clone(), Data::Value(bv(35))) .set_value(second_pointer.clone(), bv(35).into())
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
other_obj_list other_obj_list.get_value(&second_pointer, ByteSize::new(8)),
.get_value(&Data::Pointer(second_pointer.clone()), ByteSize::new(8)) bv(35).into()
.unwrap(),
Data::Value(bv(35))
); );
other_obj_list.add_abstract_object( other_obj_list.add_abstract_object(
...@@ -503,51 +484,38 @@ mod tests { ...@@ -503,51 +484,38 @@ mod tests {
ObjectType::Heap, ObjectType::Heap,
ByteSize::new(8), 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 other_obj_list
.set_value(heap_pointer.clone(), Data::Value(bv(3))) .set_value(heap_pointer.clone(), bv(3).into())
.unwrap(); .unwrap();
let mut merged = obj_list.merge(&other_obj_list); let mut merged = obj_list.merge(&other_obj_list);
assert_eq!(merged.get_value(&pointer, ByteSize::new(8)), bv(42).into());
assert_eq!( assert_eq!(
merged merged.get_value(&second_pointer, ByteSize::new(8)),
.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(),
Data::new_top(ByteSize::new(8)) Data::new_top(ByteSize::new(8))
); );
assert_eq!( assert_eq!(
merged merged.get_value(&heap_pointer, ByteSize::new(8)),
.get_value(&Data::Pointer(heap_pointer.clone()), ByteSize::new(8)) bv(3).into()
.unwrap(),
Data::Value(bv(3))
); );
assert_eq!(merged.objects.len(), 2); assert_eq!(merged.objects.len(), 2);
merged merged
.set_value(pointer.merge(&heap_pointer), Data::Value(bv(3))) .set_value(pointer.merge(&heap_pointer), bv(3).into())
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
merged merged.get_value(&pointer, ByteSize::new(8)),
.get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8)) IntervalDomain::mock(3, 42).with_stride(39).into()
.unwrap(),
Data::Value(IntervalDomain::mock(3, 42).with_stride(39))
); );
assert_eq!( assert_eq!(
merged merged.get_value(&heap_pointer, ByteSize::new(8)),
.get_value(&Data::Pointer(heap_pointer.clone()), ByteSize::new(8)) bv(3).into()
.unwrap(),
Data::Value(bv(3))
); );
assert_eq!(merged.objects.len(), 2); assert_eq!(merged.objects.len(), 2);
other_obj_list other_obj_list
.set_value(pointer.clone(), Data::Pointer(heap_pointer.clone())) .set_value(pointer.clone(), heap_pointer.clone())
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
other_obj_list other_obj_list
...@@ -564,13 +532,11 @@ mod tests { ...@@ -564,13 +532,11 @@ mod tests {
new_id("RAX".into()) 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)); other_obj_list.replace_abstract_id(&new_id("RAX".into()), &new_id("ID2".into()), &bv(0));
assert_eq!( assert_eq!(
other_obj_list other_obj_list.get_value(&pointer, ByteSize::new(8)),
.get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8)) modified_heap_pointer.clone()
.unwrap(),
Data::Pointer(modified_heap_pointer.clone())
); );
assert_eq!(other_obj_list.objects.get(&new_id("RAX".into())), None); assert_eq!(other_obj_list.objects.get(&new_id("RAX".into())), None);
assert!(matches!( assert!(matches!(
......
...@@ -51,9 +51,7 @@ impl State { ...@@ -51,9 +51,7 @@ impl State {
let caller_addresses: Vec<_> = self let caller_addresses: Vec<_> = self
.caller_stack_ids .caller_stack_ids
.iter() .iter()
.map(|caller_stack_id| { .map(|caller_stack_id| Data::from_target(caller_stack_id.clone(), offset.clone()))
PointerDomain::new(caller_stack_id.clone(), offset.clone()).into()
})
.collect(); .collect();
let mut result = Ok(()); let mut result = Ok(());
for address in caller_addresses { for address in caller_addresses {
...@@ -64,30 +62,27 @@ impl State { ...@@ -64,30 +62,27 @@ impl State {
// Note that this only returns the last error that was detected. // Note that this only returns the last error that was detected.
result result
} else { } else {
match self.adjust_pointer_for_read(address) { let pointer = self.adjust_pointer_for_read(address);
Data::Pointer(pointer) => { self.memory.set_value(pointer.clone(), value.clone())?;
self.memory.set_value(pointer, value.clone())?; if let Some(absolute_address) = pointer.get_absolute_value() {
Ok(()) if let Ok(address_to_global_data) = absolute_address.try_to_bitvec() {
} match global_memory.is_address_writeable(&address_to_global_data) {
Data::Value(absolute_address) => { Ok(true) => Ok(()),
if let Ok(address_to_global_data) = absolute_address.try_to_bitvec() { Ok(false) => Err(anyhow!("Write to read-only global data")),
match global_memory.is_address_writeable(&address_to_global_data) { Err(err) => Err(err),
Ok(true) => Ok(()), }
Ok(false) => Err(anyhow!("Write to read-only global data")), } else if let Ok((start, end)) = absolute_address.try_to_offset_interval() {
Err(err) => Err(err), match global_memory.is_interval_writeable(start as u64, end as u64) {
} Ok(true) => Ok(()),
} else if let Ok((start, end)) = absolute_address.try_to_offset_interval() { Ok(false) => Err(anyhow!("Write to read-only global data")),
match global_memory.is_interval_writeable(start as u64, end as u64) { Err(err) => Err(err),
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(())
} }
} 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 { ...@@ -122,28 +117,38 @@ impl State {
global_memory: &RuntimeMemoryImage, global_memory: &RuntimeMemoryImage,
) -> Result<Data, Error> { ) -> Result<Data, Error> {
let address = self.adjust_pointer_for_read(&self.eval(address)); let address = self.adjust_pointer_for_read(&self.eval(address));
match address { let mut result = if let Some(global_address) = address.get_absolute_value() {
Data::Value(global_address) => { if let Ok(address_bitvector) = global_address.try_to_bitvec() {
if let Ok(address_bitvector) = global_address.try_to_bitvec() { match global_memory.read(&address_bitvector, size) {
if let Some(loaded_value) = global_memory.read(&address_bitvector, size)? { Ok(Some(loaded_value)) => loaded_value.into(),
Ok(Data::Value(loaded_value.into())) Ok(None) => Data::new_top(size),
} else { Err(_) => Data::new_empty(size),
Ok(Data::Top(size)) }
} } else if let Ok((start, end)) = global_address.try_to_offset_interval() {
} else if let Ok((start, end)) = global_address.try_to_offset_interval() { if global_memory
if global_memory .is_interval_readable(start as u64, end as u64 + u64::from(size))
.is_interval_readable(start as u64, end as u64 + u64::from(size))? .ok()
{ == Some(true)
Ok(Data::new_top(size)) {
} else { Data::new_top(size)
Err(anyhow!("Target address is not readable."))
}
} else { } else {
Ok(Data::new_top(size)) Data::new_empty(size)
} }
} else {
Data::new_top(size)
} }
Data::Top(_) => Ok(Data::new_top(size)), } else {
Data::Pointer(_) => Ok(self.memory.get_value(&address, size)?), 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 { ...@@ -169,44 +174,40 @@ impl State {
/// If the pointer contains a reference to the stack with offset >= 0, replace it with a pointer /// If the pointer contains a reference to the stack with offset >= 0, replace it with a pointer
/// pointing to all possible caller IDs. /// pointing to all possible caller IDs.
fn adjust_pointer_for_read(&self, address: &Data) -> Data { fn adjust_pointer_for_read(&self, address: &Data) -> Data {
if let Data::Pointer(pointer) = address { let mut adjusted_address = address.clone();
let mut new_targets = BTreeMap::new(); let mut new_targets = BTreeMap::new();
for (id, offset) in pointer.targets() { for (id, offset) in address.get_relative_values() {
if *id == self.stack_id { if *id == self.stack_id {
if let Ok((interval_start, interval_end)) = offset.try_to_offset_interval() { if let Ok((interval_start, interval_end)) = offset.try_to_offset_interval() {
if interval_start >= 0 if interval_start >= 0 && interval_end >= 0 && !self.caller_stack_ids.is_empty()
&& 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 {
for caller_id in self.caller_stack_ids.iter() { for caller_id in self.caller_stack_ids.iter() {
new_targets.insert(caller_id.clone(), offset.clone()); 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()); new_targets.insert(id.clone(), offset.clone());
} }
} else { } 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()); 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 /// Evaluate the value of an expression in the current state
pub fn eval(&self, expression: &Expression) -> Data { pub fn eval(&self, expression: &Expression) -> Data {
use Expression::*; use Expression::*;
match expression { match expression {
Var(variable) => self.get_register(&variable), Var(variable) => self.get_register(variable),
Const(bitvector) => bitvector.clone().into(), Const(bitvector) => bitvector.clone().into(),
BinOp { op, lhs, rhs } => { BinOp { op, lhs, rhs } => {
if *op == BinOpType::IntXOr && lhs == rhs { if *op == BinOpType::IntXOr && lhs == rhs {
...@@ -294,24 +295,20 @@ impl State { ...@@ -294,24 +295,20 @@ impl State {
data: &Data, data: &Data,
global_data: &RuntimeMemoryImage, global_data: &RuntimeMemoryImage,
) -> bool { ) -> bool {
let data = self.adjust_pointer_for_read(data); let mut data = self.adjust_pointer_for_read(data);
matches!(data, Data::Pointer(_)) data.set_absolute_value(None); // Do not check absolute_values
&& self self.memory
.memory .is_out_of_bounds_mem_access(&data, ByteSize::new(1), global_data)
.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, /// 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. /// 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 { pub fn is_stack_pointer_with_nonnegative_offset(&self, data: &Data) -> bool {
if let Data::Pointer(pointer) = data { if let Some((target, offset)) = data.get_if_unique_target() {
if pointer.targets().len() == 1 { if *target == self.stack_id {
let (target, offset) = pointer.targets().iter().next().unwrap(); if let Ok(offset_val) = offset.try_to_offset() {
if *target == self.stack_id { if offset_val >= 0 {
if let Ok(offset_val) = offset.try_to_offset() { return true;
if offset_val >= 0 {
return true;
}
} }
} }
} }
...@@ -327,16 +324,13 @@ impl State { ...@@ -327,16 +324,13 @@ impl State {
if self.caller_stack_ids.is_empty() { if self.caller_stack_ids.is_empty() {
return None; return None;
} }
if let Data::Pointer(pointer) = address { if let Some((id, offset)) = address.get_if_unique_target() {
match (pointer.targets().len(), pointer.targets().iter().next()) { if self.stack_id == *id {
(1, Some((id, offset))) if self.stack_id == *id => { if let Ok((interval_start, _interval_end)) = offset.try_to_offset_interval() {
if let Ok((interval_start, _interval_end)) = offset.try_to_offset_interval() { if interval_start >= 0 {
if interval_start >= 0 { return Some(offset.clone());
return Some(offset.clone());
}
} }
} }
_ => (),
} }
} }
None None
......
...@@ -48,11 +48,10 @@ impl State { ...@@ -48,11 +48,10 @@ impl State {
let mut register: BTreeMap<Variable, Data> = BTreeMap::new(); let mut register: BTreeMap<Variable, Data> = BTreeMap::new();
register.insert( register.insert(
stack_register.clone(), stack_register.clone(),
PointerDomain::new( Data::from_target(
stack_id.clone(), stack_id.clone(),
Bitvector::zero(apint::BitWidth::from(stack_register.size)).into(), Bitvector::zero(apint::BitWidth::from(stack_register.size)).into(),
) ),
.into(),
); );
State { State {
register, register,
...@@ -153,7 +152,7 @@ impl State { ...@@ -153,7 +152,7 @@ impl State {
// get all referenced IDs // get all referenced IDs
let mut referenced_ids = BTreeSet::new(); let mut referenced_ids = BTreeSet::new();
for (_reg_name, data) in self.register.iter() { 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.insert(self.stack_id.clone());
referenced_ids.append(&mut self.caller_stack_ids.clone()); referenced_ids.append(&mut self.caller_stack_ids.clone());
...@@ -239,7 +238,7 @@ impl State { ...@@ -239,7 +238,7 @@ impl State {
/// an error with the list of possibly already freed objects is returned. /// an error with the list of possibly already freed objects is returned.
pub fn mark_mem_object_as_freed( pub fn mark_mem_object_as_freed(
&mut self, &mut self,
object_pointer: &PointerDomain<ValueDomain>, object_pointer: &Data,
) -> Result<(), Vec<(AbstractIdentifier, Error)>> { ) -> Result<(), Vec<(AbstractIdentifier, Error)>> {
self.memory.mark_mem_object_as_freed(object_pointer) self.memory.mark_mem_object_as_freed(object_pointer)
} }
...@@ -263,6 +262,9 @@ impl State { ...@@ -263,6 +262,9 @@ impl State {
ids_to_remove.remove(caller_id); ids_to_remove.remove(caller_id);
for register_value in self.register.values_mut() { for register_value in self.register.values_mut() {
register_value.remove_ids(&ids_to_remove); 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.memory.remove_ids(&ids_to_remove);
self.caller_stack_ids = BTreeSet::new(); self.caller_stack_ids = BTreeSet::new();
...@@ -341,31 +343,7 @@ impl State { ...@@ -341,31 +343,7 @@ impl State {
result: Data, result: Data,
) -> Result<(), Error> { ) -> Result<(), Error> {
if let Expression::Var(var) = expression { if let Expression::Var(var) = expression {
match (self.eval(expression), result) { self.set_register(var, self.eval(expression).intersect(&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),
_ => (),
}
Ok(()) Ok(())
} else if let Expression::BinOp { op, lhs, rhs } = expression { } else if let Expression::BinOp { op, lhs, rhs } = expression {
self.specialize_by_binop_expression_result(op, lhs, rhs, result) self.specialize_by_binop_expression_result(op, lhs, rhs, result)
...@@ -413,7 +391,7 @@ impl State { ...@@ -413,7 +391,7 @@ impl State {
arg, arg,
} => { } => {
if *low_byte == ByteSize::new(0) { 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) { if arg_value.fits_into_size(*size) {
let intermediate_result = let intermediate_result =
result.cast(CastOpType::IntSExt, arg.bytesize()); result.cast(CastOpType::IntSExt, arg.bytesize());
...@@ -598,45 +576,42 @@ impl State { ...@@ -598,45 +576,42 @@ impl State {
lhs: &Expression, lhs: &Expression,
rhs: &Expression, rhs: &Expression,
) -> Result<(), Error> { ) -> Result<(), Error> {
if let (Data::Pointer(lhs_pointer), Data::Pointer(rhs_pointer)) = let (lhs_pointer, rhs_pointer) = (self.eval(lhs), self.eval(rhs));
(self.eval(lhs), self.eval(rhs)) match (
{ lhs_pointer.get_if_unique_target(),
match ( rhs_pointer.get_if_unique_target(),
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)?) {
(Some((lhs_id, lhs_offset)), Some((rhs_id, rhs_offset))) if lhs_id == rhs_id => { // Since the pointers may or may not point to different instances referenced by the same ID we cannot compare them.
if !(self.memory.is_unique_object(lhs_id)?) { return Ok(());
// 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 { if let Ok(lhs_offset_bitvec) = lhs_offset.try_to_bitvec() {
let specialized_offset = lhs_offset.intersect(rhs_offset)?; let new_rhs_offset =
let specialized_domain: Data = rhs_offset.clone().add_not_equal_bound(&lhs_offset_bitvec)?;
PointerDomain::new(lhs_id.clone(), specialized_offset).into(); self.specialize_by_expression_result(
self.specialize_by_expression_result(lhs, specialized_domain.clone())?; rhs,
self.specialize_by_expression_result(rhs, specialized_domain)?; Data::from_target(rhs_id.clone(), new_rhs_offset),
} 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(),
)?;
}
} }
} }
_ => (), // 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(()) Ok(())
} }
......
...@@ -39,31 +39,27 @@ fn state() { ...@@ -39,31 +39,27 @@ fn state() {
let global_memory = RuntimeMemoryImage::mock(); let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(&register("RSP"), Tid::new("time0")); let mut state = State::new(&register("RSP"), Tid::new("time0"));
let stack_id = new_id("time0", "RSP"); 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 state
.store_value(&stack_addr, &Data::Value(bv(42)), &global_memory) .store_value(&stack_addr, &bv(42).into(), &global_memory)
.unwrap(); .unwrap();
state.register.insert(register("RSP"), stack_addr.clone()); state.register.insert(register("RSP"), stack_addr.clone());
assert_eq!( assert_eq!(
state state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory) .load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap(), .unwrap(),
Data::Value(bv(42)) bv(42).into()
); );
let mut other_state = State::new(&register("RSP"), Tid::new("time0")); 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 other_state
.register .register
.insert(register("RSP"), stack_addr.clone()); .insert(register("RSP"), stack_addr.clone());
other_state other_state.register.insert(register("RAX"), bv(42).into());
.register other_state.register.insert(register("RBX"), bv(35).into());
.insert(register("RAX"), Data::Value(bv(42)));
other_state
.register
.insert(register("RBX"), Data::Value(bv(35)));
let merged_state = state.merge(&other_state); 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.register.get(&register("RBX")), None);
assert_eq!( assert_eq!(
merged_state merged_state
...@@ -81,62 +77,53 @@ fn state() { ...@@ -81,62 +77,53 @@ fn state() {
); );
state.caller_stack_ids.insert(new_id("time0", "caller")); state.caller_stack_ids.insert(new_id("time0", "caller"));
state state
.store_value(&stack_addr, &Data::Value(bv(15)), &global_memory) .store_value(&stack_addr, &bv(15).into(), &global_memory)
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
state state.memory.get_value(
.memory &Data::from_target(new_id("time0", "caller"), bv(8)),
.get_value( ByteSize::new(8)
&Data::Pointer(PointerDomain::new(new_id("time0", "caller"), bv(8))), ),
ByteSize::new(8) bv(15).into()
)
.unwrap(),
Data::Value(bv(15))
); );
assert_eq!( assert_eq!(
state state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory) .load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap(), .unwrap(),
Data::Value(bv(15)) bv(15).into()
); );
// Test replace_abstract_id // 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.register.insert(register("RSP"), pointer.clone());
state state
.store_value(&pointer, &Data::Value(bv(7)), &global_memory) .store_value(&pointer, &bv(7).into(), &global_memory)
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
state state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory) .load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap(), .unwrap(),
Data::Value(bv(7)) bv(7).into()
); );
state.replace_abstract_id(&stack_id, &new_id("time0", "callee"), &bv(-8)); state.replace_abstract_id(&stack_id, &new_id("time0", "callee"), &bv(-8));
assert_eq!( assert_eq!(
state state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory) .load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap(), .unwrap(),
Data::Value(bv(7)) bv(7).into()
); );
assert_eq!( assert_eq!(
state state.memory.get_value(
.memory &Data::from_target(new_id("time0", "callee"), bv(-8)),
.get_value( ByteSize::new(8)
&Data::Pointer(PointerDomain::new(new_id("time0", "callee"), bv(-8))), ),
ByteSize::new(8) bv(7).into()
)
.unwrap(),
Data::Value(bv(7))
); );
assert_eq!( assert_eq!(
state state.memory.get_value(
.memory &Data::from_target(new_id("time0", "callee"), bv(-16)),
.get_value( ByteSize::new(8)
&Data::Pointer(PointerDomain::new(new_id("time0", "callee"), bv(-16))), ),
ByteSize::new(8)
)
.unwrap(),
Data::new_top(ByteSize::new(8)) Data::new_top(ByteSize::new(8))
); );
...@@ -159,18 +146,18 @@ fn handle_store() { ...@@ -159,18 +146,18 @@ fn handle_store() {
let stack_id = new_id("time0", "RSP"); let stack_id = new_id("time0", "RSP");
assert_eq!( assert_eq!(
state.eval(&Var(register("RSP"))), 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)); state.handle_register_assign(&register("RSP"), &reg_sub("RSP", 32));
assert_eq!( assert_eq!(
state.eval(&Var(register("RSP"))), 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)); state.handle_register_assign(&register("RSP"), &reg_add("RSP", -8));
assert_eq!( assert_eq!(
state.eval(&Var(register("RSP"))), state.eval(&Var(register("RSP"))),
Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-40))) Data::from_target(stack_id.clone(), bv(-40))
); );
state state
...@@ -245,19 +232,19 @@ fn handle_caller_stack_stores() { ...@@ -245,19 +232,19 @@ fn handle_caller_stack_stores() {
) )
.unwrap(); .unwrap();
// check that it was saved in all caller objects and not on the callee stack object // 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!( 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)) 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!( assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(), state.memory.get_value(&pointer, ByteSize::new(8)),
bv(42).into() 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!( assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(), state.memory.get_value(&pointer, ByteSize::new(8)),
bv(42).into() bv(42).into()
); );
// accessing through a positive stack register offset should yield the value of the caller stacks // 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() { ...@@ -276,7 +263,7 @@ fn clear_parameters_on_the_stack_on_extern_calls() {
let mut state = State::new(&register("RSP"), Tid::new("time0")); let mut state = State::new(&register("RSP"), Tid::new("time0"));
state.register.insert( state.register.insert(
register("RSP"), 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 // write something onto the stack
state state
...@@ -303,9 +290,9 @@ fn clear_parameters_on_the_stack_on_extern_calls() { ...@@ -303,9 +290,9 @@ fn clear_parameters_on_the_stack_on_extern_calls() {
has_var_args: false, has_var_args: false,
}; };
// check the value before // 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!( assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(), state.memory.get_value(&pointer, ByteSize::new(8)),
bv(42).into() bv(42).into()
); );
// clear stack parameter // clear stack parameter
...@@ -314,7 +301,7 @@ fn clear_parameters_on_the_stack_on_extern_calls() { ...@@ -314,7 +301,7 @@ fn clear_parameters_on_the_stack_on_extern_calls() {
.unwrap(); .unwrap();
// check the value after // check the value after
assert_eq!( 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)) Data::new_top(ByteSize::new(8))
); );
} }
...@@ -333,7 +320,7 @@ fn merge_callee_stack_to_caller_stack() { ...@@ -333,7 +320,7 @@ fn merge_callee_stack_to_caller_stack() {
// check the state before merging to the caller stack // check the state before merging to the caller stack
assert_eq!( assert_eq!(
state.register.get(&register("RSP")).unwrap(), 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); assert_eq!(state.memory.get_all_object_ids().len(), 2);
// check state after merging to the caller stack // check state after merging to the caller stack
...@@ -344,7 +331,7 @@ fn merge_callee_stack_to_caller_stack() { ...@@ -344,7 +331,7 @@ fn merge_callee_stack_to_caller_stack() {
); );
assert_eq!( assert_eq!(
state.register.get(&register("RSP")).unwrap(), 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); assert_eq!(state.memory.get_all_object_ids().len(), 1);
} }
...@@ -378,10 +365,8 @@ fn reachable_ids_under_and_overapproximation() { ...@@ -378,10 +365,8 @@ fn reachable_ids_under_and_overapproximation() {
let mut state = State::new(&register("RSP"), Tid::new("func_tid")); let mut state = State::new(&register("RSP"), Tid::new("func_tid"));
let stack_id = new_id("func_tid", "RSP"); let stack_id = new_id("func_tid", "RSP");
let heap_id = new_id("heap_obj", "RAX"); let heap_id = new_id("heap_obj", "RAX");
let stack_address: Data = let stack_address: Data = Data::from_target(stack_id.clone(), Bitvector::from_i64(-8).into());
PointerDomain::new(stack_id.clone(), Bitvector::from_i64(-8).into()).into(); let heap_address: Data = Data::from_target(heap_id.clone(), Bitvector::from_i64(0).into());
let heap_address: Data =
PointerDomain::new(heap_id.clone(), Bitvector::from_i64(0).into()).into();
// Add the heap object to the state, so that it can be recursively searched. // Add the heap object to the state, so that it can be recursively searched.
state.memory.add_abstract_object( state.memory.add_abstract_object(
heap_id.clone(), heap_id.clone(),
...@@ -408,8 +393,8 @@ fn reachable_ids_under_and_overapproximation() { ...@@ -408,8 +393,8 @@ fn reachable_ids_under_and_overapproximation() {
); );
let _ = state.store_value( let _ = state.store_value(
&PointerDomain::new(stack_id.clone(), ValueDomain::new_top(ByteSize::new(8))).into(), &Data::from_target(stack_id.clone(), ValueDomain::new_top(ByteSize::new(8))),
&Data::Value(Bitvector::from_i64(42).into()), &Bitvector::from_i64(42).into(),
&global_memory, &global_memory,
); );
assert_eq!( assert_eq!(
...@@ -435,12 +420,12 @@ fn global_mem_access() { ...@@ -435,12 +420,12 @@ fn global_mem_access() {
state state
.load_value(&address_expr, ByteSize::new(4), &global_memory) .load_value(&address_expr, ByteSize::new(4), &global_memory)
.unwrap(), .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 assert!(state
.write_to_address( .write_to_address(
&address_expr, &address_expr,
&DataDomain::Top(ByteSize::new(4)), &DataDomain::new_top(ByteSize::new(4)),
&global_memory &global_memory
) )
.is_err()); .is_err());
...@@ -451,12 +436,12 @@ fn global_mem_access() { ...@@ -451,12 +436,12 @@ fn global_mem_access() {
state state
.load_value(&address_expr, ByteSize::new(4), &global_memory) .load_value(&address_expr, ByteSize::new(4), &global_memory)
.unwrap(), .unwrap(),
DataDomain::Top(ByteSize::new(4)) DataDomain::new_top(ByteSize::new(4))
); );
assert!(state assert!(state
.write_to_address( .write_to_address(
&address_expr, &address_expr,
&DataDomain::Top(ByteSize::new(4)), &DataDomain::new_top(ByteSize::new(4)),
&global_memory &global_memory
) )
.is_ok()); .is_ok());
...@@ -469,7 +454,7 @@ fn global_mem_access() { ...@@ -469,7 +454,7 @@ fn global_mem_access() {
assert!(state assert!(state
.write_to_address( .write_to_address(
&address_expr, &address_expr,
&DataDomain::Top(ByteSize::new(4)), &DataDomain::new_top(ByteSize::new(4)),
&global_memory &global_memory
) )
.is_err()); .is_err());
...@@ -507,16 +492,16 @@ fn specialize_by_expression_results() { ...@@ -507,16 +492,16 @@ fn specialize_by_expression_results() {
); );
state.set_register( state.set_register(
&register("RAX"), &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( let x = state.specialize_by_expression_result(
&Expression::var("RAX", 8), &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!(x.is_ok());
assert_eq!( assert_eq!(
state.get_register(&register("RAX")), 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 // Expr = Const
...@@ -1015,11 +1000,11 @@ fn specialize_by_unsigned_comparison_op() { ...@@ -1015,11 +1000,11 @@ fn specialize_by_unsigned_comparison_op() {
#[test] #[test]
fn stack_pointer_with_nonnegative_offset() { fn stack_pointer_with_nonnegative_offset() {
let state = State::new(&register("RSP"), Tid::new("func_tid")); 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)); 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)); 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 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() { ...@@ -1041,16 +1026,16 @@ fn out_of_bounds_access_recognition() {
.memory .memory
.set_upper_index_bound(&heap_obj_id, &Bitvector::from_u64(7).into()); .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)); 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)); 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)); 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)); 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); state.set_register(&Variable::mock("RAX", 8), address);
let load_def = Def::load( let load_def = Def::load(
"tid", "tid",
...@@ -1059,13 +1044,13 @@ fn out_of_bounds_access_recognition() { ...@@ -1059,13 +1044,13 @@ fn out_of_bounds_access_recognition() {
); );
assert!(!state.contains_out_of_bounds_mem_access(&load_def.term, &global_data)); 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); state.set_register(&Variable::mock("RAX", 8), address);
assert!(!state.contains_out_of_bounds_mem_access(&load_def.term, &global_data)); 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); state.set_register(&Variable::mock("RAX", 8), address);
assert!(state.contains_out_of_bounds_mem_access(&load_def.term, &global_data)); 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); state.set_register(&Variable::mock("RAX", 8), address);
assert!(!state.contains_out_of_bounds_mem_access(&load_def.term, &global_data)); assert!(!state.contains_out_of_bounds_mem_access(&load_def.term, &global_data));
} }
...@@ -1076,12 +1061,12 @@ fn specialize_pointer_comparison() { ...@@ -1076,12 +1061,12 @@ fn specialize_pointer_comparison() {
let interval = IntervalDomain::mock(-5, 10); let interval = IntervalDomain::mock(-5, 10);
state.set_register( state.set_register(
&register("RAX"), &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); let interval = IntervalDomain::mock(20, 20);
state.set_register( state.set_register(
&register("RBX"), &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 { let expression = Expression::BinOp {
op: BinOpType::IntEqual, op: BinOpType::IntEqual,
...@@ -1094,7 +1079,7 @@ fn specialize_pointer_comparison() { ...@@ -1094,7 +1079,7 @@ fn specialize_pointer_comparison() {
.is_err()); .is_err());
let specialized_interval = IntervalDomain::mock_with_bounds(None, -5, 10, Some(19)); let specialized_interval = IntervalDomain::mock_with_bounds(None, -5, 10, Some(19));
let specialized_pointer = 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 assert!(state
.specialize_by_expression_result(&expression, Bitvector::from_i8(0).into()) .specialize_by_expression_result(&expression, Bitvector::from_i8(0).into())
.is_ok()); .is_ok());
......
...@@ -88,7 +88,7 @@ pub fn check_cwe( ...@@ -88,7 +88,7 @@ pub fn check_cwe(
let general_context = Context::new( let general_context = Context::new(
project, project,
analysis_results.runtime_memory_image, analysis_results.runtime_memory_image,
&pointer_inference_results, pointer_inference_results,
cwe_sender, cwe_sender,
); );
......
...@@ -216,7 +216,7 @@ impl<'a> Context<'a> { ...@@ -216,7 +216,7 @@ impl<'a> Context<'a> {
if let Ok(stack_param) = pi_state.eval_parameter_arg( if let Ok(stack_param) = pi_state.eval_parameter_arg(
parameter, parameter,
&self.project.stack_pointer_register, &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) { if state.check_if_address_points_to_taint(stack_param, pi_state) {
return true; return true;
......
...@@ -150,13 +150,11 @@ impl State { ...@@ -150,13 +150,11 @@ impl State {
/// Return whether the value at the given address (with the given size) is tainted. /// 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 { pub fn load_taint_from_memory(&self, address: &Data, size: ByteSize) -> Taint {
let mut taint = Taint::Top(size); let mut taint = Taint::Top(size);
if let Data::Pointer(pointer) = address { for (mem_id, offset) in address.get_relative_values() {
for (mem_id, offset) in pointer.targets().iter() { if let (Some(mem_region), Ok(position)) =
if let (Some(mem_region), Ok(position)) = (self.memory_taint.get(mem_id), offset.try_to_bitvec())
(self.memory_taint.get(mem_id), offset.try_to_bitvec()) {
{ taint = taint.merge(&mem_region.get(position.clone(), size));
taint = taint.merge(&mem_region.get(position.clone(), size));
}
} }
} }
taint taint
...@@ -168,30 +166,26 @@ impl State { ...@@ -168,30 +166,26 @@ impl State {
/// we merge the taint object with the object at the targets, /// we merge the taint object with the object at the targets,
/// possibly tainting all possible targets. /// possibly tainting all possible targets.
pub fn save_taint_to_memory(&mut self, address: &Data, taint: Taint) { pub fn save_taint_to_memory(&mut self, address: &Data, taint: Taint) {
if let Data::Pointer(pointer) = address { if let Some((mem_id, offset)) = address.get_if_unique_target() {
if pointer.targets().len() == 1 { if let Ok(position) = offset.try_to_bitvec() {
for (mem_id, offset) in pointer.targets().iter() { if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
if let Ok(position) = offset.try_to_bitvec() { mem_region.add(taint, position);
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) { } else {
mem_region.add(taint, position.clone()); let mut mem_region = MemRegion::new(address.bytesize());
} else { mem_region.add(taint, position);
let mut mem_region = MemRegion::new(address.bytesize()); self.memory_taint.insert(mem_id.clone(), mem_region);
mem_region.add(taint, position.clone());
self.memory_taint.insert(mem_id.clone(), mem_region);
}
}
} }
} else { }
for (mem_id, offset) in pointer.targets().iter() { } else {
if let Ok(position) = offset.try_to_bitvec() { for (mem_id, offset) in address.get_relative_values() {
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) { if let Ok(position) = offset.try_to_bitvec() {
let old_taint = mem_region.get(position.clone(), taint.bytesize()); if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
mem_region.add(old_taint.merge(&taint), position.clone()); let old_taint = mem_region.get(position.clone(), taint.bytesize());
} else { mem_region.add(old_taint.merge(&taint), position.clone());
let mut mem_region = MemRegion::new(address.bytesize()); } else {
mem_region.add(taint, position.clone()); let mut mem_region = MemRegion::new(address.bytesize());
self.memory_taint.insert(mem_id.clone(), mem_region); mem_region.add(taint, position.clone());
} self.memory_taint.insert(mem_id.clone(), mem_region);
} }
} }
} }
...@@ -214,7 +208,7 @@ impl State { ...@@ -214,7 +208,7 @@ impl State {
/// Return true if the memory object with the given ID contains a tainted value. /// 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 { 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() { for elem in mem_object.values() {
if elem.is_tainted() { if elem.is_tainted() {
return true; return true;
...@@ -234,27 +228,26 @@ impl State { ...@@ -234,27 +228,26 @@ impl State {
pi_state: &PointerInferenceState, pi_state: &PointerInferenceState,
) -> bool { ) -> bool {
use crate::analysis::pointer_inference::object::ObjectType; use crate::analysis::pointer_inference::object::ObjectType;
if let Data::Pointer(pointer) = address { for (target, offset) in address.get_relative_values() {
for (target, offset) in pointer.targets() { if let Ok(Some(ObjectType::Stack)) = pi_state.memory.get_object_type(target) {
if let Ok(Some(ObjectType::Stack)) = pi_state.memory.get_object_type(target) { // Only check if the value at the address is tainted
// Only check if the value at the address is tainted if let (Some(mem_object), Ok(target_offset)) =
if let (Some(mem_object), Ok(target_offset)) = (self.memory_taint.get(target), offset.try_to_bitvec())
(self.memory_taint.get(target), offset.try_to_bitvec()) {
{ if let Some(taint) = mem_object.get_unsized(target_offset.clone()) {
if let Some(taint) = mem_object.get_unsized(target_offset.clone()) { if taint.is_tainted() {
if taint.is_tainted() { return true;
return true;
}
} }
} }
} else { }
// Check whether the memory object contains any taint. } else {
if self.check_mem_id_for_taint(target) { // Check whether the memory object contains any taint.
return true; if self.check_mem_id_for_taint(target) {
} return true;
} }
} }
} }
false false
} }
...@@ -439,9 +432,9 @@ mod tests { ...@@ -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); let id = new_id(location);
PointerDomain::new(id, bv(offset)) DataDomain::from_target(id, bv(offset))
} }
#[test] #[test]
...@@ -453,7 +446,7 @@ mod tests { ...@@ -453,7 +446,7 @@ mod tests {
state.set_register_taint(&register("RAX"), taint.clone()); state.set_register_taint(&register("RAX"), taint.clone());
let mut other_state = State::mock(); 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); other_state.save_taint_to_memory(&address, taint);
let merged_state = state.merge(&other_state); let merged_state = state.merge(&other_state);
...@@ -466,7 +459,7 @@ mod tests { ...@@ -466,7 +459,7 @@ mod tests {
merged_state.load_taint_from_memory(&address, ByteSize::new(8)), merged_state.load_taint_from_memory(&address, ByteSize::new(8)),
taint.clone() taint.clone()
); );
let other_address = Data::Pointer(new_pointer_domain("mem", 18)); let other_address = new_pointer("mem", 18);
assert_eq!( assert_eq!(
merged_state.load_taint_from_memory(&other_address, ByteSize::new(8)), merged_state.load_taint_from_memory(&other_address, ByteSize::new(8)),
top.clone() top.clone()
......
...@@ -53,7 +53,7 @@ pub fn get_calls<'a>( ...@@ -53,7 +53,7 @@ pub fn get_calls<'a>(
let mut calls: Vec<(&str, &Tid, &str)> = Vec::new(); let mut calls: Vec<(&str, &Tid, &str)> = Vec::new();
let mut symbol_map: HashMap<&Tid, &str> = HashMap::with_capacity(dangerous_symbols.len()); let mut symbol_map: HashMap<&Tid, &str> = HashMap::with_capacity(dangerous_symbols.len());
for symbol in dangerous_symbols.iter() { 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() { for sub in subfunctions.iter() {
calls.append(&mut get_calls_to_symbols(sub, &symbol_map)); calls.append(&mut get_calls_to_symbols(sub, &symbol_map));
......
use petgraph::graph::NodeIndex; 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::analysis::pointer_inference::{Data, PointerInference as PointerInferenceComputation};
use crate::intermediate_representation::{ use crate::intermediate_representation::{
Arg, BinOpType, Bitvector, ByteSize, CallingConvention, Expression, ExternSymbol, Tid, Variable, Arg, BinOpType, Bitvector, ByteSize, CallingConvention, Expression, ExternSymbol, Tid, Variable,
...@@ -193,8 +193,7 @@ fn tainting_user_input_symbol_parameters() { ...@@ -193,8 +193,7 @@ fn tainting_user_input_symbol_parameters() {
format_string_index.insert("scanf".to_string(), 0); format_string_index.insert("scanf".to_string(), 0);
let global_address = Bitvector::from_str_radix(16, "500c").unwrap(); let global_address = Bitvector::from_str_radix(16, "500c").unwrap();
let string_address = let string_address = IntervalDomain::new(global_address.clone(), global_address).into();
DataDomain::Value(IntervalDomain::new(global_address.clone(), global_address));
let mut pi_result_state = pi_results let mut pi_result_state = pi_results
.get_node_value(call_source_node) .get_node_value(call_source_node)
...@@ -217,7 +216,7 @@ fn tainting_user_input_symbol_parameters() { ...@@ -217,7 +216,7 @@ fn tainting_user_input_symbol_parameters() {
})), })),
rhs: Box::new(Expression::Const(Bitvector::from_u64(0))), 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, &mem_image,
) )
.expect("Failed to write to address."); .expect("Failed to write to address.");
...@@ -294,7 +293,7 @@ fn processing_scanf() { ...@@ -294,7 +293,7 @@ fn processing_scanf() {
})), })),
rhs: Box::new(Expression::Const(Bitvector::from_u64(0))), 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, context.runtime_memory_image,
) )
.expect("Failed to write to address."); .expect("Failed to write to address.");
...@@ -349,7 +348,7 @@ fn processing_sscanf() { ...@@ -349,7 +348,7 @@ fn processing_sscanf() {
})), })),
rhs: Box::new(Expression::Const(Bitvector::from_u64(0))), 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, context.runtime_memory_image,
) )
.expect("Failed to write to address."); .expect("Failed to write to address.");
...@@ -418,7 +417,7 @@ fn tainting_function_arguments() { ...@@ -418,7 +417,7 @@ fn tainting_function_arguments() {
})), })),
rhs: Box::new(Expression::Const(Bitvector::from_u64(24))), 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, context.runtime_memory_image,
) )
.expect("Failed to write to address."); .expect("Failed to write to address.");
...@@ -431,7 +430,7 @@ fn tainting_function_arguments() { ...@@ -431,7 +430,7 @@ fn tainting_function_arguments() {
); );
assert!(setup.state.address_points_to_taint( 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 &setup.pi_state
)); ));
} }
......
...@@ -4,7 +4,7 @@ use super::*; ...@@ -4,7 +4,7 @@ use super::*;
use crate::analysis::{backward_interprocedural_fixpoint::Context as BackwardContext, graph::Node}; use crate::analysis::{backward_interprocedural_fixpoint::Context as BackwardContext, graph::Node};
use crate::{ use crate::{
abstract_domain::{DataDomain, PointerDomain, SizedDomain}, abstract_domain::{DataDomain, SizedDomain},
analysis::pointer_inference::{Data, State as PointerInferenceState, ValueDomain}, analysis::pointer_inference::{Data, State as PointerInferenceState, ValueDomain},
intermediate_representation::{Expression, Variable}, intermediate_representation::{Expression, Variable},
}; };
...@@ -118,8 +118,8 @@ impl Setup { ...@@ -118,8 +118,8 @@ impl Setup {
state, state,
pi_state, pi_state,
taint_source, taint_source,
base_eight_offset: Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-8))), base_eight_offset: Data::from_target(stack_id.clone(), bv(-8)),
base_sixteen_offset: Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-16))), base_sixteen_offset: Data::from_target(stack_id.clone(), bv(-16)),
} }
} }
} }
...@@ -339,7 +339,7 @@ fn creating_pi_def_map() { ...@@ -339,7 +339,7 @@ fn creating_pi_def_map() {
} else if *def_tid == def2 { } else if *def_tid == def2 {
assert_eq!( assert_eq!(
pi_state.get_register(&rdi_reg), 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() { ...@@ -568,10 +568,8 @@ fn handling_assign_and_load() {
new_state = context.update_def(&new_state, &mock_assign_stack).unwrap(); 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.get_register_taint(&r9_reg), None);
assert_eq!( assert_eq!(
new_state.address_points_to_taint( new_state
Data::Pointer(PointerDomain::new(stack_id.clone(), bv(0))), .address_points_to_taint(Data::from_target(stack_id.clone(), bv(0)), &setup.pi_state),
&setup.pi_state
),
true true
); );
...@@ -653,10 +651,8 @@ fn updating_def() { ...@@ -653,10 +651,8 @@ fn updating_def() {
new_state = context.update_def(&new_state, &mock_assign_stack).unwrap(); 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.get_register_taint(&r9_reg), None);
assert_eq!( assert_eq!(
new_state.address_points_to_taint( new_state
Data::Pointer(PointerDomain::new(stack_id.clone(), bv(0))), .address_points_to_taint(Data::from_target(stack_id.clone(), bv(0)), &setup.pi_state),
&setup.pi_state
),
true true
); );
......
...@@ -132,30 +132,26 @@ impl State { ...@@ -132,30 +132,26 @@ impl State {
/// we merge the taint object with the object at the targets, /// we merge the taint object with the object at the targets,
/// possibly tainting all possible targets. /// possibly tainting all possible targets.
pub fn save_taint_to_memory(&mut self, address: &Data, taint: Taint) { pub fn save_taint_to_memory(&mut self, address: &Data, taint: Taint) {
if let Data::Pointer(pointer) = address { if let Some((mem_id, offset)) = address.get_if_unique_target() {
if pointer.targets().len() == 1 { if let Ok(position) = offset.try_to_bitvec() {
for (mem_id, offset) in pointer.targets().iter() { if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
if let Ok(position) = offset.try_to_bitvec() { mem_region.add(taint, position);
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) { } else {
mem_region.add(taint, position.clone()); let mut mem_region = MemRegion::new(address.bytesize());
} else { mem_region.add(taint, position);
let mut mem_region = MemRegion::new(address.bytesize()); self.memory_taint.insert(mem_id.clone(), mem_region);
mem_region.add(taint, position.clone());
self.memory_taint.insert(mem_id.clone(), mem_region);
}
}
} }
} else { }
for (mem_id, offset) in pointer.targets().iter() { } else {
if let Ok(position) = offset.try_to_bitvec() { for (mem_id, offset) in address.get_relative_values() {
if let Some(mem_region) = self.memory_taint.get_mut(mem_id) { if let Ok(position) = offset.try_to_bitvec() {
let old_taint = mem_region.get(position.clone(), taint.bytesize()); if let Some(mem_region) = self.memory_taint.get_mut(mem_id) {
mem_region.add(old_taint.merge(&taint), position.clone()); let old_taint = mem_region.get(position.clone(), taint.bytesize());
} else { mem_region.add(old_taint.merge(&taint), position.clone());
let mut mem_region = MemRegion::new(address.bytesize()); } else {
mem_region.add(taint, position.clone()); let mut mem_region = MemRegion::new(address.bytesize());
self.memory_taint.insert(mem_id.clone(), mem_region); mem_region.add(taint, position.clone());
} self.memory_taint.insert(mem_id.clone(), mem_region);
} }
} }
} }
...@@ -285,7 +281,7 @@ impl State { ...@@ -285,7 +281,7 @@ impl State {
if let Some(pid_map) = self.pi_def_map.as_ref() { if let Some(pid_map) = self.pi_def_map.as_ref() {
if let Some(pi_state) = pid_map.get(def_tid) { if let Some(pi_state) = pid_map.get(def_tid) {
let address = pi_state.eval(target); 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( self.taint_def_input_register(
value, value,
stack_pointer_register, stack_pointer_register,
...@@ -361,15 +357,12 @@ impl State { ...@@ -361,15 +357,12 @@ impl State {
/// Remove the taint in the specified memory regions at the specified offsets. /// Remove the taint in the specified memory regions at the specified offsets.
pub fn remove_mem_taint_at_target(&mut self, address: &Data) { pub fn remove_mem_taint_at_target(&mut self, address: &Data) {
if let Data::Pointer(pointer) = address { for (mem_id, offset) in address.get_relative_values() {
for (mem_id, offset) in pointer.targets().iter() { if let (Some(mem_region), Ok(position)) =
if let (Some(mem_region), Ok(position)) = (self.memory_taint.get_mut(mem_id), offset.try_to_bitvec())
(self.memory_taint.get_mut(mem_id), offset.try_to_bitvec()) {
{ if let Some(taint) = mem_region.get_unsized(position.clone()) {
if let Some(taint) = mem_region.get_unsized(position.clone()) { mem_region.remove(position, Bitvector::from_u64(u64::from(taint.bytesize())));
mem_region
.remove(position, Bitvector::from_u64(u64::from(taint.bytesize())));
}
} }
} }
} }
...@@ -391,7 +384,7 @@ impl State { ...@@ -391,7 +384,7 @@ impl State {
/// Return true if the memory object with the given ID contains a tainted value. /// 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 { 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() { for elem in mem_object.values() {
if elem.is_tainted() { if elem.is_tainted() {
return true; return true;
...@@ -407,24 +400,22 @@ impl State { ...@@ -407,24 +400,22 @@ impl State {
/// return true if the memory object contains any tainted value (at any position). /// 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 { pub fn address_points_to_taint(&self, address: Data, pi_state: &PointerInferenceState) -> bool {
use crate::analysis::pointer_inference::object::ObjectType; use crate::analysis::pointer_inference::object::ObjectType;
if let Data::Pointer(pointer) = address { for (target, offset) in address.get_relative_values() {
for (target, offset) in pointer.targets() { if let Ok(Some(ObjectType::Stack)) = pi_state.memory.get_object_type(target) {
if let Ok(Some(ObjectType::Stack)) = pi_state.memory.get_object_type(target) { // Only check if the value at the address is tainted
// Only check if the value at the address is tainted if let (Some(mem_object), Ok(target_offset)) =
if let (Some(mem_object), Ok(target_offset)) = (self.memory_taint.get(target), offset.try_to_bitvec())
(self.memory_taint.get(target), offset.try_to_bitvec()) {
{ if let Some(taint) = mem_object.get_unsized(target_offset.clone()) {
if let Some(taint) = mem_object.get_unsized(target_offset.clone()) { if taint.is_tainted() {
if taint.is_tainted() { return true;
return true;
}
} }
} }
} else { }
// Check whether the memory object contains any taint. } else {
if self.check_mem_id_for_taint(target) { // Check whether the memory object contains any taint.
return true; if self.check_mem_id_for_taint(target) {
} return true;
} }
} }
} }
...@@ -445,7 +436,7 @@ impl State { ...@@ -445,7 +436,7 @@ impl State {
let taints = self.register_taint.clone(); let taints = self.register_taint.clone();
for (register, _) in taints.iter() { for (register, _) in taints.iter() {
if register_names.get(&register.name).is_none() { 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::analysis::pointer_inference::ValueDomain;
use crate::{ use crate::{abstract_domain::DataDomain, intermediate_representation::CastOpType};
abstract_domain::{DataDomain, PointerDomain},
intermediate_representation::CastOpType,
};
use super::*; use super::*;
...@@ -98,9 +95,9 @@ impl Setup { ...@@ -98,9 +95,9 @@ impl Setup {
constant: String::from("Hello World"), constant: String::from("Hello World"),
constant_address: Bitvector::from_u32(12290), constant_address: Bitvector::from_u32(12290),
def_tid: Tid::new("def"), def_tid: Tid::new("def"),
stack_pointer: Data::Pointer(PointerDomain::new(stack_id.clone(), bv(0))), stack_pointer: Data::from_target(stack_id.clone(), bv(0)),
base_eight_offset: Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-8))), base_eight_offset: Data::from_target(stack_id.clone(), bv(-8)),
base_sixteen_offset: Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-16))), base_sixteen_offset: Data::from_target(stack_id.clone(), bv(-16)),
} }
} }
} }
......
...@@ -37,7 +37,7 @@ pub static CWE_MODULE: crate::CweModule = crate::CweModule { ...@@ -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. /// 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> { 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() { if !calls.is_empty() {
return generate_cwe_warning(&calls); return generate_cwe_warning(&calls);
} }
......
...@@ -52,7 +52,7 @@ impl Term<Blk> { ...@@ -52,7 +52,7 @@ impl Term<Blk> {
.indirect_jmp_targets .indirect_jmp_targets
.iter() .iter()
.filter_map(|target| { .filter_map(|target| {
if known_block_tids.get(&target).is_some() { if known_block_tids.get(target).is_some() {
Some(target.clone()) Some(target.clone())
} else { } else {
let error_msg = let error_msg =
......
...@@ -131,7 +131,7 @@ impl Variable { ...@@ -131,7 +131,7 @@ impl Variable {
match (&self.name, &self.value) { match (&self.name, &self.value) {
(None, Some(hex_value)) => { (None, Some(hex_value)) => {
assert!(u64::from(self.size) <= 8); 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() val.into()
} }
_ => panic!(), _ => panic!(),
......
...@@ -514,12 +514,11 @@ impl ExternSymbol { ...@@ -514,12 +514,11 @@ impl ExternSymbol {
let mut symbol = self.clone(); let mut symbol = self.clone();
let mut parameters = Vec::new(); let mut parameters = Vec::new();
let mut return_values = Vec::new(); let mut return_values = Vec::new();
let input_args: Vec<&Arg> = symbol let symbol_has_input_args = symbol
.arguments .arguments
.iter() .iter()
.filter(|arg| matches!(arg.intent, ArgIntent::INPUT)) .any(|arg| matches!(arg.intent, ArgIntent::INPUT));
.collect(); if symbol.is_scanf_or_sscanf() && !symbol_has_input_args {
if symbol.is_scanf_or_sscanf() && input_args.is_empty() {
symbol.create_format_string_args_for_scanf_and_sscanf( symbol.create_format_string_args_for_scanf_and_sscanf(
conventions, conventions,
stack_pointer, stack_pointer,
......
...@@ -7,7 +7,7 @@ use crate::{intermediate_representation::Datatype, prelude::*}; ...@@ -7,7 +7,7 @@ use crate::{intermediate_representation::Datatype, prelude::*};
use regex::Regex; use regex::Regex;
use crate::{ use crate::{
abstract_domain::{DataDomain, IntervalDomain, TryToBitvec}, abstract_domain::{IntervalDomain, TryToBitvec},
analysis::pointer_inference::State as PointerInferenceState, analysis::pointer_inference::State as PointerInferenceState,
intermediate_representation::{ intermediate_representation::{
Arg, ByteSize, CallingConvention, DatatypeProperties, ExternSymbol, Project, Variable, Arg, ByteSize, CallingConvention, DatatypeProperties, ExternSymbol, Project, Variable,
...@@ -37,13 +37,13 @@ pub fn get_input_format_string( ...@@ -37,13 +37,13 @@ pub fn get_input_format_string(
runtime_memory_image: &RuntimeMemoryImage, runtime_memory_image: &RuntimeMemoryImage,
) -> Result<String, Error> { ) -> Result<String, Error> {
if let Some(format_string) = extern_symbol.parameters.get(format_string_index) { if let Some(format_string) = extern_symbol.parameters.get(format_string_index) {
if let Ok(DataDomain::Value(address)) = pi_state.eval_parameter_arg( if let Ok(Some(address)) = pi_state
format_string, .eval_parameter_arg(format_string, stack_pointer_register, runtime_memory_image)
&stack_pointer_register, .as_ref()
runtime_memory_image, .map(|param| param.get_if_absolute_value())
) { {
return parse_format_string_destination_and_return_content( return parse_format_string_destination_and_return_content(
address, address.clone(),
runtime_memory_image, runtime_memory_image,
); );
} }
......
...@@ -24,7 +24,7 @@ fn test_get_variable_parameters() { ...@@ -24,7 +24,7 @@ fn test_get_variable_parameters() {
let global_address = Bitvector::from_str_radix(16, "5000").unwrap(); let global_address = Bitvector::from_str_radix(16, "5000").unwrap();
pi_state.set_register( pi_state.set_register(
&Variable::mock("RDI", 8 as u64), &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 mut project = Project::mock_empty();
let cconv = CallingConvention::mock_with_parameter_registers( let cconv = CallingConvention::mock_with_parameter_registers(
...@@ -66,7 +66,7 @@ fn test_get_variable_parameters() { ...@@ -66,7 +66,7 @@ fn test_get_variable_parameters() {
let global_address = Bitvector::from_str_radix(16, "500c").unwrap(); let global_address = Bitvector::from_str_radix(16, "500c").unwrap();
pi_state.set_register( pi_state.set_register(
&Variable::mock("RDI", 8 as u64), &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!( assert_eq!(
...@@ -91,7 +91,7 @@ fn test_get_input_format_string() { ...@@ -91,7 +91,7 @@ fn test_get_input_format_string() {
let global_address = Bitvector::from_str_radix(16, "3002").unwrap(); let global_address = Bitvector::from_str_radix(16, "3002").unwrap();
pi_state.set_register( pi_state.set_register(
&Variable::mock("RSI", 8 as u64), &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!( 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