-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
More detailed reports for CWE-119 check (#333)
- Loading branch information
Showing
5 changed files
with
564 additions
and
448 deletions.
There are no files selected for viewing
213 changes: 213 additions & 0 deletions
213
src/cwe_checker_lib/src/checkers/cwe_119/context/bounds_computation.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
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(¶m_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(¶m_id_2); | ||
assert_eq!(lower_bound, None); | ||
assert_eq!(upper_bound.unwrap().resulting_bound, 10); | ||
} | ||
} |
Oops, something went wrong.