Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial LKM support #441

Merged
merged 13 commits into from
Feb 29, 2024
Merged
7 changes: 5 additions & 2 deletions .github/workflows/acceptance-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ jobs:
- uses: actions/checkout@v3
- name: Build and run docker image for cross compiling
run: |
cd test/artificial_samples
pushd test/artificial_samples
docker build -t cross_compiling .
docker run --rm -v $(pwd)/build:/home/cwe/artificial_samples/build cross_compiling sudo python3 -m SCons
popd
pushd test/lkm_samples
./build.sh
- uses: actions/setup-java@v1
with:
java-version: "17.0.x"
Expand Down Expand Up @@ -56,4 +59,4 @@ jobs:
- name: Build the docker image
run: docker build -t cwe_checker .
- name: Check functionality of the image
run: docker run --rm cwe_checker /bin/echo | grep -q CWE676
run: docker run --rm cwe_checker /bin/echo | grep -q CWE676
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,19 @@ test:
echo "Acceptance test binaries not found. Please see test/artificial_samples/Readme.md for build instructions."; \
exit -1; \
fi
if [ ! -d "test/lkm_samples/build" ]; then \
echo "Acceptance test LKMs not found. Please see test/lkm_samples/Readme.md for build instructions."; \
exit -1; \
fi
cargo test --no-fail-fast -p acceptance_tests_ghidra -- --show-output --ignored --test-threads 1

compile_test_files:
cd test/artificial_samples \
pushd test/artificial_samples \
&& docker build -t cross_compiling . \
&& docker run --rm -v $(pwd)/build:/home/cwe/artificial_samples/build cross_compiling sudo /home/cwe/.local/bin/scons
&& docker run --rm -v $(pwd)/build:/home/cwe/artificial_samples/build cross_compiling sudo /home/cwe/.local/bin/scons \
&& popd \
&& pushd test/lkm_samples \
&& ./build.sh

