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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
//! This module implements a check for CWE-190: Integer overflow or wraparound.
//!
//! An integer overflow can lead to undefined behavior and is especially dangerous
//! in conjunction with memory management functions.
//!
//! See <https://cwe.mitre.org/data/definitions/190.html> for a detailed description.
//!
//! ## How the check works
//!
//! For each call to a function from the CWE190 symbol list we check whether the
//! basic block directly before the call contains a multiplication instruction.
//! If one is found, the call gets flagged as a CWE hit, as there is no overflow
//! check corresponding to the multiplication before the call as well as
//! the Pointer Inference can not exclude an overflow. The default CWE190
//! symbol list contains the memory allocation functions *malloc*, *xmalloc*,
//! *calloc* and *realloc*. The list is configurable in config.json.
//!
//! ## False Positives
//!
//! - There is no check whether the result of the multiplication is actually used
//! as input to the function call. However, this does not seem to generate a lot
//! of false positives in practice.
//! - Values that are not absolute e.g. user controlled or depend on other values.
//!
//! ## False Negatives
//!
//! - All integer overflows not in a basic block right before a call to a function
//! from the CWE190 symbol list.
//! - All integer overflows caused by addition or subtraction.
use crate::abstract_domain::AbstractDomain;
use crate::abstract_domain::DataDomain;
use crate::abstract_domain::IntervalDomain;
use crate::abstract_domain::RegisterDomain;
use crate::analysis::pointer_inference::*;
use crate::analysis::vsa_results::*;
use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::log::{CweWarning, LogMessage};
use crate::utils::symbol_utils::{get_callsites, get_symbol_map};
use crate::CweModule;
/// The module name and version
pub static CWE_MODULE: CweModule = CweModule {
name: "CWE190",
version: "0.1",
run: check_cwe,
};
/// The configuration struct.
/// The `symbols` are extern function names.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct Config {
symbols: Vec<String>,
}
/// Check whether the given expression contains an integer multiplication subexpression,
/// i.e. an `IntMult` or `IntLeft` (left shift) binary operation.
fn expression_contains_multiplication(expr: &Expression) -> bool {
use Expression::*;
match expr {
BinOp {
op: BinOpType::IntMult,
..
}
| BinOp {
op: BinOpType::IntLeft,
..
} => true,
Var(_) | Const(_) | Unknown { .. } => false,
BinOp { lhs, rhs, .. } => {
expression_contains_multiplication(lhs) || expression_contains_multiplication(rhs)
}
UnOp { arg, .. } | Cast { arg, .. } | Subpiece { arg, .. } => {
expression_contains_multiplication(arg)
}
}
}
/// Check whether the given block contains a multiplication expression.
/// Expressions computing the address of a `Load` or `Store` instruction are ignored
/// since the addresses themselves cannot be inputs to the call at the end of the block.
fn block_contains_multiplication(block: &Term<Blk>) -> bool {
block.term.defs.iter().any(|def| match &def.term {
Def::Assign { value, .. } | Def::Store { value, .. } => {
expression_contains_multiplication(value)
}
Def::Load { .. } => false,
})
}
/// Generate the CWE warning for a detected instance of the CWE.
fn generate_cwe_warning(callsite: &Tid, called_symbol: &ExternSymbol) -> CweWarning {
CweWarning::new(
CWE_MODULE.name,
CWE_MODULE.version,
format!(
"(Integer Overflow or Wraparound) Potential overflow due to multiplication before call to {} at {}",
called_symbol.name, callsite.address
))
.tids(vec![format!("{callsite}")])
.addresses(vec![callsite.address.clone()])
.symbols(vec![called_symbol.name.clone()])
}
/// Determines if all parameters are only absolute values and their included intervals are not top valued.
fn contains_top_value(pir: &PointerInference, jmp_tid: &Tid, parms: Vec<&Arg>) -> bool {
for arg in parms {
if let Some(value) = pir.eval_parameter_arg_at_call(jmp_tid, arg) {
if !contains_only_non_top_absolute_value(&value) {
return true;
}
}
}
false
}
/// Checks if the multiplication of element count and size parameters result in an overflow.
fn calloc_parm_mul_is_top(pir: &PointerInference, jmp_tid: &Tid, parms: Vec<&Arg>) -> bool {
if let (Some(nmeb), Some(size)) = (
pir.eval_parameter_arg_at_call(jmp_tid, parms[0]),
pir.eval_parameter_arg_at_call(jmp_tid, parms[1]),
) {
return !contains_only_non_top_absolute_value(&nmeb.bin_op(BinOpType::IntMult, &size));
}
false
}
/// Determines if the data domain only has absolute values and their included interval is not top valued.
fn contains_only_non_top_absolute_value(data_domain: &DataDomain<IntervalDomain>) -> bool {
if let Some(interval) = data_domain.get_if_absolute_value() {
if !interval.is_top() {
return true;
}
}
false
}
/// Run the CWE check.
/// For each call to one of the symbols configured in config.json
/// we check whether the block containing the call also contains a multiplication instruction.
pub fn check_cwe(
analysis_results: &AnalysisResults,
cwe_params: &serde_json::Value,
) -> (Vec<LogMessage>, Vec<CweWarning>) {
let project = analysis_results.project;
let pointer_inference_results = analysis_results.pointer_inference.unwrap();
let config: Config = serde_json::from_value(cwe_params.clone()).unwrap();
let mut cwe_warnings = Vec::new();
let symbol_map = get_symbol_map(project, &config.symbols);
for sub in project.program.term.subs.values() {
for (block, jump, symbol) in get_callsites(sub, &symbol_map) {
if block_contains_multiplication(block) {
let parms = match symbol.name.as_str() {
"calloc" => {
if calloc_parm_mul_is_top(
pointer_inference_results,
&jump.tid,
vec![&symbol.parameters[0], &symbol.parameters[1]],
) {
cwe_warnings.push(generate_cwe_warning(&jump.tid, symbol));
};
vec![&symbol.parameters[0], &symbol.parameters[1]]
}
"realloc" => vec![&symbol.parameters[1]],
_ => symbol.parameters.iter().collect(),
};
if contains_top_value(pointer_inference_results, &jump.tid, parms) {
cwe_warnings.push(generate_cwe_warning(&jump.tid, symbol));
}
}
}
}
(Vec::new(), cwe_warnings)
}