Unverified Commit b14c336f by Enkelmann Committed by GitHub

Handle global memory in pointer inference analysis (#133)

parent 0cf8b8d7
...@@ -166,6 +166,7 @@ fn run_with_ghidra(args: CmdlineArgs) { ...@@ -166,6 +166,7 @@ fn run_with_ghidra(args: CmdlineArgs) {
if args.debug { if args.debug {
cwe_checker_rs::analysis::pointer_inference::run( cwe_checker_rs::analysis::pointer_inference::run(
&project, &project,
&runtime_memory_image,
serde_json::from_value(config["Memory"].clone()).unwrap(), serde_json::from_value(config["Memory"].clone()).unwrap(),
true, true,
); );
......
use super::object::ObjectType; use super::object::ObjectType;
use crate::abstract_domain::*;
use crate::analysis::graph::Graph; use crate::analysis::graph::Graph;
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::utils::log::*; use crate::utils::log::*;
use crate::{abstract_domain::*, utils::binary::RuntimeMemoryImage};
use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::collections::{BTreeMap, BTreeSet, HashSet};
use super::state::State; use super::state::State;
...@@ -21,6 +21,9 @@ pub struct Context<'a> { ...@@ -21,6 +21,9 @@ pub struct Context<'a> {
pub graph: Graph<'a>, pub graph: Graph<'a>,
/// A reference to the `Project` object representing the binary /// A reference to the `Project` object representing the binary
pub project: &'a Project, pub project: &'a Project,
/// The runtime memory image for reading global read-only variables.
/// Note that values of writeable global memory segments are not tracked.
pub runtime_memory_image: &'a RuntimeMemoryImage,
/// Maps the TIDs of functions that shall be treated as extern symbols to the `ExternSymbol` object representing it. /// Maps the TIDs of functions that shall be treated as extern symbols to the `ExternSymbol` object representing it.
pub extern_symbol_map: BTreeMap<Tid, &'a ExternSymbol>, pub extern_symbol_map: BTreeMap<Tid, &'a ExternSymbol>,
/// A channel where found CWE warnings and log messages should be sent to. /// A channel where found CWE warnings and log messages should be sent to.
...@@ -39,10 +42,11 @@ impl<'a> Context<'a> { ...@@ -39,10 +42,11 @@ impl<'a> Context<'a> {
/// Create a new context object for a given project. /// Create a new context object for a given project.
/// Also needs two channels as input to know where CWE warnings and log messages should be sent to. /// Also needs two channels as input to know where CWE warnings and log messages should be sent to.
pub fn new( pub fn new(
project: &Project, project: &'a Project,
runtime_memory_image: &'a RuntimeMemoryImage,
config: Config, config: Config,
log_collector: crossbeam_channel::Sender<LogThreadMsg>, log_collector: crossbeam_channel::Sender<LogThreadMsg>,
) -> Context { ) -> Context<'a> {
let mut extern_symbol_map = BTreeMap::new(); let mut extern_symbol_map = BTreeMap::new();
for symbol in project.program.term.extern_symbols.iter() { for symbol in project.program.term.extern_symbols.iter() {
extern_symbol_map.insert(symbol.tid.clone(), symbol); extern_symbol_map.insert(symbol.tid.clone(), symbol);
...@@ -59,6 +63,7 @@ impl<'a> Context<'a> { ...@@ -59,6 +63,7 @@ impl<'a> Context<'a> {
Context { Context {
graph, graph,
project, project,
runtime_memory_image,
extern_symbol_map, extern_symbol_map,
log_collector, log_collector,
allocation_symbols: config.allocation_symbols, allocation_symbols: config.allocation_symbols,
...@@ -169,8 +174,11 @@ impl<'a> Context<'a> { ...@@ -169,8 +174,11 @@ impl<'a> Context<'a> {
) -> Option<State> { ) -> Option<State> {
match extern_symbol.get_unique_parameter() { match extern_symbol.get_unique_parameter() {
Ok(parameter) => { Ok(parameter) => {
let parameter_value = let parameter_value = state.eval_parameter_arg(
state.eval_parameter_arg(parameter, &self.project.stack_pointer_register); parameter,
&self.project.stack_pointer_register,
&self.runtime_memory_image,
);
match parameter_value { match parameter_value {
Ok(memory_object_pointer) => { Ok(memory_object_pointer) => {
if let Data::Pointer(pointer) = memory_object_pointer { if let Data::Pointer(pointer) = memory_object_pointer {
...@@ -225,7 +233,11 @@ impl<'a> Context<'a> { ...@@ -225,7 +233,11 @@ impl<'a> Context<'a> {
extern_symbol: &ExternSymbol, extern_symbol: &ExternSymbol,
) { ) {
for parameter in extern_symbol.parameters.iter() { for parameter in extern_symbol.parameters.iter() {
match state.eval_parameter_arg(parameter, &self.project.stack_pointer_register) { match state.eval_parameter_arg(
parameter,
&self.project.stack_pointer_register,
&self.runtime_memory_image,
) {
Ok(value) => { Ok(value) => {
if state.memory.is_dangling_pointer(&value, true) { if state.memory.is_dangling_pointer(&value, true) {
let warning = CweWarning { let warning = CweWarning {
...@@ -304,7 +316,11 @@ impl<'a> Context<'a> { ...@@ -304,7 +316,11 @@ impl<'a> Context<'a> {
extern_symbol: &ExternSymbol, extern_symbol: &ExternSymbol,
) -> Option<State> { ) -> Option<State> {
self.log_debug( self.log_debug(
new_state.clear_stack_parameter(extern_symbol, &self.project.stack_pointer_register), new_state.clear_stack_parameter(
extern_symbol,
&self.project.stack_pointer_register,
self.runtime_memory_image,
),
Some(&call.tid), Some(&call.tid),
); );
let calling_conv = extern_symbol.get_calling_convention(&self.project); let calling_conv = extern_symbol.get_calling_convention(&self.project);
...@@ -320,9 +336,11 @@ impl<'a> Context<'a> { ...@@ -320,9 +336,11 @@ impl<'a> Context<'a> {
} }
} else { } else {
for parameter in extern_symbol.parameters.iter() { for parameter in extern_symbol.parameters.iter() {
if let Ok(data) = if let Ok(data) = state.eval_parameter_arg(
state.eval_parameter_arg(parameter, &self.project.stack_pointer_register) parameter,
{ &self.project.stack_pointer_register,
&self.runtime_memory_image,
) {
possible_referenced_ids.append(&mut data.referenced_ids()); possible_referenced_ids.append(&mut data.referenced_ids());
} }
} }
......
...@@ -109,8 +109,9 @@ fn context_problem_implementation() { ...@@ -109,8 +109,9 @@ fn context_problem_implementation() {
use Expression::*; use Expression::*;
let (project, config) = mock_project(); let (project, config) = mock_project();
let runtime_memory_image = RuntimeMemoryImage::mock();
let (log_sender, _log_receiver) = crossbeam_channel::unbounded(); let (log_sender, _log_receiver) = crossbeam_channel::unbounded();
let context = Context::new(&project, config, log_sender); let context = Context::new(&project, &runtime_memory_image, config, log_sender);
let mut state = State::new(&register("RSP"), Tid::new("main")); let mut state = State::new(&register("RSP"), Tid::new("main"));
let def = Term { let def = Term {
...@@ -271,8 +272,9 @@ fn update_return() { ...@@ -271,8 +272,9 @@ fn update_return() {
use crate::analysis::pointer_inference::object::ObjectType; use crate::analysis::pointer_inference::object::ObjectType;
use crate::analysis::pointer_inference::Data; use crate::analysis::pointer_inference::Data;
let (project, config) = mock_project(); let (project, config) = mock_project();
let runtime_memory_image = RuntimeMemoryImage::mock();
let (log_sender, _log_receiver) = crossbeam_channel::unbounded(); let (log_sender, _log_receiver) = crossbeam_channel::unbounded();
let context = Context::new(&project, config, log_sender); let context = Context::new(&project, &runtime_memory_image, config, log_sender);
let state_before_return = State::new(&register("RSP"), Tid::new("callee")); let state_before_return = State::new(&register("RSP"), Tid::new("callee"));
let mut state_before_return = context let mut state_before_return = context
.update_def( .update_def(
......
...@@ -35,7 +35,10 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont ...@@ -35,7 +35,10 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
match &def.term { match &def.term {
Def::Store { address, value } => { Def::Store { address, value } => {
let mut new_state = state.clone(); let mut new_state = state.clone();
self.log_debug(new_state.handle_store(address, value), Some(&def.tid)); self.log_debug(
new_state.handle_store(address, value, self.runtime_memory_image),
Some(&def.tid),
);
Some(new_state) Some(new_state)
} }
Def::Assign { var, value } => { Def::Assign { var, value } => {
...@@ -45,7 +48,10 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont ...@@ -45,7 +48,10 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
} }
Def::Load { var, address } => { Def::Load { var, address } => {
let mut new_state = state.clone(); let mut new_state = state.clone();
self.log_debug(new_state.handle_load(var, address), Some(&def.tid)); self.log_debug(
new_state.handle_load(var, address, &self.runtime_memory_image),
Some(&def.tid),
);
Some(new_state) Some(new_state)
} }
} }
......
...@@ -16,11 +16,14 @@ ...@@ -16,11 +16,14 @@
use super::fixpoint::Computation; use super::fixpoint::Computation;
use super::forward_interprocedural_fixpoint::GeneralizedContext; use super::forward_interprocedural_fixpoint::GeneralizedContext;
use super::interprocedural_fixpoint_generic::NodeValue; use super::interprocedural_fixpoint_generic::NodeValue;
use crate::abstract_domain::{BitvectorDomain, DataDomain};
use crate::analysis::graph::{Graph, Node}; use crate::analysis::graph::{Graph, Node};
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::utils::log::*; use crate::utils::log::*;
use crate::{
abstract_domain::{BitvectorDomain, DataDomain},
utils::binary::RuntimeMemoryImage,
};
use petgraph::graph::NodeIndex; use petgraph::graph::NodeIndex;
use petgraph::visit::IntoNodeReferences; use petgraph::visit::IntoNodeReferences;
use petgraph::Direction; use petgraph::Direction;
...@@ -70,10 +73,11 @@ impl<'a> PointerInference<'a> { ...@@ -70,10 +73,11 @@ impl<'a> PointerInference<'a> {
/// Generate a new pointer inference compuation for a project. /// Generate a new pointer inference compuation for a project.
pub fn new( pub fn new(
project: &'a Project, project: &'a Project,
runtime_memory_image: &'a RuntimeMemoryImage,
config: Config, config: Config,
log_sender: crossbeam_channel::Sender<LogThreadMsg>, log_sender: crossbeam_channel::Sender<LogThreadMsg>,
) -> PointerInference<'a> { ) -> PointerInference<'a> {
let context = Context::new(project, config, log_sender.clone()); let context = Context::new(project, runtime_memory_image, config, log_sender.clone());
let mut entry_sub_to_entry_blocks_map = HashMap::new(); let mut entry_sub_to_entry_blocks_map = HashMap::new();
let subs: HashMap<Tid, &Term<Sub>> = project let subs: HashMap<Tid, &Term<Sub>> = project
...@@ -398,10 +402,20 @@ pub fn extract_pi_analysis_results( ...@@ -398,10 +402,20 @@ pub fn extract_pi_analysis_results(
/// ///
/// If `print_debug` is set to `true` print debug information to *stdout*. /// If `print_debug` is set to `true` print debug information to *stdout*.
/// Note that the format of the debug information is currently unstable and subject to change. /// Note that the format of the debug information is currently unstable and subject to change.
pub fn run(project: &Project, config: Config, print_debug: bool) -> PointerInference { pub fn run<'a>(
project: &'a Project,
runtime_memory_image: &'a RuntimeMemoryImage,
config: Config,
print_debug: bool,
) -> PointerInference<'a> {
let logging_thread = LogThread::spawn(collect_all_logs); let logging_thread = LogThread::spawn(collect_all_logs);
let mut computation = PointerInference::new(project, config, logging_thread.get_msg_sender()); let mut computation = PointerInference::new(
project,
runtime_memory_image,
config,
logging_thread.get_msg_sender(),
);
computation.compute_with_speculative_entry_points(project); computation.compute_with_speculative_entry_points(project);
...@@ -457,13 +471,16 @@ mod tests { ...@@ -457,13 +471,16 @@ mod tests {
use super::*; use super::*;
impl<'a> PointerInference<'a> { impl<'a> PointerInference<'a> {
pub fn mock(project: &'a Project) -> PointerInference<'a> { pub fn mock(
project: &'a Project,
mem_image: &'a RuntimeMemoryImage,
) -> PointerInference<'a> {
let config = Config { let config = Config {
allocation_symbols: vec!["malloc".to_string()], allocation_symbols: vec!["malloc".to_string()],
deallocation_symbols: vec!["free".to_string()], deallocation_symbols: vec!["free".to_string()],
}; };
let (log_sender, _) = crossbeam_channel::unbounded(); let (log_sender, _) = crossbeam_channel::unbounded();
PointerInference::new(project, config, log_sender) PointerInference::new(project, mem_image, config, log_sender)
} }
} }
} }
...@@ -70,7 +70,7 @@ impl AbstractObjectList { ...@@ -70,7 +70,7 @@ impl AbstractObjectList {
/// Returns an error if the address is a `Data::Value`, i.e. not a pointer. /// Returns an error if the address is a `Data::Value`, i.e. not a pointer.
pub fn get_value(&self, address: &Data, size: ByteSize) -> Result<Data, Error> { pub fn get_value(&self, address: &Data, size: ByteSize) -> Result<Data, Error> {
match address { match address {
Data::Value(value) => Err(anyhow!("Load from non-pointer value:\n{:?}", value)), Data::Value(_) => Err(anyhow!("Load from non-pointer value")),
Data::Top(_) => Ok(Data::new_top(size)), Data::Top(_) => Ok(Data::new_top(size)),
Data::Pointer(pointer) => { Data::Pointer(pointer) => {
let mut merged_value: Option<Data> = None; let mut merged_value: Option<Data> = None;
......
use crate::utils::binary::RuntimeMemoryImage;
use super::*; use super::*;
impl State { impl State {
...@@ -55,7 +57,12 @@ impl State { ...@@ -55,7 +57,12 @@ impl State {
} }
/// Store `value` at the given `address`. /// Store `value` at the given `address`.
pub fn store_value(&mut self, address: &Data, value: &Data) -> Result<(), Error> { pub fn store_value(
&mut self,
address: &Data,
value: &Data,
global_memory: &RuntimeMemoryImage,
) -> Result<(), Error> {
// If the address is a unique caller stack address, write to *all* caller stacks. // If the address is a unique caller stack address, write to *all* caller stacks.
if let Some(offset) = self.unwrap_offset_if_caller_stack_address(address) { if let Some(offset) = self.unwrap_offset_if_caller_stack_address(address) {
let caller_addresses: Vec<_> = self let caller_addresses: Vec<_> = self
...@@ -67,53 +74,95 @@ impl State { ...@@ -67,53 +74,95 @@ impl State {
.collect(); .collect();
let mut result = Ok(()); let mut result = Ok(());
for address in caller_addresses { for address in caller_addresses {
if let Err(err) = self.store_value(&address, &value.clone()) { if let Err(err) = self.store_value(&address, &value.clone(), global_memory) {
result = Err(err); result = Err(err);
} }
} }
// Note that this only returns the last error that was detected. // Note that this only returns the last error that was detected.
result result
} else if let Data::Pointer(pointer) = self.adjust_pointer_for_read(address) { } else {
match self.adjust_pointer_for_read(address) {
Data::Pointer(pointer) => {
self.memory.set_value(pointer, value.clone())?; self.memory.set_value(pointer, value.clone())?;
Ok(()) Ok(())
} else { }
// TODO: Implement recognition of stores to global memory. Data::Value(BitvectorDomain::Value(address_to_global_data)) => {
Err(anyhow!("Memory write to non-pointer data")) match global_memory.is_address_writeable(&address_to_global_data) {
Ok(true) => Ok(()),
Ok(false) => Err(anyhow!("Write to read-only global data")),
Err(err) => Err(err),
}
}
Data::Value(BitvectorDomain::Top(_)) | Data::Top(_) => Ok(()),
}
} }
} }
/// Write a value to the address one gets when evaluating the address expression. /// Write a value to the address one gets when evaluating the address expression.
pub fn write_to_address(&mut self, address: &Expression, value: &Data) -> Result<(), Error> { pub fn write_to_address(
&mut self,
address: &Expression,
value: &Data,
global_memory: &RuntimeMemoryImage,
) -> Result<(), Error> {
match self.eval(address) { match self.eval(address) {
Ok(address_data) => self.store_value(&address_data, value), Ok(address_data) => self.store_value(&address_data, value, global_memory),
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
/// Evaluate the given store instruction on the given state and return the resulting state. /// Evaluate the store instruction, given by its address and value expressions,
/// and modify the state accordingly.
/// ///
/// The function panics if given anything else than a store expression. /// If an error occurs, the state is still modified before the error is returned.
pub fn handle_store(&mut self, address: &Expression, value: &Expression) -> Result<(), Error> { /// E.g. if the value expression cannot be evaluated,
/// the value at the target address is overwritten with a `Top` value.
pub fn handle_store(
&mut self,
address: &Expression,
value: &Expression,
global_memory: &RuntimeMemoryImage,
) -> Result<(), Error> {
match self.eval(value) { match self.eval(value) {
Ok(data) => self.write_to_address(address, &data), Ok(data) => self.write_to_address(address, &data, global_memory),
Err(err) => { Err(err) => {
// we still need to write to the target location before reporting the error // we still need to write to the target location before reporting the error
self.write_to_address(address, &Data::new_top(value.bytesize()))?; self.write_to_address(address, &Data::new_top(value.bytesize()), global_memory)?;
Err(err) Err(err)
} }
} }
} }
/// Evaluate the given load instruction and return the data read on success. /// Evaluate the given load instruction and return the data read on success.
pub fn load_value(&self, address: &Expression, size: ByteSize) -> Result<Data, Error> { pub fn load_value(
Ok(self &self,
.memory address: &Expression,
.get_value(&self.adjust_pointer_for_read(&self.eval(address)?), size)?) size: ByteSize,
global_memory: &RuntimeMemoryImage,
) -> Result<Data, Error> {
let address = self.adjust_pointer_for_read(&self.eval(address)?);
match address {
Data::Value(BitvectorDomain::Value(address_bitvector)) => {
let loaded_value = global_memory.read(&address_bitvector, size)?;
if loaded_value.is_top() {
Ok(Data::Top(loaded_value.bytesize()))
} else {
Ok(Data::Value(loaded_value))
}
}
Data::Value(BitvectorDomain::Top(_)) | Data::Top(_) => Ok(Data::new_top(size)),
Data::Pointer(_) => Ok(self.memory.get_value(&address, size)?),
}
} }
/// Handle a load instruction by assigning the value loaded from the address given by the `address` expression to `var`. /// Handle a load instruction by assigning the value loaded from the address given by the `address` expression to `var`.
pub fn handle_load(&mut self, var: &Variable, address: &Expression) -> Result<(), Error> { pub fn handle_load(
match self.load_value(address, var.size) { &mut self,
var: &Variable,
address: &Expression,
global_memory: &RuntimeMemoryImage,
) -> Result<(), Error> {
match self.load_value(address, var.size, global_memory) {
Ok(data) => { Ok(data) => {
self.set_register(var, data); self.set_register(var, data);
Ok(()) Ok(())
...@@ -196,6 +245,7 @@ impl State { ...@@ -196,6 +245,7 @@ impl State {
&self, &self,
parameter: &Arg, parameter: &Arg,
stack_pointer: &Variable, stack_pointer: &Variable,
global_memory: &RuntimeMemoryImage,
) -> Result<Data, Error> { ) -> Result<Data, Error> {
match parameter { match parameter {
Arg::Register(var) => self.eval(&Expression::Var(var.clone())), Arg::Register(var) => self.eval(&Expression::Var(var.clone())),
...@@ -210,6 +260,7 @@ impl State { ...@@ -210,6 +260,7 @@ impl State {
)), )),
}, },
*size, *size,
global_memory,
), ),
} }
} }
......
...@@ -3,6 +3,7 @@ use super::Data; ...@@ -3,6 +3,7 @@ use super::Data;
use crate::abstract_domain::*; use crate::abstract_domain::*;
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::utils::binary::RuntimeMemoryImage;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
mod access_handling; mod access_handling;
...@@ -89,6 +90,7 @@ impl State { ...@@ -89,6 +90,7 @@ impl State {
&mut self, &mut self,
extern_call: &ExternSymbol, extern_call: &ExternSymbol,
stack_pointer_register: &Variable, stack_pointer_register: &Variable,
global_memory: &RuntimeMemoryImage,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut result_log = Ok(()); let mut result_log = Ok(());
for arg in &extern_call.parameters { for arg in &extern_call.parameters {
...@@ -105,7 +107,9 @@ impl State { ...@@ -105,7 +107,9 @@ impl State {
.unwrap(), .unwrap(),
)), )),
}; };
if let Err(err) = self.write_to_address(&location_expression, &data_top) { if let Err(err) =
self.write_to_address(&location_expression, &data_top, global_memory)
{
result_log = Err(err); result_log = Err(err);
} }
} }
......
...@@ -24,6 +24,7 @@ use crate::abstract_domain::{BitvectorDomain, DataDomain}; ...@@ -24,6 +24,7 @@ use crate::abstract_domain::{BitvectorDomain, DataDomain};
use crate::analysis::pointer_inference::State; use crate::analysis::pointer_inference::State;
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::utils::binary::RuntimeMemoryImage;
use crate::utils::log::{CweWarning, LogMessage}; use crate::utils::log::{CweWarning, LogMessage};
use crate::utils::symbol_utils::{get_callsites, get_symbol_map}; use crate::utils::symbol_utils::{get_callsites, get_symbol_map};
use crate::CweModule; use crate::CweModule;
...@@ -43,20 +44,24 @@ pub struct Config { ...@@ -43,20 +44,24 @@ pub struct Config {
/// Compute the program state at the end of the given basic block /// Compute the program state at the end of the given basic block
/// assuming nothing is known about the state at the start of the block. /// assuming nothing is known about the state at the start of the block.
fn compute_block_end_state(project: &Project, block: &Term<Blk>) -> State { fn compute_block_end_state(
project: &Project,
global_memory: &RuntimeMemoryImage,
block: &Term<Blk>,
) -> State {
let stack_register = &project.stack_pointer_register; let stack_register = &project.stack_pointer_register;
let mut state = State::new(stack_register, block.tid.clone()); let mut state = State::new(stack_register, block.tid.clone());
for def in block.term.defs.iter() { for def in block.term.defs.iter() {
match &def.term { match &def.term {
Def::Store { address, value } => { Def::Store { address, value } => {
let _ = state.handle_store(address, value); let _ = state.handle_store(address, value, global_memory);
} }
Def::Assign { var, value } => { Def::Assign { var, value } => {
let _ = state.handle_register_assign(var, value); let _ = state.handle_register_assign(var, value);
} }
Def::Load { var, address } => { Def::Load { var, address } => {
let _ = state.handle_load(var, address); let _ = state.handle_load(var, address, global_memory);
} }
} }
} }
...@@ -66,14 +71,15 @@ fn compute_block_end_state(project: &Project, block: &Term<Blk>) -> State { ...@@ -66,14 +71,15 @@ fn compute_block_end_state(project: &Project, block: &Term<Blk>) -> State {
/// Check whether a parameter value of the call to `symbol` has value `sizeof(void*)`. /// Check whether a parameter value of the call to `symbol` has value `sizeof(void*)`.
fn check_for_pointer_sized_arg( fn check_for_pointer_sized_arg(
project: &Project, project: &Project,
global_memory: &RuntimeMemoryImage,
block: &Term<Blk>, block: &Term<Blk>,
symbol: &ExternSymbol, symbol: &ExternSymbol,
) -> bool { ) -> bool {
let pointer_size = project.stack_pointer_register.size; let pointer_size = project.stack_pointer_register.size;
let state = compute_block_end_state(project, block); let state = compute_block_end_state(project, global_memory, block);
for parameter in symbol.parameters.iter() { for parameter in symbol.parameters.iter() {
if let Ok(DataDomain::Value(BitvectorDomain::Value(param_value))) = if let Ok(DataDomain::Value(BitvectorDomain::Value(param_value))) =
state.eval_parameter_arg(parameter, &project.stack_pointer_register) state.eval_parameter_arg(parameter, &project.stack_pointer_register, global_memory)
{ {
if Ok(u64::from(pointer_size)) == param_value.try_to_u64() { if Ok(u64::from(pointer_size)) == param_value.try_to_u64() {
return true; return true;
...@@ -113,7 +119,12 @@ pub fn check_cwe( ...@@ -113,7 +119,12 @@ pub fn check_cwe(
let symbol_map = get_symbol_map(project, &config.symbols); let symbol_map = get_symbol_map(project, &config.symbols);
for sub in project.program.term.subs.iter() { for sub in project.program.term.subs.iter() {
for (block, jmp, symbol) in get_callsites(sub, &symbol_map) { for (block, jmp, symbol) in get_callsites(sub, &symbol_map) {
if check_for_pointer_sized_arg(project, block, symbol) { if check_for_pointer_sized_arg(
project,
analysis_results.runtime_memory_image,
block,
symbol,
) {
cwe_warnings.push(generate_cwe_warning(jmp, symbol)) cwe_warnings.push(generate_cwe_warning(jmp, symbol))
} }
} }
......
...@@ -84,7 +84,12 @@ pub fn check_cwe( ...@@ -84,7 +84,12 @@ pub fn check_cwe(
let config: Config = serde_json::from_value(cwe_params.clone()).unwrap(); let config: Config = serde_json::from_value(cwe_params.clone()).unwrap();
let symbol_map = crate::utils::symbol_utils::get_symbol_map(project, &config.symbols[..]); let symbol_map = crate::utils::symbol_utils::get_symbol_map(project, &config.symbols[..]);
let general_context = Context::new(project, &pointer_inference_results, cwe_sender); let general_context = Context::new(
project,
analysis_results.runtime_memory_image,
&pointer_inference_results,
cwe_sender,
);
for edge in general_context.get_graph().edge_references() { for edge in general_context.get_graph().edge_references() {
if let Edge::ExternCallStub(jmp) = edge.weight() { if let Edge::ExternCallStub(jmp) = edge.weight() {
......
...@@ -9,6 +9,7 @@ use crate::analysis::pointer_inference::PointerInference as PointerInferenceComp ...@@ -9,6 +9,7 @@ use crate::analysis::pointer_inference::PointerInference as PointerInferenceComp
use crate::analysis::pointer_inference::State as PointerInferenceState; use crate::analysis::pointer_inference::State as PointerInferenceState;
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::utils::binary::RuntimeMemoryImage;
use crate::utils::log::CweWarning; use crate::utils::log::CweWarning;
use petgraph::graph::NodeIndex; use petgraph::graph::NodeIndex;
use petgraph::visit::IntoNodeReferences; use petgraph::visit::IntoNodeReferences;
...@@ -26,6 +27,8 @@ use std::sync::Arc; ...@@ -26,6 +27,8 @@ use std::sync::Arc;
pub struct Context<'a> { pub struct Context<'a> {
/// A pointer to the corresponding project struct. /// A pointer to the corresponding project struct.
project: &'a Project, project: &'a Project,
/// A pointer to the representation of the runtime memory image.
runtime_memory_image: &'a RuntimeMemoryImage,
/// A pointer to the results of the pointer inference analysis. /// A pointer to the results of the pointer inference analysis.
/// They are used to determine the targets of pointers to memory, /// They are used to determine the targets of pointers to memory,
/// which in turn is used to keep track of taint on the stack or on the heap. /// which in turn is used to keep track of taint on the stack or on the heap.
...@@ -62,6 +65,7 @@ impl<'a> Context<'a> { ...@@ -62,6 +65,7 @@ impl<'a> Context<'a> {
/// since this function can be expensive! /// since this function can be expensive!
pub fn new( pub fn new(
project: &'a Project, project: &'a Project,
runtime_memory_image: &'a RuntimeMemoryImage,
pointer_inference_results: &'a PointerInferenceComputation<'a>, pointer_inference_results: &'a PointerInferenceComputation<'a>,
cwe_collector: crossbeam_channel::Sender<CweWarning>, cwe_collector: crossbeam_channel::Sender<CweWarning>,
) -> Self { ) -> Self {
...@@ -89,6 +93,7 @@ impl<'a> Context<'a> { ...@@ -89,6 +93,7 @@ impl<'a> Context<'a> {
} }
Context { Context {
project, project,
runtime_memory_image,
pointer_inference_results, pointer_inference_results,
block_start_node_map: Arc::new(block_start_node_map), block_start_node_map: Arc::new(block_start_node_map),
extern_symbol_map: Arc::new(extern_symbol_map), extern_symbol_map: Arc::new(extern_symbol_map),
...@@ -220,9 +225,11 @@ impl<'a> Context<'a> { ...@@ -220,9 +225,11 @@ impl<'a> Context<'a> {
return true; return true;
} }
} }
if let Ok(stack_param) = pi_state if let Ok(stack_param) = pi_state.eval_parameter_arg(
.eval_parameter_arg(parameter, &self.project.stack_pointer_register) parameter,
{ &self.project.stack_pointer_register,
&self.runtime_memory_image,
) {
if state.check_if_address_points_to_taint(stack_param, pi_state) { if state.check_if_address_points_to_taint(stack_param, pi_state) {
return true; return true;
} }
...@@ -428,14 +435,16 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont ...@@ -428,14 +435,16 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::utils::binary::RuntimeMemoryImage;
impl<'a> Context<'a> { impl<'a> Context<'a> {
pub fn mock( pub fn mock(
project: &'a Project, project: &'a Project,
runtime_memory_image: &'a RuntimeMemoryImage,
pi_results: &'a PointerInferenceComputation<'a>, pi_results: &'a PointerInferenceComputation<'a>,
) -> Context<'a> { ) -> Context<'a> {
let (cwe_sender, _) = crossbeam_channel::unbounded(); let (cwe_sender, _) = crossbeam_channel::unbounded();
let mut context = Context::new(project, pi_results, cwe_sender); let mut context = Context::new(project, runtime_memory_image, pi_results, cwe_sender);
let taint_source = Box::new(Term { let taint_source = Box::new(Term {
tid: Tid::new("taint_source"), tid: Tid::new("taint_source"),
term: Jmp::Call { term: Jmp::Call {
...@@ -454,8 +463,9 @@ mod tests { ...@@ -454,8 +463,9 @@ mod tests {
#[test] #[test]
fn check_parameter_arg_for_taint() { fn check_parameter_arg_for_taint() {
let project = Project::mock_empty(); let project = Project::mock_empty();
let pi_results = PointerInferenceComputation::mock(&project); let runtime_memory_image = RuntimeMemoryImage::mock();
let context = Context::mock(&project, &pi_results); let pi_results = PointerInferenceComputation::mock(&project, &runtime_memory_image);
let context = Context::mock(&project, &runtime_memory_image, &pi_results);
let (mut state, _pi_state) = State::mock_with_pi_state(); let (mut state, _pi_state) = State::mock_with_pi_state();
assert_eq!( assert_eq!(
...@@ -476,8 +486,9 @@ mod tests { ...@@ -476,8 +486,9 @@ mod tests {
#[test] #[test]
fn handle_generic_call() { fn handle_generic_call() {
let project = Project::mock_empty(); let project = Project::mock_empty();
let pi_results = PointerInferenceComputation::mock(&project); let runtime_memory_image = RuntimeMemoryImage::mock();
let context = Context::mock(&project, &pi_results); let pi_results = PointerInferenceComputation::mock(&project, &runtime_memory_image);
let context = Context::mock(&project, &runtime_memory_image, &pi_results);
let mut state = State::mock(); let mut state = State::mock();
assert!(context assert!(context
...@@ -496,8 +507,9 @@ mod tests { ...@@ -496,8 +507,9 @@ mod tests {
#[test] #[test]
fn update_def() { fn update_def() {
let project = Project::mock_empty(); let project = Project::mock_empty();
let pi_results = PointerInferenceComputation::mock(&project); let runtime_memory_image = RuntimeMemoryImage::mock();
let context = Context::mock(&project, &pi_results); let pi_results = PointerInferenceComputation::mock(&project, &runtime_memory_image);
let context = Context::mock(&project, &runtime_memory_image, &pi_results);
let (mut state, pi_state) = State::mock_with_pi_state(); let (mut state, pi_state) = State::mock_with_pi_state();
state.set_pointer_inference_state(Some(pi_state)); state.set_pointer_inference_state(Some(pi_state));
...@@ -548,8 +560,9 @@ mod tests { ...@@ -548,8 +560,9 @@ mod tests {
#[test] #[test]
fn update_jump() { fn update_jump() {
let project = Project::mock_empty(); let project = Project::mock_empty();
let pi_results = PointerInferenceComputation::mock(&project); let runtime_memory_image = RuntimeMemoryImage::mock();
let context = Context::mock(&project, &pi_results); let pi_results = PointerInferenceComputation::mock(&project, &runtime_memory_image);
let context = Context::mock(&project, &runtime_memory_image, &pi_results);
let (state, _pi_state) = State::mock_with_pi_state(); let (state, _pi_state) = State::mock_with_pi_state();
let jump = Term { let jump = Term {
......
...@@ -26,6 +26,7 @@ use crate::abstract_domain::{BitvectorDomain, DataDomain}; ...@@ -26,6 +26,7 @@ use crate::abstract_domain::{BitvectorDomain, DataDomain};
use crate::analysis::pointer_inference::State; use crate::analysis::pointer_inference::State;
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::utils::binary::RuntimeMemoryImage;
use crate::utils::log::{CweWarning, LogMessage}; use crate::utils::log::{CweWarning, LogMessage};
use crate::utils::symbol_utils::{get_callsites, get_symbol_map}; use crate::utils::symbol_utils::{get_callsites, get_symbol_map};
use crate::CweModule; use crate::CweModule;
...@@ -46,6 +47,7 @@ fn get_umask_permission_arg( ...@@ -46,6 +47,7 @@ fn get_umask_permission_arg(
block: &Term<Blk>, block: &Term<Blk>,
umask_symbol: &ExternSymbol, umask_symbol: &ExternSymbol,
project: &Project, project: &Project,
global_memory: &RuntimeMemoryImage,
) -> Result<u64, Error> { ) -> Result<u64, Error> {
let stack_register = &project.stack_pointer_register; let stack_register = &project.stack_pointer_register;
let mut state = State::new(stack_register, block.tid.clone()); let mut state = State::new(stack_register, block.tid.clone());
...@@ -53,19 +55,20 @@ fn get_umask_permission_arg( ...@@ -53,19 +55,20 @@ fn get_umask_permission_arg(
for def in block.term.defs.iter() { for def in block.term.defs.iter() {
match &def.term { match &def.term {
Def::Store { address, value } => { Def::Store { address, value } => {
let _ = state.handle_store(address, value); let _ = state.handle_store(address, value, global_memory);
} }
Def::Assign { var, value } => { Def::Assign { var, value } => {
let _ = state.handle_register_assign(var, value); let _ = state.handle_register_assign(var, value);
} }
Def::Load { var, address } => { Def::Load { var, address } => {
let _ = state.handle_load(var, address); let _ = state.handle_load(var, address, global_memory);
} }
} }
} }
let parameter = umask_symbol.get_unique_parameter()?; let parameter = umask_symbol.get_unique_parameter()?;
let param_value = state.eval_parameter_arg(parameter, &project.stack_pointer_register)?; let param_value =
state.eval_parameter_arg(parameter, &project.stack_pointer_register, global_memory)?;
if let DataDomain::Value(BitvectorDomain::Value(umask_arg)) = param_value { if let DataDomain::Value(BitvectorDomain::Value(umask_arg)) = param_value {
Ok(umask_arg.try_to_u64()?) Ok(umask_arg.try_to_u64()?)
} else { } else {
...@@ -108,7 +111,12 @@ pub fn check_cwe( ...@@ -108,7 +111,12 @@ pub fn check_cwe(
if !umask_symbol_map.is_empty() { if !umask_symbol_map.is_empty() {
for sub in project.program.term.subs.iter() { for sub in project.program.term.subs.iter() {
for (block, jmp, umask_symbol) in get_callsites(sub, &umask_symbol_map) { for (block, jmp, umask_symbol) in get_callsites(sub, &umask_symbol_map) {
match get_umask_permission_arg(block, umask_symbol, project) { match get_umask_permission_arg(
block,
umask_symbol,
project,
analysis_results.runtime_memory_image,
) {
Ok(permission_const) => { Ok(permission_const) => {
if is_chmod_style_arg(permission_const) { if is_chmod_style_arg(permission_const) {
cwes.push(generate_cwe_warning(sub, jmp, permission_const)); cwes.push(generate_cwe_warning(sub, jmp, permission_const));
......
use super::serde::JsonBuilder; use super::serde::JsonBuilder;
use super::OcamlSendable; use super::OcamlSendable;
use crate::term::*;
use crate::utils::log::CweWarning; use crate::utils::log::CweWarning;
use crate::{analysis::pointer_inference::PointerInference, term::*};
use super::failwith_on_panic; use super::failwith_on_panic;
#[allow(unreachable_code)]
#[allow(unused_variables)]
fn run_pointer_inference(program_jsonbuilder_val: ocaml::Value) -> (Vec<CweWarning>, Vec<String>) { fn run_pointer_inference(program_jsonbuilder_val: ocaml::Value) -> (Vec<CweWarning>, Vec<String>) {
let json_builder = unsafe { JsonBuilder::from_ocaml(&program_jsonbuilder_val) }; let json_builder = unsafe { JsonBuilder::from_ocaml(&program_jsonbuilder_val) };
let program_json = serde_json::Value::from(json_builder); let program_json = serde_json::Value::from(json_builder);
...@@ -13,11 +15,12 @@ fn run_pointer_inference(program_jsonbuilder_val: ocaml::Value) -> (Vec<CweWarni ...@@ -13,11 +15,12 @@ fn run_pointer_inference(program_jsonbuilder_val: ocaml::Value) -> (Vec<CweWarni
project.replace_let_bindings(); project.replace_let_bindings();
let mut project: crate::intermediate_representation::Project = project.into(); let mut project: crate::intermediate_representation::Project = project.into();
let mut all_logs = project.normalize(); let all_logs = project.normalize();
let config: crate::analysis::pointer_inference::Config = let config: crate::analysis::pointer_inference::Config =
serde_json::from_value(crate::utils::read_config_file("config.json")["Memory"].clone()) serde_json::from_value(crate::utils::read_config_file("config.json")["Memory"].clone())
.unwrap(); .unwrap();
let pi_analysis = crate::analysis::pointer_inference::run(&project, config, false); // let pi_analysis = crate::analysis::pointer_inference::run(&project, config, false);
let pi_analysis: PointerInference = panic!("Running the pointer inference analysis with the BAP backend is deprecated. Please use the Ghidra backend for this analysis instead.");
let (mut logs, cwes) = pi_analysis.collected_logs; let (mut logs, cwes) = pi_analysis.collected_logs;
all_logs.append(&mut logs); all_logs.append(&mut logs);
( (
...@@ -38,6 +41,7 @@ caml!(rs_run_pointer_inference(program_jsonbuilder_val) { ...@@ -38,6 +41,7 @@ caml!(rs_run_pointer_inference(program_jsonbuilder_val) {
}) })
}); });
#[allow(unused_variables)]
fn run_pointer_inference_and_print_debug(program_jsonbuilder_val: ocaml::Value) { fn run_pointer_inference_and_print_debug(program_jsonbuilder_val: ocaml::Value) {
let json_builder = unsafe { JsonBuilder::from_ocaml(&program_jsonbuilder_val) }; let json_builder = unsafe { JsonBuilder::from_ocaml(&program_jsonbuilder_val) };
let program_json = serde_json::Value::from(json_builder); let program_json = serde_json::Value::from(json_builder);
...@@ -50,7 +54,8 @@ fn run_pointer_inference_and_print_debug(program_jsonbuilder_val: ocaml::Value) ...@@ -50,7 +54,8 @@ fn run_pointer_inference_and_print_debug(program_jsonbuilder_val: ocaml::Value)
let config: crate::analysis::pointer_inference::Config = let config: crate::analysis::pointer_inference::Config =
serde_json::from_value(crate::utils::read_config_file("config.json")["Memory"].clone()) serde_json::from_value(crate::utils::read_config_file("config.json")["Memory"].clone())
.unwrap(); .unwrap();
crate::analysis::pointer_inference::run(&project, config, true); // Note: This discard all CweWarnings and log messages. panic!("Running the pointer inference analysis with the BAP backend is deprecated. Please use the Ghidra backend for this analysis instead.");
// crate::analysis::pointer_inference::run(&project, config, true); // Note: This discard all CweWarnings and log messages.
} }
caml!(rs_run_pointer_inference_and_print_debug(program_jsonbuilder_val) { caml!(rs_run_pointer_inference_and_print_debug(program_jsonbuilder_val) {
......
...@@ -104,6 +104,7 @@ impl<'a> AnalysisResults<'a> { ...@@ -104,6 +104,7 @@ impl<'a> AnalysisResults<'a> {
pub fn compute_pointer_inference(&self, config: &serde_json::Value) -> PointerInference<'a> { pub fn compute_pointer_inference(&self, config: &serde_json::Value) -> PointerInference<'a> {
crate::analysis::pointer_inference::run( crate::analysis::pointer_inference::run(
self.project, self.project,
self.runtime_memory_image,
serde_json::from_value(config.clone()).unwrap(), serde_json::from_value(config.clone()).unwrap(),
false, false,
) )
......
...@@ -192,6 +192,21 @@ impl RuntimeMemoryImage { ...@@ -192,6 +192,21 @@ impl RuntimeMemoryImage {
} }
Err(anyhow!("Pointer target not in global memory.")) Err(anyhow!("Pointer target not in global memory."))
} }
/// Check whether the given address points to a writeable segment in the runtime memory image.
///
/// Returns an error if the address does not point to global memory.
pub fn is_address_writeable(&self, address: &Bitvector) -> Result<bool, Error> {
let address = address.try_to_u64().unwrap();
for segment in self.memory_segments.iter() {
if address >= segment.base_address
&& address < segment.base_address + segment.bytes.len() as u64
{
return Ok(segment.write_flag);
}
}
Err(anyhow!("Address not contained in runtime memory image"))
}
} }
#[cfg(test)] #[cfg(test)]
...@@ -202,13 +217,22 @@ pub mod tests { ...@@ -202,13 +217,22 @@ pub mod tests {
/// Create a mock runtime memory image for unit tests. /// Create a mock runtime memory image for unit tests.
pub fn mock() -> RuntimeMemoryImage { pub fn mock() -> RuntimeMemoryImage {
RuntimeMemoryImage { RuntimeMemoryImage {
memory_segments: vec![MemorySegment { memory_segments: vec![
MemorySegment {
bytes: [0xb0u8, 0xb1, 0xb2, 0xb3, 0xb4].to_vec(), bytes: [0xb0u8, 0xb1, 0xb2, 0xb3, 0xb4].to_vec(),
base_address: 0x1000, base_address: 0x1000,
read_flag: true, read_flag: true,
write_flag: false, write_flag: false,
execute_flag: false, execute_flag: false,
}], },
MemorySegment {
bytes: [0u8; 8].to_vec(),
base_address: 0x2000,
read_flag: true,
write_flag: true,
execute_flag: false,
},
],
is_little_endian: true, is_little_endian: true,
} }
} }
......
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