Unverified Commit cbe2f035 by Enkelmann Committed by GitHub

More detailed reports for CWE-119 check (#333)

parent e23a4fcd
use super::Context;
use crate::abstract_domain::{AbstractIdentifier, DataDomain, IntervalDomain, TryToBitvec};
use crate::prelude::*;
/// This struct contains the computed bound for an object.
/// If the object is a parameter object,
/// it also contains metadata about the source object used to determine the bound for the parameter object.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct BoundsMetadata {
/// The source object (and the offset into the source object that the object points to)
/// if the bound of the memory object is derived from another object (e.g. for parameter objects).
pub source: Option<DataDomain<IntervalDomain>>,
/// The resulting bound for the memory object.
pub resulting_bound: i64,
}
impl BoundsMetadata {
/// Create a new instance without source metadata.
pub fn new(resulting_bound: i64) -> BoundsMetadata {
BoundsMetadata {
source: None,
resulting_bound,
}
}
/// Create an instance where the source of the bound is given by `id + offset`.
pub fn from_source(
id: &AbstractIdentifier,
offset: &IntervalDomain,
resulting_bound: i64,
) -> BoundsMetadata {
BoundsMetadata {
source: Some(DataDomain::from_target(id.clone(), offset.clone())),
resulting_bound,
}
}
}
/// If `bound` is `None`, replace it with `new_bound`.
/// Else only replace it if the bound in `new_bound` is smaller than the existing bound.
fn replace_if_smaller_bound(bound: &mut Option<BoundsMetadata>, new_bound: BoundsMetadata) {
if let Some(old_bound) = bound {
if old_bound.resulting_bound > new_bound.resulting_bound {
*bound = Some(new_bound);
}
} else {
*bound = Some(new_bound);
}
}
/// If `bound` is `None`, replace it with `new_bound`.
/// Else only replace it if the bound in `new_bound` is larger than the existing bound.
fn replace_if_larger_bound(bound: &mut Option<BoundsMetadata>, new_bound: BoundsMetadata) {
if let Some(old_bound) = bound {
if old_bound.resulting_bound < new_bound.resulting_bound {
*bound = Some(new_bound);
}
} else {
*bound = Some(new_bound);
}
}
impl<'a> Context<'a> {
/// Compute the bounds of the memory object associated with the given parameter ID.
///
/// Since the memory object associated to a parameter may not be unique
/// the bounds are only approximated from those objects where exact bounds could be determined.
/// If different objects were found the bounds are approximated by the strictest bounds that were found.
fn compute_bounds_of_param_id(
&self,
param_object_id: &AbstractIdentifier,
) -> (Option<BoundsMetadata>, Option<BoundsMetadata>) {
let object_data = self.recursively_substitute_param_values(&DataDomain::from_target(
param_object_id.clone(),
Bitvector::zero(param_object_id.bytesize().into()).into(),
));
let mut lower_bound: Option<BoundsMetadata> = None;
let mut upper_bound: Option<BoundsMetadata> = None;
for (id, offset) in object_data.get_relative_values() {
// Right now we ignore cases where we do not know the exact offset into the object.
let concrete_offset = match offset.try_to_offset() {
Ok(offset) => offset,
Err(_) => continue,
};
if self
.malloc_tid_to_object_size_map
.contains_key(id.get_tid())
{
replace_if_larger_bound(
&mut lower_bound,
BoundsMetadata::from_source(id, offset, -concrete_offset),
);
let object_size = self.compute_size_of_heap_object(id);
if let Ok(concrete_object_size) = object_size.try_to_offset() {
replace_if_smaller_bound(
&mut upper_bound,
BoundsMetadata::from_source(
id,
offset,
concrete_object_size - concrete_offset,
),
);
}
} else if self.is_stack_frame_id(id) {
let stack_frame_upper_bound = self
.function_signatures
.get(id.get_tid())
.unwrap()
.get_stack_params_total_size();
replace_if_smaller_bound(
&mut upper_bound,
BoundsMetadata::from_source(
id,
offset,
stack_frame_upper_bound - concrete_offset,
),
);
// We do not set a lower bound since we do not know the concrete call site for stack pointers,
// which we would need to determine a correct lower bound.
}
// FIXME: Cases not handled here include unresolved parameter IDs, unknown IDs and global pointers.
// For the first two we do not have any size information.
// For global pointers we need some kind of pre-analysis so that we do not have to assume
// that the pointer may address the complete range of global data addresses.
}
(lower_bound, upper_bound)
}
/// Compute the bounds of a memory object given by the provided `object_id`.
///
/// Returns `(lower_bound, upper_bound)`, where the bounds may be `None` if they could not be determined.
pub fn compute_bounds_of_id(
&self,
object_id: &AbstractIdentifier,
current_stack_frame_id: &AbstractIdentifier,
) -> (Option<BoundsMetadata>, Option<BoundsMetadata>) {
if self
.malloc_tid_to_object_size_map
.contains_key(object_id.get_tid())
{
let object_size = self.compute_size_of_heap_object(object_id);
if let Ok(object_size) = object_size.try_to_offset() {
(
Some(BoundsMetadata::new(0)),
Some(BoundsMetadata::new(object_size)),
)
} else {
(Some(BoundsMetadata::new(0)), None)
}
} else if object_id == current_stack_frame_id {
let stack_frame_upper_bound = self
.function_signatures
.get(object_id.get_tid())
.unwrap()
.get_stack_params_total_size();
(None, Some(BoundsMetadata::new(stack_frame_upper_bound)))
} else if object_id.get_tid() == current_stack_frame_id.get_tid()
&& object_id.get_path_hints().is_empty()
{
// Handle parameter IDs
self.compute_bounds_of_param_id(object_id)
} else {
// The type of object is unknown, thus the size restrictions are also unknown.
(None, None)
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::analysis::pointer_inference::Data;
use std::collections::{HashMap, HashSet};
#[test]
fn test_compute_bounds_of_param_id() {
let mut context = Context::mock_x64();
let param_id = AbstractIdentifier::mock("func", "RDI", 8);
let param_id_2 = AbstractIdentifier::mock("func", "RSI", 8);
let callsite_id = AbstractIdentifier::mock("callsite_id", "RDI", 8);
let callsite_id_2 = AbstractIdentifier::mock("callsite_id", "RSI", 8);
let malloc_call_id = AbstractIdentifier::mock("malloc_call", "RAX", 8);
let main_stack_id = AbstractIdentifier::mock("main", "RSP", 8);
let param_value = Data::from_target(malloc_call_id.clone(), Bitvector::from_i64(2).into());
let param_value_2 =
Data::from_target(main_stack_id.clone(), Bitvector::from_i64(-10).into());
let param_replacement_map = HashMap::from([
(callsite_id, param_value.clone()),
(callsite_id_2, param_value_2.clone()),
]);
let callee_to_callsites_map =
HashMap::from([(Tid::new("func"), HashSet::from([Tid::new("callsite_id")]))]);
context.param_replacement_map = param_replacement_map;
context.callee_to_callsites_map = callee_to_callsites_map;
context
.malloc_tid_to_object_size_map
.insert(Tid::new("malloc_call"), Data::from(Bitvector::from_i64(42)));
context.call_to_caller_fn_map = HashMap::from([
(Tid::new("malloc_call"), Tid::new("main")),
(Tid::new("callsite_id"), Tid::new("main")),
]);
// Test bound computation if the param gets resolved to a heap object
let (lower_bound, upper_bound) = context.compute_bounds_of_param_id(&param_id);
assert_eq!(lower_bound.unwrap().resulting_bound, -2);
assert_eq!(upper_bound.unwrap().resulting_bound, 40);
// Test bound computation if the param gets resolved to a caller stack frame
let (lower_bound, upper_bound) = context.compute_bounds_of_param_id(&param_id_2);
assert_eq!(lower_bound, None);
assert_eq!(upper_bound.unwrap().resulting_bound, 10);
}
}
...@@ -48,91 +48,3 @@ fn test_compute_size_value_of_malloc_like_call() { ...@@ -48,91 +48,3 @@ fn test_compute_size_value_of_malloc_like_call() {
) )
.is_none()); .is_none());
} }
#[test]
fn test_substitute_param_values_context_sensitive() {
let mut context = Context::mock_x64();
let param_id = AbstractIdentifier::mock("func", "RDI", 8);
let callsite_id = AbstractIdentifier::mock("callsite_id", "RDI", 8);
let recursive_param_id = AbstractIdentifier::mock("main", "RSI", 8);
let recursive_callsite_id = AbstractIdentifier::mock("recursive_callsite_id", "RSI", 8);
let param_value = Data::from_target(recursive_param_id.clone(), Bitvector::from_i64(1).into());
let recursive_param_value = Data::from(Bitvector::from_i64(41));
let param_replacement_map = HashMap::from([
(callsite_id, param_value.clone()),
(recursive_callsite_id.clone(), recursive_param_value),
]);
let callee_to_callsites_map = HashMap::from([
(Tid::new("func"), HashSet::from([Tid::new("callsite_id")])),
(
Tid::new("main"),
HashSet::from([Tid::new("recursive_callsite_id")]),
),
]);
let call_to_caller_map = HashMap::from([
(Tid::new("callsite_id"), Tid::new("main")),
(
Tid::new("recursive_callsite_id"),
Tid::new("somer_other_fn_id"),
),
]);
context.param_replacement_map = param_replacement_map;
context.callee_to_callsites_map = callee_to_callsites_map;
context.call_to_caller_fn_map = call_to_caller_map;
// non-recursive substitution
let result = context.substitute_param_values_context_sensitive(
&Data::from_target(param_id.clone(), Bitvector::from_i64(5).into()),
&Tid::new("callsite_id"),
&Tid::new("func"),
);
assert_eq!(
result,
Data::from_target(recursive_param_id.clone(), Bitvector::from_i64(6).into())
);
// recursive substitution
let result = context.recursively_substitute_param_values_context_sensitive(
&Data::from_target(param_id, Bitvector::from_i64(5).into()),
&Tid::new("func"),
&[Tid::new("callsite_id"), Tid::new("recursive_callsite_id")],
);
println!("{:#}", result.to_json_compact());
assert_eq!(result, Bitvector::from_i64(47).into());
}
#[test]
fn test_substitute_param_values() {
let mut context = Context::mock_x64();
let param_id = AbstractIdentifier::mock("func", "RDI", 8);
let callsite_id = AbstractIdentifier::mock("callsite_id", "RDI", 8);
let recursive_param_id = AbstractIdentifier::mock("main", "RSI", 8);
let recursive_callsite_id = AbstractIdentifier::mock("recursive_callsite_id", "RSI", 8);
let param_value = Data::from_target(recursive_param_id.clone(), Bitvector::from_i64(1).into());
let recursive_param_value = Data::from(Bitvector::from_i64(39));
let param_replacement_map = HashMap::from([
(callsite_id, param_value.clone()),
(recursive_callsite_id.clone(), recursive_param_value),
]);
let callee_to_callsites_map = HashMap::from([
(Tid::new("func"), HashSet::from([Tid::new("callsite_id")])),
(
Tid::new("main"),
HashSet::from([Tid::new("recursive_callsite_id")]),
),
]);
context.param_replacement_map = param_replacement_map;
context.callee_to_callsites_map = callee_to_callsites_map;
// non-recursive substitution
let (result_absolute, result) = context.substitute_param_values(&param_id);
assert!(result_absolute.is_none());
assert_eq!(result, param_value);
// recursive substitution
let result = context.recursively_substitute_param_values(&Data::from_target(
param_id,
Bitvector::from_i64(5).into(),
));
assert_eq!(result, Bitvector::from_i64(45).into());
}
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