Unverified Commit 74976f0d by Enkelmann Committed by GitHub

implement FunctionSignature analysis (#267)

parent e5631994
......@@ -8,6 +8,8 @@ on:
env:
CARGO_TERM_COLOR: always
GHIDRA_RELEASE_TAG: Ghidra_10.1_build
GHIDRA_VERSION: ghidra_10.1_PUBLIC_20211210
jobs:
......@@ -28,10 +30,10 @@ jobs:
architecture: x64
- name: Install Ghidra
run: |
curl -fSL https://www.ghidra-sre.org/ghidra_9.2.1_PUBLIC_20201215.zip -o ghidra.zip
unzip -q ghidra.zip
mv ghidra_9.2.1_PUBLIC /opt/ghidra
rm ghidra.zip
wget https://github.com/NationalSecurityAgency/ghidra/releases/download/${GHIDRA_RELEASE_TAG}/${GHIDRA_VERSION}.zip
unzip -d ghidra ${GHIDRA_VERSION}.zip
mv ghidra/ghidra_* /opt/ghidra
rm ${GHIDRA_VERSION}.zip
- uses: actions-rs/toolchain@v1
with:
profile: minimal
......
......@@ -121,6 +121,12 @@ impl<T: RegisterDomain> DataDomain<T> {
self.contains_top_values = true;
}
/// Indicate that the domain does not contain any `Top` values
/// in addition to the contained absolute and relative values.
pub fn unset_contains_top_flag(&mut self) {
self.contains_top_values = false;
}
/// Return a new value representing a variable plus an offset,
/// where the variable is represented by the given abstract ID.
pub fn from_target(id: AbstractIdentifier, offset: T) -> Self {
......
......@@ -43,6 +43,15 @@ impl AbstractIdentifier {
AbstractIdentifier(Arc::new(AbstractIdentifierData { time, location }))
}
/// Create a new abstract identifier where the abstract location is a register.
/// Panics if the register is a temporary register.
pub fn new_from_var(time: Tid, variable: &Variable) -> AbstractIdentifier {
AbstractIdentifier(Arc::new(AbstractIdentifierData {
time,
location: AbstractLocation::from_var(variable).unwrap(),
}))
}
/// Get the register associated to the abstract location.
/// Panics if the abstract location is a memory location and not a register.
pub fn unwrap_register(&self) -> &Variable {
......@@ -56,6 +65,11 @@ impl AbstractIdentifier {
pub fn get_tid(&self) -> &Tid {
&self.time
}
/// Get the location component of the abstract ID
pub fn get_location(&self) -> &AbstractLocation {
&self.location
}
}
impl std::fmt::Display for AbstractIdentifier {
......
use crate::{abstract_domain::AbstractDomain, prelude::*};
use std::fmt::Display;
/// Access flags to track different kind of access/usage patterns of a variable.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct AccessPattern {
/// The variable was used in the computation of a pointer that was dereferenced for reading a value.
dereferenced: bool,
/// The variable was accessed to compute some nontrivial value
/// or the value was stored in some location.
read: bool,
/// The variable was used in the computation of a pointer that was dereferenced for writing a value.
mutably_dereferenced: bool,
}
impl AccessPattern {
/// Generate a new `AccessPattern` object with none of the access flags set.
pub fn new() -> Self {
Self {
dereferenced: false,
read: false,
mutably_dereferenced: false,
}
}
/// Set the access flag for immutable pointer dereference.
pub fn set_dereference_flag(&mut self) {
self.dereferenced = true;
}
/// Set the access flag for read access.
pub fn set_read_flag(&mut self) {
self.read = true;
}
/// Set the access flag for pointer dereference (with write access to the target of the pointer).
pub fn set_mutably_dereferenced_flag(&mut self) {
self.mutably_dereferenced = true;
}
/// Set all access flags to indicate that any kind of access to the variable may have occured.
pub fn set_unknown_access_flags(&mut self) {
self.read = true;
self.dereferenced = true;
self.mutably_dereferenced = true;
}
/// Returns true if any of the access flags is set.
pub fn is_accessed(&self) -> bool {
self.read || self.dereferenced || self.mutably_dereferenced
}
/// Returns true if the dereferenced or mutably dereferenced access flag is set.
pub fn is_dereferenced(&self) -> bool {
self.dereferenced || self.mutably_dereferenced
}
/// Returns true if the mutably dereferenced access flag is set.
pub fn is_mutably_dereferenced(&self) -> bool {
self.mutably_dereferenced
}
}
impl Default for AccessPattern {
fn default() -> Self {
Self::new()
}
}
impl AbstractDomain for AccessPattern {
/// An access flag in the merged `AccessPattern` object is set
/// if it is set in at least one of the input objects.
fn merge(&self, other: &Self) -> Self {
AccessPattern {
dereferenced: self.dereferenced || other.dereferenced,
read: self.read || other.read,
mutably_dereferenced: self.mutably_dereferenced || other.mutably_dereferenced,
}
}
/// Returns true if all of the access flags are set.
fn is_top(&self) -> bool {
self.read && self.dereferenced && self.mutably_dereferenced
}
}
impl Display for AccessPattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.read {
write!(f, "r")?;
} else {
write!(f, "-")?;
}
if self.dereferenced {
write!(f, "d")?;
} else {
write!(f, "-")?;
}
if self.mutably_dereferenced {
write!(f, "w")?;
} else {
write!(f, "-")?;
}
Ok(())
}
}
use crate::abstract_domain::{AbstractDomain, AbstractIdentifier, BitvectorDomain, DataDomain};
use crate::{
analysis::{forward_interprocedural_fixpoint, graph::Graph},
intermediate_representation::Project,
};
use super::*;
/// The context struct for the fixpoint algorithm.
pub struct Context<'a> {
graph: &'a Graph<'a>,
project: &'a Project,
}
impl<'a> Context<'a> {
/// Generate a new context object.
pub fn new(project: &'a Project, graph: &'a Graph<'a>) -> Self {
Context { graph, project }
}
/// Compute the return values of a call and return them (without adding them to the caller state).
///
/// The `callee_state` is the state of the callee at the return site.
/// The return values are expressed in the abstract IDs that are known to the caller.
/// If a return value may contain `Top` values,
/// i.e. values for which the origin is not known or not expressible in the abstract IDs known to the caller,
/// then a call- and register-specific abstract ID is added to the corresponding return value.
/// This ID is not added to the tracked IDs of the caller state.
fn compute_return_values_of_call<'cconv>(
&self,
caller_state: &mut State,
callee_state: &State,
calling_convention: &'cconv CallingConvention,
call: &Term<Jmp>,
) -> Vec<(&'cconv Variable, DataDomain<BitvectorDomain>)> {
let mut return_value_list = Vec::new();
for return_register in &calling_convention.integer_return_register {
let return_value = self.compute_return_register_value_of_call(
caller_state,
callee_state,
return_register,
call,
);
return_value_list.push((return_register, return_value));
}
for return_expr in &calling_convention.float_return_register {
for return_register in return_expr.input_vars() {
let return_value = self.compute_return_register_value_of_call(
caller_state,
callee_state,
return_register,
call,
);
return_value_list.push((return_register, return_value));
}
}
return_value_list
}
/// Compute the return value for the given register.
///
/// The return value contains the IDs of all possible input IDs of the call that it may reference.
/// If the value may also contain a value not originating from the caller
/// then replace it with a call- and register-specific abstract ID.
fn compute_return_register_value_of_call(
&self,
caller_state: &mut State,
callee_state: &State,
return_register: &Variable,
call: &Term<Jmp>,
) -> DataDomain<BitvectorDomain> {
let callee_value = callee_state.get_register(return_register);
let mut return_value: DataDomain<BitvectorDomain> =
DataDomain::new_empty(return_register.size);
// For absolute or Top-values originating in the callee the Top-flag of the return value is set.
if callee_value.contains_top() || callee_value.get_absolute_value().is_some() {
return_value.set_contains_top_flag();
}
// For every relative value in the callee we check whether it is relative a parameter to the callee.
// If yes, we can compute it relative to the value of the parameter at the callsite and add the result to the return value.
// Else we just set the Top-flag of the return value to indicate some value originating in the callee.
for (callee_id, callee_offset) in callee_value.get_relative_values() {
if let Some(param_arg) = callee_state.get_arg_corresponding_to_id(callee_id) {
let param_value = caller_state.eval_parameter_arg(&param_arg);
if param_value.contains_top() || param_value.get_absolute_value().is_some() {
return_value.set_contains_top_flag()
}
for (param_id, param_offset) in param_value.get_relative_values() {
let value = DataDomain::from_target(
param_id.clone(),
param_offset.clone() + callee_offset.clone(),
);
return_value = return_value.merge(&value);
}
} else {
return_value.set_contains_top_flag();
}
}
// If the Top-flag of the return value was set we replace it with an ID representing the return register
// to indicate where the unknown value originated from.
if return_value.contains_top() {
let id = AbstractIdentifier::new_from_var(call.tid.clone(), return_register);
let value =
DataDomain::from_target(id, Bitvector::zero(return_register.size.into()).into());
return_value = return_value.merge(&value);
return_value.unset_contains_top_flag();
}
return_value
}
}
impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
type Value = State;
fn get_graph(&self) -> &Graph<'a> {
self.graph
}
fn merge(&self, state_left: &State, state_right: &State) -> State {
state_left.merge(state_right)
}
fn update_def(&self, state: &State, def: &Term<Def>) -> Option<State> {
let mut new_state = state.clone();
match &def.term {
Def::Assign { var, value } => {
new_state.set_read_flag_for_input_ids_of_expression(value);
new_state.set_register(var, state.eval(value));
}
Def::Load { var, address } => {
new_state.set_deref_flag_for_input_ids_of_expression(address);
let value = new_state.load_value(new_state.eval(address), var.size);
new_state.set_register(var, value);
}
Def::Store { address, value } => {
new_state.set_mutable_deref_flag_for_input_ids_of_expression(address);
if state
.get_offset_if_exact_stack_pointer(&state.eval(address))
.is_some()
{
// Only flag inputs of non-trivial expressions as accessed to prevent flagging callee-saved registers as parameters.
// Sometimes parameter registers are callee-saved (for no apparent reason).
new_state.set_read_flag_for_input_ids_of_nontrivial_expression(value);
} else {
new_state.set_read_flag_for_input_ids_of_expression(value);
}
new_state.write_value(new_state.eval(address), new_state.eval(value));
}
}
Some(new_state)
}
fn update_jump(
&self,
state: &State,
jump: &Term<Jmp>,
_untaken_conditional: Option<&Term<Jmp>>,
_target: &Term<Blk>,
) -> Option<State> {
let mut new_state = state.clone();
match &jump.term {
Jmp::BranchInd(address) | Jmp::Return(address) => {
new_state.set_read_flag_for_input_ids_of_expression(address);
}
Jmp::CBranch { condition, .. } => {
new_state.set_read_flag_for_input_ids_of_expression(condition);
}
_ => (),
}
Some(new_state)
}
fn update_call(
&self,
_state: &State,
_call: &Term<Jmp>,
_target: &crate::analysis::graph::Node,
) -> Option<State> {
// No knowledge is transferred from the caller to the callee.
None
}
fn update_call_stub(&self, state: &State, call: &Term<Jmp>) -> Option<State> {
let mut new_state = state.clone();
match &call.term {
Jmp::CallInd { target, .. } => {
new_state.set_read_flag_for_input_ids_of_expression(target);
if let Some(cconv) = self.project.get_standard_calling_convention() {
new_state.handle_unknown_function_stub(call, cconv);
return Some(new_state);
}
}
Jmp::Call { target, .. } => {
if let Some(extern_symbol) = self.project.program.term.extern_symbols.get(target) {
let cconv = self.project.get_calling_convention(extern_symbol);
new_state.handle_extern_symbol(call, extern_symbol, cconv);
if !extern_symbol.no_return {
return Some(new_state);
}
} else if let Some(cconv) = self.project.get_standard_calling_convention() {
new_state.handle_unknown_function_stub(call, cconv);
return Some(new_state);
}
}
_ => (),
}
// The call could not be properly handled, so we treat it as a dead end in the control flow graph.
None
}
fn update_return(
&self,
state: Option<&State>,
state_before_call: Option<&State>,
call_term: &Term<Jmp>,
_return_term: &Term<Jmp>,
) -> Option<State> {
if state.is_none() || state_before_call.is_none() {
return None;
}
let calling_convention = match self.project.get_standard_calling_convention() {
Some(cconv) => cconv,
None => return None,
};
let old_state = state_before_call.unwrap();
let callee_state = state.unwrap();
let mut new_state = old_state.clone();
// Merge parameter access patterns with the access patterns from the callee.
let parameters = callee_state.get_params_of_current_function();
new_state.merge_parameter_access(&parameters);
// Compute values for return register (but do not add them to `new_state` yet)
let return_value_list = self.compute_return_values_of_call(
&mut new_state,
callee_state,
calling_convention,
call_term,
);
// From now on the operations on new_state are allowed to modify register values.
// Only retain callee-saved register from the caller register values.
new_state.clear_non_callee_saved_register(&calling_convention.callee_saved_register);
// Now we can insert the return values into the state
for (var, value) in return_value_list {
new_state.set_register(var, value);
}
Some(new_state)
}
fn specialize_conditional(
&self,
state: &State,
condition: &Expression,
_block_before_condition: &Term<Blk>,
_is_true: bool,
) -> Option<State> {
let mut new_state = state.clone();
new_state.set_read_flag_for_input_ids_of_expression(condition);
Some(new_state)
}
}
#[cfg(test)]
pub mod tests;
use super::*;
use std::collections::HashSet;
#[test]
fn test_compute_return_values_of_call() {
let project = Project::mock_empty();
let cconv = CallingConvention::mock();
let graph = crate::analysis::graph::get_program_cfg(&project.program, HashSet::new());
let context = Context::new(&project, &graph);
let mut caller_state = State::mock_x64("caller");
let mut callee_state = State::mock_x64("callee");
let call = Term {
tid: Tid::new("call_tid"),
term: Jmp::Call {
target: Tid::new("callee"),
return_: Some(Tid::new("return_tid")),
},
};
// Test returning a value of unknown origin (since RAX does not contain a reference to the input register).
let return_values =
context.compute_return_values_of_call(&mut caller_state, &callee_state, &cconv, &call);
let expected_val = DataDomain::from_target(
AbstractIdentifier::new_from_var(Tid::new("call_tid"), &Variable::mock("RAX", 8)),
Bitvector::from_i64(0).into(),
);
assert_eq!(return_values.iter().len(), 1);
assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val));
// Test returning a known value.
let param_ref = DataDomain::from_target(
AbstractIdentifier::new_from_var(Tid::new("callee"), &Variable::mock("RDI", 8)),
Bitvector::from_i64(0).into(),
);
callee_state.set_register(&Variable::mock("RAX", 8), param_ref);
let expected_val = DataDomain::from_target(
AbstractIdentifier::new_from_var(Tid::new("caller"), &Variable::mock("RDI", 8)),
Bitvector::from_i64(0).into(),
);
let return_values =
context.compute_return_values_of_call(&mut caller_state, &callee_state, &cconv, &call);
assert_eq!(return_values.iter().len(), 1);
assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val));
}
//! A fixpoint algorithm computing parameters of functions and their access patterns.
//!
//! The fixpoint algorithm tracks the values of registers and the stack,
//! although only stack accesses with known, constant offset are processed.
//! Accesses to potential function parameters are collected together with the type of the access
//! (is the value read, dereferenced for read access or dereferenced for write access).
//!
//! Known limitations of the analysis:
//! * The analysis is an overapproximation in the sense that it may generate more input parameters
//! than actually exist in some cases.
//! * Only registers that are potential parameter registers in the standard calling convention
//! of the CPU architecture are considered as potential parameter registers.
//! For functions that use other registers
//! than those in the standard calling convention for parameter passing
//! the results of this analysis will be wrong.
//! * Parameters that are used as input values for variadic functions (e.g. sprintf) may be missed
//! since detection of variadic function parameters is not yet implemented for this analysis.
//! * If only a part (e.g. a single byte) of a stack parameter is accessed instead of the whole parameter
//! then a duplicate stack parameter may be generated.
//! A proper sanitation for this case is not yet implemented,
//! although error messages are generated if such a case is detected.
//! * For floating point parameter registers the base register is detected as a parameter,
//! although only a smaller sub-register is the actual parameter in many cases.
//! Also, if a function uses sub-registers of floating point registers as local variables,
//! the registers may be incorrectly flagged as input parameters.
use crate::abstract_domain::AbstractDomain;
use crate::analysis::fixpoint::Computation;
use crate::analysis::forward_interprocedural_fixpoint::create_computation;
use crate::analysis::forward_interprocedural_fixpoint::GeneralizedContext;
use crate::analysis::graph::*;
use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::log::LogMessage;
use std::collections::BTreeMap;
use std::collections::HashMap;
mod context;
use context::*;
mod state;
use state::State;
mod access_pattern;
pub use access_pattern::AccessPattern;
/// Generate the computation object for the fixpoint computation
/// and set the node values for all function entry nodes.
fn generate_fixpoint_computation<'a>(
project: &'a Project,
graph: &'a Graph,
) -> Computation<GeneralizedContext<'a, Context<'a>>> {
let context = Context::new(project, graph);
let mut computation = create_computation(context, None);
// Set the node values for all function entry nodes.
for node in graph.node_indices() {
if let Node::BlkStart(block, sub) = graph[node] {
if let Some(entry_block) = sub.term.blocks.get(0) {
if entry_block.tid == block.tid {
// The node of a function entry point
computation.set_node_value(
node,
NodeValue::Value(State::new(
&sub.tid,
&project.stack_pointer_register,
project.get_standard_calling_convention().unwrap(),
)),
)
}
}
}
}
computation
}
/// Extract the function signatures from the computed fixpoint.
///
/// This function needs to merge the signatures at all nodes corresponding to a function
/// to ensure that parameter accesses on non-returning execution paths of a function
/// are also recognized in the function signature.
fn extract_fn_signatures_from_fixpoint<'a>(
project: &'a Project,
graph: &'a Graph,
fixpoint: Computation<GeneralizedContext<'a, Context<'a>>>,
) -> BTreeMap<Tid, FunctionSignature> {
let mut fn_sig_map: BTreeMap<Tid, FunctionSignature> = project
.program
.term
.subs
.keys()
.map(|tid| (tid.clone(), FunctionSignature::new()))
.collect();
for node in graph.node_indices() {
match fixpoint.get_node_value(node) {
None => (),
Some(NodeValue::Value(state)) => {
let fn_sig = fn_sig_map
.get_mut(state.get_current_function_tid())
.unwrap();
fn_sig.merge_with_fn_sig_of_state(state);
}
Some(NodeValue::CallFlowCombinator {
call_stub,
interprocedural_flow,
}) => {
if let Some(state) = call_stub {
let fn_sig = fn_sig_map
.get_mut(state.get_current_function_tid())
.unwrap();
fn_sig.merge_with_fn_sig_of_state(state);
}
if let Some(state) = interprocedural_flow {
let fn_sig = fn_sig_map
.get_mut(state.get_current_function_tid())
.unwrap();
fn_sig.merge_with_fn_sig_of_state(state);
}
}
}
}
fn_sig_map
}
/// Compute the function signatures for all functions in the project.
///
/// Returns a map from the function TIDs to their signatures,
/// and a list of log messages recorded during the computation of the signatures.
///
/// For more information on the used algorithm see the module-level documentation.
pub fn compute_function_signatures<'a>(
project: &'a Project,
graph: &'a Graph,
) -> (Vec<LogMessage>, BTreeMap<Tid, FunctionSignature>) {
let mut computation = generate_fixpoint_computation(project, graph);
computation.compute_with_max_steps(100);
let mut fn_sig_map = extract_fn_signatures_from_fixpoint(project, graph, computation);
// Sanitize the parameters
let mut logs = Vec::new();
for (fn_tid, fn_sig) in fn_sig_map.iter_mut() {
if fn_sig.sanitize(project).is_err() {
logs.push(
LogMessage::new_error("Function parameters are not properly sanitized")
.location(fn_tid.clone())
.source("Function Signature Analysis"),
);
}
}
(logs, fn_sig_map)
}
/// The signature of a function.
/// Currently only contains information on the parameters of a function and their access patterns.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct FunctionSignature {
/// The parameters of the function together with their access patterns.
pub parameters: HashMap<Arg, AccessPattern>,
}
impl FunctionSignature {
/// Generate an empty function signature.
pub fn new() -> Self {
Self {
parameters: HashMap::new(),
}
}
/// Merge the parameter list of `self` with the given parameter list.
fn merge_parameter_list(&mut self, params: &[(Arg, AccessPattern)]) {
for (arg, sig_new) in params {
if let Some(sig_self) = self.parameters.get_mut(arg) {
*sig_self = sig_self.merge(sig_new);
} else {
self.parameters.insert(arg.clone(), *sig_new);
}
}
}
/// Merge the function signature with the signature extracted from the given state.
fn merge_with_fn_sig_of_state(&mut self, state: &State) {
let params = state.get_params_of_current_function();
self.merge_parameter_list(&params);
}
/// Sanitize the function signature:
/// * Remove the return address from the list of stack parameters for x86-based architectures.
/// * Check for unaligned stack parameters or stack parameters that are not pointer-sized
/// and return an error message if one is found.
/// This may indicate an error in the analysis
/// as no proper sanitation pass is implemented for such cases yet.
fn sanitize(&mut self, project: &Project) -> Result<(), Error> {
match project.cpu_architecture.as_str() {
"x86" | "x86_32" | "x86_64" => {
let return_addr_expr = Expression::Var(project.stack_pointer_register.clone());
let return_addr_arg = Arg::Stack {
address: return_addr_expr,
size: project.stack_pointer_register.size,
data_type: None,
};
self.parameters.remove(&return_addr_arg);
}
_ => (),
}
self.check_for_unaligned_stack_params(&project.stack_pointer_register)
}
/// Return an error if an unaligned stack parameter
/// or a stack parameter of different size than the generic pointer size is found.
fn check_for_unaligned_stack_params(&self, stack_register: &Variable) -> Result<(), Error> {
for arg in self.parameters.keys() {
if let Arg::Stack { size, .. } = arg {
if *size != stack_register.size {
return Err(anyhow!("Unexpected stack parameter size"));
}
if let Ok(offset) = arg.eval_stack_offset(stack_register) {
if offset.try_to_u64()? % u64::from(stack_register.size) != 0 {
return Err(anyhow!("Unexpected stack parameter alignment"));
}
}
}
}
Ok(())
}
}
impl Default for FunctionSignature {
fn default() -> Self {
Self::new()
}
}
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use crate::abstract_domain::*;
use crate::intermediate_representation::*;
use crate::prelude::*;
use super::AccessPattern;
/// Methods of [`State`] related to handling call instructions.
mod call_handling;
/// The state tracks knowledge about known register values,
/// known values on the stack, and access patterns of tracked variables.
///
/// The values and access patterns are tracked as upper bounds.
/// For example, if some access flag for a variable is set, then the variable may have been accessed,
/// but it does not have to be accessed in the current state.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct State {
/// Known register values.
register: DomainMap<Variable, DataDomain<BitvectorDomain>, MergeTopStrategy>,
/// The abstract ID representing the stack of the current function.
stack_id: AbstractIdentifier,
/// The content of the current stack frame.
stack: MemRegion<DataDomain<BitvectorDomain>>,
/// Maps each tracked ID to an [`AccessPattern`], which tracks known access patterns to the object.
tracked_ids: DomainMap<AbstractIdentifier, AccessPattern, UnionMergeStrategy>,
}
impl State {
/// Generate a new state corresponding to the function start state for the given function TID.
///
/// Only registers that are parameter registers in the given calling convention are added to the tracked IDs.
pub fn new(
func_tid: &Tid,
stack_register: &Variable,
calling_convention: &CallingConvention,
) -> State {
let mut register_map = BTreeMap::new();
let mut tracked_ids = BTreeMap::new();
// Generate tracked IDs for all parameters and also add them to the register map
for var in calling_convention.get_all_parameter_register() {
let id = AbstractIdentifier::new_from_var(func_tid.clone(), var);
let value =
DataDomain::from_target(id.clone(), Bitvector::zero(var.size.into()).into());
register_map.insert(var.clone(), value);
if var != stack_register {
tracked_ids.insert(id, AccessPattern::new());
}
}
// Generate all stack-related objects
let stack_id = AbstractIdentifier::new_from_var(func_tid.clone(), stack_register);
let stack_value = DataDomain::from_target(
stack_id.clone(),
Bitvector::zero(stack_register.size.into()).into(),
);
register_map.insert(stack_register.clone(), stack_value);
let stack = MemRegion::new(stack_register.size);
State {
register: DomainMap::from(register_map),
stack_id,
stack,
tracked_ids: DomainMap::from(tracked_ids),
}
}
/// Get the value of the given register in the current state.
pub fn get_register(&self, register: &Variable) -> DataDomain<BitvectorDomain> {
self.register
.get(register)
.cloned()
.unwrap_or_else(|| DataDomain::new_top(register.size))
}
/// Set the value of the given register in the current state.
pub fn set_register(&mut self, register: &Variable, value: DataDomain<BitvectorDomain>) {
if value.is_top() {
self.register.remove(register);
} else {
self.register.insert(register.clone(), value);
}
}
/// Get the TID of the function that this state belongs to.
pub fn get_current_function_tid(&self) -> &Tid {
self.stack_id.get_tid()
}
/// Load the value at the given address.
///
/// Only constant addresses on the stack are tracked.
/// Thus this function will always return a `Top` domain for any address
/// that may not be a stack address with constant offset.
///
/// This function does not set any access flags for input IDs in the address value.
pub fn load_value(
&mut self,
address: DataDomain<BitvectorDomain>,
size: ByteSize,
) -> DataDomain<BitvectorDomain> {
if let Some(stack_offset) = self.get_offset_if_exact_stack_pointer(&address) {
self.load_value_from_stack(stack_offset, size)
} else {
DataDomain::new_top(size)
}
}
/// Load the value at the given stack offset.
/// If the offset is non-negative a corresponding stack parameter is generated if necessary.
fn load_value_from_stack(
&mut self,
stack_offset: Bitvector,
size: ByteSize,
) -> DataDomain<BitvectorDomain> {
if !stack_offset.sign_bit().to_bool() {
// Stack offset is nonnegative, i.e. this is a stack parameter access.
self.get_stack_param(stack_offset, size)
} else {
self.stack.get(stack_offset, size)
}
}
/// Load a value of unknown bytesize at the given stack offset.
/// If the offset is non-negative, a corresponding stack parameter is generated if necessary.
///
/// One must be careful to not rely on the correctness of the bytesize of the returned value!
/// If the size of the value cannot be guessed from the contents of the stack,
/// then a size of 1 byte is assumed, which will be wrong in general!
fn load_unsized_value_from_stack(&mut self, offset: Bitvector) -> DataDomain<BitvectorDomain> {
if !offset.sign_bit().to_bool() {
// This is a pointer to a stack parameter of the current function
self.stack
.get_unsized(offset.clone())
.unwrap_or_else(|| self.get_stack_param(offset, ByteSize::new(1)))
} else {
self.stack
.get_unsized(offset)
.unwrap_or_else(|| DataDomain::new_top(ByteSize::new(1)))
}
}
/// If `address` is a stack offset, then write `value` onto the stack.
///
/// If address points to a stack parameter, whose ID does not yet exists,
/// then the ID is generated and added to the tracked IDs.
///
/// This function does not set any access flags for input IDs of the given address or value.
pub fn write_value(
&mut self,
address: DataDomain<BitvectorDomain>,
value: DataDomain<BitvectorDomain>,
) {
if let Some(stack_offset) = self.get_offset_if_exact_stack_pointer(&address) {
// We generate a new stack parameter object, but do not set any access flags,
// since the stack parameter is not accessed but overwritten.
if !stack_offset.sign_bit().to_bool() {
let _ = self
.generate_stack_param_id_if_nonexistent(stack_offset.clone(), value.bytesize());
}
self.stack.add(value, stack_offset);
}
}
/// If the stack parameter ID corresponding to the given stack offset does not exist
/// then generate it, add it to the list of tracked IDs, and return it.
fn generate_stack_param_id_if_nonexistent(
&mut self,
stack_offset: Bitvector,
size: ByteSize,
) -> Option<AbstractIdentifier> {
assert!(!stack_offset.sign_bit().to_bool());
let stack_pos = AbstractLocation::from_stack_position(
self.stack_id.unwrap_register(),
stack_offset.try_to_i64().unwrap(),
size,
);
let param_id = AbstractIdentifier::new(self.stack_id.get_tid().clone(), stack_pos);
if self.tracked_ids.contains_key(&param_id) {
None
} else {
self.tracked_ids
.insert(param_id.clone(), AccessPattern::new());
Some(param_id)
}
}
/// Get the value located at a positive stack offset.
///
/// If no corresponding stack parameter ID exists for the value,
/// generate it and then return it as an unmodified stack parameter.
/// Otherwise just read the value at the given stack address.
fn get_stack_param(
&mut self,
address: Bitvector,
size: ByteSize,
) -> DataDomain<BitvectorDomain> {
assert!(!address.sign_bit().to_bool());
if let Some(param_id) = self.generate_stack_param_id_if_nonexistent(address.clone(), size) {
let stack_param =
DataDomain::from_target(param_id, Bitvector::zero(size.into()).into());
self.stack.add(stack_param.clone(), address);
stack_param
} else {
self.stack.get(address, size)
}
}
/// If the address is an exactly known pointer to the stack with a constant offset, then return the offset.
pub fn get_offset_if_exact_stack_pointer(
&self,
address: &DataDomain<BitvectorDomain>,
) -> Option<Bitvector> {
if let Some((target, offset)) = address.get_if_unique_target() {
if *target == self.stack_id {
return offset.try_to_bitvec().ok();
}
}
None
}
/// Evaluate the value of the given expression on the current state.
pub fn eval(&self, expression: &Expression) -> DataDomain<BitvectorDomain> {
match expression {
Expression::Var(var) => self.get_register(var),
Expression::Const(bitvector) => bitvector.clone().into(),
Expression::BinOp { op, lhs, rhs } => self.eval(lhs).bin_op(*op, &self.eval(rhs)),
Expression::UnOp { op, arg } => self.eval(arg).un_op(*op),
Expression::Cast { op, size, arg } => self.eval(arg).cast(*op, *size),
Expression::Unknown {
description: _,
size,
} => DataDomain::new_top(*size),
Expression::Subpiece {
low_byte,
size,
arg,
} => self.eval(arg).subpiece(*low_byte, *size),
}
}
/// Evaluate the value of the given parameter on the current state.
///
/// Note that this may alter the state
/// since stack parameters of the argument may access stack parameters of the the current stack frame,
/// which may need to be generated first.
pub fn eval_parameter_arg(&mut self, parameter: &Arg) -> DataDomain<BitvectorDomain> {
match parameter {
Arg::Register { expr, data_type: _ } => self.eval(expr),
Arg::Stack {
address,
size,
data_type: _,
} => {
self.set_deref_flag_for_input_ids_of_expression(address);
let address = self.eval(address);
self.load_value(address, *size)
}
}
}
/// If the given expression is not an [`Expression::Var`] set the read flags
/// for all IDs that may be referenced when computing the value of the expression.
///
/// [`Expression::Var`] accesses also happen when writing a callee-saved register to the stack.
/// This function can be used to prevent accidentially flagging callee-saved registers as input registers.
pub fn set_read_flag_for_input_ids_of_nontrivial_expression(
&mut self,
expression: &Expression,
) {
match expression {
Expression::Var(_) => (),
_ => self.set_read_flag_for_input_ids_of_expression(expression),
}
}
/// Set the read flag for every ID that may be referenced when computing the value of the expression.
pub fn set_read_flag_for_input_ids_of_expression(&mut self, expression: &Expression) {
for register in expression.input_vars() {
for id in self.get_register(register).referenced_ids() {
if let Some(object) = self.tracked_ids.get_mut(id) {
object.set_read_flag();
}
}
}
}
/// Set the read and dereferenced flag for every ID
/// that may be referenced when computing the value of the expression.
pub fn set_deref_flag_for_input_ids_of_expression(&mut self, expression: &Expression) {
for register in expression.input_vars() {
for id in self.get_register(register).referenced_ids() {
if let Some(object) = self.tracked_ids.get_mut(id) {
object.set_read_flag();
object.set_dereference_flag();
}
}
}
}
/// Set the read and mutably dereferenced flag for every ID
/// that may be referenced when computing the value of the expression.
pub fn set_mutable_deref_flag_for_input_ids_of_expression(&mut self, expression: &Expression) {
for register in expression.input_vars() {
for id in self.get_register(register).referenced_ids() {
if let Some(object) = self.tracked_ids.get_mut(id) {
object.set_read_flag();
object.set_mutably_dereferenced_flag();
}
}
}
}
}
impl AbstractDomain for State {
/// Merge two states
fn merge(&self, other: &Self) -> Self {
let stack_id = self.stack_id.clone();
let stack = self.stack.merge(&other.stack);
State {
register: self.register.merge(&other.register),
stack_id,
stack,
tracked_ids: self.tracked_ids.merge(&other.tracked_ids),
}
}
/// The state does not have an explicit `Top` element.
fn is_top(&self) -> bool {
false
}
}
impl State {
/// Generate a compact JSON-representation of the state for pretty printing.
#[allow(dead_code)]
pub fn to_json_compact(&self) -> serde_json::Value {
let mut json_map = serde_json::Map::new();
json_map.insert(
"Stack-ID".to_string(),
serde_json::Value::String(format!("{}", self.stack_id)),
);
let regs = self
.register
.iter()
.map(|(var, value)| (format!("{}", var), value.to_json_compact()))
.collect();
json_map.insert("Register".to_string(), serde_json::Value::Object(regs));
let access_patterns = self
.tracked_ids
.iter()
.map(|(id, pattern)| {
(
format!("{}", id),
serde_json::Value::String(format!("{}", pattern)),
)
})
.collect();
json_map.insert(
"Tracked IDs".to_string(),
serde_json::Value::Object(access_patterns),
);
let stack = self
.stack
.iter()
.map(|(index, value)| (format!("{}", *index), value.to_json_compact()))
.collect();
json_map.insert("Stack".to_string(), serde_json::Value::Object(stack));
serde_json::Value::Object(json_map)
}
}
#[cfg(test)]
mod tests;
use super::*;
impl State {
/// Handle a call to an extern symbol.
///
/// Marks every possible input ID as accessed and writes to every return register a value
/// that may point to any of the input IDs.
pub fn handle_extern_symbol(
&mut self,
call: &Term<Jmp>,
extern_symbol: &ExternSymbol,
calling_convention: &CallingConvention,
) {
let input_ids = self.collect_input_ids_of_call(&extern_symbol.parameters);
self.clear_non_callee_saved_register(&calling_convention.callee_saved_register);
self.generate_return_values_for_call(&input_ids, &extern_symbol.return_values, &call.tid);
}
/// Handle a call to a completely unknown function
/// by assuming that every input register of the given calling convention is an input
/// and every integer return register of the calling convention is an output.
///
/// Marks every possible input ID as accessed and writes to every return register a value
/// that may point to any of the input IDs.
pub fn handle_unknown_function_stub(
&mut self,
call: &Term<Jmp>,
calling_convention: &CallingConvention,
) {
let mut parameters =
generate_args_from_registers(&calling_convention.integer_parameter_register);
for float_param in &calling_convention.float_parameter_register {
parameters.push(Arg::Register {
expr: float_param.clone(),
data_type: None,
});
}
let mut return_register =
generate_args_from_registers(&calling_convention.integer_return_register);
for float_return_register in &calling_convention.float_return_register {
return_register.push(Arg::Register {
expr: float_return_register.clone(),
data_type: None,
});
}
let input_ids = self.collect_input_ids_of_call(&parameters);
self.clear_non_callee_saved_register(&calling_convention.callee_saved_register);
self.generate_return_values_for_call(&input_ids, &return_register, &call.tid);
}
/// Get all input IDs referenced in the parameters of a call.
/// Marks every input ID as accessed (with access flags for unknown access)
/// and generates stack parameter IDs for the current function if necessary.
fn collect_input_ids_of_call(&mut self, parameters: &[Arg]) -> BTreeSet<AbstractIdentifier> {
let mut input_ids = BTreeSet::new();
for input_param in parameters {
for (id, offset) in self
.eval_parameter_arg(input_param)
.get_relative_values()
.iter()
{
input_ids.insert(id.clone());
// If the relative value points to the stack we also have to collect all IDs contained in the pointed-to value.
if *id == self.stack_id {
if let Ok(offset) = offset.try_to_bitvec() {
let value = self.load_unsized_value_from_stack(offset);
for id in value.get_relative_values().keys() {
input_ids.insert(id.clone());
}
}
}
}
}
// Mark every input ID as accessed
for id in &input_ids {
if let Some(object) = self.tracked_ids.get_mut(id) {
object.set_unknown_access_flags();
}
}
input_ids
}
/// Delete the content of all non-callee-saved registers from the state.
pub fn clear_non_callee_saved_register(&mut self, callee_saved: &[Variable]) {
self.register.retain(|var, _| callee_saved.contains(var));
}
/// Fill every return register that might be a pointer with a value that may point to any input ID
/// or to an output ID specific to the call and output register.
fn generate_return_values_for_call(
&mut self,
input_ids: &BTreeSet<AbstractIdentifier>,
return_args: &[Arg],
call_tid: &Tid,
) {
// Fill every output register with a value that may point to any input ID
// or to an output ID specific to the call and output register.
let generic_pointer_size = self.stack_id.unwrap_register().size;
let generic_output_relative_values: BTreeMap<AbstractIdentifier, BitvectorDomain> =
input_ids
.iter()
.map(|id| (id.clone(), BitvectorDomain::new_top(generic_pointer_size)))
.collect();
let mut generic_output = DataDomain::new_top(generic_pointer_size);
generic_output.set_relative_values(generic_output_relative_values);
for output_arg in return_args {
if let Arg::Register {
expr: Expression::Var(var),
data_type: _,
} = output_arg
{
if var.size == generic_pointer_size {
let specific_target = DataDomain::from_target(
AbstractIdentifier::new_from_var(call_tid.clone(), var),
Bitvector::zero(var.size.into()).into(),
);
let output = generic_output.merge(&specific_target);
self.set_register(var, output);
}
}
}
}
/// Return a list of parameter arguments and their associated object signatures for the current state.
///
/// A register (or stack position with positive offset) is considered a parameter
/// if any access to its value at function start is recorded in the corresponding object signature.
pub fn get_params_of_current_function(&self) -> Vec<(Arg, AccessPattern)> {
let mut params = Vec::new();
for (id, access_pattern) in self.tracked_ids.iter() {
if id.get_tid() == self.get_current_function_tid() {
if access_pattern.is_accessed() {
params.push((generate_arg_from_abstract_id(id), *access_pattern));
} else if matches!(id.get_location(), &AbstractLocation::Pointer { .. }) {
// This is a stack parameter.
// If it was only loaded into a register but otherwise not used, then the read-flag needs to be set.
let mut access_pattern = *access_pattern;
access_pattern.set_read_flag();
params.push((generate_arg_from_abstract_id(id), access_pattern));
}
}
}
params
}
/// Merges the access patterns of callee parameters with those of the caller (represented by `self`).
/// The result represents the access patterns after returning to the caller and is written to `self`.
///
/// If a parameter is a pointer to the stack frame of self, it is dereferenced
/// to set the access patterns of the target.
/// Note that this may create new stack parameter objects for self.
pub fn merge_parameter_access(&mut self, params: &[(Arg, AccessPattern)]) {
for (parameter, call_access_pattern) in params {
for (id, offset) in self.eval_parameter_arg(parameter).get_relative_values() {
if let Some(object) = self.tracked_ids.get_mut(id) {
*object = object.merge(call_access_pattern);
}
if *id == self.stack_id && call_access_pattern.is_dereferenced() {
if let Ok(offset) = offset.try_to_bitvec() {
// We also have to dereference the stack pointer and set the access flags of the pointed-to value
let value = self.load_unsized_value_from_stack(offset.clone());
for id in value.referenced_ids() {
if let Some(object) = self.tracked_ids.get_mut(id) {
// Since we do not know whether the value itself was also dereferenced in the callee,
// we have to assume some unknown access to the value.
object.set_unknown_access_flags();
}
}
}
if call_access_pattern.is_mutably_dereferenced() {
// The stack value may have been overwritten by the call
if let Ok(offset) = offset.try_to_offset() {
self.stack.mark_interval_values_as_top(
offset,
offset,
ByteSize::new(1),
);
}
}
}
}
}
}
/// If the given abstract ID represents a possible parameter of the current function
/// then return an argument object corresponding to the parameter.
pub fn get_arg_corresponding_to_id(&self, id: &AbstractIdentifier) -> Option<Arg> {
if id.get_tid() == self.stack_id.get_tid() {
Some(generate_arg_from_abstract_id(id))
} else {
None
}
}
}
/// Generate register arguments from a list of registers.
fn generate_args_from_registers(registers: &[Variable]) -> Vec<Arg> {
registers
.iter()
.map(|var| Arg::from_var(var.clone(), None))
.collect()
}
/// Generate an argument representing the location in the given abstract ID.
/// If the location is a pointer, it is assumed that the pointer points to the stack.
/// Panics if the location contains a second level of indirection.
fn generate_arg_from_abstract_id(id: &AbstractIdentifier) -> Arg {
match id.get_location() {
AbstractLocation::Register(var) => Arg::from_var(var.clone(), None),
AbstractLocation::Pointer(var, mem_location) => match mem_location {
AbstractMemoryLocation::Location { offset, size } => Arg::Stack {
address: Expression::Var(var.clone()).plus_const(*offset),
size: *size,
data_type: None,
},
AbstractMemoryLocation::Pointer { .. } => {
panic!("Memory location is not a stack offset.")
}
},
}
}
use super::*;
impl State {
/// Generate a mock state for an ARM-32 state.
pub fn mock() -> State {
State::new(
&Tid::new("mock_fn"),
&Variable::mock("sp", 4),
&CallingConvention::mock_standard_arm_32(),
)
}
/// Generate a mock state for an x64 state.
pub fn mock_x64(tid_name: &str) -> State {
State::new(
&Tid::new(tid_name),
&Variable::mock("RSP", 8),
&CallingConvention::mock(),
)
}
}
/// Mock an abstract ID representing the stack.
fn mock_stack_id() -> AbstractIdentifier {
AbstractIdentifier::new_from_var(Tid::new("mock_fn"), &Variable::mock("sp", 4))
}
/// Mock an abstract ID of a stack parameter
fn mock_stack_param_id(offset: i64, size: u64) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("mock_fn"),
AbstractLocation::from_stack_position(
mock_stack_id().unwrap_register(),
offset,
ByteSize::new(size),
),
)
}
#[test]
fn test_new() {
let state = State::mock();
// Test the generated stack
assert_eq!(&state.stack_id, &mock_stack_id());
assert_eq!(state.stack.iter().len(), 0);
// Assert that the register values are as expected
assert_eq!(state.register.len(), 9); // 8 parameters plus stack pointer
assert_eq!(
state.get_register(&Variable::mock("sp", 4)),
DataDomain::from_target(
mock_stack_id(),
Bitvector::zero(ByteSize::new(4).into()).into()
)
);
// Check the generated tracked IDs
assert_eq!(state.tracked_ids.len(), 8);
for (id, access_pattern) in state.tracked_ids.iter() {
assert_eq!(
state.get_register(id.unwrap_register()),
DataDomain::from_target(id.clone(), Bitvector::zero(ByteSize::new(4).into()).into())
);
assert_eq!(access_pattern, &AccessPattern::new());
}
}
#[test]
fn test_store_and_load_from_stack() {
let mut state = State::mock();
let address = DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(-4).into());
let value: DataDomain<BitvectorDomain> = Bitvector::from_i32(0).into();
// write and load a value to the current stack frame
state.write_value(address.clone(), value.clone());
assert_eq!(state.stack.iter().len(), 1);
assert_eq!(
state.stack.get(Bitvector::from_i32(-4), ByteSize::new(4)),
value.clone()
);
assert_eq!(state.load_value(address, ByteSize::new(4)), value);
// Load a parameter register and check that the parameter gets generated
let address = DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(4).into());
let stack_param_id = mock_stack_param_id(4, 4);
let stack_param =
DataDomain::from_target(stack_param_id.clone(), Bitvector::from_i32(0).into());
assert_eq!(state.tracked_ids.iter().len(), 8);
assert_eq!(
state.load_value(address.clone(), ByteSize::new(4)),
stack_param
);
assert_eq!(state.tracked_ids.iter().len(), 9);
assert_eq!(
state
.tracked_ids
.get(&stack_param_id)
.unwrap()
.is_accessed(),
false
); // The load method does not set access flags.
}
#[test]
fn test_load_unsized_from_stack() {
let mut state = State::mock();
// Load an existing stack param (generated by a sized load at the same address)
let address = DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(0).into());
let stack_param_id = mock_stack_param_id(0, 4);
let stack_param =
DataDomain::from_target(stack_param_id.clone(), Bitvector::from_i32(0).into());
state.load_value(address, ByteSize::new(4));
let unsized_load = state.load_unsized_value_from_stack(Bitvector::from_i32(0));
assert_eq!(unsized_load, stack_param);
assert!(state.tracked_ids.get(&stack_param_id).is_some());
// Load a non-existing stack param
let stack_param_id = mock_stack_param_id(4, 1);
let stack_param = DataDomain::from_target(stack_param_id.clone(), Bitvector::from_i8(0).into());
let unsized_load = state.load_unsized_value_from_stack(Bitvector::from_i32(4));
assert_eq!(unsized_load, stack_param);
assert!(state.tracked_ids.get(&stack_param_id).is_some());
// Unsized load from the current stack frame
let unsized_load = state.load_unsized_value_from_stack(Bitvector::from_i32(-4));
assert_eq!(unsized_load, DataDomain::new_top(ByteSize::new(1)));
}
#[test]
fn test_eval() {
let mut state = State::mock();
// Test the eval method
let expr = Expression::Var(Variable::mock("sp", 4)).plus_const(42);
assert_eq!(
state.eval(&expr),
DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(42).into())
);
// Test the eval_parameter_arg method
let arg = Arg::from_var(Variable::mock("sp", 4), None);
assert_eq!(
state.eval_parameter_arg(&arg),
DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(0).into())
);
}
#[test]
fn test_extern_symbol_handling() {
let mut state = State::mock();
let extern_symbol = ExternSymbol::mock_arm32();
let cconv = CallingConvention::mock_arm32();
let call = Term {
tid: Tid::new("call_tid"),
term: Jmp::Call {
target: extern_symbol.tid.clone(),
return_: Some(Tid::new("return_tid")),
},
};
let param_id = AbstractIdentifier::new_from_var(Tid::new("mock_fn"), &Variable::mock("r0", 4));
let return_val_id =
AbstractIdentifier::new_from_var(Tid::new("call_tid"), &Variable::mock("r0", 4));
// Test extern symbol handling.
state.handle_extern_symbol(&call, &extern_symbol, &cconv);
assert_eq!(
state
.tracked_ids
.get(&param_id)
.unwrap()
.is_mutably_dereferenced(),
true
);
let return_val = state.get_register(&Variable::mock("r0", 4));
assert_eq!(return_val.get_relative_values().iter().len(), 2);
assert_eq!(
return_val.get_relative_values().get(&param_id).unwrap(),
&BitvectorDomain::new_top(ByteSize::new(4))
);
assert_eq!(
return_val.get_relative_values().get(&param_id).unwrap(),
&BitvectorDomain::new_top(ByteSize::new(4))
);
assert_eq!(
return_val
.get_relative_values()
.get(&return_val_id)
.unwrap(),
&Bitvector::from_i32(0).into()
);
}
......@@ -5,6 +5,7 @@ pub mod backward_interprocedural_fixpoint;
pub mod dead_variable_elimination;
pub mod fixpoint;
pub mod forward_interprocedural_fixpoint;
pub mod function_signature;
pub mod graph;
pub mod interprocedural_fixpoint_generic;
pub mod pointer_inference;
......
......@@ -446,7 +446,11 @@ mod tests {
let (mut state, _pi_state) = State::mock_with_pi_state();
assert_eq!(
context.check_parameters_for_taint(&state, &ExternSymbol::mock(), NodeIndex::new(0)),
context.check_parameters_for_taint(
&state,
&ExternSymbol::mock_x64(),
NodeIndex::new(0)
),
false
);
......@@ -455,7 +459,11 @@ mod tests {
Taint::Tainted(ByteSize::new(8)),
);
assert_eq!(
context.check_parameters_for_taint(&state, &ExternSymbol::mock(), NodeIndex::new(0)),
context.check_parameters_for_taint(
&state,
&ExternSymbol::mock_x64(),
NodeIndex::new(0)
),
true
);
}
......
......@@ -52,6 +52,46 @@ impl Arg {
Arg::Stack { data_type, .. } => data_type.clone(),
}
}
/// If the argument is a stack argument,
/// return its offset relative to the current stack register value.
/// Return an error for register arguments or if the offset could not be computed.
pub fn eval_stack_offset(&self, stack_register: &Variable) -> Result<Bitvector, Error> {
let expression = match self {
Arg::Register { .. } => return Err(anyhow!("The argument is not a stack argument.")),
Arg::Stack { address, .. } => address,
};
Self::eval_stack_offset_expression(expression, stack_register)
}
/// If the given expression computes a constant offset to the given stack register,
/// then return the offset.
/// Else return an error.
fn eval_stack_offset_expression(
expression: &Expression,
stack_register: &Variable,
) -> Result<Bitvector, Error> {
match expression {
Expression::Var(var) => {
if var == stack_register {
Ok(Bitvector::zero(var.size.into()))
} else {
Err(anyhow!("Input register is not the stack register"))
}
}
Expression::Const(bitvec) => Ok(bitvec.clone()),
Expression::BinOp { op, lhs, rhs } => {
let lhs = Self::eval_stack_offset_expression(lhs, stack_register)?;
let rhs = Self::eval_stack_offset_expression(rhs, stack_register)?;
lhs.bin_op(*op, &rhs)
}
Expression::UnOp { op, arg } => {
let arg = Self::eval_stack_offset_expression(arg, stack_register)?;
arg.un_op(*op)
}
_ => Err(anyhow!("Expression type not supported for argument values")),
}
}
}
/// An extern symbol represents a funtion that is dynamically linked from another binary.
......@@ -131,6 +171,30 @@ pub struct CallingConvention {
pub callee_saved_register: Vec<Variable>,
}
impl CallingConvention {
/// Return a list of all parameter registers of the calling convention.
/// For parameters, where only a part of a register is the actual parameter,
/// the parameter register is approximated by the (larger) base register.
pub fn get_all_parameter_register(&self) -> Vec<&Variable> {
let mut register_list: Vec<&Variable> = self.integer_parameter_register.iter().collect();
for float_param_expr in self.float_parameter_register.iter() {
register_list.append(&mut float_param_expr.input_vars());
}
register_list
}
/// Return a list of all return registers of the calling convention.
/// For return register, where only a part of a register is the actual return register,
/// the return register is approximated by the (larger) base register.
pub fn get_all_return_register(&self) -> Vec<&Variable> {
let mut register_list: Vec<&Variable> = self.integer_return_register.iter().collect();
for float_param_expr in self.float_return_register.iter() {
register_list.append(&mut float_param_expr.input_vars());
}
register_list
}
}
#[cfg(test)]
mod tests {
use super::*;
......@@ -159,6 +223,17 @@ mod tests {
}
}
pub fn mock_arm32() -> CallingConvention {
CallingConvention {
name: "__stdcall".to_string(), // so that the mock is useable as standard calling convention in tests
integer_parameter_register: vec![Variable::mock("r0", 4)],
float_parameter_register: vec![Expression::Var(Variable::mock("d0", 8))],
integer_return_register: vec![Variable::mock("r0", 4)],
float_return_register: vec![],
callee_saved_register: vec![Variable::mock("r4", 4)],
}
}
pub fn mock_with_parameter_registers(
integer_parameter_register: Vec<Variable>,
float_parameter_register: Vec<Variable>,
......@@ -209,7 +284,7 @@ mod tests {
}
impl ExternSymbol {
pub fn mock() -> ExternSymbol {
pub fn mock_x64() -> ExternSymbol {
ExternSymbol {
tid: Tid::new("mock_symbol"),
addresses: vec!["UNKNOWN".to_string()],
......@@ -222,6 +297,20 @@ mod tests {
}
}
pub fn mock_arm32() -> ExternSymbol {
// There is also the mock_standard_arm32() method. Only on of the two should exist!
ExternSymbol {
tid: Tid::new("mock_symbol"),
addresses: vec!["UNKNOWN".to_string()],
name: "mock_symbol".to_string(),
calling_convention: Some("__stdcall".to_string()),
parameters: vec![Arg::mock_register("r0", 4)],
return_values: vec![Arg::mock_register("r0", 4)],
no_return: false,
has_var_args: false,
}
}
pub fn mock_string() -> Self {
ExternSymbol {
tid: Tid::new("sprintf"),
......
use super::ByteSize;
use crate::prelude::*;
use std::fmt::Display;
/// A variable represents a register with a known size and name.
///
......@@ -19,6 +20,16 @@ pub struct Variable {
pub is_temp: bool,
}
impl Display for Variable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.name, self.size.as_bit_length())?;
if self.is_temp {
write!(f, "(temp)")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
......
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