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
//! This module implements a check for CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition.
//!
//! Time-of-check Time-of-use race conditions happen when a property of a resource
//! (e.g. access rights of a file) get checked before the resource is accessed, leaving
//! a short time window for an attacker to change the entity and thus invalidating
//! the check before the access.
//!
//! See <https://cwe.mitre.org/data/definitions/367.html> for a detailed description.
//!
//! ## How the check works
//!
//! For pairs of (check-call, use-call), configurable in config.json, we check whether
//! a function may call the check-call before the use-call.
//!
//! ## False Positives
//!
//! - The check-call and the use-call may access different, unrelated resources
//! (e. g. different files).
//!
//! ## False Negatives
//!
//! - If the check-call and the use-call happen in different functions it will not
//!   be found by the check.

use crate::analysis::graph::{Edge, Node};
use crate::intermediate_representation::Jmp;
use crate::prelude::*;
use crate::utils::graph_utils::is_sink_call_reachable_from_source_call;
use crate::utils::log::{CweWarning, LogMessage};
use crate::CweModule;
use petgraph::visit::EdgeRef;
use std::collections::HashMap;

/// The module name and version
pub static CWE_MODULE: CweModule = CweModule {
    name: "CWE367",
    version: "0.1",
    run: check_cwe,
};

/// The configuration struct contains pairs of the form `(source_symbol, sink_symbol)`.
/// The `source_symbol` corresponds to a check-call and the `sink_symbol` corresponds to a use-call.
/// An execution path from a source call to a sink call corresponds to a possible Time-of-check Time-of-use Race Condition.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
struct Config {
    pairs: Vec<(String, String)>,
}

/// Generate a CWE warning for a found CWE hit.
fn generate_cwe_warning(
    source: &str,
    sink: &str,
    source_callsite: Tid,
    sink_callsite: Tid,
    sub_name: &str,
) -> CweWarning {
    CweWarning::new(
        CWE_MODULE.name,
        CWE_MODULE.version,
        format!(
            "(Time-of-check Time-of-use Race Condition) '{}' is reachable from '{}' at {} ({}). This could lead to a TOCTOU.",
            sink, source, sink_callsite.address, sub_name
        ))
        .tids(vec![format!("{source_callsite}"), format!("{sink_callsite}")])
        .addresses(vec![source_callsite.address, sink_callsite.address])
        .symbols(vec![source.into(), sink.into()])
}

/// Run the check. See the module-level documentation for more information.
pub fn check_cwe(
    analysis_results: &AnalysisResults,
    cwe_params: &serde_json::Value,
) -> (Vec<LogMessage>, Vec<CweWarning>) {
    let config: Config = serde_json::from_value(cwe_params.clone()).unwrap();
    let project = analysis_results.project;
    let graph = analysis_results.control_flow_graph;
    let mut cwe_warnings = Vec::new();

    let symbol_map: HashMap<&str, Tid> = project
        .program
        .term
        .extern_symbols
        .iter()
        .map(|(tid, symbol)| (symbol.name.as_str(), tid.clone()))
        .collect();

    for (source, sink) in config.pairs {
        if let (Some(source_tid), Some(sink_tid)) = (
            symbol_map.get(source.as_str()),
            symbol_map.get(sink.as_str()),
        ) {
            for edge in graph.edge_references() {
                if let Edge::ExternCallStub(jmp) = edge.weight() {
                    if let Jmp::Call { target, .. } = &jmp.term {
                        if target == source_tid {
                            if let Some(sink_callsite) = is_sink_call_reachable_from_source_call(
                                graph,
                                edge.target(),
                                target,
                                sink_tid,
                            ) {
                                let source_callsite = graph[edge.target()].get_block().tid.clone();
                                let sub_name = match graph[edge.target()] {
                                    Node::BlkStart(_blk, sub) => sub.term.name.as_str(),
                                    _ => panic!("Malformed control flow graph."),
                                };
                                cwe_warnings.push(generate_cwe_warning(
                                    source.as_str(),
                                    sink.as_str(),
                                    source_callsite,
                                    sink_callsite,
                                    sub_name,
                                ));
                            }
                        }
                    }
                }
            }
        }
    }

    (Vec::new(), cwe_warnings)
}