From 05ef5412deaad8fc4e8513ea0054a5aa0944b3f0 Mon Sep 17 00:00:00 2001 From: Valentin Obst Date: Tue, 27 Feb 2024 18:13:13 +0100 Subject: [PATCH] config: add separate config for LKMs Add a separate configuration file for Linux loadable kernel modules (LKMs). Use this one instead of the default configuration when analyzing an LKM and install it to the same location as the default configuration. Limit the checks that are executed to those that make sense in kernel space. Cases where an LKM is analyzed are recognized by checking if a relocatable object file contains the ".modinfo" and ".gnu.linkonce.this_module" sections. Multiple checks, e.g., CWE789, CWE416, and CWE134, require more work to function properly for LKMs. We mainly need to teach the cew_checker about the parameters and semantics of some functions. They are included, but effectively disabled since their configuration is empty. CWE467 actually works since the functions are called the same as in user space and thus Ghidra recognizes their parameters. --- src/caller/src/main.rs | 29 ++-- src/cwe_checker_lib/src/checkers.rs | 5 + .../runtime_memory_image.rs | 17 +++ src/installer/src/main.rs | 6 +- src/lkm_config.json | 131 ++++++++++++++++++ 5 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 src/lkm_config.json diff --git a/src/caller/src/main.rs b/src/caller/src/main.rs index b52bbf084..b1bed5d0b 100644 --- a/src/caller/src/main.rs +++ b/src/caller/src/main.rs @@ -100,14 +100,6 @@ fn run_with_ghidra(args: &CmdlineArgs) -> Result<(), Error> { return Ok(()); } - // Get the configuration file - let config: serde_json::Value = if let Some(ref config_path) = args.config { - let file = std::io::BufReader::new(std::fs::File::open(config_path).unwrap()); - serde_json::from_reader(file).context("Parsing of the configuration file failed")? - } else { - read_config_file("config.json")? - }; - // Get the bare metal configuration file if it is provided let bare_metal_config_opt: Option = args.bare_metal_config.as_ref().map(|config_path| { @@ -116,18 +108,31 @@ fn run_with_ghidra(args: &CmdlineArgs) -> Result<(), Error> { .expect("Parsing of the bare metal configuration file failed") }); - // Filter the modules to be executed if the `--partial` parameter is set. + let binary_file_path = PathBuf::from(args.binary.clone().unwrap()); + + let (binary, project, mut all_logs) = + disassemble_binary(&binary_file_path, bare_metal_config_opt, args.verbose)?; + + // Filter the modules to be executed. if let Some(ref partial_module_list) = args.partial { filter_modules_for_partial_run(&mut modules, partial_module_list); + } else if project.runtime_memory_image.is_lkm { + modules.retain(|module| cwe_checker_lib::checkers::MODULES_LKM.contains(&module.name)); } else { // TODO: CWE78 is disabled on a standard run for now, // because it uses up huge amounts of RAM and computation time on some binaries. modules.retain(|module| module.name != "CWE78"); } - let binary_file_path = PathBuf::from(args.binary.clone().unwrap()); - let (binary, project, mut all_logs) = - disassemble_binary(&binary_file_path, bare_metal_config_opt, args.verbose)?; + // Get the configuration file. + let config: serde_json::Value = if let Some(ref config_path) = args.config { + let file = std::io::BufReader::new(std::fs::File::open(config_path).unwrap()); + serde_json::from_reader(file).context("Parsing of the configuration file failed")? + } else if project.runtime_memory_image.is_lkm { + read_config_file("lkm_config.json")? + } else { + read_config_file("config.json")? + }; // Generate the control flow graph of the program let (control_flow_graph, mut logs_graph) = graph::get_program_cfg_with_logs(&project.program); diff --git a/src/cwe_checker_lib/src/checkers.rs b/src/cwe_checker_lib/src/checkers.rs index 6950ec5f3..b1ae64374 100644 --- a/src/cwe_checker_lib/src/checkers.rs +++ b/src/cwe_checker_lib/src/checkers.rs @@ -5,6 +5,11 @@ //! but directly incorporated into the [`pointer_inference`](crate::analysis::pointer_inference) module. //! See there for detailed information about this check. +/// Checkers that are supported for Linux kernel modules. +pub const MODULES_LKM: [&str; 9] = [ + "CWE134", "CWE190", "CWE215", "CWE416", "CWE457", "CWE467", "CWE476", "CWE676", "CWE789", +]; + pub mod cwe_119; pub mod cwe_134; pub mod cwe_190; diff --git a/src/cwe_checker_lib/src/intermediate_representation/runtime_memory_image.rs b/src/cwe_checker_lib/src/intermediate_representation/runtime_memory_image.rs index 31318c9d8..b6f0d9d25 100644 --- a/src/cwe_checker_lib/src/intermediate_representation/runtime_memory_image.rs +++ b/src/cwe_checker_lib/src/intermediate_representation/runtime_memory_image.rs @@ -9,6 +9,8 @@ pub struct RuntimeMemoryImage { pub memory_segments: Vec, /// Endianness pub is_little_endian: bool, + /// True iff we are analyzing a Linux loadable kernel module. + pub is_lkm: bool, } impl RuntimeMemoryImage { @@ -22,6 +24,7 @@ impl RuntimeMemoryImage { RuntimeMemoryImage { memory_segments: Vec::new(), is_little_endian, + is_lkm: false, } } @@ -51,6 +54,7 @@ impl RuntimeMemoryImage { let mut memory_image = RuntimeMemoryImage { memory_segments, is_little_endian: true, + is_lkm: false, }; memory_image.add_global_memory_offset(pe_file.image_base as u64); Ok(memory_image) @@ -76,6 +80,7 @@ impl RuntimeMemoryImage { Ok(Self { memory_segments, is_little_endian: elf_file.header.endianness().unwrap().is_little(), + is_lkm: false, }) } @@ -114,6 +119,8 @@ impl RuntimeMemoryImage { }) .collect(), is_little_endian: elf_file.header.endianness().unwrap().is_little(), + is_lkm: get_section(".modinfo", &elf_file).is_some() + && get_section(".gnu.linkonce.this_module", &elf_file).is_some(), }) } @@ -157,6 +164,7 @@ impl RuntimeMemoryImage { MemorySegment::new_bare_metal_ram_segment(ram_base_address, ram_size), ], is_little_endian, + is_lkm: false, }) } @@ -359,6 +367,15 @@ impl RuntimeMemoryImage { } } +/// Returns the section header of the first section with this name. +fn get_section<'a>(name: &str, elf_file: &'a elf::Elf<'a>) -> Option<&'a elf::SectionHeader> { + let sh_strtab = &elf_file.shdr_strtab; + + elf_file.section_headers.iter().find(|section_header| { + matches!(sh_strtab.get_at(section_header.sh_name), Some(sh_name) if sh_name == name) + }) +} + #[cfg(test)] mod tests { use crate::{bitvec, intermediate_representation::*}; diff --git a/src/installer/src/main.rs b/src/installer/src/main.rs index cb50b4fa1..2833699e8 100644 --- a/src/installer/src/main.rs +++ b/src/installer/src/main.rs @@ -30,13 +30,17 @@ struct GhidraConfig { ghidra_path: PathBuf, } -/// Copies src/config.json to specified location +/// Copies the configuration files to the specified location. fn copy_config_json(location: &Path) -> Result<()> { let repo_dir = env::current_dir().unwrap(); std::fs::copy( repo_dir.join("src/config.json"), location.join("config.json"), )?; + std::fs::copy( + repo_dir.join("src/lkm_config.json"), + location.join("lkm_config.json"), + )?; Ok(()) } diff --git a/src/lkm_config.json b/src/lkm_config.json new file mode 100644 index 000000000..d094d5f11 --- /dev/null +++ b/src/lkm_config.json @@ -0,0 +1,131 @@ +{ + "CWE134": { + "_comment": "Functions that take format string arguments.", + "format_string_symbols": [], + "format_string_index": {} + }, + "CWE190": { + "symbols": [] + }, + "CWE215": { + "symbols": [] + }, + "CWE416": { + "_comment": "Functions that invalidate the pointer passed as the first argument.", + "deallocation_symbols": [], + "always_include_full_path_to_free_site": true + }, + "CWE457": { + "symbols": [] + }, + "CWE467": { + "_comment": "Any function that takes something of type `size_t` could be a possible candidate.", + "symbols": [ + "bcmp", + "memchr", + "memcmp", + "memcpy", + "memmove", + "memscan", + "memset", + "memset16", + "memset32", + "memset64", + "strlcat", + "strlcpy", + "strncasecmp", + "strncat", + "strnchr", + "strnchrnul", + "strncmp", + "strncpy", + "strnlen", + "strnstr", + "strscpy" + ] + }, + "CWE476": { + "_comment": "Any function that possibly returns a NULL value.", + "parameters": [ + "strict_call_policy=true", + "strict_memory_policy=false", + "max_steps=100" + ], + "symbols": [ + "__kmalloc", + "__kmalloc_node", + "__kmalloc_node_track_caller", + "__vcalloc", + "kmalloc_large_node", + "kmalloc_node_trace", + "kmalloc_order", + "kmalloc_order_trace", + "kmalloc_trace", + "kmem_cache_alloc_node", + "kmem_cache_alloc_trace", + "kmemdup", + "kmemdup_nul", + "krealloc", + "kstrdup", + "kstrdup_const", + "kstrndup", + "kvmalloc_node", + "kvmemdup", + "kvrealloc", + "memdup_user_nul", + "strndup_user", + "vcalloc", + "vmalloc_array", + "vmemdup_user" + ] + }, + "CWE676": { + "_comment": "https://github.com/01org/safestringlib/wiki/SDL-List-of-Banned-Functions", + "symbols": [ + "memcmp", + "memcpy", + "memmove", + "memset", + "strcat", + "strcpy", + "strlen", + "strncat", + "strncpy" + ] + }, + "CWE789": { + "_comment": "Allocation functions that accept a size argument.", + "stack_threshold": 7500, + "heap_threshold": 1000000, + "symbols": [] + }, + "Memory": { + "allocation_symbols": [ + "__kmalloc", + "__kmalloc_node", + "__kmalloc_node_track_caller", + "__vcalloc", + "kmalloc_large_node", + "kmalloc_node_trace", + "kmalloc_order", + "kmalloc_order_trace", + "kmalloc_trace", + "kmem_cache_alloc_node", + "kmem_cache_alloc_trace", + "kmemdup", + "kmemdup_nul", + "krealloc", + "kstrdup", + "kstrdup_const", + "kstrndup", + "kvmalloc_node", + "kvmemdup", + "kvrealloc", + "memdup_user_nul", + "strndup_user", + "vcalloc", + "vmalloc_array", + "vmemdup_user" + ] + } +}