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-243: Creation of chroot Jail Without Changing Working Directory.
//!
//! Creating a chroot Jail without changing the working directory afterwards does
//! not prevent access to files outside of the jail.
//!
//! See <https://cwe.mitre.org/data/definitions/243.html> for detailed a description.
//!
//! ## How the check works
//!
//! According to <http://www.unixwiz.net/techtips/chroot-practices.html>, there are
//! several ways to achieve the safe creation of a chroot jail.
//! One can either call chdir after chroot
//! or, if chdir is called before chroot, drop priviledges after the chroot call.
//! The functions used to drop priviledges are configurable in config.json.
//! We check whether each function that calls
//! chroot is using one of these safe call sequences to create the chroot jail.
//! If not, a warning is emitted.
//!
//! ## False Positives
//!
//! None known.
//!
//! ## False Negatives
//!
//! We do not check whether the parameters to chdir, chroot and the priviledge dropping functions
//! are suitable to create a safe chroot jail.

use crate::analysis::graph::Node;
use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::graph_utils::is_sink_call_reachable_from_source_call;
use crate::utils::log::{CweWarning, LogMessage};
use crate::utils::symbol_utils::find_symbol;
use crate::CweModule;

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

/// The configuration struct contains the list of functions
/// that are assumed to be used to correctly drop priviledges after a `chroot` call.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct Config {
    priviledge_dropping_functions: Vec<String>,
}

/// Check whether the given block calls the given TID.
/// If yes, return the TID of the jump term that contains the call.
fn blk_calls_tid(blk: &Term<Blk>, tid: &Tid) -> Option<Tid> {
    for jmp in blk.term.jmps.iter() {
        match &jmp.term {
            Jmp::Call { target, .. } if target == tid => {
                return Some(jmp.tid.clone());
            }
            _ => (),
        }
    }
    None
}

/// Check whether the given `sub` calls both the `chdir_tid`
/// and at least one of the `priviledge_dropping_tids`.
/// If yes, return true.
fn sub_calls_chdir_and_priviledge_dropping_func(
    sub: &Term<Sub>,
    chdir_tid: &Tid,
    priviledge_dropping_tids: &[Tid],
) -> bool {
    let mut is_chdir_called = false;
    for blk in sub.term.blocks.iter() {
        if blk_calls_tid(blk, chdir_tid).is_some() {
            is_chdir_called = true;
            break;
        }
    }
    if !is_chdir_called {
        return false;
    }
    for blk in sub.term.blocks.iter() {
        if priviledge_dropping_tids
            .iter()
            .any(|tid| blk_calls_tid(blk, tid).is_some())
        {
            return true;
        }
    }
    false
}

/// Generate a CWE warning for a CWE hit.
fn generate_cwe_warning(sub: &Term<Sub>, callsite: &Tid) -> CweWarning {
    CweWarning::new(
        CWE_MODULE.name,
        CWE_MODULE.version,
        format!(
            "(The program utilizes chroot without dropping privileges and/or changing the directory) at {} ({})",
            callsite.address, sub.term.name
        ))
        .tids(vec![format!("{callsite}")])
        .addresses(vec![callsite.address.clone()])
        .symbols(vec![sub.term.name.clone()])
}

/// Run the check.
///
/// For each call to `chroot` we check
/// - that it is either followed by a call to `chdir` in the same function
/// - or that the same function contains calls to `chdir`
/// and a call to a function that can be used to drop priviledges.
///
/// If both are false, we assume that the chroot-jail is insecure and report a CWE hit.
pub fn check_cwe(
    analysis_results: &AnalysisResults,
    cwe_params: &serde_json::Value,
) -> (Vec<LogMessage>, Vec<CweWarning>) {
    let project = analysis_results.project;
    let graph = analysis_results.control_flow_graph;

    let config: Config = serde_json::from_value(cwe_params.clone()).unwrap();
    let priviledge_dropping_tids: Vec<Tid> = config
        .priviledge_dropping_functions
        .into_iter()
        .filter_map(|func_name| {
            if let Some((tid, _)) = find_symbol(&project.program, &func_name) {
                Some(tid.clone())
            } else {
                None
            }
        })
        .collect();

    let chroot_tid = match find_symbol(&project.program, "chroot") {
        Some((tid, _)) => tid.clone(),
        None => return (Vec::new(), Vec::new()), // chroot is never called by the program
    };

    let mut cwe_warnings = Vec::new();
    for node in graph.node_indices() {
        if let Node::BlkEnd(blk, sub) = graph[node] {
            if let Some(callsite_tid) = blk_calls_tid(blk, &chroot_tid) {
                if let Some(chdir_tid) =
                    find_symbol(&project.program, "chdir").map(|(tid, _)| tid.clone())
                {
                    if graph.neighbors(node).count() > 1 {
                        panic!("Malformed Control flow graph: More than one edge for extern function call")
                    }
                    let chroot_return_to_node = graph.neighbors(node).next().unwrap();
                    // If chdir is called after chroot, we assume a secure chroot jail.
                    if is_sink_call_reachable_from_source_call(
                        graph,
                        chroot_return_to_node,
                        &chroot_tid,
                        &chdir_tid,
                    )
                    .is_none()
                    {
                        // If chdir is not called after chroot, it has to be called before it.
                        // Additionally priviledges must be dropped to secure the chroot jail in this case.
                        if !sub_calls_chdir_and_priviledge_dropping_func(
                            sub,
                            &chdir_tid,
                            &priviledge_dropping_tids[..],
                        ) {
                            cwe_warnings.push(generate_cwe_warning(sub, &callsite_tid));
                        }
                    }
                } else {
                    // There is no chdir symbol, so the chroot jail cannot be secured.
                    cwe_warnings.push(generate_cwe_warning(sub, &callsite_tid));
                }
            }
        }
    }

    (Vec::new(), cwe_warnings)
}