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
//! This module implements a check for CWE-560: Use of umask() with chmod-style Argument.
//!
//! The program uses the system call umask(2) with arguments for chmod(2). For instance,
//! instead of a reasonable value like 0022 a value like 0666 is passed. This may result in wrong
//! read and/or write access to files and directories, which could be utilized to bypass
//! protection mechanisms.
//!
//! See <https://cwe.mitre.org/data/definitions/560.html> for a detailed description.
//!
//! ## How the check works
//!
//! This check looks for umask calls and checks if they have a reasonable value, i.e. smaller than
//! a certain value, currently set to 0o777 and greater than a reasonable value for umask, currently set to 0o177.
//!
//! ## False Positives
//!
//! - A value deemed unreasonable by the check could theoretically be intended by the programmer.
//! But these cases should be very rare in real programs, so be sure to double check them!
//!
//! ## False Negatives
//!
//! - If the input to umask is not defined in the basic block before the call, the check will not see it.
//! However, a log message will be generated whenever the check is unable to determine the parameter value of umask.

use crate::abstract_domain::TryToBitvec;
use crate::analysis::pointer_inference::State;
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;
use std::collections::BTreeSet;

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

/// An upper bound for the value of a presumably correct umask argument.
pub static UPPER_BOUND_CORRECT_UMASK_ARG_VALUE: u64 = 0o177;
/// An upper bound for the value of a chmod-style argument.
pub static UPPER_BOUND_CORRECT_CHMOD_ARG_VALUE: u64 = 0o777;

/// Compute the parameter value of umask out of the basic block right before the umask call.
///
/// The function uses the same `State` struct as the pointer inference analysis for the computation.
fn get_umask_permission_arg(
    block: &Term<Blk>,
    umask_symbol: &ExternSymbol,
    project: &Project,
) -> Result<u64, Error> {
    let stack_register = &project.stack_pointer_register;
    let mut state = State::new(stack_register, block.tid.clone(), BTreeSet::new());

    for def in block.term.defs.iter() {
        match &def.term {
            Def::Store { address, value } => {
                let _ = state.handle_store(address, value, &project.runtime_memory_image);
            }
            Def::Assign { var, value } => {
                state.handle_register_assign(var, value);
            }
            Def::Load { var, address } => {
                let _ = state.handle_load(var, address, &project.runtime_memory_image);
            }
        }
    }

    let parameter = umask_symbol.get_unique_parameter()?;
    let param_value = state.eval_parameter_arg(parameter, &project.runtime_memory_image)?;
    if let Ok(umask_arg) = param_value.try_to_bitvec() {
        Ok(umask_arg.try_to_u64()?)
    } else {
        Err(anyhow!("Parameter value unknown"))
    }
}

/// Is the given argument value considered to be a chmod-style argument?
///
/// Note that `0o777` is not considered a chmod-style argument as it also denotes a usually correct umask argument.
fn is_chmod_style_arg(arg: u64) -> bool {
    arg > UPPER_BOUND_CORRECT_UMASK_ARG_VALUE && arg != UPPER_BOUND_CORRECT_CHMOD_ARG_VALUE
}

/// Generate the CWE warning for a detected instance of the CWE.
fn generate_cwe_warning(sub: &Term<Sub>, jmp: &Term<Jmp>, permission_const: u64) -> CweWarning {
    CweWarning::new(CWE_MODULE.name, CWE_MODULE.version,
        format!("(Use of umask() with chmod-style Argument) Function {} calls umask with argument {:#o}", sub.term.name, permission_const))
        .tids(vec![format!("{}", jmp.tid)])
        .addresses(vec![jmp.tid.address.clone()])
        .other(vec![vec![
            "umask_arg".to_string(),
            format!("{permission_const:#o}"),
        ]])
}

/// Execute the CWE check.
///
/// For each call to umask we check whether the parameter value is a chmod-style parameter.
/// If yes, generate a CWE warning.
/// If the parameter value cannot be determined, generate a log message.
///
/// Only the basic block right before the umask call is evaluated when trying to determine the parameter value of umask.
pub fn check_cwe(
    analysis_results: &AnalysisResults,
    _cwe_params: &serde_json::Value,
) -> (Vec<LogMessage>, Vec<CweWarning>) {
    let project = analysis_results.project;
    let mut cwes = Vec::new();
    let mut log_messages = Vec::new();
    let umask_symbol_map = get_symbol_map(project, &["umask".to_string()]);
    if !umask_symbol_map.is_empty() {
        for sub in project.program.term.subs.values() {
            for (block, jmp, umask_symbol) in get_callsites(sub, &umask_symbol_map) {
                match get_umask_permission_arg(block, umask_symbol, project) {
                    Ok(permission_const) => {
                        if is_chmod_style_arg(permission_const) {
                            cwes.push(generate_cwe_warning(sub, jmp, permission_const));
                        }
                    }
                    Err(err) => {
                        let log = LogMessage::new_info(format!(
                            "Could not determine umask argument: {err}"
                        ))
                        .location(jmp.tid.clone())
                        .source(CWE_MODULE.name);
                        log_messages.push(log);
                    }
                }
            }
        }
    }

    (log_messages, cwes)
}