Skip to content
hbgitPublic

About

Map2Check: Finding Software Vulnerabilities

Topics

Resources

Code of conduct

Contributing

Stars

31 stars

Watchers

7 watching

Forks

Latest commit

 

History

1,651 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Map2Check

Finding Software Vulnerabilities using Symbolic Execution and Fuzzing

CI OpenSSF Best Practices Gitter


Map2Check is a bug hunting tool that automatically generates and checks safety properties in C programs and WebAssembly (WASM) binaries. It tracks memory pointers and variable assignments to check user-specified assertions, overflow, and pointer safety. The generation of the test cases is based on assertions (safety properties) from the code instructions, adopting the LLVM framework version 16, AFL++, KLEE to generate input values to the test cases generated by Map2Check.

WASM verification works by lifting .wasm binaries to LLVM IR (via WABT's wasm2c + clang-16) and reusing the existing Map2Check instrumentation passes and the KLEE backend — see Verifying WebAssembly (WASM) binaries.

Extra documentation is available at https://map2check.github.io

Explanatory video

A video explaining Map2Check is available on YouTube:

Map2Check explanatory video


Requirements for using the tool

To use the Map2Check tool is necessary a Linux 64-bit OS system (Ubuntu 22.04 recommended). In the linux OS, you should install the requirements, typing the commands:

$ sudo apt install python3 libc6-dev

Our binary tool is avaliable at https://github.com/hbgit/Map2Check/releases

Install Map2Check

In order to install Map2Check on your PC, you should download and save the map2check zip file on your disk from https://github.com/hbgit/Map2Check/releases. After that, you should type the following command:

$ unzip map2check-vx.y.2026-ii.jj.140ba2d2.zip
$ cd map2check-vx.y.2026-ii.jj.140ba2d2

Releases starting from v8.0.0-Songbirds include WebAssembly verification support (the --wasm option and the lib/WasmRuntimeStubs.bc runtime). Note that verifying .wasm inputs additionally requires WABT 1.0.41 and clang-16 installed on the host — see Verifying WebAssembly (WASM) binaries. To generate a release layout yourself, build inside the Docker image and run ninja install: the self-contained distribution (binary, KLEE, instrumentation bitcode and WASM runtime) is produced in the release/ directory.

Running the tool

Map2Check can be invoked through a standard command-line interface. Map2Check should be called in the installation directory as follows:

Example programs are available in the examples/ directory.

$ ./map2check --memtrack program.c

In this case, --memtrack is the option to check for memory errors. For help and others tool options:

$ ./map2check --help

When you use a LLVM bytecode as input for the tool, be sure to add -g flag when generating the file, it is not required, but map2check will provide better info (like line numbers).

Engines, test suites and analysis options (9.0)

Map2Check generates inputs with two engines: the AFL++ 4.40c fuzzer (persistent mode, PCGUARD, CmpLog) and the KLEE 3.1 symbolic executor. By default, and whenever --timeout is given, the two run as an alternating hybrid. They take turns, each turn ends once its engine stops finding new coverage, and every round doubles the turn's window. The engines also hand each other input vectors.

Option Effect
(default, with --timeout) Alternating hybrid with seed exchange. Same as --alternate-engines.
--fixed-hybrid The 8.x schedule: the fuzzer for 0.2 of the budget, then KLEE.
--fixed-hybrid --seed-exchange The 8.x schedule with seed exchange (0.2 / 0.6 / 0.2, with a last fuzzer phase).
--nondet-generator afl / symex One engine only: AFL++ or KLEE. In 8.x the fuzzer value was fuzzer.
--slice Slice the program before the analysis (sbt-slicer), for reachability, --check-asserts, --memtrack, --memcleanup-property and --check-overflow.
--generate-test-suite Emit a Test-Comp test suite (test-suite/).
--generate-test-suite --cover-branches Emit a Cover-Branches suite, up to 50 cases, from KLEE's paths and the fuzzer's corpus.
--add-invariants Optional, and off in the default build. See below.

Verdicts. A TRUE verdict needs KLEE to have explored every path. Map2Check reports UNKNOWN instead when KLEE stopped early for any reason:

  • its timer ran out;
  • it concretized a symbolic input (for example a floating-point value);
  • it killed states;
  • it hit its memory cap;
  • it crashed.

A call to abort() in the program under analysis is treated the way SV-COMP treats it: as an assumption. It prunes that path and does not end the search.

Environment knobs. These exist for experiments; the defaults are the measured choices.

Variable Default Meaning
MAP2CHECK_FUZZER_SUITE on 0 leaves the fuzzer's corpus out of Cover-Branches suites.
MAP2CHECK_SLICE_CLEANUP none light or o2: an opt pipeline over the fresh slice. Measured neutral.
MAP2CHECK_SLICER_FLAGS — Extra sbt-slicer flags, e.g. --cda=ntscd. Measured neutral.
MAP2CHECK_PREOPT off ssa: mem2reg + simplifycfg before instrumentation, for reachability and assert only. Measured neutral in the hybrid.
MAP2CHECK_CHECK_CSTRINGS off 1: check the strings a %s or puts reads. It fixes one wrong TRUE on Juliet, but produces false positives on memory the runtime does not track (CASTLE: FP from 1 to 4).
MAP2CHECK_CLAM_PROFILE default memory or none: see --add-invariants.
MAP2CHECK_KLEE_MAX_MEMORY KLEE's 2000 KLEE's --max-memory, in MB. Raising it to 6000 changed nothing on Intel-TDX (0/8 either way).

--add-invariants inserts abstract-interpretation invariants computed by Clam (formerly crab-llvm). The build must be configured with -DENABLE_CLAM=ON and Clam installed at $CLAM_DIR; otherwise the option is refused with exit code 3.

The option stays optional while it is under study. So far:

  • In the SV-COMP 2019/2020 builds, the invariants reached neither the instrumentation nor KLEE: they were emitted as llvm.assume.
  • On the current hybrid, the first measurement found no gain.
  • Clam is bounded to 0.2 of the budget and falls back to the plain compile when it fails.
  • A dedicated study round is planned. See docs/reports/tacas-experiment-log.md (INV-1, R25).

Measurements of every option above are recorded in docs/reports/tacas-experiment-log.md.


Verifying WebAssembly (WASM) binaries

Overview

Map2Check can verify third-party .wasm binaries (e.g. IoT/Edge modules) without access to their source code. Instead of implementing a dedicated WASM verification engine, Map2Check lifts the binary to LLVM IR and reuses its consolidated instrumentation passes and SMT-based backend:

.wasm ──▶ [1] Lifter: wasm2c (WABT) → C → clang-16 → LLVM IR
      ──▶ [2] Map2Check passes (WASM mode): linear-memory bounds checking,
              overflow and reachability instrumentation
      ──▶ [3] KLEE symbolic execution
      ──▶ [4] Result: TRUE / FALSE / UNKNOWN + witness

The analysis is language-agnostic: it instruments the elements common to every lifted module (the linear memory array and the wasm_rt_* runtime calls), so it works regardless of whether the .wasm was produced from C, Rust, Go, or AssemblyScript. Properties currently detected on WASM inputs:

Property Example CWEs Status
Linear-memory out-of-bounds access (bounds checking) CWE-121/122/124/126/127 ✅ Supported
Arithmetic overflow CWE-190 ✅ Supported
Reachability of target/error functions — ✅ Supported
Memory leak / use-after-free / double-free CWE-401/416/415 ❌ Future work¹

¹ In lifted WASM code the source language's allocator is compiled inside the linear memory, so malloc/free calls are not visible in the IR. Full memory tracking requires an export convention or allocator-detection heuristics — planned as future work.

Runtime requirements for --wasm
  • WABT ≥ 1.0.41 — wasm2c on PATH and the wasm-rt.h headers (searched at /opt/wabt-1.0.41/include, then /opt/wabt/include, then /usr/include). The wabt package from Ubuntu apt (1.0.27) is too old; install the GitHub release.
  • clang-16 at /usr/bin/clang-16 (used to compile the lifted C to LLVM bitcode).

Both are pre-installed in the development Docker image (Dockerfile.dev). Only .wasm (binary) files are accepted; for a .wat (text format) file, convert it first with wat2wasm module.wat -o module.wasm (also part of WABT).

Usage
$ ./map2check --wasm --memtrack --timeout 290 module.wasm

Relevant options:

  • --wasm — enable the WASM pipeline (lifting + WASM-mode instrumentation). Implies memtrack mode.
  • --entry-function <name> — override the entry function name (default: main). Map2Check automatically generates a main() wrapper that initializes the wasm2c runtime and calls the module's w2c_*_start entry point, so in most cases you do not need this flag.
  • --timeout <s> — give KLEE enough time to explore the lifted IR. Lifted modules embed the whole WASI libc, so short timeouts (e.g. 110 s) tend to yield UNKNOWN; 290 s is the value validated on the Juliet benchmarks.
End-to-end example

Inside the development container, create a C program with a heap buffer overflow, compile it to WASM with the wasi-sdk (pre-installed at $WASI_SDK_PATH), and verify the resulting binary:

$ cat > test_heap_overflow.c << 'EOF'
#include <stdlib.h>
int main() {
    int* p = (int*)malloc(10 * sizeof(int));
    if (!p) return -1;
    p[0] = 42;
    p[10] = 999;   /* out-of-bounds write */
    free(p);
    return 0;
}
EOF

$ $WASI_SDK_PATH/bin/clang --target=wasm32-wasip1 \
    test_heap_overflow.c -o test_heap_overflow.wasm

$ ./map2check --wasm --memtrack --timeout 290 test_heap_overflow.wasm
# Expected result:
Adopting z3 solver...
Started Map2Check
WASM mode: lifting test_heap_overflow.wasm

...

----------------------------------------------------
Violated property:
	file map2check_property line 8 function main
	OVERFLOW

VERIFICATION FAILED

A safe program (e.g. writing to p[9] instead of p[10]) is not flagged. The pipeline was validated on a subset of the Juliet Test Suite compiled to WASM (CWE-121/122/124/126/127): all 15 seeded bugs were detected as FALSE with a 290 s timeout — see docs/reports/wasm-juliet-validation.csv. The integration test scripts tests/integration/test_wasm_pipeline.sh and tests/integration/test_wasm_entrypoint.sh reproduce this workflow automatically.


How to build Map2Check?

You can build Map2Check using our development Docker image (Dockerfile.dev), which provides Ubuntu 22.04 with LLVM 16, KLEE 3.1, WABT 1.0.41 and wasi-sdk 33.0 pre-installed:

$ git clone https://github.com/hbgit/Map2Check.git
$ cd Map2Check
# Build the development Docker image
$ docker build -t map2check-dev --no-cache -f Dockerfile.dev .
$ docker run -it --rm -v $(pwd):/workspace map2check-dev /bin/bash

Inside the container:

$ mkdir build && cd build
$ cmake .. -G Ninja \
    -DLLVM_DIR=/usr/lib/llvm-16/lib/cmake/llvm
$ ninja && ninja install
# binário em: release/bin/map2check (ou release/map2check)

The Dockerfile.dev image already builds and installs KLEE 3.1 (to /opt/klee) and installs AFL++ 4.40c as a standalone toolchain (found at run time under /usr/local/bin), so do not pass -DSKIP_KLEE=ON or -DSKIP_AFL_PLUS_PLUS=ON — those flags skip the cmake/FindKlee.cmake / cmake/FindAFLPlusPlus.cmake modules entirely. Only pass them ON if you deliberately want a build without KLEE/AFL++ support (e.g. -DSKIP_AFL_PLUS_PLUS=ON -DSKIP_KLEE=ON for a minimal/CI build).

Building with WASM support requires no additional CMake flag — the WasmLifter frontend module is always compiled. The only extra build-time dependency is the WABT 1.0.41 header wasm-rt.h: when CMake finds it (searched at /opt/wabt-1.0.41/include, /usr/include, /usr/local/include), it compiles the KLEE-compatible wasm2c runtime WasmRuntimeStubs.c to bitcode and installs it as release/lib/WasmRuntimeStubs.bc, which is linked into the lifted module when --wasm is used. If wasm-rt.h is not found, CMake prints a warning and the build proceeds without WASM support (the recommended way to get a WASM-enabled build is the Docker image above, which ships WABT and the wasi-sdk out of the box).

Minor test:

$ cd ../release/
$ ./map2check --memtrack svcomp_960521-1_false-valid-free.c
# Expected Result:
Adopting z3 solver... 
Started Map2Check
Compiling /workspace/release/../tests/regression_test/test_cases/sv-benchmarks/memsafety/960521-1-1.c

...

>>Memory list log

  Line content   :   { free(a); free(b); } /* ditto */
  Address        : 0x55ba60a90700
  PointsTo       : 0x7f95f0003d54
  Is Free        : TRUE
  Is Dynamic     : FALSE
  Var Name       : b
  Line Number    : 29
  Function Scope : main

----------------------------------------------------
Violated property:
	file map2check_property line 29 function main
	FALSE-FREE: Operand of free must have zero pointer offset

VERIFICATION FAILED

Requires: CMake ≥ 3.20, C++17, LLVM 16, Boost.

More details at https://map2check.github.io/docker.html

How to run the tests

Unit tests (no KLEE/AFL++ required):

$ mkdir build && cd build
$ cmake .. -G Ninja \
    -DLLVM_DIR=/usr/lib/llvm-16/lib/cmake/llvm \
    -DSKIP_AFL_PLUS_PLUS=ON -DSKIP_KLEE=ON -DENABLE_TEST=ON
$ ninja && ctest --output-on-failure
# Expected results:
Test project /workspace/build
    Start 1: HelloGTest
1/7 Test #1: HelloGTest .......................   Passed    0.00 sec
    Start 2: AllocationLogTest
2/7 Test #2: AllocationLogTest ................   Passed    0.00 sec
    Start 3: HeapLogTest
3/7 Test #3: HeapLogTest ......................   Passed    0.00 sec
    Start 4: ContainerReallocTest
4/7 Test #4: ContainerReallocTest .............   Passed    0.00 sec
    Start 5: BTreeTest
5/7 Test #5: BTreeTest ........................   Passed    0.06 sec
    Start 6: ContainerBTreeTest
6/7 Test #6: ContainerBTreeTest ...............   Passed    0.05 sec
    Start 7: MemTrackTest
7/7 Test #7: MemTrackTest .....................   Passed    0.00 sec

100% tests passed, 0 tests failed out of 7

WASM integration tests (require the Docker image, i.e. KLEE + WABT + wasi-sdk):

$ ./tests/integration/test_wasm_pipeline.sh      # heap overflow detection + CLI flag
$ ./tests/integration/test_wasm_entrypoint.sh    # entry-point wrapper generation

These are also run on every push/PR by the e2e-wasm job in .github/workflows/ci.yml, which exercises the full .wasm → LLVM IR → passes → KLEE pipeline inside the published map2check-dev image.


Instructions for SV-COMP

Use the map2check-wrapper.py script in the Map2Check binary directory to verify each single test-case.

Usage:

$ ./map2check-wrapper.py -p propertyFile.prp file.i

Map2Check accepts the property file and the verification task and provides as verification result: TRUE + Witness, [FALSE|FALSE(p)] + Witness, or UNKNOWN. FALSE(p), with p in {valid-free, valid-deref, valid-memtrack, valid-memcleanup}, means that the (partial) property p is violated. For each verification result the witness file (called witness.graphml) is generated Map2Check root-path folder. There is timeout of 897 seconds set by this script, using "timeout" tool that is part of coreutils on debian. If these constraints are violated, it should be treated as UNKNOWN result.


Authors

Maintainers:

Questions and bug reports:
E-mail: map2check.tool@gmail.com

      .-.          
      /v\
     // \\    > L I N U X - GPL <
    /(   )\
     ^^-^^

bug found -> verified safe

Map2Check . Linux . GPL

About

Map2Check: Finding Software Vulnerabilities

Topics

Resources

Code of conduct

Contributing

Stars

31 stars

Watchers

7 watching

Forks

Releases

Packages

Used by

Contributors

Languages