codestyle-check:
cargo fmt -- --check
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ You can adjust the behavior of most checks via a configuration file located at `
If you modify it, add the command line flag `--config=src/config.json` to tell the *cwe_checker* to use the modified file.
For information about other available command line flags you can pass the `--help` flag to the *cwe_checker*.

There is _experimental_ support for the analysis of Linux loadable kernel modules
(LKMs). `cwe_checker` will recognize if you pass an LKM and will execute a
Enkelmann marked this conversation as resolved.
Show resolved Hide resolved
subset of the CWE checks available for user-space programs. Analyses are
configurable via a separate [configuration file](src/lkm_config.json).

If you use the stable version, you can also look at the [online documentation](https://fkie-cad.github.io/cwe_checker/index.html) for more information.

### For Bare-Metal Binaries ###
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ pub struct RuntimeMemoryImage {
}

impl RuntimeMemoryImage {
/// Base address of the first [`MemorySegment`] when mapping relocatable
/// object files.
pub const ELF_REL_BASE_ADDRESS: u64 = 0x100_000;

/// Generate a runtime memory image containing no memory segments.
/// Primarily useful in situations where any access to global memory would be an error.
pub fn empty(is_little_endian: bool) -> RuntimeMemoryImage {
Expand Down Expand Up @@ -89,26 +85,24 @@ impl RuntimeMemoryImage {
/// These files do not contain information about the expected memory layout.
/// Ghidra implements a basic loader that essentially concatenates all
/// `SHF_ALLOC` sections that are not `SHT_NULL`. They are placed in memory
/// as close as possible while respecting their alignment at a fixed
/// address.
/// as close as possible while respecting their alignment, starting at a
/// fixed address. We start mapping at zero and shift by the actual base
/// address that Ghidra has chosen after running our plugin.
///
/// It is important that this implementation stays in sync with
/// `processSectionHeaders` in [`ElfProgramBuilder`] for the cases that we
/// care about.
/// NOTE: It is important that this implementation stays in sync with what
/// `processSectionHeaders` in [`ElfProgramBuilder`] does in the cases that
/// we care about.
///
/// [`ElfProgramBuilder`]: https://github.com/NationalSecurityAgency/ghidra/blob/master/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java
fn from_elf_sections(binary: &[u8], elf_file: elf::Elf) -> Result<Self, Error> {
let mut next_base = Self::ELF_REL_BASE_ADDRESS;
let mut next_base = 0;

Ok(Self {
memory_segments: elf_file
.section_headers
.iter()
.filter_map(|section_header| {
if section_header.is_alloc()
&& section_header.sh_type != elf::section_header::SHT_NULL
&& section_header.sh_size != 0
{
if is_loaded(section_header) {
let mem_seg =
MemorySegment::from_elf_section(binary, next_base, section_header);
next_base = mem_seg.base_address + mem_seg.bytes.len() as u64;
Expand Down Expand Up @@ -172,7 +166,7 @@ impl RuntimeMemoryImage {
pub fn get_base_address(binary: &[u8]) -> Result<u64, Error> {
match Object::parse(binary)? {
Object::Elf(elf_file) => match elf_file.header.e_type {
elf::header::ET_REL => Ok(Self::ELF_REL_BASE_ADDRESS),
elf::header::ET_REL => Ok(0),
elf::header::ET_DYN | elf::header::ET_EXEC => {
elf_file
.program_headers
Expand Down Expand Up @@ -376,6 +370,14 @@ fn get_section<'a>(name: &str, elf_file: &'a elf::Elf<'a>) -> Option<&'a elf::Se
})
}

/// Returns true iff the section header will be loaded into memory by Ghidra.
#[inline]
fn is_loaded(section_header: &elf::SectionHeader) -> bool {
section_header.is_alloc()
&& section_header.sh_type != elf::section_header::SHT_NULL
&& section_header.sh_size != 0
}

#[cfg(test)]
mod tests {
use crate::{bitvec, intermediate_representation::*};
Expand Down
5 changes: 5 additions & 0 deletions src/cwe_checker_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ through the `--config` command line option.
Start by taking a look at the standard configuration file located at `src/config.json`
and read the [check-specific documentation](crate::checkers) for more details about each field in the configuration file.

There is _experimental_ support for the analysis of Linux loadable kernel modules
(LKMs). `cwe_checker` will recognize if you pass an LKM and will execute a
subset of the CWE checks available for user-space programs. Analyses are
configurable via a separate [configuration file](../../../src/lkm_config.json).
Enkelmann marked this conversation as resolved.
Show resolved Hide resolved

## For bare-metal binaries

The cwe_checker offers experimental support for analyzing bare-metal binaries.
Expand Down
4 changes: 1 addition & 3 deletions src/cwe_checker_lib/src/utils/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ impl MemorySegment {
let bytes: Vec<u8> = match section_header.file_range() {
Some(range) => binary[range].to_vec(),
// `SHT_NOBITS`
None => core::iter::repeat(0)
.take(section_header.sh_size as usize)
.collect(),
None => vec![0; section_header.sh_size as usize],
};
let alignment = section_header.sh_addralign.next_power_of_two();
Self {
Expand Down
1 change: 1 addition & 0 deletions src/lkm_config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
Enkelmann marked this conversation as resolved.
Show resolved Hide resolved
"_comment": "This file is loaded instead of config.json when analyzing an LKM. The analysis of LKMs requires a different set of options compared to the analysis of user-space programs.",
"CWE134": {
"_comment": "Functions that take format string arguments.",
"format_string_symbols": [],
Expand Down
1 change: 1 addition & 0 deletions test/lkm_samples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
75 changes: 75 additions & 0 deletions test/lkm_samples/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
FROM ubuntu:noble as builder

# This container is used to build the sample kernel modules.

# Kernel release to build the modules against. When changing the minor or
# major release make sure to update the curl command below.
ARG LX_VER="6.7.6"

# Install tools required to build a kernel.
RUN set -x && \
echo 'debconf debconf/frontend select Noninteractive' | \
debconf-set-selections && \
apt-get update && \
apt-get install -y -q apt-utils dialog && \
apt-get install -y -q \
aptitude \
bc \
bison \
bsdmainutils \
clang \
clang-tools \
curl \
flex \
git \
libelf-dev \
libncurses5-dev \
libssl-dev \
lld \
llvm \
make \
sparse \
sudo && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

RUN set -x && \
useradd -m cwe && \
echo "cwe:cwe" | \
chpasswd && \
adduser cwe sudo && \
sed -i.bkp -e 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' /etc/sudoers

USER cwe

# Download the kernel.
WORKDIR /home/cwe
RUN set -x && \
curl https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${LX_VER}.tar.gz -o linux.tar.gz && \
tar xf linux.tar.gz && \
mv linux-${LX_VER} linux

# Build a minimal kernel with support for external modules.
WORKDIR /home/cwe/linux
ENV ARCH=arm64
ENV LLVM=1
COPY modules.config.fragment .
COPY debug.config.fragment .
RUN set -x && \
make allnoconfig && \
./scripts/kconfig/merge_config.sh -n -m .config debug.config.fragment modules.config.fragment && \
make -j$(nproc) Image modules modules_prepare

# Build our sample modules.
WORKDIR /home/cwe/build
COPY *.c .
COPY Makefile .
ENV KBUILD_VERBOSE=1
RUN set -x && \
mkdir build && \
make all && \
for m in $(find . -name '*.ko'); do cp $m "build/${m%.*}_aarch64_clang.ko"; done

# Copy into a new Docker image to save space.
FROM scratch
COPY --from=builder /home/cwe/build/build /build
9 changes: 9 additions & 0 deletions test/lkm_samples/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
obj-m += cwe_467.o
obj-m += cwe_476.o
obj-m += cwe_676.o

all:
make LLVM=1 ARCH=arm64 -C /home/cwe/linux M=$(PWD) modules

clean:
make LLVM=1 ARCH=arm64 -C /home/cwe/linux M=$(PWD) clean
16 changes: 16 additions & 0 deletions test/lkm_samples/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Test Linux Kernel Modules for the Acceptance Test Suite

For the acceptance test suite of the `cwe_checker`, the C-files inside this
directory have to be compiled for a variety of CPU architectures and
C-compilers. The provided script should be used for the build process.

## Prerequisites

- Have Docker installed on your system.

## Build commands

Inside this directory run the following commands:
```shell
./build.sh
```
18 changes: 18 additions & 0 deletions test/lkm_samples/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash

# Script that builds the sample kernel modules and places them in ./build.

set -xeuo pipefail

DOCKER="sudo -E docker"
NAME=lkm_samples

$DOCKER build --progress plain -t ${NAME} .

sudo rm -rf build/

# Create a dummy container, copy the modules, and delete it.
ID=$($DOCKER create ${NAME} /does/not/exist)
$DOCKER cp ${ID}:/build .
$DOCKER rm ${ID}
sudo chown $(id -u):$(id -g) -R ./build
46 changes: 46 additions & 0 deletions test/lkm_samples/cwe_467.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0

#include <linux/module.h>
#include <linux/printk.h>
#include <linux/slab.h>
#include <linux/string.h>

const char *long_string = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
char buf[10];

static int simple_sizeof_ptr_02(void)
{
char *ptr = kmalloc(0x10, __GFP_ZERO);

strncpy(ptr, long_string, sizeof(ptr));

return 42;
}

static int simple_sizeof_ptr_01(void)
{
strncpy(buf, long_string, sizeof(&buf));

return 42;
}

static int __init test_init(void)
{
pr_info("Hello, World\n");

simple_sizeof_ptr_01();
simple_sizeof_ptr_02();

return 0;
}

static void __exit test_exit(void)
{
pr_info("Goodbye, World\n");
}

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Valentin Obst");

module_init(test_init);
module_exit(test_exit);
47 changes: 47 additions & 0 deletions test/lkm_samples/cwe_476.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-2.0

#include <linux/module.h>
#include <linux/printk.h>
#include <linux/slab.h>

static int simple_null_deref(void)
{
char *ptr = kmalloc(0x42, __GFP_ZERO);

pr_info("%c\n", *ptr);

return 42;
}

static int simple_not_null_deref(void)
{
char *ptr = kmalloc(0x42, __GFP_ZERO);

if (!ptr)
return 1337;

pr_info("%c\n", *ptr);

return 42;
}

static int __init test_init(void)
{
pr_info("Hello, World\n");

simple_not_null_deref();
simple_null_deref();

return 0;
}

static void __exit test_exit(void)
{
pr_info("Goodbye, World\n");
}

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Valentin Obst");

module_init(test_init);
module_exit(test_exit);
Loading
Loading