diff --git a/src/config.json b/src/config.json index 94bdeb315..2229a4935 100644 --- a/src/config.json +++ b/src/config.json @@ -237,7 +237,8 @@ "malloc", "calloc", "realloc", - "xmalloc" + "xmalloc", + "strdup" ] }, "StringAbstraction": { diff --git a/src/cwe_checker_lib/src/analysis/function_signature/mod.rs b/src/cwe_checker_lib/src/analysis/function_signature/mod.rs index 8d05ea941..f8832f135 100644 --- a/src/cwe_checker_lib/src/analysis/function_signature/mod.rs +++ b/src/cwe_checker_lib/src/analysis/function_signature/mod.rs @@ -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. diff --git a/src/cwe_checker_lib/src/analysis/function_signature/stubs.rs b/src/cwe_checker_lib/src/analysis/function_signature/stubs.rs index 63e06cb1e..5e530cd43 100644 --- a/src/cwe_checker_lib/src/analysis/function_signature/stubs.rs +++ b/src/cwe_checker_lib/src/analysis/function_signature/stubs.rs @@ -1,3 +1,6 @@ +//! 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; diff --git a/src/cwe_checker_lib/src/analysis/pointer_inference/context/mod.rs b/src/cwe_checker_lib/src/analysis/pointer_inference/context/mod.rs index 15eebde8c..9252fdf58 100644 --- a/src/cwe_checker_lib/src/analysis/pointer_inference/context/mod.rs +++ b/src/cwe_checker_lib/src/analysis/pointer_inference/context/mod.rs @@ -1,4 +1,5 @@ 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, /// Maps the TIDs of internal functions to the function signatures computed for it. pub fn_signatures: &'a BTreeMap, + /// Maps the names of stubbed extern symbols to the corresponding function signatures. + pub extern_fn_param_access_patterns: BTreeMap<&'static str, Vec>, /// 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, } diff --git a/src/cwe_checker_lib/src/analysis/pointer_inference/context/stubs.rs b/src/cwe_checker_lib/src/analysis/pointer_inference/context/stubs.rs new file mode 100644 index 000000000..48dd74163 --- /dev/null +++ b/src/cwe_checker_lib/src/analysis/pointer_inference/context/stubs.rs @@ -0,0 +1,167 @@ +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( + ¶m, + &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()) + } +} diff --git a/src/cwe_checker_lib/src/analysis/pointer_inference/context/tests.rs b/src/cwe_checker_lib/src/analysis/pointer_inference/context/tests.rs index b89ba6fd0..09a4b9f49 100644 --- a/src/cwe_checker_lib/src/analysis/pointer_inference/context/tests.rs +++ b/src/cwe_checker_lib/src/analysis/pointer_inference/context/tests.rs @@ -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()) + ); +} diff --git a/src/cwe_checker_lib/src/analysis/pointer_inference/context/trait_impls.rs b/src/cwe_checker_lib/src/analysis/pointer_inference/context/trait_impls.rs index fbe0190d3..3c9bf3441 100644 --- a/src/cwe_checker_lib/src/analysis/pointer_inference/context/trait_impls.rs +++ b/src/cwe_checker_lib/src/analysis/pointer_inference/context/trait_impls.rs @@ -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 {