Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix & improve Propagate Control Flow normalization pass #462

Merged
merged 11 commits into from
May 15, 2024
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
0.9-dev
===

- Improve Control Flow Propagation normalization pass (PR #462)
- Improve taint analysis abstraction to simplify interprocedural bottom-up
analysis of memory taint (PR #451)
- Added check for CWE-252: Unchecked Return Value (PR #451)
Expand Down
102 changes: 100 additions & 2 deletions src/cwe_checker_lib/src/analysis/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,14 @@

use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::log::LogMessage;
use petgraph::graph::DiGraph;
use crate::utils::{debug::ToJsonCompact, log::LogMessage};
use std::collections::{HashMap, HashSet};

pub use petgraph::graph::NodeIndex;
use petgraph::{
graph::DiGraph,
visit::{EdgeRef, IntoNodeReferences},
};

/// The graph type of an interprocedural control flow graph
pub type Graph<'a> = DiGraph<Node<'a>, Edge<'a>>;
Expand Down Expand Up @@ -182,6 +185,25 @@ pub enum Edge<'a> {
ReturnCombine(&'a Term<Jmp>),
}

impl<'a> std::fmt::Display for Edge<'a> {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Block => {
write!(formatter, "Block")
}
Self::Jump(..) => {
write!(formatter, "Jump")
}
Self::Call { .. } => write!(formatter, "Call",),
Self::ExternCallStub { .. } => write!(formatter, "ExternCallStub",),
Self::CrCallStub => write!(formatter, "CrCallStub"),
Self::CrReturnStub => write!(formatter, "CrReturnStub"),
Self::CallCombine(..) => write!(formatter, "CallCombine"),
Self::ReturnCombine(..) => write!(formatter, "ReturnCombine"),
}
}
}

/// A builder struct for building graphs
struct GraphBuilder<'a> {
program: &'a Term<Program>,
Expand Down Expand Up @@ -531,6 +553,82 @@ pub fn get_entry_nodes_of_subs(graph: &Graph) -> HashMap<Tid, NodeIndex> {
sub_to_entry_node_map
}

impl ToJsonCompact for Graph<'_> {
fn to_json_compact(&self) -> serde_json::Value {
let mut map = serde_json::Map::new();
let mut node_counts_map = serde_json::Map::new();
let mut edge_counts_map = serde_json::Map::new();
let mut nodes_map = serde_json::Map::new();
let mut edges_map = serde_json::Map::new();

let total_nodes = self.node_count();
let mut blk_start_nodes = 0u64;
let mut blk_end_nodes = 0u64;
let mut call_return_nodes = 0u64;
let mut call_source_nodes = 0u64;

for (idx, node) in self.node_references() {
nodes_map.insert(idx.index().to_string(), node.to_string().into());
match node {
Node::BlkStart(..) => blk_start_nodes += 1,
Node::BlkEnd(..) => blk_end_nodes += 1,
Node::CallReturn { .. } => call_return_nodes += 1,
Node::CallSource { .. } => call_source_nodes += 1,
}
}

node_counts_map.insert("total".into(), total_nodes.into());
node_counts_map.insert("blk_start".into(), blk_start_nodes.into());
node_counts_map.insert("blk_end".into(), blk_end_nodes.into());
node_counts_map.insert("call_return".into(), call_return_nodes.into());
node_counts_map.insert("call_source".into(), call_source_nodes.into());

let total_edges = self.edge_count();
let mut block_edges = 0u64;
let mut jump_edges = 0u64;
let mut call_edges = 0u64;
let mut extern_call_stub_edges = 0u64;
let mut cr_call_stub_edges = 0u64;
let mut cr_return_stub_edges = 0u64;
let mut call_combine_edges = 0u64;
let mut return_combine_edges = 0u64;

for edge in self.edge_references() {
edges_map.insert(
format!("{} -> {}", edge.source().index(), edge.target().index()),
edge.weight().to_string().into(),
);
match edge.weight() {
Edge::Block => block_edges += 1,
Edge::Jump(..) => jump_edges += 1,
Edge::Call(..) => call_edges += 1,
Edge::ExternCallStub(..) => extern_call_stub_edges += 1,
Edge::CrCallStub => cr_call_stub_edges += 1,
Edge::CrReturnStub => cr_return_stub_edges += 1,
Edge::CallCombine(..) => call_combine_edges += 1,
Edge::ReturnCombine(..) => return_combine_edges += 1,
}
}

edge_counts_map.insert("total".into(), total_edges.into());
edge_counts_map.insert("block".into(), block_edges.into());
edge_counts_map.insert("jump".into(), jump_edges.into());
edge_counts_map.insert("call".into(), call_edges.into());
edge_counts_map.insert("extern_call_stub".into(), extern_call_stub_edges.into());
edge_counts_map.insert("cr_call_stub".into(), cr_call_stub_edges.into());
edge_counts_map.insert("cr_return_stub".into(), cr_return_stub_edges.into());
edge_counts_map.insert("call_combine".into(), call_combine_edges.into());
edge_counts_map.insert("return_combine".into(), return_combine_edges.into());

map.insert("node_counts".into(), node_counts_map.into());
map.insert("edge_counts".into(), edge_counts_map.into());
map.insert("nodes".into(), nodes_map.into());
map.insert("edges".into(), edges_map.into());

serde_json::Value::Object(map)
}
}

#[cfg(test)]
mod tests {
use crate::expr;
Expand Down
25 changes: 21 additions & 4 deletions src/cwe_checker_lib/src/intermediate_representation/blk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,22 @@ pub struct Blk {
}

impl Term<Blk> {
/// Remove indirect jump target addresses for which no corresponding target block exists.
/// Return an error message for each removed address.
/// Remove indirect jump target addresses for which no corresponding target
/// block exists.
///
/// Returns an error message for each removed address.
pub fn remove_nonexisting_indirect_jump_targets(
&mut self,
known_block_tids: &HashSet<Tid>,
all_jump_targets: &HashSet<Tid>,
) -> Result<(), Vec<LogMessage>> {
let mut logs = Vec::new();

self.term.indirect_jmp_targets = self
.term
.indirect_jmp_targets
.iter()
.filter_map(|target| {
if known_block_tids.get(target).is_some() {
if all_jump_targets.contains(target) {
Some(target.clone())
} else {
let error_msg =
Expand All @@ -61,12 +64,26 @@ impl Term<Blk> {
}
})
.collect();

if logs.is_empty() {
Ok(())
} else {
Err(logs)
}
}

/// Returns a new artificial sink block with the given suffix attached to
/// its ID.
pub fn artificial_sink(id_suffix: &str) -> Self {
Self {
tid: Tid::artificial_sink_block(id_suffix),
term: Blk {
defs: Vec::with_capacity(0),
jmps: Vec::with_capacity(0),
indirect_jmp_targets: Vec::with_capacity(0),
},
}
}
}

impl fmt::Display for Blk {
Expand Down
Loading
Loading