Skip to content

Commit

Permalink
config: add separate config for LKMs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Valentin Obst committed Feb 27, 2024
1 parent 5436969 commit 05ef541
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 13 deletions.
29 changes: 17 additions & 12 deletions src/caller/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BareMetalConfig> =
args.bare_metal_config.as_ref().map(|config_path| {
Expand All @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions src/cwe_checker_lib/src/checkers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub struct RuntimeMemoryImage {
pub memory_segments: Vec<MemorySegment>,
/// Endianness
pub is_little_endian: bool,
/// True iff we are analyzing a Linux loadable kernel module.
pub is_lkm: bool,
}

impl RuntimeMemoryImage {
Expand All @@ -22,6 +24,7 @@ impl RuntimeMemoryImage {
RuntimeMemoryImage {
memory_segments: Vec::new(),
is_little_endian,
is_lkm: false,
}
}

Expand Down Expand Up @@ -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)
Expand All @@ -76,6 +80,7 @@ impl RuntimeMemoryImage {
Ok(Self {
memory_segments,
is_little_endian: elf_file.header.endianness().unwrap().is_little(),
is_lkm: false,
})
}

Expand Down Expand Up @@ -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(),
})
}

Expand Down Expand Up @@ -157,6 +164,7 @@ impl RuntimeMemoryImage {
MemorySegment::new_bare_metal_ram_segment(ram_base_address, ram_size),
],
is_little_endian,
is_lkm: false,
})
}

Expand Down Expand Up @@ -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::*};
Expand Down
6 changes: 5 additions & 1 deletion src/installer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down
131 changes: 131 additions & 0 deletions src/lkm_config.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
}

0 comments on commit 05ef541

Please sign in to comment.