Unverified Commit 9f09ebeb by Enkelmann Committed by GitHub

Refactor caller stack handling in PointerInference analysis (#287)

parent 0662acda
......@@ -78,6 +78,26 @@ impl<T: RegisterDomain> DataDomain<T> {
}
}
/// Replace all abstract IDs in self with the corresponding values given by the `replacement_map`.
///
/// For IDs without a replacement value the `contains_top_values` flag will be set.
pub fn replace_all_ids(&mut self, replacement_map: &BTreeMap<AbstractIdentifier, Self>) {
let mut new_self = DataDomain {
size: self.size,
relative_values: BTreeMap::new(),
absolute_value: self.absolute_value.clone(),
contains_top_values: self.contains_top_values,
};
for (id, offset) in self.relative_values.iter() {
if let Some(replacement_value) = replacement_map.get(id) {
new_self = new_self.merge(&(replacement_value.clone() + offset.clone().into()));
} else {
new_self.contains_top_values = true;
}
}
*self = new_self;
}
/// Return an iterator over all referenced abstract IDs.
pub fn referenced_ids(&self) -> impl Iterator<Item = &AbstractIdentifier> {
self.relative_values.keys()
......@@ -262,14 +282,33 @@ mod tests {
let mut targets = BTreeMap::new();
targets.insert(new_id("Rax"), bv(1));
targets.insert(new_id("Rbx"), bv(2));
let mut data = DataDomain::mock_from_target_map(targets);
targets.insert(new_id("Rcx"), bv(3));
// Test replacing exactly one ID.
let mut data = DataDomain::mock_from_target_map(targets.clone());
data.replace_abstract_id(&new_id("Rbx"), &new_id("replaced_Rbx"), &bv(10));
assert_eq!(data.relative_values.len(), 2);
assert_eq!(data.relative_values.len(), 3);
assert_eq!(*data.relative_values.get(&new_id("Rax")).unwrap(), bv(1));
assert_eq!(
*data.relative_values.get(&new_id("replaced_Rbx")).unwrap(),
bv(12)
);
// Test replacing all IDs using a replacement map.
let mut data = DataDomain::mock_from_target_map(targets);
let replacement_map = BTreeMap::from_iter([
(
new_id("Rax"),
DataDomain::from_target(new_id("replaced_Rax"), bv(0)),
),
(new_id("Rbx"), bv(10).into()),
]);
data.replace_all_ids(&replacement_map);
assert_eq!(data.relative_values.len(), 1);
assert_eq!(
*data.relative_values.get(&new_id("replaced_Rax")).unwrap(),
bv(1)
);
assert!(data.contains_top());
assert_eq!(data.absolute_value.unwrap(), bv(12));
}
#[test]
......
......@@ -69,6 +69,24 @@ where
}
}
impl<K, V, S> FromIterator<(K, V)> for DomainMap<K, V, S>
where
K: PartialOrd + Ord + Clone,
V: AbstractDomain,
S: MapMergeStrategy<K, V>,
{
/// Generate a new `DomainMap` from an iterator over the key-value pairs that it should contain.
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
{
DomainMap {
inner: Arc::new(iter.into_iter().collect()),
phantom: PhantomData,
}
}
}
impl<K, V, S> AbstractDomain for DomainMap<K, V, S>
where
K: PartialOrd + Ord + Clone,
......
......@@ -20,7 +20,12 @@ use std::sync::Arc;
/// E.g. it may represent the union of all values at the specific *location* for each time the program point is visited during an execution trace
/// or it may only represent the value at the last time the program point was visited.
///
/// An abstract identifier is given by a time identifier and a location identifier.
/// Alternatively one can also add path hints to an identifier to further distinguish points in time in an execution trace.
/// Path hints are given as a possibly empty array of time identifiers.
/// To prevent infinitely long path hints, each time identifier is only allowed to appear at most once in the array.
/// The specific meaning of the path hints depends upon the use case.
///
/// An abstract identifier is given by a time identifier, a location identifier and a path hints array (containing time identifiers).
///
/// For the location identifier see `AbstractLocation`.
/// The time identifier is given by a `Tid`.
......@@ -35,23 +40,71 @@ pub struct AbstractIdentifier(Arc<AbstractIdentifierData>);
pub struct AbstractIdentifierData {
time: Tid,
location: AbstractLocation,
path_hints: Vec<Tid>,
}
impl AbstractIdentifier {
/// Create a new abstract identifier.
pub fn new(time: Tid, location: AbstractLocation) -> AbstractIdentifier {
AbstractIdentifier(Arc::new(AbstractIdentifierData { time, location }))
AbstractIdentifier(Arc::new(AbstractIdentifierData {
time,
location,
path_hints: Vec::new(),
}))
}
/// Create a new abstract identifier where the abstract location is a register.
/// Panics if the register is a temporary register.
pub fn new_from_var(time: Tid, variable: &Variable) -> AbstractIdentifier {
pub fn from_var(time: Tid, variable: &Variable) -> AbstractIdentifier {
AbstractIdentifier(Arc::new(AbstractIdentifierData {
time,
location: AbstractLocation::from_var(variable).unwrap(),
path_hints: Vec::new(),
}))
}
/// Create an abstract identifier from a parameter argument.
///
/// If the argument is a sub-register, then the created identifier contains the whole base register.
pub fn from_arg(time: &Tid, arg: &Arg) -> AbstractIdentifier {
let location_register = match arg {
Arg::Register { expr, .. } | Arg::Stack { address: expr, .. } => {
match &expr.input_vars()[..] {
[var] => *var,
_ => panic!("Malformed argument expression encountered"),
}
}
};
let location = match arg {
Arg::Register { .. } => AbstractLocation::from_var(location_register).unwrap(),
Arg::Stack { size, .. } => AbstractLocation::from_stack_position(
location_register,
arg.eval_stack_offset().unwrap().try_to_i64().unwrap(),
*size,
),
};
AbstractIdentifier::new(time.clone(), location)
}
/// Create a new abstract identifier
/// by pushing the given path hint to the array of path hints of `self`.
/// Returns an error if the path hint is already contained in the path hints of `self`.
pub fn with_path_hint(&self, path_hint: Tid) -> Result<Self, Error> {
if self.path_hints.iter().any(|tid| *tid == path_hint) {
Err(anyhow!("Path hint already contained."))
} else {
let mut new_id = self.clone();
let inner = Arc::make_mut(&mut new_id.0);
inner.path_hints.push(path_hint);
Ok(new_id)
}
}
/// Get the path hints array of `self`.
pub fn get_path_hints(&self) -> &[Tid] {
&self.path_hints
}
/// Get the register associated to the abstract location.
/// Panics if the abstract location is a memory location and not a register.
pub fn unwrap_register(&self) -> &Variable {
......@@ -74,7 +127,15 @@ impl AbstractIdentifier {
impl std::fmt::Display for AbstractIdentifier {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "{} @ {}", self.0.time, self.0.location)
if self.path_hints.is_empty() {
write!(formatter, "{} @ {}", self.0.time, self.0.location)
} else {
write!(formatter, "{}(", self.0.time)?;
for hint in &self.0.path_hints {
write!(formatter, "->{}", hint)?;
}
write!(formatter, ") @ {}", self.0.location)
}
}
}
......@@ -159,3 +220,27 @@ impl std::fmt::Display for AbstractMemoryLocation {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constraint_enforcements() {
// Test that no temporary registers are allowed as abstract locations.
assert!(AbstractLocation::from_var(&Variable {
name: "var".to_string(),
size: ByteSize::new(8),
is_temp: true,
})
.is_err());
// Test uniqueness of TIDs in path hint array.
let id = AbstractIdentifier::new(
Tid::new("time_id"),
AbstractLocation::from_var(&Variable::mock("var", 8)).unwrap(),
);
let id = id.with_path_hint(Tid::new("first_hint")).unwrap();
let id = id.with_path_hint(Tid::new("second_hint")).unwrap();
assert!(id.with_path_hint(Tid::new("first_hint")).is_err());
}
}
......@@ -107,18 +107,6 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegion<T> {
}
}
/// Clear all values that might be fully or partially overwritten if one writes a value with byte size `value_size`
/// to an offset contained in the interval from `start` to `end` (both bounds included in the interval).
///
/// This represents the effect of writing arbitrary values (with known byte size)
/// to arbitrary offsets contained in the interval.
/// Note that if one only wants to mark values in the interval as potentially overwritten without deleting them,
/// then one should use the [`MemRegion::mark_interval_values_as_top`] method instead.
pub fn clear_offset_interval(&mut self, start: i64, end: i64, value_size: ByteSize) {
let size = end - start + (u64::from(value_size) as i64);
self.clear_interval(start, size);
}
/// Add a value to the memory region.
pub fn add(&mut self, value: T, position: Bitvector) {
assert_eq!(
......@@ -259,6 +247,19 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegion<T> {
self.clear_top_values();
}
/// Add the given offset to the indices of all values contained in the memory region.
pub fn add_offset_to_all_indices(&mut self, offset: i64) {
if offset == 0 {
return;
}
let mut new_values = BTreeMap::new();
for (index, value) in self.inner.values.iter() {
new_values.insert(*index + offset, value.clone());
}
let inner = Arc::make_mut(&mut self.inner);
inner.values = new_values;
}
/// Merge two memory regions.
///
/// Values at the same position and with the same size get merged via their merge function.
......
......@@ -99,7 +99,7 @@ impl<'a> Context<'a> {
// If the Top-flag of the return value was set we replace it with an ID representing the return register
// to indicate where the unknown value originated from.
if return_value.contains_top() {
let id = AbstractIdentifier::new_from_var(call.tid.clone(), return_register);
let id = AbstractIdentifier::from_var(call.tid.clone(), return_register);
let value =
DataDomain::from_target(id, Bitvector::zero(return_register.size.into()).into());
return_value = return_value.merge(&value);
......
......@@ -22,19 +22,19 @@ fn test_compute_return_values_of_call() {
let return_values =
context.compute_return_values_of_call(&mut caller_state, &callee_state, &cconv, &call);
let expected_val = DataDomain::from_target(
AbstractIdentifier::new_from_var(Tid::new("call_tid"), &Variable::mock("RAX", 8)),
AbstractIdentifier::from_var(Tid::new("call_tid"), &Variable::mock("RAX", 8)),
Bitvector::from_i64(0).into(),
);
assert_eq!(return_values.iter().len(), 3);
assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val));
// Test returning a known value.
let param_ref = DataDomain::from_target(
AbstractIdentifier::new_from_var(Tid::new("callee"), &Variable::mock("RDI", 8)),
AbstractIdentifier::from_var(Tid::new("callee"), &Variable::mock("RDI", 8)),
Bitvector::from_i64(0).into(),
);
callee_state.set_register(&Variable::mock("RAX", 8), param_ref);
let expected_val = DataDomain::from_target(
AbstractIdentifier::new_from_var(Tid::new("caller"), &Variable::mock("RDI", 8)),
AbstractIdentifier::from_var(Tid::new("caller"), &Variable::mock("RDI", 8)),
Bitvector::from_i64(0).into(),
);
let return_values =
......
......@@ -57,14 +57,19 @@ fn generate_fixpoint_computation<'a>(
if let Some(entry_block) = sub.term.blocks.get(0) {
if entry_block.tid == block.tid {
// The node of a function entry point
let calling_convention = project
.get_specific_calling_convention(&sub.term.calling_convention)
.unwrap_or_else(|| {
project
.get_standard_calling_convention()
.expect("No standard calling convention found.")
});
computation.set_node_value(
node,
NodeValue::Value(State::new(
&sub.tid,
&project.stack_pointer_register,
project
.get_specific_calling_convention(&sub.term.calling_convention)
.unwrap(),
calling_convention,
)),
)
}
......@@ -166,6 +171,20 @@ impl FunctionSignature {
}
}
/// The returned number is the maximum of stack offset plus parameter size
/// taken over all stack parameters in the function signature.
pub fn get_stack_params_total_size(&self) -> i64 {
let mut stack_params_total_size: i64 = 0;
for param in self.parameters.keys() {
if let Ok(param_offset) = param.eval_stack_offset() {
let param_upper_bound =
param_offset.try_to_i64().unwrap() + (u64::from(param.bytesize()) as i64);
stack_params_total_size = std::cmp::max(stack_params_total_size, param_upper_bound);
}
}
stack_params_total_size
}
/// Merge the parameter list of `self` with the given parameter list.
fn merge_parameter_list(&mut self, params: &[(Arg, AccessPattern)]) {
for (arg, sig_new) in params {
......@@ -213,7 +232,7 @@ impl FunctionSignature {
if *size != stack_register.size {
return Err(anyhow!("Unexpected stack parameter size"));
}
if let Ok(offset) = arg.eval_stack_offset(stack_register) {
if let Ok(offset) = arg.eval_stack_offset() {
if offset.try_to_u64()? % u64::from(stack_register.size) != 0 {
return Err(anyhow!("Unexpected stack parameter alignment"));
}
......@@ -229,3 +248,27 @@ impl Default for FunctionSignature {
Self::new()
}
}
#[cfg(test)]
pub mod tests {
use super::*;
impl FunctionSignature {
/// Create a mock x64 function signature with 2 parameters, one of which is accessed mutably.
pub fn mock_x64() -> FunctionSignature {
let mut write_access_pattern = AccessPattern::new();
write_access_pattern.set_unknown_access_flags();
let parameters = HashMap::from_iter([
(
Arg::from_var(Variable::mock("RDI", 8), None),
AccessPattern::new(),
),
(
Arg::from_var(Variable::mock("RSI", 8), None),
write_access_pattern,
),
]);
FunctionSignature { parameters }
}
}
}
......@@ -41,7 +41,7 @@ impl State {
let mut tracked_ids = BTreeMap::new();
// Generate tracked IDs for all parameters and also add them to the register map
for var in calling_convention.get_all_parameter_register() {
let id = AbstractIdentifier::new_from_var(func_tid.clone(), var);
let id = AbstractIdentifier::from_var(func_tid.clone(), var);
let value =
DataDomain::from_target(id.clone(), Bitvector::zero(var.size.into()).into());
register_map.insert(var.clone(), value);
......@@ -50,7 +50,7 @@ impl State {
}
}
// Generate all stack-related objects
let stack_id = AbstractIdentifier::new_from_var(func_tid.clone(), stack_register);
let stack_id = AbstractIdentifier::from_var(func_tid.clone(), stack_register);
let stack_value = DataDomain::from_target(
stack_id.clone(),
Bitvector::zero(stack_register.size.into()).into(),
......
......@@ -112,7 +112,7 @@ impl State {
{
if var.size == generic_pointer_size {
let specific_target = DataDomain::from_target(
AbstractIdentifier::new_from_var(call_tid.clone(), var),
AbstractIdentifier::from_var(call_tid.clone(), var),
Bitvector::zero(var.size.into()).into(),
);
let output = generic_output.merge(&specific_target);
......
......@@ -22,7 +22,7 @@ impl State {
/// Mock an abstract ID representing the stack.
fn mock_stack_id() -> AbstractIdentifier {
AbstractIdentifier::new_from_var(Tid::new("mock_fn"), &Variable::mock("sp", 4))
AbstractIdentifier::from_var(Tid::new("mock_fn"), &Variable::mock("sp", 4))
}
/// Mock an abstract ID of a stack parameter
......@@ -149,9 +149,9 @@ fn test_extern_symbol_handling() {
return_: Some(Tid::new("return_tid")),
},
};
let param_id = AbstractIdentifier::new_from_var(Tid::new("mock_fn"), &Variable::mock("r0", 4));
let param_id = AbstractIdentifier::from_var(Tid::new("mock_fn"), &Variable::mock("r0", 4));
let return_val_id =
AbstractIdentifier::new_from_var(Tid::new("call_tid"), &Variable::mock("r0", 4));
AbstractIdentifier::from_var(Tid::new("call_tid"), &Variable::mock("r0", 4));
// Test extern symbol handling.
state.handle_extern_symbol(&call, &extern_symbol, &cconv);
assert_eq!(
......
use super::*;
use crate::analysis::function_signature::AccessPattern;
impl<'a> Context<'a> {
/// Create a map that maps each abstract ID known to the callee
/// to the value that represents it in the caller.
///
/// For parameter IDs this is the value of the parameter on function call.
/// For IDs of objects created in the callee it is the ID together with a path hint given by the call TID.
/// For other IDs (including the callee stack frame ID) it is a `Top` value,
/// i.e. the value of the ID should be unknown to the caller.
pub fn create_callee_id_to_caller_data_map(
&self,
state_before_call: &State,
state_before_return: &State,
call_tid: &Tid,
) -> BTreeMap<AbstractIdentifier, Data> {
let stack_register = &self.project.stack_pointer_register;
let mut id_map = BTreeMap::new();
let callee_tid = state_before_return.get_fn_tid();
let callee_fn_sig = self.fn_signatures.get(callee_tid).unwrap();
for param in callee_fn_sig.parameters.keys() {
let param_id = AbstractIdentifier::from_arg(callee_tid, param);
if let Ok(param_value) =
state_before_call.eval_parameter_arg(param, self.runtime_memory_image)
{
id_map.insert(param_id, param_value);
} else {
id_map.insert(param_id, Data::new_top(param.bytesize()));
}
}
for object_id in state_before_return.memory.get_all_object_ids() {
if object_id.get_tid() != callee_tid || !object_id.get_path_hints().is_empty() {
// Object is neither a parameter object nor the stack frame of the callee.
if let Ok(new_object_id) = object_id.with_path_hint(call_tid.clone()) {
id_map.insert(
object_id,
Data::from_target(
new_object_id,
Bitvector::zero(stack_register.size.into()).into(),
),
);
} else {
id_map.insert(object_id, Data::new_top(stack_register.size));
}
}
}
id_map.insert(
state_before_return.stack_id.clone(),
Data::new_top(stack_register.size),
);
id_map
}
/// Create a map from the parameter IDs (of the function that the given state corresponds to)
/// to the corresponding access patterns.
pub fn create_id_to_access_pattern_map(
&self,
state: &State,
) -> BTreeMap<AbstractIdentifier, &AccessPattern> {
let mut id_to_access_pattern_map = BTreeMap::new();
let fn_tid = state.get_fn_tid();
let callee_fn_sig = self.fn_signatures.get(fn_tid).unwrap();
for (param, access_pattern) in &callee_fn_sig.parameters {
let param_id = AbstractIdentifier::from_arg(fn_tid, param);
id_to_access_pattern_map.insert(param_id.clone(), access_pattern);
}
id_to_access_pattern_map
}
/// Identify caller IDs used in more than one parameter,
/// for which at least one parameter has write access to the corresponding memory object.
/// For these IDs the analysis in the callee is unsound for the corresponding callsite!
pub fn get_unsound_caller_ids(
&self,
callee_id_to_caller_data_map: &BTreeMap<AbstractIdentifier, Data>,
callee_id_to_access_pattern_map: &BTreeMap<AbstractIdentifier, &AccessPattern>,
) -> BTreeSet<AbstractIdentifier> {
let mut ids_touched = BTreeSet::new();
let mut ids_modified = BTreeSet::new();
let mut unsound_caller_ids = BTreeSet::new();
for (callee_id, access_pattern) in callee_id_to_access_pattern_map {
for id in callee_id_to_caller_data_map
.get(callee_id)
.unwrap()
.referenced_ids()
{
if ids_modified.get(id).is_some()
|| (access_pattern.is_mutably_dereferenced() && ids_touched.get(id).is_some())
{
unsound_caller_ids.insert(id.clone());
}
ids_touched.insert(id.clone());
if access_pattern.is_mutably_dereferenced() {
ids_modified.insert(id.clone());
}
}
}
unsound_caller_ids
}
}
use super::object::ObjectType;
use crate::analysis::function_signature::FunctionSignature;
use crate::analysis::graph::Graph;
use crate::intermediate_representation::*;
......@@ -11,13 +10,15 @@ use super::state::State;
use super::ValueDomain;
use super::{Config, Data, VERSION};
// contains trait implementations for the `Context` struct,
// especially the implementation of the `interprocedural_fixpoint::Context` trait.
/// Contains methods of the `Context` struct that deal with the manipulation of abstract IDs.
mod id_manipulation;
/// Contains trait implementations for the `Context` struct,
/// especially the implementation of the [`forward_interprocedural_fixpoint::Context`](crate::analysis::forward_interprocedural_fixpoint::Context) trait.
mod trait_impls;
/// Contains all context information needed for the pointer inference fixpoint computation.
///
/// The struct also implements the `interprocedural_fixpoint::Context` trait to enable the fixpoint computation.
/// The struct also implements the [`forward_interprocedural_fixpoint::Context`](crate::analysis::forward_interprocedural_fixpoint::Context) trait to enable the fixpoint computation.
pub struct Context<'a> {
/// The program control flow graph on which the fixpoint will be computed
pub graph: &'a Graph<'a>,
......@@ -185,9 +186,8 @@ impl<'a> Context<'a> {
);
new_state.memory.add_abstract_object(
object_id.clone(),
Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(),
super::object::ObjectType::Heap,
address_bytesize,
Some(super::object::ObjectType::Heap),
);
new_state.memory.set_lower_index_bound(
&object_id,
......@@ -266,13 +266,15 @@ impl<'a> Context<'a> {
}
/// Check all parameter registers of a call for dangling pointers and report possible use-after-frees.
fn check_parameter_register_for_dangling_pointer(
fn check_parameter_register_for_dangling_pointer<'iter, I>(
&self,
state: &mut State,
call: &Term<Jmp>,
extern_symbol: &ExternSymbol,
) {
for parameter in extern_symbol.parameters.iter() {
parameters: I,
) where
I: Iterator<Item = &'iter Arg>,
{
for parameter in parameters {
match state.eval_parameter_arg(parameter, self.runtime_memory_image) {
Ok(value) => {
if state.memory.is_dangling_pointer(&value, true) {
......@@ -287,8 +289,8 @@ impl<'a> Context<'a> {
symbols: Vec::new(),
other: Vec::new(),
description: format!(
"(Use After Free) Call to {} may access freed memory at {}",
extern_symbol.name, call.tid.address
"(Use After Free) Call at {} may access freed memory",
call.tid.address
),
};
let _ = self.log_collector.send(LogThreadMsg::Cwe(warning));
......@@ -354,14 +356,14 @@ impl<'a> Context<'a> {
}
}
/// Adjust the stack register after a call to an extern function.
/// Adjust the stack register after a call to a function.
///
/// On x86, this removes the return address from the stack
/// (other architectures pass the return address in a register, not on the stack).
/// On other architectures the stack register retains the value it had before the call.
/// Note that in some calling conventions the callee also clears function parameters from the stack.
/// We do not detect and handle these cases yet.
fn adjust_stack_register_on_extern_call(
fn adjust_stack_register_on_return_from_call(
&self,
state_before_call: &State,
new_state: &mut State,
......@@ -383,7 +385,7 @@ impl<'a> Context<'a> {
}
/// Handle an extern symbol call, whose concrete effect on the state is unknown.
/// Basically, we assume that the call may write to all memory objects and register that is has access to.
/// Basically, we assume that the call may write to all memory objects and registers that is has access to.
fn handle_generic_extern_call(
&self,
state: &State,
......@@ -438,7 +440,7 @@ impl<'a> Context<'a> {
let mut new_state = state_before_call.clone();
new_state.clear_non_callee_saved_register(&calling_conv.callee_saved_register[..]);
// Adjust stack register value (for x86 architecture).
self.adjust_stack_register_on_extern_call(state_before_call, &mut new_state);
self.adjust_stack_register_on_return_from_call(state_before_call, &mut new_state);
let mut possible_referenced_ids = BTreeSet::new();
for parameter_register in calling_conv.integer_parameter_register.iter() {
......@@ -463,19 +465,6 @@ impl<'a> Context<'a> {
}
}
/// Get the offset of the current stack pointer to the base of the current stack frame.
fn get_current_stack_offset(&self, state: &State) -> ValueDomain {
if let Some((stack_id, stack_offset_domain)) = state
.get_register(&self.project.stack_pointer_register)
.get_if_unique_target()
{
if *stack_id == state.stack_id {
return stack_offset_domain.clone();
}
}
ValueDomain::new_top(self.project.stack_pointer_register.size)
}
/// Report a NULL dereference CWE at the address of the given TID.
fn report_null_deref(&self, tid: &Tid) {
let warning = CweWarning {
......
use super::*;
use std::collections::BTreeMap;
impl AbstractObject {
/// Get all abstract IDs that the object may contain pointers to.
......@@ -18,25 +19,6 @@ impl AbstractObject {
referenced_ids
}
/// For pointer values replace an abstract identifier with another one and add the offset_adjustment to the pointer offsets.
/// This is needed to adjust stack pointers on call and return instructions.
pub fn replace_abstract_id(
&mut self,
old_id: &AbstractIdentifier,
new_id: &AbstractIdentifier,
offset_adjustment: &ValueDomain,
) {
let inner = Arc::make_mut(&mut self.inner);
for elem in inner.memory.values_mut() {
elem.replace_abstract_id(old_id, new_id, offset_adjustment);
}
inner.memory.clear_top_values();
if inner.pointer_targets.get(old_id).is_some() {
inner.pointer_targets.remove(old_id);
inner.pointer_targets.insert(new_id.clone());
}
}
/// Remove the provided IDs from the target lists of all pointers in the memory object.
/// Also remove them from the pointer_targets list.
///
......@@ -56,4 +38,23 @@ impl AbstractObject {
}
inner.memory.clear_top_values(); // In case the previous operation left *Top* values in the memory struct.
}
/// Replace all abstract IDs in `self` with the values given by the replacement map.
/// IDs not contained as keys in the replacement map are replaced by `Top` values.
pub fn replace_ids(&mut self, replacement_map: &BTreeMap<AbstractIdentifier, Data>) {
let inner = Arc::make_mut(&mut self.inner);
for elem in inner.memory.values_mut() {
elem.replace_all_ids(replacement_map);
}
inner.memory.clear_top_values();
let mut new_pointer_targets = BTreeSet::new();
for target in &inner.pointer_targets {
if let Some(replacement_value) = replacement_map.get(target) {
for new_target in replacement_value.referenced_ids() {
new_pointer_targets.insert(new_target.clone());
}
}
}
inner.pointer_targets = new_pointer_targets;
}
}
......@@ -87,12 +87,12 @@ impl std::convert::Into<AbstractObject> for Inner {
impl AbstractObject {
/// Create a new abstract object with given object type and address bytesize.
pub fn new(type_: ObjectType, address_bytesize: ByteSize) -> AbstractObject {
pub fn new(type_: Option<ObjectType>, address_bytesize: ByteSize) -> AbstractObject {
let inner = Inner {
pointer_targets: BTreeSet::new(),
is_unique: true,
state: ObjectState::Alive,
type_: Some(type_),
type_,
memory: MemRegion::new(address_bytesize),
lower_index_bound: BitvectorDomain::Top(address_bytesize),
upper_index_bound: BitvectorDomain::Top(address_bytesize),
......@@ -124,6 +124,14 @@ impl AbstractObject {
inner.upper_index_bound = upper_bound;
}
/// Add an offset to the upper index bound that is still considered to be contained in the abstract object.
pub fn add_to_upper_index_bound(&mut self, offset: i64) {
let inner = Arc::make_mut(&mut self.inner);
let offset =
Bitvector::from_i64(offset).into_resize_signed(inner.upper_index_bound.bytesize());
inner.upper_index_bound = inner.upper_index_bound.clone() + offset.into();
}
/// Get the state of the memory object.
pub fn get_state(&self) -> ObjectState {
self.inner.state
......@@ -193,6 +201,80 @@ impl AbstractObject {
}
}
}
/// Overwrite the values in `self` with those in `other`
/// under the assumption that the zero offset in `other` corresponds to the offset `offset_other` in `self`.
///
/// If `self` is not a unique memory object or if `offset_other` is not a precisely known offset,
/// then the function tries to merge `self` and `other`,
/// since we do not exactly know which values of `self` were overwritten by `other`.
///
/// All values of `self` are marked as possibly overwritten, i.e. `Top`,
/// but they are only deleted if they intersect a non-`Top` value of `other`.
/// This approximates the fact that we currently do not track exactly which indices
/// in `other` were overwritten with a `Top` element and which indices simply were not
/// accessed at all in `other`.
///
/// The upper and lower index bounds of `self` are kept and not overwritten.
pub fn overwrite_with(&mut self, other: &AbstractObject, offset_other: &ValueDomain) {
if let Ok(obj_offset) = offset_other.try_to_offset() {
if self.inner.is_unique {
let inner = Arc::make_mut(&mut self.inner);
// Overwrite values in the memory region of self with those of other.
inner.memory.mark_all_values_as_top();
for (elem_offset, elem) in other.inner.memory.iter() {
inner
.memory
.insert_at_byte_index(elem.clone(), obj_offset + elem_offset);
}
// Merge all other properties with those of other.
inner.is_unique &= other.inner.is_unique;
inner.state = inner.state.merge(other.inner.state);
inner
.pointer_targets
.append(&mut other.inner.pointer_targets.clone());
// TODO: We should log cases where the index bounds are violated by `other`.
} else {
let inner = Arc::make_mut(&mut self.inner);
let mut other = other.clone();
let other_inner = Arc::make_mut(&mut other.inner);
other_inner.memory.add_offset_to_all_indices(obj_offset);
inner.memory = inner.memory.merge(&other_inner.memory);
inner.is_unique &= other.inner.is_unique;
inner.state = inner.state.merge(other.inner.state);
inner
.pointer_targets
.append(&mut other.inner.pointer_targets.clone());
// TODO: We should log cases where the index bounds are violated by `other`.
}
} else {
let inner = Arc::make_mut(&mut self.inner);
inner.memory.mark_all_values_as_top();
inner.is_unique &= other.inner.is_unique;
inner.state = inner.state.merge(other.inner.state);
inner
.pointer_targets
.append(&mut other.inner.pointer_targets.clone());
}
}
/// Add an offset to all values contained in the abstract object.
/// The offset is also added to the lower and upper index bounds.
pub fn add_offset_to_all_indices(&mut self, offset: &ValueDomain) {
let inner = Arc::make_mut(&mut self.inner);
if let Ok(offset) = offset.try_to_offset() {
inner.memory.add_offset_to_all_indices(offset);
let offset =
Bitvector::from_i64(offset).into_resize_signed(inner.lower_index_bound.bytesize());
inner.lower_index_bound = inner.lower_index_bound.clone() + offset.clone().into();
inner.upper_index_bound = inner.upper_index_bound.clone() + offset.into();
} else {
inner.memory = MemRegion::new(inner.memory.get_address_bytesize());
inner.lower_index_bound = inner.lower_index_bound.top();
inner.upper_index_bound = inner.upper_index_bound.top();
}
}
}
impl AbstractDomain for AbstractObject {
......
use crate::intermediate_representation::Variable;
use super::*;
use crate::intermediate_representation::Variable;
use std::collections::BTreeMap;
fn new_abstract_object() -> AbstractObject {
let inner = Inner {
......@@ -75,45 +75,6 @@ fn abstract_object() {
}
#[test]
fn replace_id() {
use std::collections::BTreeMap;
let mut object = new_abstract_object();
let mut target_map = BTreeMap::new();
target_map.insert(new_id("time_1", "RAX"), bv(20));
target_map.insert(new_id("time_234", "RAX"), bv(30));
target_map.insert(new_id("time_1", "RBX"), bv(40));
let pointer = DataDomain::mock_from_target_map(target_map.clone());
object.set_value(pointer, &bv(-15)).unwrap();
assert_eq!(object.get_referenced_ids_overapproximation().len(), 3);
object.replace_abstract_id(
&new_id("time_1", "RAX"),
&new_id("time_234", "RAX"),
&bv(10),
);
target_map.remove(&new_id("time_1", "RAX"));
let modified_pointer = DataDomain::mock_from_target_map(target_map);
assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
modified_pointer
);
object.replace_abstract_id(
&new_id("time_1", "RBX"),
&new_id("time_234", "RBX"),
&bv(10),
);
let mut target_map = BTreeMap::new();
target_map.insert(new_id("time_234", "RAX"), bv(30));
target_map.insert(new_id("time_234", "RBX"), bv(50));
let modified_pointer = DataDomain::mock_from_target_map(target_map);
assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
modified_pointer
);
}
#[test]
fn remove_ids() {
use std::collections::BTreeMap;
let mut object = new_abstract_object();
......@@ -145,3 +106,53 @@ fn access_contained_in_bounds() {
assert!(object.access_contained_in_bounds(&IntervalDomain::mock(92, 92), ByteSize::new(8)));
assert!(!object.access_contained_in_bounds(&IntervalDomain::mock(93, 93), ByteSize::new(8)));
}
#[test]
fn overwrite_with() {
let mut object = new_abstract_object();
object.set_value(bv(1).into(), &bv(0).into()).unwrap();
object.set_value(bv(2).into(), &bv(8).into()).unwrap();
let mut other_object = new_abstract_object();
other_object.set_value(bv(3).into(), &bv(0).into()).unwrap();
other_object.set_value(bv(4).into(), &bv(8).into()).unwrap();
object.overwrite_with(&other_object, &bv(8).into());
let mut expected_result = new_abstract_object();
let mut data: Data = bv(1).into();
data.set_contains_top_flag();
expected_result.set_value(data, &bv(0).into()).unwrap();
expected_result
.set_value(bv(3).into(), &bv(8).into())
.unwrap();
expected_result
.set_value(bv(4).into(), &bv(16).into())
.unwrap();
assert_eq!(object, expected_result);
}
#[test]
fn replace_ids() {
let set_value = |object: &mut AbstractObject, tid: &str, register: &str, offset: i64| {
object
.set_value(
Data::from_target(new_id(tid, register), bv(0).into()),
&bv(offset).into(),
)
.unwrap();
};
let mut object = new_abstract_object();
set_value(&mut object, "before", "RAX", 0);
set_value(&mut object, "before", "RBX", 8);
let mut replacement_map = BTreeMap::new();
replacement_map.insert(
new_id("before", "RAX"),
Data::from_target(new_id("after", "RCX"), bv(0).into()),
);
let mut expected_result = new_abstract_object();
set_value(&mut expected_result, "after", "RCX", 0);
object.replace_ids(&replacement_map);
assert_eq!(object, expected_result);
}
......@@ -5,7 +5,15 @@ impl AbstractObject {
/// and with the given size of the accessed value is contained in the bounds of the memory object.
/// If `offset` contains more than one possible index value,
/// then only return `true` if the access is contained in the abstract object for all possible offset values.
///
/// If `offset` is a `Top` value, then the function assumes this to be due to analysis inaccuracies
/// and does not flag them as possible out-of-bounds access.
pub fn access_contained_in_bounds(&self, offset: &ValueDomain, size: ByteSize) -> bool {
if offset.is_top() {
// Currently TOP offsets happen a lot due to inaccuracies in the analysis.
// So for the time being we do not flag them as possible CWEs.
return true;
}
if let Ok(offset_interval) = offset.try_to_interval() {
if let Ok(lower_bound) = self.inner.lower_index_bound.try_to_bitvec() {
if lower_bound.checked_sgt(&offset_interval.start).unwrap() {
......
......@@ -13,7 +13,7 @@ impl AbstractObjectList {
/// 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 {
for id in address.referenced_ids() {
if let Some((object, _offset_id)) = self.objects.get(id) {
if let Some(object) = self.objects.get(id) {
match (report_unknown_states, object.get_state()) {
(_, ObjectState::Dangling) => return true,
(true, ObjectState::Unknown) => {
......@@ -34,7 +34,7 @@ impl AbstractObjectList {
/// as flagged.
pub fn mark_dangling_pointer_targets_as_flagged(&mut self, address: &Data) {
for id in address.referenced_ids() {
let (object, _) = self.objects.get_mut(id).unwrap();
let object = self.objects.get_mut(id).unwrap();
if matches!(
object.get_state(),
ObjectState::Unknown | ObjectState::Dangling
......@@ -69,11 +69,8 @@ impl AbstractObjectList {
}
}
for (id, offset) in address.get_relative_values() {
if let Some((object, base_offset)) = self.objects.get(id) {
let adjusted_offset = offset.clone() + base_offset.clone();
if !adjusted_offset.is_top()
&& !object.access_contained_in_bounds(&adjusted_offset, size)
{
if let Some(object) = self.objects.get(id) {
if !object.access_contained_in_bounds(offset, size) {
return true;
}
}
......@@ -86,8 +83,9 @@ impl AbstractObjectList {
///
/// Any `bound` value other than a constant bitvector is interpreted as the memory object not having a lower bound.
pub fn set_lower_index_bound(&mut self, object_id: &AbstractIdentifier, bound: &ValueDomain) {
let (object, base_offset) = self.objects.get_mut(object_id).unwrap();
let bound = (bound.clone() + base_offset.clone())
let object = self.objects.get_mut(object_id).unwrap();
let bound = bound
.clone()
.try_to_bitvec()
.map(|bitvec| bitvec.into())
.unwrap_or_else(|_| BitvectorDomain::new_top(bound.bytesize()));
......@@ -99,8 +97,9 @@ impl AbstractObjectList {
///
/// Any `bound` value other than a constant bitvector is interpreted as the memory object not having an upper bound.
pub fn set_upper_index_bound(&mut self, object_id: &AbstractIdentifier, bound: &ValueDomain) {
let (object, base_offset) = self.objects.get_mut(object_id).unwrap();
let bound = (bound.clone() + base_offset.clone())
let object = self.objects.get_mut(object_id).unwrap();
let bound = bound
.clone()
.try_to_bitvec()
.map(|bitvec| bitvec.into())
.unwrap_or_else(|_| BitvectorDomain::new_top(bound.bytesize()));
......@@ -117,14 +116,17 @@ impl AbstractObjectList {
) -> Result<(), Vec<(AbstractIdentifier, Error)>> {
let ids: Vec<AbstractIdentifier> = object_pointer.referenced_ids().cloned().collect();
let mut possible_double_free_ids = Vec::new();
if ids.len() > 1 {
if ids.len() > 1
|| object_pointer.contains_top()
|| object_pointer.get_absolute_value().is_some()
{
for id in ids {
if let Err(error) = self.objects.get_mut(&id).unwrap().0.mark_as_maybe_freed() {
if let Err(error) = self.objects.get_mut(&id).unwrap().mark_as_maybe_freed() {
possible_double_free_ids.push((id.clone(), error));
}
}
} 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().mark_as_freed() {
possible_double_free_ids.push((id.clone(), error));
}
}
......
......@@ -3,36 +3,13 @@
use super::*;
impl AbstractObjectList {
/// Replace one abstract identifier with another one. Adjust offsets of all pointers accordingly.
///
/// **Example:**
/// Assume the `old_id` points to offset 0 in the corresponding memory object and the `new_id` points to offset -32.
/// Then the offset_adjustment is -32.
/// The offset_adjustment gets *added* to the base offset in `self.memory.ids` (so that it points to offset -32 in the memory object),
/// while it gets *subtracted* from all pointer values (so that they still point to the same spot in the corresponding memory object).
pub fn replace_abstract_id(
&mut self,
old_id: &AbstractIdentifier,
new_id: &AbstractIdentifier,
offset_adjustment: &ValueDomain,
) {
let negative_offset = -offset_adjustment.clone();
for (object, _) in self.objects.values_mut() {
object.replace_abstract_id(old_id, new_id, &negative_offset);
}
if let Some((object, old_offset)) = self.objects.remove(old_id) {
let new_offset = old_offset + offset_adjustment.clone();
self.objects.insert(new_id.clone(), (object, new_offset));
}
}
/// Return all IDs that may be referenced by the memory object pointed to by the given ID.
/// The returned set is an overapproximation of the actual referenced IDs.
pub fn get_referenced_ids_overapproximation(
&self,
id: &AbstractIdentifier,
) -> BTreeSet<AbstractIdentifier> {
if let Some((object, _offset)) = self.objects.get(id) {
if let Some(object) = self.objects.get(id) {
object.get_referenced_ids_overapproximation().clone()
} else {
BTreeSet::new()
......@@ -46,10 +23,10 @@ impl AbstractObjectList {
&self,
id: &AbstractIdentifier,
) -> BTreeSet<AbstractIdentifier> {
if let Some((object, _offset)) = self.objects.get(id) {
if let Some(object) = self.objects.get(id) {
object.get_referenced_ids_underapproximation()
} else {
panic!("Abstract ID not associated to an object")
BTreeSet::new()
}
}
}
......@@ -4,9 +4,10 @@
use super::*;
impl AbstractObjectList {
/// Remove the memory object that `object_id` points to from the object list.
pub fn remove_object(&mut self, object_id: &AbstractIdentifier) {
self.objects.remove(object_id);
/// Get a reference to the object corresponding to the given ID.
#[cfg(test)]
pub fn get_object(&self, id: &AbstractIdentifier) -> Option<&AbstractObject> {
self.objects.get(id)
}
/// Add a new abstract object to the object list
......@@ -16,18 +17,28 @@ impl AbstractObjectList {
pub fn add_abstract_object(
&mut self,
object_id: AbstractIdentifier,
initial_offset: ValueDomain,
type_: ObjectType,
address_bytesize: ByteSize,
generic_address_bytesize: ByteSize,
type_: Option<ObjectType>,
) {
let new_object = AbstractObject::new(type_, address_bytesize);
if let Some((object, offset)) = self.objects.get_mut(&object_id) {
let new_object = AbstractObject::new(type_, generic_address_bytesize);
if let Some(object) = self.objects.get_mut(&object_id) {
// If the identifier already exists, we have to assume that more than one object may be referenced by this identifier.
object.mark_as_not_unique();
*object = object.merge(&new_object);
*offset = offset.merge(&initial_offset);
} else {
self.objects.insert(object_id, (new_object, initial_offset));
self.objects.insert(object_id, new_object);
}
}
/// Insert an existing object to the object list.
/// If the object identifier already exists, the object is marked as non-unique
/// and merged with the corresponding object already present in the object list.
pub fn insert(&mut self, id: AbstractIdentifier, object: AbstractObject) {
if let Some(existing_object) = self.objects.get_mut(&id) {
existing_object.mark_as_not_unique();
*existing_object = existing_object.merge(&object);
} else {
self.objects.insert(id, object);
}
}
......@@ -48,32 +59,14 @@ impl AbstractObjectList {
self.objects.keys().cloned().collect()
}
/// Get an iterator over the contained abstract objects in `self`.
pub fn iter(&self) -> std::collections::btree_map::Iter<AbstractIdentifier, AbstractObject> {
self.objects.iter()
}
/// Get the number of objects that are currently tracked.
#[cfg(test)]
pub fn get_num_objects(&self) -> usize {
self.objects.len()
}
/// Append those objects from another object list, whose abstract IDs are not known to self.
pub fn append_unknown_objects(&mut self, other_object_list: &AbstractObjectList) {
for (id, (other_object, other_offset)) in other_object_list.objects.iter() {
if self.objects.get(id) == None {
self.objects
.insert(id.clone(), (other_object.clone(), other_offset.clone()));
}
}
}
/// Remove the provided IDs as targets from all pointers in all objects.
/// Also remove the objects, that these IDs point to.
pub fn remove_ids(&mut self, ids_to_remove: &BTreeSet<AbstractIdentifier>) {
for id in ids_to_remove {
if self.objects.get(id).is_some() {
self.objects.remove(id);
}
}
for (object, _) in self.objects.values_mut() {
object.remove_ids(ids_to_remove);
}
}
}
......@@ -17,12 +17,7 @@ mod list_manipulation;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct AbstractObjectList {
/// The abstract objects.
///
/// Each abstract object comes with an offset given as a [`ValueDomain`].
/// This offset determines where the zero offset corresponding to the abstract identifier inside the object is.
/// Note that this offset may be a `Top` element
/// if the exact offset corresponding to the identifier is unknown.
objects: BTreeMap<AbstractIdentifier, (AbstractObject, ValueDomain)>,
objects: BTreeMap<AbstractIdentifier, AbstractObject>,
}
impl AbstractObjectList {
......@@ -35,15 +30,9 @@ impl AbstractObjectList {
address_bytesize: ByteSize,
) -> AbstractObjectList {
let mut objects = BTreeMap::new();
let mut stack_object = AbstractObject::new(ObjectType::Stack, address_bytesize);
let mut stack_object = AbstractObject::new(Some(ObjectType::Stack), address_bytesize);
stack_object.set_upper_index_bound(Bitvector::zero(address_bytesize.into()).into());
objects.insert(
stack_id,
(
stack_object,
Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(),
),
);
objects.insert(stack_id, stack_object);
AbstractObjectList { objects }
}
......@@ -54,9 +43,8 @@ impl AbstractObjectList {
/// If the address does not contain any relative targets an empty value is returned.
pub fn get_value(&self, address: &Data, size: ByteSize) -> Data {
let mut merged_value = Data::new_empty(size);
for (id, offset_pointer) in address.get_relative_values() {
if let Some((object, offset_identifier)) = self.objects.get(id) {
let offset = offset_pointer.clone() + offset_identifier.clone();
for (id, offset) in address.get_relative_values() {
if let Some(object) = self.objects.get(id) {
if let Ok(concrete_offset) = offset.try_to_bitvec() {
let value = object.get_value(concrete_offset, size);
merged_value = merged_value.merge(&value);
......@@ -73,30 +61,33 @@ impl AbstractObjectList {
merged_value
}
/// Get a mutable reference to the object with the given abstract ID.
pub fn get_object_mut(&mut self, id: &AbstractIdentifier) -> Option<&mut AbstractObject> {
self.objects.get_mut(id)
}
/// Set the value at a given address.
///
/// If the address has more than one target,
/// we merge-write the value to all targets.
pub fn set_value(&mut self, pointer: Data, value: Data) -> Result<(), Error> {
let targets = pointer.get_relative_values();
match targets.len() {
0 => Ok(()),
1 => {
let (id, pointer_offset) = targets.iter().next().unwrap();
let (object, id_offset) = self.objects.get_mut(id).unwrap();
let adjusted_offset = pointer_offset.clone() + id_offset.clone();
object.set_value(value, &adjusted_offset)
}
_ => {
// There is more than one object that the pointer may write to.
// We merge-write to all possible targets
for (id, offset) in targets {
let (object, object_offset) = self.objects.get_mut(id).unwrap();
let adjusted_offset = offset.clone() + object_offset.clone();
object.merge_value(value.clone(), &adjusted_offset);
}
Ok(())
if let Some((id, offset)) = pointer.get_if_unique_target() {
let object = self
.objects
.get_mut(id)
.ok_or_else(|| anyhow!("Abstract object does not exist."))?;
object.set_value(value, offset)
} else {
// There may be more than one object that the pointer may write to.
// We merge-write to all possible targets
for (id, offset) in pointer.get_relative_values() {
let object = self
.objects
.get_mut(id)
.ok_or_else(|| anyhow!("Abstract object does not exist."))?;
object.merge_value(value.clone(), offset);
}
Ok(())
}
}
......@@ -113,7 +104,7 @@ impl AbstractObjectList {
object_id: &AbstractIdentifier,
new_possible_reference_targets: &BTreeSet<AbstractIdentifier>,
) {
if let Some((object, _)) = self.objects.get_mut(object_id) {
if let Some(object) = self.objects.get_mut(object_id) {
object.assume_arbitrary_writes(new_possible_reference_targets);
}
}
......@@ -125,7 +116,7 @@ impl AbstractObjectList {
object_id: &AbstractIdentifier,
) -> Result<Option<ObjectType>, ()> {
match self.objects.get(object_id) {
Some((object, _)) => Ok(object.get_object_type()),
Some(object) => Ok(object.get_object_type()),
None => Err(()),
}
}
......@@ -135,7 +126,7 @@ impl AbstractObjectList {
/// Returns an error if the ID is not contained in the object list.
pub fn is_unique_object(&self, object_id: &AbstractIdentifier) -> Result<bool, Error> {
match self.objects.get(object_id) {
Some((object, _)) => Ok(object.is_unique()),
Some(object) => Ok(object.is_unique()),
None => Err(anyhow!("Object ID not contained in object list.")),
}
}
......@@ -151,12 +142,11 @@ impl AbstractDomain for AbstractObjectList {
/// where more than one ID should point to the same object.
fn merge(&self, other: &Self) -> Self {
let mut merged_objects = self.objects.clone();
for (id, (other_object, other_offset)) in other.objects.iter() {
if let Some((object, offset)) = merged_objects.get_mut(id) {
for (id, other_object) in other.objects.iter() {
if let Some(object) = merged_objects.get_mut(id) {
*object = object.merge(other_object);
*offset = offset.merge(other_offset);
} else {
merged_objects.insert(id.clone(), (other_object.clone(), other_offset.clone()));
merged_objects.insert(id.clone(), other_object.clone());
}
}
AbstractObjectList {
......@@ -176,11 +166,8 @@ impl AbstractObjectList {
pub fn to_json_compact(&self) -> serde_json::Value {
use serde_json::*;
let mut object_map = Map::new();
for (id, (object, offset)) in self.objects.iter() {
object_map.insert(
format!("{} (base offset {})", id, offset),
object.to_json_compact(),
);
for (id, object) in self.objects.iter() {
object_map.insert(format!("{}", id), object.to_json_compact());
}
Value::Object(object_map)
}
......
......@@ -17,7 +17,6 @@ fn new_id(name: &str) -> AbstractIdentifier {
fn abstract_object_list() {
let mut obj_list = AbstractObjectList::from_stack_id(new_id("RSP".into()), ByteSize::new(8));
assert_eq!(obj_list.objects.len(), 1);
assert_eq!(obj_list.objects.values().next().unwrap().1, bv(0));
let pointer = DataDomain::from_target(new_id("RSP".into()), bv(8));
obj_list.set_value(pointer.clone(), bv(42).into()).unwrap();
......@@ -42,9 +41,8 @@ fn abstract_object_list() {
other_obj_list.add_abstract_object(
new_id("RAX".into()),
bv(0),
ObjectType::Heap,
ByteSize::new(8),
Some(ObjectType::Heap),
);
let heap_pointer = DataDomain::from_target(new_id("RAX".into()), bv(8));
other_obj_list
......@@ -94,66 +92,25 @@ fn abstract_object_list() {
new_id("RAX".into())
);
let modified_heap_pointer = DataDomain::from_target(new_id("ID2".into()), bv(8));
other_obj_list.replace_abstract_id(&new_id("RAX".into()), &new_id("ID2".into()), &bv(0));
assert_eq!(
other_obj_list.get_value(&pointer, ByteSize::new(8)),
modified_heap_pointer.clone()
);
assert_eq!(other_obj_list.objects.get(&new_id("RAX".into())), None);
assert!(matches!(
other_obj_list.objects.get(&new_id("ID2".into())),
Some(_)
));
let mut ids_to_keep = BTreeSet::new();
ids_to_keep.insert(new_id("ID2".into()));
ids_to_keep.insert(new_id("RAX".into()));
other_obj_list.remove_unused_objects(&ids_to_keep);
assert_eq!(other_obj_list.objects.len(), 1);
assert_eq!(
other_obj_list.objects.iter().next().unwrap().0,
&new_id("ID2".into())
&new_id("RAX".into())
);
assert_eq!(
other_obj_list
.objects
.values()
.next()
.unwrap()
.0
.get_state(),
other_obj_list.objects.values().next().unwrap().get_state(),
crate::analysis::pointer_inference::object::ObjectState::Alive
);
let modified_heap_pointer = DataDomain::from_target(new_id("RAX".into()), bv(8));
other_obj_list
.mark_mem_object_as_freed(&modified_heap_pointer)
.unwrap();
assert_eq!(
other_obj_list
.objects
.values()
.next()
.unwrap()
.0
.get_state(),
other_obj_list.objects.values().next().unwrap().get_state(),
crate::analysis::pointer_inference::object::ObjectState::Dangling
);
}
#[test]
fn append_unknown_objects_test() {
let mut obj_list = AbstractObjectList::from_stack_id(new_id("stack"), ByteSize::new(8));
let mut other_obj_list = AbstractObjectList::from_stack_id(new_id("stack"), ByteSize::new(8));
other_obj_list.add_abstract_object(
new_id("heap_obj"),
bv(0).into(),
ObjectType::Heap,
ByteSize::new(8),
);
obj_list.append_unknown_objects(&other_obj_list);
assert_eq!(obj_list.objects.len(), 2);
assert!(obj_list.objects.get(&new_id("stack")).is_some());
assert!(obj_list.objects.get(&new_id("heap_obj")).is_some());
}
......@@ -35,44 +35,26 @@ impl State {
value: &Data,
global_memory: &RuntimeMemoryImage,
) -> Result<(), Error> {
// If the address is a unique caller stack address, write to *all* caller stacks.
if let Some(offset) = self.unwrap_offset_if_caller_stack_address(address) {
let caller_addresses: Vec<_> = self
.caller_stack_ids
.iter()
.map(|caller_stack_id| Data::from_target(caller_stack_id.clone(), offset.clone()))
.collect();
let mut result = Ok(());
for address in caller_addresses {
if let Err(err) = self.store_value(&address, &value.clone(), global_memory) {
result = Err(err);
self.memory.set_value(address.clone(), value.clone())?;
if let Some(absolute_address) = address.get_absolute_value() {
if let Ok(address_to_global_data) = absolute_address.try_to_bitvec() {
match global_memory.is_address_writeable(&address_to_global_data) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow!("Write to read-only global data")),
Err(err) => Err(err),
}
}
// Note that this only returns the last error that was detected.
result
} else {
let pointer = self.adjust_pointer_for_read(address);
self.memory.set_value(pointer.clone(), value.clone())?;
if let Some(absolute_address) = pointer.get_absolute_value() {
if let Ok(address_to_global_data) = absolute_address.try_to_bitvec() {
match global_memory.is_address_writeable(&address_to_global_data) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow!("Write to read-only global data")),
Err(err) => Err(err),
}
} else if let Ok((start, end)) = absolute_address.try_to_offset_interval() {
match global_memory.is_interval_writeable(start as u64, end as u64) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow!("Write to read-only global data")),
Err(err) => Err(err),
}
} else {
// We assume inexactness of the algorithm instead of a possible CWE here.
Ok(())
} else if let Ok((start, end)) = absolute_address.try_to_offset_interval() {
match global_memory.is_interval_writeable(start as u64, end as u64) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow!("Write to read-only global data")),
Err(err) => Err(err),
}
} else {
// We assume inexactness of the algorithm instead of a possible CWE here.
Ok(())
}
} else {
Ok(())
}
}
......@@ -105,7 +87,7 @@ impl State {
size: ByteSize,
global_memory: &RuntimeMemoryImage,
) -> Result<Data, Error> {
let address = self.adjust_pointer_for_read(&self.eval(address));
let address = self.eval(address);
let mut result = if let Some(global_address) = address.get_absolute_value() {
if let Ok(address_bitvector) = global_address.try_to_bitvec() {
match global_memory.read(&address_bitvector, size) {
......@@ -160,38 +142,6 @@ impl State {
}
}
/// If the pointer contains a reference to the stack with offset >= 0, replace it with a pointer
/// pointing to all possible caller IDs.
fn adjust_pointer_for_read(&self, address: &Data) -> Data {
let mut adjusted_address = address.clone();
let mut new_targets = BTreeMap::new();
for (id, offset) in address.get_relative_values() {
if *id == self.stack_id {
if let Ok((interval_start, interval_end)) = offset.try_to_offset_interval() {
if interval_start >= 0 && interval_end >= 0 && !self.caller_stack_ids.is_empty()
{
for caller_id in self.caller_stack_ids.iter() {
new_targets.insert(caller_id.clone(), offset.clone());
}
// Note that the id of the current stack frame was *not* added.
} else {
new_targets.insert(id.clone(), offset.clone());
}
} else {
for caller_id in self.caller_stack_ids.iter() {
new_targets.insert(caller_id.clone(), offset.clone());
}
// Note that we also add the id of the current stack frame
new_targets.insert(id.clone(), offset.clone());
}
} else {
new_targets.insert(id.clone(), offset.clone());
}
}
adjusted_address.set_relative_values(new_targets);
adjusted_address
}
/// Evaluate the value of an expression in the current state
pub fn eval(&self, expression: &Expression) -> Data {
use Expression::*;
......@@ -257,16 +207,11 @@ impl State {
def: &Def,
global_data: &RuntimeMemoryImage,
) -> bool {
let (raw_address, size) = match def {
let (address, size) = match def {
Def::Load { address, var } => (self.eval(address), var.size),
Def::Store { address, value } => (self.eval(address), value.bytesize()),
_ => return false,
};
if self.is_stack_pointer_with_nonnegative_offset(&raw_address) {
// Access to a parameter or the return address of the function
return false;
}
let address = self.adjust_pointer_for_read(&raw_address);
self.memory
.is_out_of_bounds_mem_access(&address, size, global_data)
}
......@@ -279,47 +224,12 @@ impl State {
data: &Data,
global_data: &RuntimeMemoryImage,
) -> bool {
let mut data = self.adjust_pointer_for_read(data);
let mut data = data.clone();
data.set_absolute_value(None); // Do not check absolute_values
self.memory
.is_out_of_bounds_mem_access(&data, ByteSize::new(1), global_data)
}
/// Return `true` if `data` is a pointer to the current stack frame with a constant positive address,
/// i.e. if it accesses a stack parameter (or the return-to address for x86) of the current function.
pub fn is_stack_pointer_with_nonnegative_offset(&self, data: &Data) -> bool {
if let Some((target, offset)) = data.get_if_unique_target() {
if *target == self.stack_id {
if let Ok(offset_val) = offset.try_to_offset() {
if offset_val >= 0 {
return true;
}
}
}
}
false
}
/// If the given address is a positive stack offset and `self.caller_stack_ids` is non-empty,
/// i.e. it is an access to the caller stack, return the offset.
///
/// In all other cases, including the case that the address has more than one target, return `None`.
fn unwrap_offset_if_caller_stack_address(&self, address: &Data) -> Option<ValueDomain> {
if self.caller_stack_ids.is_empty() {
return None;
}
if let Some((id, offset)) = address.get_if_unique_target() {
if self.stack_id == *id {
if let Ok((interval_start, _interval_end)) = offset.try_to_offset_interval() {
if interval_start >= 0 {
return Some(offset.clone());
}
}
}
}
None
}
/// Check whether the given `def` could result in a memory access through a NULL pointer.
///
/// If no NULL pointer dereference is detected then `Ok(false)` is returned.
......
//! Methods of [`State`] for manipulating abstract IDs.
use super::*;
use crate::analysis::pointer_inference::object::AbstractObject;
impl State {
/// Replace all occurences of old_id with new_id and adjust offsets accordingly.
/// This is needed to replace stack/caller IDs on call and return instructions.
///
/// **Example:**
/// Assume the old_id points to offset 0 in the corresponding memory object and the new_id points to offset -32.
/// Then the offset_adjustment is -32.
/// The offset_adjustment gets *added* to the base offset in self.memory.ids (so that it points to offset -32 in the memory object),
/// while it gets *subtracted* from all pointer values (so that they still point to the same spot in the corresponding memory object).
pub fn replace_abstract_id(
&mut self,
old_id: &AbstractIdentifier,
new_id: &AbstractIdentifier,
offset_adjustment: &ValueDomain,
) {
for register_data in self.register.values_mut() {
register_data.replace_abstract_id(old_id, new_id, &(-offset_adjustment.clone()));
}
self.memory
.replace_abstract_id(old_id, new_id, offset_adjustment);
if &self.stack_id == old_id {
self.stack_id = new_id.clone();
}
if self.caller_stack_ids.get(old_id).is_some() {
self.caller_stack_ids.remove(old_id);
self.caller_stack_ids.insert(new_id.clone());
}
if self.ids_known_to_caller.get(old_id).is_some() {
self.ids_known_to_caller.remove(old_id);
self.ids_known_to_caller.insert(new_id.clone());
}
}
/// Search (recursively) through all memory objects referenced by the given IDs
/// and add all IDs reachable through concrete pointers contained in them to the set of IDs.
///
......@@ -84,23 +53,40 @@ impl State {
ids
}
/// Recursively remove all `caller_stack_ids` not corresponding to the given caller.
pub fn remove_other_caller_stack_ids(&mut self, caller_id: &AbstractIdentifier) {
let mut ids_to_remove = self.caller_stack_ids.clone();
ids_to_remove.remove(caller_id);
for register_value in self.register.values_mut() {
register_value.remove_ids(&ids_to_remove);
if register_value.is_empty() {
*register_value = register_value.top();
/// Add the given `param_object` from the callee state to `self`
/// (where `self` represents the state after returning from the callee).
///
/// `param_value_at_call` is the value that the parameter had at the callsite.
/// It is assumed that all IDs contained in the `param_object` are already replaced with values relative to the caller.
///
/// If the `param_object` corresponds to a unique object in `self`
/// then the contents of that object are overwritten with those of `param_object`.
/// Else the contents are only merged with all possible caller objects,
/// since the exact object that corresponds to the callee object is unknown.
pub fn add_param_object_from_callee(
&mut self,
param_object: AbstractObject,
param_value_at_call: &Data,
) -> Result<(), Error> {
if let Some((caller_id, offset)) = param_value_at_call.get_if_unique_target() {
// The corresponding caller object is unique
if let Some(caller_object) = self.memory.get_object_mut(caller_id) {
caller_object.overwrite_with(&param_object, offset);
} else {
return Err(anyhow!("Missing caller memory object"));
}
} else {
// We cannot exactly identify to which caller object the callee object corresponds.
for (caller_id, offset) in param_value_at_call.get_relative_values() {
if let Some(caller_object) = self.memory.get_object_mut(caller_id) {
let mut param_object = param_object.clone();
param_object.add_offset_to_all_indices(offset);
*caller_object = caller_object.merge(&param_object);
} else {
return Err(anyhow!("Missing caller memory object"));
}
}
}
self.memory.remove_ids(&ids_to_remove);
self.caller_stack_ids = BTreeSet::new();
self.caller_stack_ids.insert(caller_id.clone());
self.ids_known_to_caller = self
.ids_known_to_caller
.difference(&ids_to_remove)
.cloned()
.collect();
Ok(())
}
}
......@@ -86,7 +86,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
domain_input_string: T,
) {
for (target, offset) in pointer.iter() {
if pi_state.caller_stack_ids.contains(target) || pi_state.stack_id == *target {
if pi_state.stack_id == *target {
if let Ok(offset_value) = offset.try_to_offset() {
state.add_new_stack_offset_to_string_entry(
offset_value,
......@@ -115,7 +115,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
let mut domains: Vec<T> = Vec::new();
for (target, offset) in pointer.iter() {
// Check the stack offset map if the target points to a stack position.
if pi_state.caller_stack_ids.contains(target) || pi_state.stack_id == *target {
if pi_state.stack_id == *target {
if let Ok(offset_value) = offset.try_to_offset() {
if let Some(domain) = state.get_stack_offset_to_string_map().get(&offset_value)
{
......
......@@ -220,7 +220,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -283,7 +283,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -308,7 +308,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -336,7 +336,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -359,7 +359,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -401,7 +401,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -447,7 +447,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
let input_target = DataDomain::from(Bitvector::from_i32(0x7000));
......
......@@ -207,7 +207,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -290,7 +290,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -366,7 +366,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -439,7 +439,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -481,7 +481,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -502,7 +502,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -523,7 +523,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -576,7 +576,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -658,7 +658,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -716,7 +716,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......
......@@ -19,7 +19,7 @@ fn test_handle_sprintf_and_snprintf_calls() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -78,7 +78,7 @@ fn test_parse_format_string_and_add_new_string_domain() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -118,7 +118,7 @@ fn test_create_string_domain_for_sprintf_snprintf() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -151,7 +151,7 @@ fn test_create_string_domain_using_data_type_approximations() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -194,7 +194,7 @@ fn test_create_string_domain_using_constants_and_sub_domains() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -358,7 +358,7 @@ fn test_fetch_constant_and_domain_for_format_specifier() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -539,7 +539,7 @@ fn test_fetch_subdomains_if_available() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -597,7 +597,7 @@ fn test_fetch_constant_domain_if_available() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
let string_data: DataDomain<IntervalDomain> = DataDomain::from(Bitvector::from_i32(0x7000));
......
......@@ -119,7 +119,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -153,7 +153,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -204,7 +204,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -226,7 +226,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -253,7 +253,7 @@ mod tests {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......
......@@ -115,7 +115,7 @@ fn test_handle_generic_symbol_calls() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -139,7 +139,7 @@ fn test_handle_unknown_symbol_calls() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -167,7 +167,7 @@ fn test_add_new_string_abstract_domain() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -222,7 +222,7 @@ fn test_merge_domains_from_multiple_pointer_targets() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -304,7 +304,7 @@ fn test_handle_sprintf_and_snprintf_calls_known_format_string() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -339,7 +339,7 @@ fn test_handle_sprintf_and_snprintf_calls_unknown_format_string() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -374,7 +374,7 @@ fn test_insert_constant_char_into_format_string() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -406,7 +406,7 @@ fn test_insert_constant_string_into_format_string() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -435,7 +435,7 @@ fn test_handle_free() {
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......
......@@ -25,7 +25,7 @@ fn test_update_def() {
);
let mem_image = RuntimeMemoryImage::mock();
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
setup.context.block_first_def_set = HashSet::new();
......@@ -138,7 +138,7 @@ fn test_update_jump() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -163,7 +163,7 @@ fn test_update_return() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......@@ -219,7 +219,7 @@ fn test_update_call_stub() {
"func",
);
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
......
......@@ -626,7 +626,7 @@ impl<T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> State<T>
/// Checks whether a target refers to the Stack.
pub fn is_stack_pointer(pi_state: &PointerInferenceState, target: &AbstractIdentifier) -> bool {
pi_state.caller_stack_ids.contains(target) || pi_state.stack_id == *target
pi_state.stack_id == *target
}
}
......
......@@ -329,42 +329,36 @@ fn test_add_pointer_to_variable_maps_if_tracked() {
#[test]
fn test_pointer_targets_partially_tracked() {
let sp_reg = Variable::mock("sp", 4);
let mut mock_state =
State::<CharacterInclusionDomain>::mock_with_default_pi_state(Sub::mock("func"));
let pi_state = mock_state.get_pointer_inference_state().unwrap().clone();
let stack_id = AbstractIdentifier::new(
Tid::new("func"),
AbstractLocation::from_var(&sp_reg).unwrap(),
);
let caller_stack_id = AbstractIdentifier::new(
Tid::new("caller_func"),
AbstractLocation::from_var(&sp_reg).unwrap(),
let heap_id = AbstractIdentifier::new(
Tid::new("heap"),
AbstractLocation::from_var(&Variable::mock("r0", 4)).unwrap(),
);
let stack_id = pi_state.stack_id.clone();
let mut string_pointer = DataDomain::from_target(
stack_id,
heap_id.clone(),
IntervalDomain::new(Bitvector::from_i32(0), Bitvector::from_i32(0)),
);
string_pointer.insert_relative_value(
caller_stack_id.clone(),
stack_id.clone(),
IntervalDomain::new(Bitvector::from_i32(-8), Bitvector::from_i32(-8)),
);
let mut pi_state = mock_state.get_pointer_inference_state().unwrap().clone();
pi_state.caller_stack_ids.insert(caller_stack_id);
mock_state.set_pointer_inference_state(Some(pi_state.clone()));
assert!(!mock_state.pointer_targets_partially_tracked(&pi_state, &string_pointer));
mock_state
.stack_offset_to_string_map
.insert(0, CharacterInclusionDomain::Top);
.insert(-8, CharacterInclusionDomain::Top);
assert!(mock_state.pointer_targets_partially_tracked(&pi_state, &string_pointer));
assert!(mock_state.stack_offset_to_string_map.contains_key(&(-8)));
assert!(mock_state.heap_to_string_map.contains_key(&heap_id));
}
#[test]
......
......@@ -223,7 +223,7 @@ pub mod tests {
let project = mock_project();
let graph = crate::analysis::graph::get_program_cfg(&project.program, HashSet::new());
let mut pi_results = PointerInferenceComputation::mock(&project);
pi_results.compute();
pi_results.compute(false);
let mut format_string_index: HashMap<String, usize> = HashMap::new();
format_string_index.insert("sprintf".to_string(), 1);
// Get the BlkEnd node with the function call.
......
......@@ -58,42 +58,41 @@ impl Arg {
/// If the argument is a stack argument,
/// return its offset relative to the current stack register value.
/// Return an error for register arguments or if the offset could not be computed.
pub fn eval_stack_offset(&self, stack_register: &Variable) -> Result<Bitvector, Error> {
pub fn eval_stack_offset(&self) -> Result<Bitvector, Error> {
let expression = match self {
Arg::Register { .. } => return Err(anyhow!("The argument is not a stack argument.")),
Arg::Stack { address, .. } => address,
};
Self::eval_stack_offset_expression(expression, stack_register)
Self::eval_stack_offset_expression(expression)
}
/// If the given expression computes a constant offset to the given stack register,
/// then return the offset.
/// Else return an error.
fn eval_stack_offset_expression(
expression: &Expression,
stack_register: &Variable,
) -> Result<Bitvector, Error> {
fn eval_stack_offset_expression(expression: &Expression) -> Result<Bitvector, Error> {
match expression {
Expression::Var(var) => {
if var == stack_register {
Ok(Bitvector::zero(var.size.into()))
} else {
Err(anyhow!("Input register is not the stack register"))
}
}
Expression::Var(var) => Ok(Bitvector::zero(var.size.into())),
Expression::Const(bitvec) => Ok(bitvec.clone()),
Expression::BinOp { op, lhs, rhs } => {
let lhs = Self::eval_stack_offset_expression(lhs, stack_register)?;
let rhs = Self::eval_stack_offset_expression(rhs, stack_register)?;
let lhs = Self::eval_stack_offset_expression(lhs)?;
let rhs = Self::eval_stack_offset_expression(rhs)?;
lhs.bin_op(*op, &rhs)
}
Expression::UnOp { op, arg } => {
let arg = Self::eval_stack_offset_expression(arg, stack_register)?;
let arg = Self::eval_stack_offset_expression(arg)?;
arg.un_op(*op)
}
_ => Err(anyhow!("Expression type not supported for argument values")),
}
}
/// Return the bytesize of the argument.
pub fn bytesize(&self) -> ByteSize {
match self {
Arg::Register { expr, .. } => expr.bytesize(),
Arg::Stack { size, .. } => *size,
}
}
}
/// An extern symbol represents a funtion that is dynamically linked from another binary.
......
......@@ -145,6 +145,15 @@ impl MemorySegment {
}
impl RuntimeMemoryImage {
/// Generate a runtime memory image containing no memory segments.
/// Primarily useful in situations where any access to global memory would be an error.
pub fn empty(is_little_endian: bool) -> RuntimeMemoryImage {
RuntimeMemoryImage {
memory_segments: Vec::new(),
is_little_endian,
}
}
/// Generate a runtime memory image for a given binary.
///
/// The function can parse ELF and PE files as input.
......
......@@ -17,10 +17,22 @@ void print_array_sum(int* array) {
int main() {
int* array = calloc(5, sizeof(int));
// intraprocedural buffer overflow
for(int i = 0; i<= 10; i++) {
array[i] = i*i; // Out-of-bounds write for arrays that are too small.
}
// interprocedural buffer overflow
set_array_elements(array);
free(array);
array = malloc(5 * sizeof(int));
// intraprocedural buffer overflow
int sum = 0;
for(int i = 0; i<= 10; i++) {
sum += array[i]; // Out-of-bounds read for arrays that are too small.
}
printf("%d\n", sum);
// interprocedural buffer overflow
print_array_sum(array);
puts((void*) array - 1); // Parameter is an out-of-bounds pointer.
......
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