Commit 6c352b89 by Melvin Klimke Committed by Enkelmann

Ported CWE 676 and CWE 782 to Rust (#88)

parent fd569776
pub mod cwe_676;
pub mod cwe_782;
/*!
This module implements a check for CWE-676: Use of Potentially Dangerous Function.
Potentially dangerous functions like memcpy can lead to security issues like buffer overflows.
See <https://cwe.mitre.org/data/definitions/676.html> for a detailed description.
How the check works:
* Calls to dangerous functions are flagged. The list of functions that are considered
dangerous can be configured in config.json. The default list is taken from
<https://github.com/01org/safestringlib/wiki/SDL-List-of-Banned-Functions>.
False Positives
* None known
*
False Negatives
* None known
*/
use std::collections::HashMap;
use crate::{
intermediate_representation::{ExternSymbol, Program, Project, Sub, Term, Tid},
utils::{
log::{CweWarning, LogMessage},
symbol_utils::get_calls_to_symbols,
},
};
use serde::{Deserialize, Serialize};
const VERSION: &str = "0.1";
pub static CWE_MODULE: crate::CweModule = crate::CweModule {
name: "CWE676",
version: VERSION,
run: check_cwe,
};
/// struct containing dangerous symbols from config.json
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct Config {
symbols: Vec<String>,
}
/// For each subroutine and each found dangerous symbol, check for calls to the corresponding symbol
pub fn get_calls<'a>(
subfunctions: &'a [Term<Sub>],
dangerous_symbols: &'a [&'a ExternSymbol],
) -> Vec<(&'a str, &'a Tid, &'a str)> {
let mut calls: Vec<(&str, &Tid, &str)> = Vec::new();
let mut symbol_map: HashMap<&Tid, &str> = HashMap::with_capacity(dangerous_symbols.len());
for symbol in dangerous_symbols.iter() {
symbol_map.insert(&symbol.tid, &symbol.name.as_str());
}
for sub in subfunctions.iter() {
calls.append(&mut get_calls_to_symbols(sub, &symbol_map));
}
calls
}
/// Generate cwe warnings for potentially dangerous function calls
pub fn generate_cwe_warnings<'a>(
dangerous_calls: Vec<(&'a str, &'a Tid, &'a str)>,
) -> Vec<CweWarning> {
let mut cwe_warnings: Vec<CweWarning> = Vec::new();
for (sub_name, jmp_tid, target_name) in dangerous_calls.iter() {
let address: &String = &jmp_tid.address;
let description: String = format!(
"(Use of Potentially Dangerous Function) {} ({}) -> {}",
sub_name, address, target_name
);
let cwe_warning = CweWarning::new(
String::from(CWE_MODULE.name),
String::from(CWE_MODULE.version),
description,
)
.addresses(vec![address.clone()])
.tids(vec![format!("{}", jmp_tid)])
.symbols(vec![String::from(*sub_name)])
.other(vec![vec![
String::from("dangerous_function"),
String::from(*target_name),
]]);
cwe_warnings.push(cwe_warning);
}
cwe_warnings
}
/// Filter external symbols by dangerous symbols
pub fn resolve_symbols<'a>(
external_symbols: &'a [ExternSymbol],
symbols: &'a [String],
) -> Vec<&'a ExternSymbol> {
external_symbols
.iter()
.filter(|symbol| {
symbols
.iter()
.any(|dangerous_function| *symbol.name == *dangerous_function)
})
.collect()
}
pub fn check_cwe(
project: &Project,
cwe_params: &serde_json::Value,
) -> (Vec<LogMessage>, Vec<CweWarning>) {
let config: Config = serde_json::from_value(cwe_params.clone()).unwrap();
let prog: &Term<Program> = &project.program;
let subfunctions: &Vec<Term<Sub>> = &prog.term.subs;
let external_symbols: &Vec<ExternSymbol> = &prog.term.extern_symbols;
let dangerous_symbols = resolve_symbols(external_symbols, &config.symbols);
let dangerous_calls = get_calls(subfunctions, &dangerous_symbols);
(vec![], generate_cwe_warnings(dangerous_calls))
}
/*!
This module implements a check for CWE-782: Exposed IOCTL with Insufficient Access Control.
See <https://cwe.mitre.org/data/definitions/782.html> for a detailed description.
How the check works:
* Calls to ioctl() get flagged as CWE hits.
False Positives:
* We cannot check whether the call contains sufficient access control.
False Negatives:
* There are other ways to expose I/O control without access control.
*/
use std::collections::HashMap;
use crate::{
intermediate_representation::{Program, Project, Sub, Term, Tid},
utils::{
log::{CweWarning, LogMessage},
symbol_utils::{find_symbol, get_calls_to_symbols},
},
};
const VERSION: &str = "0.1";
pub static CWE_MODULE: crate::CweModule = crate::CweModule {
name: "CWE782",
version: VERSION,
run: check_cwe,
};
/// check whether the ioctl symbol is called by any subroutine. If so, generate the cwe warning.
pub fn handle_sub(sub: &Term<Sub>, symbol: &HashMap<&Tid, &str>) -> Vec<CweWarning> {
let calls: Vec<(&str, &Tid, &str)> = get_calls_to_symbols(sub, &symbol);
if !calls.is_empty() {
return generate_cwe_warning(&calls);
}
vec![]
}
/// generate the cwe warning for CWE 782
pub fn generate_cwe_warning(calls: &[(&str, &Tid, &str)]) -> Vec<CweWarning> {
let mut cwe_warnings: Vec<CweWarning> = Vec::new();
for (sub_name, jmp_tid, _) in calls.iter() {
let address: &String = &jmp_tid.address;
let description = format!(
"(Exposed IOCTL with Insufficient Access Control) Program uses ioctl at {} ({}).
Be sure to double check the program and the corresponding driver.",
sub_name, address
);
let cwe_warning = CweWarning::new(
String::from(CWE_MODULE.name),
String::from(CWE_MODULE.version),
description,
)
.addresses(vec![address.clone()])
.tids(vec![format!("{}", jmp_tid)])
.symbols(vec![String::from(*sub_name)]);
cwe_warnings.push(cwe_warning);
}
cwe_warnings
}
pub fn check_cwe(
project: &Project,
_cwe_params: &serde_json::Value,
) -> (Vec<LogMessage>, Vec<CweWarning>) {
let prog: &Term<Program> = &project.program;
let mut warnings: Vec<CweWarning> = Vec::new();
if let Some((tid, name)) = find_symbol(prog, "ioctl") {
let symbol: &HashMap<&Tid, &str> = &[(tid, name)].iter().cloned().collect();
prog.term
.subs
.iter()
.for_each(|sub| warnings.append(&mut handle_sub(sub, symbol)));
}
(vec![], warnings)
}
......@@ -13,6 +13,7 @@ use crate::utils::log::{CweWarning, LogMessage};
pub mod abstract_domain;
pub mod analysis;
pub mod bil;
pub mod checkers;
pub mod ffi;
pub mod intermediate_representation;
pub mod pcode;
......@@ -49,5 +50,9 @@ impl std::fmt::Display for CweModule {
/// Get a list of all known analysis modules.
pub fn get_modules() -> Vec<&'static CweModule> {
vec![&crate::analysis::pointer_inference::CWE_MODULE]
vec![
&crate::checkers::cwe_782::CWE_MODULE,
&crate::checkers::cwe_676::CWE_MODULE,
&crate::analysis::pointer_inference::CWE_MODULE,
]
}
......@@ -77,7 +77,7 @@ impl From<ExternSymbol> for IrExternSymbol {
IrExternSymbol {
tid: symbol.tid,
name: symbol.name,
calling_convention: symbol.calling_convention,
calling_convention: None, // We do not parse more than one calling convention from BAP at the moment. So we assume everything uses the standard one.
parameters,
return_values,
no_return: false, // Last time I checked BAP had an attribute for non-returning functions, but did not actually set it.
......
......@@ -12,6 +12,45 @@ pub struct CweWarning {
pub description: String,
}
impl CweWarning {
/// Creates a new CweWarning by only setting name, version and description
pub fn new(name: String, version: String, description: String) -> CweWarning {
CweWarning {
name,
version,
addresses: Vec::new(),
tids: Vec::new(),
symbols: Vec::new(),
other: Vec::new(),
description,
}
}
/// Sets the address field of the CweWarning
pub fn addresses(mut self, addresses: Vec<String>) -> CweWarning {
self.addresses = addresses;
self
}
/// Sets the Tids field of the CweWarning
pub fn tids(mut self, tids: Vec<String>) -> CweWarning {
self.tids = tids;
self
}
/// Sets the symbols field of the CweWarning
pub fn symbols(mut self, symbols: Vec<String>) -> CweWarning {
self.symbols = symbols;
self
}
/// Sets the other field of the CweWarning
pub fn other(mut self, other: Vec<Vec<String>>) -> CweWarning {
self.other = other;
self
}
}
impl std::fmt::Display for CweWarning {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
......
pub mod log;
pub mod symbol_utils;
/// Get the contents of a configuration file.
pub fn read_config_file(filename: &str) -> serde_json::Value {
......
use std::collections::HashMap;
use crate::intermediate_representation::{Jmp, Program, Sub, Term, Tid};
/// Find the extern symbol object for a symbol name and return the symbol tid and name.
pub fn find_symbol<'a>(prog: &'a Term<Program>, name: &str) -> Option<(&'a Tid, &'a str)> {
let mut symbol: Option<(&'a Tid, &'a str)> = None;
prog.term.extern_symbols.iter().for_each(|sym| {
if name == sym.name {
symbol = Some((&sym.tid, &sym.name));
}
});
symbol
}
/// Match direct calls' target tids in the program's subroutines with
/// with the tids of the external symbols given to the function.
/// When a match was found, add a triple of (caller name, callsite tid, callee name)
/// to a vector. Lastly, return the vector with all callsites of all given external symbols.
pub fn get_calls_to_symbols<'a, 'b>(
sub: &'a Term<Sub>,
symbols: &'b HashMap<&'a Tid, &'a str>,
) -> Vec<(&'a str, &'a Tid, &'a str)> {
let mut calls: Vec<(&'a str, &'a Tid, &'a str)> = Vec::new();
for blk in sub.term.blocks.iter() {
for jmp in blk.term.jmps.iter() {
if let Jmp::Call { target: dst, .. } = &jmp.term {
if symbols.contains_key(dst) {
calls.push((
sub.term.name.as_str(),
&jmp.tid,
symbols.get(dst).clone().unwrap(),
));
}
}
}
}
calls
}
......@@ -8,7 +8,7 @@ class TestCheckPath(unittest.TestCase):
def setUp(self):
if 'travis' in os.environ['USER']:
abs_path = os.path.abspath('test/artificial_samples/build/check_path_x64_gcc.out')
self.cmd = 'docker run --rm -v %s:/tmp/input cwe-checker:latest cwe_checker /tmp/input -config=/home/bap/cwe_checker/src/config.json -json -check-path -no-logging' % abs_path
self.cmd = 'docker run --rm -v %s:/tmp/input cwe-checker:latest cwe_checker /tmp/input -json -check-path -no-logging' % abs_path
else:
self.cmd = 'cwe_checker test/artificial_samples/build/check_path_x64_gcc.out -config=src/config.json -json -check-path -no-logging'
......
......@@ -8,7 +8,7 @@ class TestJson(unittest.TestCase):
def setUp(self):
if 'travis' in os.environ['USER']:
abs_path = os.path.abspath('test/artificial_samples/build/cwe_190_x64_gcc.out')
self.cmd = 'docker run --rm -v %s:/tmp/input cwe-checker:latest cwe_checker /tmp/input -config=/home/bap/cwe_checker/src/config.json -json -no-logging' % abs_path
self.cmd = 'docker run --rm -v %s:/tmp/input cwe-checker:latest cwe_checker /tmp/input -json -no-logging' % abs_path
else:
self.cmd = 'cwe_checker test/artificial_samples/build/cwe_190_x64_gcc.out -config=src/config.json -json -no-logging'
......
......@@ -121,6 +121,6 @@ let () =
let () = try
Sys.chdir (Sys.getenv "PWD" ^ "/test/unit")
with _ -> (* In the docker image the environment variable PWD is not set *)
Sys.chdir "/home/bap/cwe_checker/test/unit"
Sys.chdir "/home/cwe/cwe_checker/test/unit"
in
exit (Sys.command "make all")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment