1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
//! This module implements a check for CWE-476: NULL Pointer Dereference.
//!
//! Functions like `malloc()` may return NULL values instead of pointers to indicate
//! failed calls. If one tries to access memory through this return value without
//! checking it for being NULL first, this can crash the program.
//!
//! See <https://cwe.mitre.org/data/definitions/476.html> for a detailed description.
//!
//! ## How the check works
//!
//! Using dataflow analysis we search for an execution path where a memory access using the return value of
//! a symbol happens before the return value is checked through a conditional jump instruction.
//!
//! ### Symbols configurable in config.json
//!
//! The symbols are the functions whose return values are assumed to be potential
//! NULL pointers.
//!
//! ## False Positives
//!
//! - If a possible NULL pointer is temporarily saved in a memory location
//! that the [Pointer Inference analysis](crate::analysis::pointer_inference)
//! could not track, the analysis may miss a correct NULL pointer check and
//! thus generate false positives.
//! - The analysis is intraprocedural. If a parameter to a function is a
//! potential NULL pointer, this gets flagged as a CWE hit even if the
//! function may expect NULL pointers in its parameters. If a function returns
//! a potential NULL pointer this gets flagged as a CWE hit, although the
//! function may be supposed to return potential NULL pointers.
//!
//! ## False Negatives
//!
//! - We do not check whether an access to a potential NULL pointer happens
//! regardless of a prior check.
//! - We do not check whether the conditional jump instruction checks
//! specifically for the return value being NULL or something else
//! - For functions with more than one return value we do not distinguish between
//! the return values.
use crate::analysis::forward_interprocedural_fixpoint::create_computation;
use crate::analysis::forward_interprocedural_fixpoint::Context as _;
use crate::analysis::graph::{Edge, Node};
use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use crate::analysis::taint::state::State as TaState;
use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::{
log::{CweWarning, LogMessage},
symbol_utils,
};
use crate::CweModule;
use petgraph::visit::EdgeRef;
use std::collections::BTreeMap;
mod context;
use context::*;
/// The module name and version.
pub static CWE_MODULE: CweModule = CweModule {
name: "CWE476",
version: "0.3",
run: check_cwe,
};
/// The configuration struct.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct Config {
/// The names of symbols for which the analysis should check whether the
/// return values are checked for being a NULL pointer by the analysed
/// binary. This list is configurable via the `config.json` configuration
/// file.
symbols: Vec<String>,
}
/// Run the CWE check.
///
/// We check whether the return values of symbols configurable in the config
/// file are being checked for NULL pointers before any memory access (and thus
/// potential NULL pointer dereferences) through these values can happen.
pub fn check_cwe(
analysis_results: &AnalysisResults,
cwe_params: &serde_json::Value,
) -> (Vec<LogMessage>, Vec<CweWarning>) {
let project = analysis_results.project;
let pi_result = analysis_results.pointer_inference.unwrap();
let (cwe_sender, cwe_receiver) = crossbeam_channel::unbounded();
let config: Config = serde_json::from_value(cwe_params.clone()).unwrap();
let symbol_map = symbol_utils::get_symbol_map(project, &config.symbols[..]);
let general_context = Context::new(project, pi_result, cwe_sender);
for edge in general_context.get_graph().edge_references() {
let Edge::ExternCallStub(jmp) = edge.weight() else {
continue;
};
let Jmp::Call { target, .. } = &jmp.term else {
continue;
};
let Some(symbol) = symbol_map.get(target) else {
continue;
};
let return_node = edge.target();
let Node::BlkStart(.., current_sub) = general_context.get_graph()[return_node] else {
panic!("Malformed control flow graph.");
};
let mut context = general_context.clone();
context.set_taint_source(jmp, current_sub);
let mut computation = create_computation(context, None);
computation.set_node_value(
return_node,
NodeValue::Value(TaState::new_return(symbol, pi_result, return_node)),
);
computation.compute_with_max_steps(100);
}
let mut cwe_warnings = BTreeMap::new();
for cwe in cwe_receiver.try_iter() {
match &cwe.addresses[..] {
[taint_source_address, ..] => cwe_warnings.insert(taint_source_address.clone(), cwe),
_ => panic!(),
};
}
let cwe_warnings = cwe_warnings.into_values().collect();
(Vec::new(), cwe_warnings)
}