Unverified Commit 92b24f9e by Enkelmann Committed by GitHub

Implement stubs of common LibC functions for PointerInference analysis (#349)

parent 46bf2bd2
......@@ -237,7 +237,8 @@
"malloc",
"calloc",
"realloc",
"xmalloc"
"xmalloc",
"strdup"
]
},
"StringAbstraction": {
......
......@@ -42,7 +42,7 @@ mod state;
use state::State;
mod access_pattern;
pub use access_pattern::AccessPattern;
mod stubs;
pub mod stubs;
/// Generate the computation object for the fixpoint computation
/// and set the node values for all function entry nodes.
......
//! This module contains stubs for frequently used LibC-symbols
//! as well as helper functions for handling the effects of calls to these functions.
use super::State;
use crate::abstract_domain::AbstractDomain;
use crate::abstract_domain::BitvectorDomain;
......
use crate::abstract_domain::*;
use crate::analysis::function_signature::AccessPattern;
use crate::analysis::function_signature::FunctionSignature;
use crate::analysis::graph::Graph;
use crate::intermediate_representation::*;
......@@ -11,6 +12,8 @@ use super::{Config, Data, VERSION};
/// Contains methods of the `Context` struct that deal with the manipulation of abstract IDs.
mod id_manipulation;
/// Methods and functions for handling extern symbol stubs.
mod stubs;
/// 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;
......@@ -27,6 +30,8 @@ pub struct Context<'a> {
pub extern_symbol_map: &'a BTreeMap<Tid, ExternSymbol>,
/// Maps the TIDs of internal functions to the function signatures computed for it.
pub fn_signatures: &'a BTreeMap<Tid, FunctionSignature>,
/// Maps the names of stubbed extern symbols to the corresponding function signatures.
pub extern_fn_param_access_patterns: BTreeMap<&'static str, Vec<AccessPattern>>,
/// A channel where found CWE warnings and log messages should be sent to.
/// The receiver may filter or modify the warnings before presenting them to the user.
/// For example, the same CWE warning will be found several times
......@@ -50,6 +55,8 @@ impl<'a> Context<'a> {
project: analysis_results.project,
extern_symbol_map: &analysis_results.project.program.term.extern_symbols,
fn_signatures: analysis_results.function_signatures.unwrap(),
extern_fn_param_access_patterns:
crate::analysis::function_signature::stubs::generate_param_access_stubs(),
log_collector,
allocation_symbols: config.allocation_symbols,
}
......
use super::*;
impl<'a> Context<'a> {
/// Handle the parameters of a call to sscanf by assuming that arbitrary values are written to the targets of the variadic parameters.
pub fn handle_params_of_sscanf_call(
&self,
state: &State,
new_state: &mut State,
sscanf_symbol: &ExternSymbol,
call_tid: &Tid,
) -> Result<(), Error> {
use crate::utils::arguments;
let format_string_address = state
.eval_parameter_arg(
&sscanf_symbol.parameters[1],
&self.project.runtime_memory_image,
)?
.get_if_absolute_value()
.ok_or_else(|| anyhow!("Format string may not be a constant string"))?
.try_to_bitvec()?;
let format_string = arguments::parse_format_string_destination_and_return_content(
format_string_address,
&self.project.runtime_memory_image,
)?;
// Calculate the data types of the parameters
let format_string_param_types = arguments::parse_format_string_parameters(
&format_string,
&self.project.datatype_properties,
)?;
// All variadic parameters are pointers (to their respective data types)
let format_string_params =
vec![
(Datatype::Pointer, self.project.stack_pointer_register.size);
format_string_param_types.len()
];
let format_string_args = arguments::calculate_parameter_locations(
format_string_params,
sscanf_symbol,
self.project,
);
for (arg, (datatype, size)) in format_string_args
.iter()
.zip(format_string_param_types.iter())
{
if let Ok(param) = state.eval_parameter_arg(arg, &self.project.runtime_memory_image) {
if *datatype != Datatype::Pointer {
self.log_debug(
new_state.store_value(
&param,
&Data::new_top(*size),
&self.project.runtime_memory_image,
),
Some(call_tid),
);
} else {
for id in param.referenced_ids() {
new_state
.memory
.assume_arbitrary_writes_to_object(id, &BTreeSet::new());
}
}
}
}
Ok(())
}
/// For stubbed function that may write to a memory object provided through a parameter
/// we assume for the corresponding memory objects that arbitrary writes to them may have happened.
///
/// This function uses the same access patterns for stubbed functions as the [`function_signature`](crate::analysis::function_signature) analysis
/// for determine which parameters are accessed mutably.
pub fn handle_parameter_access_for_stubbed_functions(
&self,
state: &State,
new_state: &mut State,
extern_symbol: &ExternSymbol,
) {
let access_patterns = self
.extern_fn_param_access_patterns
.get(extern_symbol.name.as_str())
.unwrap();
for (arg, access_pattern) in extern_symbol.parameters.iter().zip(access_patterns.iter()) {
if access_pattern.is_mutably_dereferenced() {
if let Ok(param) = state.eval_parameter_arg(arg, &self.project.runtime_memory_image)
{
for id in param.referenced_ids() {
new_state
.memory
.assume_arbitrary_writes_to_object(id, &BTreeSet::new());
}
}
}
}
}
/// Compute the return values for stubbed extern symbols.
/// Note that this function does not handle malloc-like symbols that return a newly created heap object as a return value.
pub fn compute_return_value_for_stubbed_function(
&self,
state: &State,
extern_symbol: &ExternSymbol,
) -> Data {
use return_value_stubs::*;
match extern_symbol.name.as_str() {
"memcpy" | "memmove" | "memset" | "strcat" | "strcpy" | "strncat" | "strncpy" => {
copy_param(state, extern_symbol, 0, &self.project.runtime_memory_image)
}
"fgets" => or_null(copy_param(
state,
extern_symbol,
0,
&self.project.runtime_memory_image,
)),
"strchr" | "strrchr" | "strstr" => or_null(param_plus_unknown_offset(
state,
extern_symbol,
0,
&self.project.runtime_memory_image,
)),
_ => untracked(self.project.stack_pointer_register.size),
}
}
}
/// Helper functions for computing return values for extern symbol calls.
pub mod return_value_stubs {
use super::*;
/// An untracked value is just a `Top` value.
/// It is used for any non-pointer return values.
pub fn untracked(register_size: ByteSize) -> Data {
Data::new_top(register_size)
}
/// A return value that is just a copy of a parameter.
pub fn copy_param(
state: &State,
extern_symbol: &ExternSymbol,
param_index: usize,
global_memory: &RuntimeMemoryImage,
) -> Data {
state
.eval_parameter_arg(&extern_symbol.parameters[param_index], global_memory)
.unwrap_or_else(|_| Data::new_top(extern_symbol.parameters[param_index].bytesize()))
}
/// A return value that adds an unknown offset to a given parameter.
/// E.g. if the parameter is a pointer to a string,
/// this return value would describe a pointer to an offset inside the string.
pub fn param_plus_unknown_offset(
state: &State,
extern_symbol: &ExternSymbol,
param_index: usize,
global_memory: &RuntimeMemoryImage,
) -> Data {
let param = state
.eval_parameter_arg(&extern_symbol.parameters[param_index], global_memory)
.unwrap_or_else(|_| Data::new_top(extern_symbol.parameters[param_index].bytesize()));
param.add_offset(&IntervalDomain::new_top(param.bytesize()))
}
/// The return value may also be zero in addition to its other possible values.
pub fn or_null(data: Data) -> Data {
data.merge(&Bitvector::zero(data.bytesize().into()).into())
}
}
......@@ -336,3 +336,35 @@ fn get_unsound_caller_ids() {
);
assert_eq!(unsound_ids, BTreeSet::from_iter([new_id("caller", "RAX")]));
}
#[test]
fn handle_extern_symbol_stubs() {
let context = mock_context();
let mut state = State::new(&context.project.stack_pointer_register, Tid::new("main"));
let mut extern_symbol = ExternSymbol::mock_x64("strchr");
extern_symbol.parameters = vec![Arg::mock_register("RDI", 8), Arg::mock_register("RSI", 8)];
state.set_register(
&Variable::mock("RDI", 8),
Data::from_target(
AbstractIdentifier::mock("param", "RBX", 8),
Bitvector::from_u64(0).into(),
),
);
let mut new_state = state.clone();
let cconv = CallingConvention::mock_x64();
new_state.clear_non_callee_saved_register(&cconv.callee_saved_register[..]);
context.handle_parameter_access_for_stubbed_functions(&state, &mut new_state, &extern_symbol);
let return_value = context.compute_return_value_for_stubbed_function(&state, &extern_symbol);
new_state.set_register(&cconv.integer_return_register[0], return_value);
assert_eq!(
new_state.get_register(&Variable::mock("RAX", 8)),
Data::from_target(
AbstractIdentifier::mock("param", "RBX", 8),
IntervalDomain::new_top(ByteSize::new(8)),
)
.merge(&Bitvector::from_u64(0).into())
);
}
......@@ -235,6 +235,18 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
self.adjust_stack_register_on_return_from_call(state, &mut new_state);
match extern_symbol.name.as_str() {
"sscanf" => {
self.log_debug(
self.handle_params_of_sscanf_call(
state,
&mut new_state,
extern_symbol,
&call.tid,
),
Some(&call.tid),
);
Some(new_state)
}
malloc_like_fn if self.allocation_symbols.iter().any(|x| x == malloc_like_fn) => {
Some(self.add_new_object_in_call_return_register(
new_state,
......@@ -242,6 +254,23 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
extern_symbol,
))
}
stubbed_fn
if self
.extern_fn_param_access_patterns
.contains_key(stubbed_fn) =>
{
self.handle_parameter_access_for_stubbed_functions(
state,
&mut new_state,
extern_symbol,
);
let return_value =
self.compute_return_value_for_stubbed_function(state, extern_symbol);
new_state.set_register(&cconv.integer_return_register[0], return_value);
Some(new_state)
}
_ => Some(self.handle_generic_extern_call(state, new_state, call, extern_symbol)),
}
} else {
......
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