use crate::abstract_domain::*;
use crate::analysis::callgraph::CallGraph;
use crate::analysis::function_signature::FunctionSignature;
use crate::analysis::graph::Graph;
use crate::analysis::pointer_inference::{Data, PointerInference};
use crate::intermediate_representation::*;
use crate::utils::log::{CweWarning, LogMessage, LogThreadMsg};
use crate::{analysis::vsa_results::VsaResult, prelude::*};
use std::collections::{BTreeMap, HashMap, HashSet};
use super::state::State;
mod bounds_computation;
pub use bounds_computation::BoundsMetadata;
mod param_replacement;
mod trait_impls;
pub struct Context<'a> {
pub project: &'a Project,
pub graph: &'a Graph<'a>,
pub pointer_inference: &'a PointerInference<'a>,
pub function_signatures: &'a BTreeMap<Tid, FunctionSignature>,
pub callee_to_callsites_map: HashMap<Tid, HashSet<Tid>>,
pub param_replacement_map: HashMap<AbstractIdentifier, Data>,
pub malloc_tid_to_object_size_map: HashMap<Tid, Data>,
pub call_to_caller_fn_map: HashMap<Tid, Tid>,
pub callgraph: CallGraph<'a>,
pub log_collector: crossbeam_channel::Sender<LogThreadMsg>,
}
impl<'a> Context<'a> {
pub fn new<'b>(
analysis_results: &'b AnalysisResults<'a>,
log_collector: crossbeam_channel::Sender<LogThreadMsg>,
) -> Context<'a>
where
'a: 'b,
{
let project = analysis_results.project;
let callgraph = crate::analysis::callgraph::get_program_callgraph(&project.program);
Context {
project,
graph: analysis_results.control_flow_graph,
pointer_inference: analysis_results.pointer_inference.unwrap(),
function_signatures: analysis_results.function_signatures.unwrap(),
callee_to_callsites_map: compute_callee_to_call_sites_map(project),
param_replacement_map: param_replacement::compute_param_replacement_map(
analysis_results,
),
malloc_tid_to_object_size_map: compute_size_values_of_malloc_calls(analysis_results),
call_to_caller_fn_map: compute_call_to_caller_map(project),
callgraph,
log_collector,
}
}
pub fn is_stack_frame_id(&self, id: &AbstractIdentifier) -> bool {
self.project.program.term.subs.contains_key(id.get_tid())
&& *id
== AbstractIdentifier::from_var(
id.get_tid().clone(),
&self.project.stack_pointer_register,
)
}
pub fn compute_size_of_heap_object(&self, object_id: &AbstractIdentifier) -> BitvectorDomain {
if let Some(object_size) = self.malloc_tid_to_object_size_map.get(object_id.get_tid()) {
let fn_tid_at_malloc_call = self.call_to_caller_fn_map[object_id.get_tid()].clone();
let object_size = self.recursively_substitute_param_values_context_sensitive(
object_size,
&fn_tid_at_malloc_call,
object_id.get_path_hints(),
);
let object_size = self.recursively_substitute_param_values(&object_size);
let object_size = match object_size.get_absolute_value() {
Some(size) => {
if let Ok((lower_bound, upper_bound)) = size.try_to_offset_interval() {
let (lower_bound, upper_bound) = (
Bitvector::from_i64(lower_bound)
.into_resize_signed(object_size.bytesize()),
Bitvector::from_i64(upper_bound)
.into_resize_signed(object_size.bytesize()),
);
if lower_bound.is_zero() || upper_bound.is_zero() {
self.log_info(object_id.get_tid(), "Heap object may have size zero. This may indicate an instance of CWE-687.");
}
if upper_bound.sign_bit().to_bool() || upper_bound.is_zero() {
BitvectorDomain::new_top(object_size.bytesize())
} else if lower_bound.sign_bit().to_bool() || lower_bound.is_zero() {
upper_bound.into()
} else {
lower_bound.into()
}
} else {
BitvectorDomain::new_top(object_size.bytesize())
}
}
None => BitvectorDomain::new_top(object_size.bytesize()),
};
object_size
} else {
BitvectorDomain::new_top(object_id.bytesize())
}
}
pub fn log_debug(&self, tid: &Tid, msg: impl ToString) {
let log_msg = LogMessage {
text: msg.to_string(),
level: crate::utils::log::LogLevel::Debug,
location: Some(tid.clone()),
source: Some(super::CWE_MODULE.name.to_string()),
};
self.log_collector.send(log_msg.into()).unwrap();
}
pub fn log_info(&self, tid: &Tid, msg: impl ToString) {
let log_msg = LogMessage {
text: msg.to_string(),
level: crate::utils::log::LogLevel::Info,
location: Some(tid.clone()),
source: Some(super::CWE_MODULE.name.to_string()),
};
self.log_collector.send(log_msg.into()).unwrap();
}
pub fn check_param_at_call(
&self,
state: &mut State,
param: &Arg,
call_tid: &Tid,
target_fn_name: Option<&str>,
) {
if let Some(possible_address) = self
.pointer_inference
.eval_parameter_arg_at_call(call_tid, param)
{
let warnings = state.check_address_access(&possible_address, ByteSize::new(1), self);
if !warnings.is_empty() {
let description = match target_fn_name {
Some(target_name) => format!(
"(Buffer Overflow) Call to {} at {} may access out-of-bounds memory.",
target_name, &call_tid.address
),
None => format!(
"(Buffer Overflow) Call at {} may access out-of-bounds memory.",
&call_tid.address
),
};
let mut cwe_warning =
CweWarning::new("CWE119", super::CWE_MODULE.version, description);
cwe_warning.tids = vec![format!("{call_tid}")];
cwe_warning.addresses = vec![call_tid.address.to_string()];
cwe_warning.other = vec![warnings];
self.log_collector.send(cwe_warning.into()).unwrap();
}
}
}
}
fn compute_callee_to_call_sites_map(project: &Project) -> HashMap<Tid, HashSet<Tid>> {
let mut callee_to_call_sites_map: HashMap<Tid, HashSet<Tid>> = HashMap::new();
for sub in project.program.term.subs.values() {
for blk in &sub.term.blocks {
for jmp in &blk.term.jmps {
match &jmp.term {
Jmp::Call { target, .. } => {
let callsites = callee_to_call_sites_map.entry(target.clone()).or_default();
callsites.insert(jmp.tid.clone());
}
Jmp::CallInd { .. } => (), _ => (),
}
}
}
}
callee_to_call_sites_map
}
fn compute_size_values_of_malloc_calls(analysis_results: &AnalysisResults) -> HashMap<Tid, Data> {
let project = analysis_results.project;
let pointer_inference = analysis_results.pointer_inference.unwrap();
let mut malloc_size_map = HashMap::new();
for sub in analysis_results.project.program.term.subs.values() {
for blk in &sub.term.blocks {
for jmp in &blk.term.jmps {
if let Jmp::Call { target, .. } = &jmp.term {
if let Some(symbol) = project.program.term.extern_symbols.get(target) {
if let Some(size_value) = compute_size_value_of_malloc_like_call(
&jmp.tid,
symbol,
pointer_inference,
) {
malloc_size_map.insert(jmp.tid.clone(), size_value);
}
}
}
}
}
}
malloc_size_map
}
fn compute_size_value_of_malloc_like_call(
jmp_tid: &Tid,
called_symbol: &ExternSymbol,
pointer_inference: &PointerInference,
) -> Option<Data> {
match called_symbol.name.as_str() {
"malloc" | "operator.new" | "operator.new[]" => {
let size_param = &called_symbol.parameters[0];
match pointer_inference.eval_parameter_arg_at_call(jmp_tid, size_param) {
Some(size_value) => Some(size_value),
None => Some(Data::new_top(size_param.bytesize())),
}
}
"realloc" => {
let size_param = &called_symbol.parameters[1];
match pointer_inference.eval_parameter_arg_at_call(jmp_tid, size_param) {
Some(size_value) => Some(size_value),
None => Some(Data::new_top(size_param.bytesize())),
}
}
"reallocarray" => {
match called_symbol.parameters.as_slice() {
[_, count_param, size_param] => {
match (
pointer_inference.eval_parameter_arg_at_call(jmp_tid, count_param),
pointer_inference.eval_parameter_arg_at_call(jmp_tid, size_param),
) {
(Some(count_value), Some(size_value)) => {
Some(count_value.bin_op(BinOpType::IntMult, &size_value))
}
_ => Some(Data::new_top(size_param.bytesize())),
}
}
_ => None,
}
}
"calloc" => {
let count_param = &called_symbol.parameters[0];
let size_param = &called_symbol.parameters[1];
match (
pointer_inference.eval_parameter_arg_at_call(jmp_tid, count_param),
pointer_inference.eval_parameter_arg_at_call(jmp_tid, size_param),
) {
(Some(count_value), Some(size_value)) => {
Some(count_value.bin_op(BinOpType::IntMult, &size_value))
}
_ => Some(Data::new_top(size_param.bytesize())),
}
}
_ => None,
}
}
fn compute_call_to_caller_map(project: &Project) -> HashMap<Tid, Tid> {
let mut call_to_caller_map = HashMap::new();
for (sub_tid, sub) in &project.program.term.subs {
for block in &sub.term.blocks {
for jmp in &block.term.jmps {
match &jmp.term {
Jmp::Call { .. } | Jmp::CallInd { .. } | Jmp::CallOther { .. } => {
call_to_caller_map.insert(jmp.tid.clone(), sub_tid.clone());
}
_ => (),
}
}
}
}
call_to_caller_map
}
#[cfg(test)]
pub mod tests;