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)
}