├── .dockerignore ├── .github └── workflows │ ├── acceptance-tests.yml │ ├── codestyle_checks.yml │ ├── docker-publish-stable.yml │ ├── docker-publish.yml │ └── unit-tests.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGES.md ├── CONTRIBUTORS.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── bare_metal └── stm32f407vg.json ├── cwe_checker_to_ida ├── CweCheckerParser.py ├── Generator.py └── cwe_checker_to_ida.py ├── doc ├── images │ ├── cwe_checker_logo.png │ ├── example_ghidra_integration.png │ ├── example_ida_anotation.png │ ├── example_usage.png │ ├── extern_calls.png │ ├── internal_function_call.png │ └── node_edge.png └── slides │ ├── cwe_checker_BlackHatUSA2019.pdf │ ├── cwe_checker_BlackHatUSA2022.pdf │ └── cwe_checker_pts19.pdf ├── flake.lock ├── flake.nix ├── ghidra_plugin └── cwe_checker_ghidra_plugin.py ├── src ├── caller │ ├── Cargo.toml │ └── src │ │ ├── cfg_stats.rs │ │ └── main.rs ├── config.json ├── cwe_checker_lib │ ├── Cargo.toml │ ├── benches │ │ ├── _data │ │ │ └── .gitkeep │ │ └── benchmarks.rs │ └── src │ │ ├── abstract_domain │ │ ├── bitvector.rs │ │ ├── bricks.rs │ │ ├── bricks │ │ │ ├── brick.rs │ │ │ ├── tests.rs │ │ │ └── widening.rs │ │ ├── character_inclusion.rs │ │ ├── data.rs │ │ ├── data │ │ │ ├── arithmetics.rs │ │ │ ├── conditional_specialization.rs │ │ │ └── trait_impl.rs │ │ ├── domain_map.rs │ │ ├── identifier │ │ │ ├── location.rs │ │ │ ├── mem_location.rs │ │ │ └── mod.rs │ │ ├── interval.rs │ │ ├── interval │ │ │ ├── bin_ops.rs │ │ │ ├── simple_interval.rs │ │ │ ├── simple_interval │ │ │ │ └── tests.rs │ │ │ └── tests.rs │ │ ├── mem_region.rs │ │ ├── mem_region │ │ │ └── tests.rs │ │ ├── mod.rs │ │ └── strings.rs │ │ ├── analysis │ │ ├── backward_interprocedural_fixpoint │ │ │ ├── mock_context.rs │ │ │ ├── mod.rs │ │ │ └── tests.rs │ │ ├── callgraph.rs │ │ ├── fixpoint.rs │ │ ├── forward_interprocedural_fixpoint.rs │ │ ├── function_signature │ │ │ ├── access_pattern.rs │ │ │ ├── context │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── global_var_propagation.rs │ │ │ ├── mod.rs │ │ │ ├── state │ │ │ │ ├── call_handling │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── memory_handling.rs │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── stubs.rs │ │ │ └── tests.rs │ │ ├── graph.rs │ │ ├── graph │ │ │ ├── algo.rs │ │ │ ├── call.rs │ │ │ ├── intraprocedural_cfg.rs │ │ │ └── intraprocedural_cfg │ │ │ │ ├── dom.rs │ │ │ │ ├── natural_loops.rs │ │ │ │ └── properties.rs │ │ ├── interprocedural_fixpoint_generic.rs │ │ ├── mod.rs │ │ ├── pointer_inference │ │ │ ├── context │ │ │ │ ├── id_manipulation.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── stubs.rs │ │ │ │ ├── tests.rs │ │ │ │ └── trait_impls.rs │ │ │ ├── mod.rs │ │ │ ├── object │ │ │ │ ├── id_manipulation.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── tests.rs │ │ │ │ └── value_access.rs │ │ │ ├── object_list │ │ │ │ ├── id_manipulation.rs │ │ │ │ ├── list_manipulation.rs │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── state │ │ │ │ ├── access_handling.rs │ │ │ │ ├── id_manipulation.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── tests │ │ │ │ │ ├── access_handling.rs │ │ │ │ │ ├── id_manipulation.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── specialized_expressions.rs │ │ │ │ └── value_specialization.rs │ │ │ ├── statistics.rs │ │ │ └── vsa_result_impl.rs │ │ ├── string_abstraction │ │ │ ├── context │ │ │ │ ├── mod.rs │ │ │ │ ├── symbol_calls.rs │ │ │ │ ├── symbol_calls │ │ │ │ │ ├── memcpy.rs │ │ │ │ │ ├── scanf.rs │ │ │ │ │ ├── sprintf.rs │ │ │ │ │ ├── sprintf │ │ │ │ │ │ └── tests.rs │ │ │ │ │ ├── strcat.rs │ │ │ │ │ └── tests.rs │ │ │ │ ├── tests.rs │ │ │ │ ├── trait_impls.rs │ │ │ │ └── trait_impls │ │ │ │ │ └── tests.rs │ │ │ ├── mod.rs │ │ │ ├── state │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ └── tests.rs │ │ ├── taint │ │ │ ├── mod.rs │ │ │ ├── state.rs │ │ │ └── state │ │ │ │ ├── memory_taint.rs │ │ │ │ ├── register_taint.rs │ │ │ │ └── tests.rs │ │ └── vsa_results │ │ │ └── mod.rs │ │ ├── checkers.rs │ │ ├── checkers │ │ ├── cwe_119 │ │ │ ├── context │ │ │ │ ├── bounds_computation.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── param_replacement.rs │ │ │ │ ├── tests.rs │ │ │ │ └── trait_impls.rs │ │ │ ├── mod.rs │ │ │ ├── state.rs │ │ │ └── stubs.rs │ │ ├── cwe_134.rs │ │ ├── cwe_190.rs │ │ ├── cwe_215.rs │ │ ├── cwe_243.rs │ │ ├── cwe_252.rs │ │ ├── cwe_252 │ │ │ ├── context.rs │ │ │ └── isolated_returns.rs │ │ ├── cwe_332.rs │ │ ├── cwe_337.rs │ │ ├── cwe_367.rs │ │ ├── cwe_416 │ │ │ ├── context.rs │ │ │ ├── mod.rs │ │ │ └── state.rs │ │ ├── cwe_426.rs │ │ ├── cwe_467.rs │ │ ├── cwe_476.rs │ │ ├── cwe_476 │ │ │ └── context.rs │ │ ├── cwe_560.rs │ │ ├── cwe_676.rs │ │ ├── cwe_78.rs │ │ ├── cwe_782.rs │ │ └── cwe_789.rs │ │ ├── ghidra_pcode │ │ ├── block │ │ │ ├── mod.rs │ │ │ └── tests.rs │ │ ├── calling_convention │ │ │ └── mod.rs │ │ ├── datatype_properties.rs │ │ ├── function │ │ │ ├── extern_function.rs │ │ │ ├── extern_function │ │ │ │ └── domain_knowledge │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── scanf_sscanf.rs │ │ │ └── mod.rs │ │ ├── instruction │ │ │ └── mod.rs │ │ ├── ir_passes │ │ │ ├── entry_points.rs │ │ │ ├── fn_start_blocks.rs │ │ │ ├── inlining.rs │ │ │ ├── jump_targets.rs │ │ │ ├── mod.rs │ │ │ ├── nonret_ext_functions.rs │ │ │ ├── remove_empty_functions.rs │ │ │ ├── replace_call_to_ext_fn.rs │ │ │ ├── single_target_indirect_calls.rs │ │ │ ├── subregister_substitution.rs │ │ │ └── subregister_substitution │ │ │ │ └── tests.rs │ │ ├── mod.rs │ │ ├── pcode_opcode.rs │ │ ├── pcode_operation │ │ │ ├── jumps │ │ │ │ ├── mod.rs │ │ │ │ └── tests.rs │ │ │ ├── mod.rs │ │ │ └── tests.rs │ │ ├── program.rs │ │ ├── register_properties.rs │ │ ├── term.rs │ │ └── varnode │ │ │ └── mod.rs │ │ ├── intermediate_representation │ │ ├── bitvector.rs │ │ ├── blk.rs │ │ ├── def.rs │ │ ├── expression.rs │ │ ├── expression │ │ │ ├── builder.rs │ │ │ ├── tests.rs │ │ │ └── trivial_operation_substitution.rs │ │ ├── jmp.rs │ │ ├── macros │ │ │ ├── mod.rs │ │ │ └── tests.rs │ │ ├── mod.rs │ │ ├── program.rs │ │ ├── project.rs │ │ ├── project │ │ │ └── ir_passes │ │ │ │ ├── control_flow_propagation.rs │ │ │ │ ├── dead_variable_elim.rs │ │ │ │ ├── dead_variable_elim │ │ │ │ └── fixpoint_computation.rs │ │ │ │ ├── input_expression_propagation.rs │ │ │ │ ├── input_expression_propagation │ │ │ │ ├── fixpoint_computation.rs │ │ │ │ └── tests.rs │ │ │ │ ├── intraprocedural_dead_block_elim.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── stack_pointer_alignment_substitution.rs │ │ │ │ ├── stack_pointer_alignment_substitution │ │ │ │ ├── legacy.rs │ │ │ │ └── legacy │ │ │ │ │ └── tests.rs │ │ │ │ └── trivial_expression_substitution.rs │ │ ├── runtime_memory_image.rs │ │ ├── sub.rs │ │ ├── term.rs │ │ ├── term │ │ │ ├── builder_high_lvl.rs │ │ │ └── builder_low_lvl.rs │ │ └── variable.rs │ │ ├── lib.rs │ │ ├── pipeline │ │ ├── mod.rs │ │ └── results.rs │ │ └── utils │ │ ├── arguments.rs │ │ ├── arguments │ │ └── tests.rs │ │ ├── binary.rs │ │ ├── debug.rs │ │ ├── ghidra.rs │ │ ├── graph_utils.rs │ │ ├── log.rs │ │ ├── mod.rs │ │ └── symbol_utils.rs ├── ghidra │ └── p_code_extractor │ │ ├── Block.java │ │ ├── CallingConvention.java │ │ ├── DatatypeProperties.java │ │ ├── ExternFunction.java │ │ ├── Function.java │ │ ├── Instruction.java │ │ ├── PcodeExtractor.java │ │ ├── PcodeOp.java │ │ ├── PcodeProject.java │ │ ├── Program.java │ │ ├── RegisterProperties.java │ │ ├── Term.java │ │ └── Varnode.java ├── installer │ ├── Cargo.toml │ └── src │ │ └── main.rs └── lkm_config.json └── test ├── Cargo.toml ├── artificial_samples ├── .dockerignore ├── Dockerfile ├── Readme.md ├── SConstruct ├── arrays.c ├── c_constructs.c ├── check_path.c ├── cwe_119.c ├── cwe_134.c ├── cwe_190.c ├── cwe_243.c ├── cwe_243_clean.c ├── cwe_252.c ├── cwe_332.c ├── cwe_337.c ├── cwe_367.c ├── cwe_415.c ├── cwe_416.c ├── cwe_426.c ├── cwe_457.c ├── cwe_467.c ├── cwe_476.c ├── cwe_478.c ├── cwe_560.c ├── cwe_676.c ├── cwe_78.c ├── cwe_782.c ├── cwe_789.c ├── install_cross_compilers.sh └── memory_access.c ├── bare_metal_samples └── test_sample.bin ├── lkm_samples ├── .gitignore ├── Dockerfile ├── Makefile ├── Readme.md ├── build.sh ├── cwe_252.c ├── cwe_467.c ├── cwe_476.c ├── cwe_676.c ├── debug.config.fragment └── modules.config.fragment └── src └── lib.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | test/**/build 2 | target/ 3 | src/cwe_checker_lib/benches/_data/ 4 | -------------------------------------------------------------------------------- /.github/workflows/acceptance-tests.yml: -------------------------------------------------------------------------------- 1 | name: Acceptance tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | 16 | acceptance-tests: 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Build the cwe_checker docker image 22 | run: docker build -t cwe_checker . 23 | - name: Build and run docker image for cross compiling 24 | run: | 25 | pushd test/artificial_samples 26 | docker build -t cross_compiling . 27 | docker run --rm -v $(pwd)/build:/home/cwe/artificial_samples/build cross_compiling sudo python3 -m SCons 28 | popd 29 | pushd test/lkm_samples 30 | ./build.sh 31 | - uses: actions-rs/toolchain@v1 32 | with: 33 | profile: minimal 34 | toolchain: 1.82.0 35 | override: true 36 | - uses: actions-rs/cargo@v1 37 | with: 38 | command: test 39 | args: --locked --no-fail-fast -p acceptance_tests_ghidra -F docker -- --show-output --ignored 40 | - name: Generate zip with test binaries 41 | run: | 42 | zip artificial_samples.zip test/artificial_samples/build/*.out 43 | zip lkm_samples.zip test/lkm_samples/build/*.ko 44 | - name: Archive test binaries 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: acceptance-test-binaries 48 | path: | 49 | artificial_samples.zip 50 | lkm_samples.zip 51 | 52 | 53 | docker-build: 54 | runs-on: ubuntu-latest 55 | 56 | steps: 57 | - uses: actions/checkout@v3 58 | - name: Build the docker image 59 | run: docker build -t cwe_checker . 60 | - name: Check functionality of the image 61 | run: docker run --rm cwe_checker /bin/echo | grep -q CWE676 62 | -------------------------------------------------------------------------------- /.github/workflows/codestyle_checks.yml: -------------------------------------------------------------------------------- 1 | name: codestyle-checks 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | RUSTDOCFLAGS: -Dwarnings 10 | 11 | jobs: 12 | fmt: 13 | name: Rustfmt 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: 1.82.0 21 | override: true 22 | components: rustfmt 23 | - uses: actions-rs/cargo@v1 24 | with: 25 | command: fmt 26 | args: --all -- --check 27 | 28 | clippy: 29 | name: Clippy 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | toolchain: 1.82.0 37 | override: true 38 | components: clippy 39 | - uses: actions-rs/cargo@v1 40 | with: 41 | command: clippy 42 | args: -- -D clippy::all -D missing_docs 43 | - uses: actions-rs/cargo@v1 44 | with: 45 | command: clippy 46 | args: -p cwe_checker_lib --bench "benchmarks" -- -D clippy::all 47 | 48 | doc: 49 | name: Rustdoc 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v3 53 | - uses: actions-rs/toolchain@v1 54 | with: 55 | profile: minimal 56 | toolchain: 1.82.0 57 | override: true 58 | components: rust-docs 59 | - uses: actions-rs/cargo@v1 60 | with: 61 | command: doc 62 | args: --no-deps --document-private-items 63 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish-stable.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker images for release versions 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build-and-publish-image: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Get tag name 13 | uses: olegtarasov/get-tag@v2.1 14 | id: tagName 15 | 16 | - name: Checkout repository 17 | uses: actions/checkout@v3 18 | 19 | - name: Set up QEMU 20 | uses: docker/setup-qemu-action@v2 21 | 22 | - name: Set up Docker Buildx 23 | uses: docker/setup-buildx-action@v2 24 | 25 | - name: Build test docker image 26 | uses: docker/build-push-action@v3 27 | with: 28 | context: . 29 | load: true 30 | tags: cwe_checker:test 31 | 32 | - name: Check functionality of the image 33 | run: docker run --rm cwe_checker:test /bin/echo | grep -q CWE676 34 | 35 | - name: Login to DockerHub 36 | uses: docker/login-action@v2 37 | with: 38 | username: ${{ secrets.DOCKERHUB_USERNAME }} 39 | password: ${{ secrets.DOCKERHUB_TOKEN }} 40 | 41 | - name: Login to ghcr.io Container registry 42 | uses: docker/login-action@v2 43 | with: 44 | registry: ghcr.io 45 | username: ${{ github.actor }} 46 | password: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | # The linux/arm64 platform target has been temporarily disabled due to memory exhaustion problems in the CI 49 | - name: Build and push Docker image 50 | uses: docker/build-push-action@v3 51 | with: 52 | context: . 53 | platforms: | 54 | linux/amd64 55 | push: true 56 | tags: | 57 | fkiecad/cwe_checker:stable 58 | fkiecad/cwe_checker:${{ steps.tagName.outputs.tag }} 59 | ghcr.io/fkie-cad/cwe_checker:stable 60 | ghcr.io/fkie-cad/cwe_checker:${{ steps.tagName.outputs.tag }} -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image based on master branch 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | 7 | jobs: 8 | build-and-publish-image: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v3 14 | 15 | - name: Set up QEMU 16 | uses: docker/setup-qemu-action@v2 17 | 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v2 20 | 21 | - name: Build test docker image 22 | uses: docker/build-push-action@v3 23 | with: 24 | context: . 25 | load: true 26 | tags: cwe_checker:test 27 | 28 | - name: Check functionality of the image 29 | run: docker run --rm cwe_checker:test /bin/echo | grep -q CWE676 30 | 31 | - name: Login to DockerHub 32 | uses: docker/login-action@v2 33 | with: 34 | username: ${{ secrets.DOCKERHUB_USERNAME }} 35 | password: ${{ secrets.DOCKERHUB_TOKEN }} 36 | 37 | - name: Login to ghcr.io Container registry 38 | uses: docker/login-action@v2 39 | with: 40 | registry: ghcr.io 41 | username: ${{ github.actor }} 42 | password: ${{ secrets.GITHUB_TOKEN }} 43 | 44 | # The linux/arm64 platform target has been temporarily disabled due to memory exhaustion problems in the CI 45 | - name: Build and push Docker image 46 | uses: docker/build-push-action@v3 47 | with: 48 | context: . 49 | platforms: | 50 | linux/amd64 51 | push: true 52 | tags: | 53 | fkiecad/cwe_checker:latest 54 | ghcr.io/fkie-cad/cwe_checker:latest -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | unit-tests: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | profile: minimal 19 | toolchain: 1.82.0 20 | override: true 21 | - uses: actions-rs/cargo@v1 22 | with: 23 | command: test 24 | args: --locked 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/c,ocaml,python 2 | 3 | ### C ### 4 | # Prerequisites 5 | *.d 6 | 7 | # Object files 8 | *.o 9 | *.ko 10 | *.obj 11 | *.elf 12 | 13 | # Linker output 14 | *.ilk 15 | *.map 16 | *.exp 17 | 18 | # Precompiled Headers 19 | *.gch 20 | *.pch 21 | 22 | # Libraries 23 | *.lib 24 | *.a 25 | *.la 26 | *.lo 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | # Debug files 43 | *.dSYM/ 44 | *.su 45 | *.idb 46 | *.pdb 47 | 48 | # Kernel Module Compile Results 49 | *.mod* 50 | *.cmd 51 | .tmp_versions/ 52 | modules.order 53 | Module.symvers 54 | Mkfile.old 55 | dkms.conf 56 | 57 | ### OCaml ### 58 | *.annot 59 | *.cmo 60 | *.cma 61 | *.cmi 62 | *.cmx 63 | *.cmxs 64 | *.cmxa 65 | 66 | # ocamlbuild working directory 67 | _build/ 68 | 69 | # ocamlbuild targets 70 | *.byte 71 | *.native 72 | 73 | # oasis generated files 74 | setup.data 75 | setup.log 76 | 77 | # Merlin configuring file for Vim and Emacs 78 | .merlin 79 | 80 | ### Python ### 81 | # Byte-compiled / optimized / DLL files 82 | __pycache__/ 83 | *.py[cod] 84 | *$py.class 85 | 86 | # C extensions 87 | 88 | # Distribution / packaging 89 | .Python 90 | build/ 91 | develop-eggs/ 92 | dist/ 93 | downloads/ 94 | eggs/ 95 | .eggs/ 96 | lib/ 97 | lib64/ 98 | parts/ 99 | sdist/ 100 | var/ 101 | wheels/ 102 | *.egg-info/ 103 | .installed.cfg 104 | *.egg 105 | MANIFEST 106 | 107 | # PyInstaller 108 | # Usually these files are written by a python script from a template 109 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 110 | *.manifest 111 | *.spec 112 | 113 | # Installer logs 114 | pip-log.txt 115 | pip-delete-this-directory.txt 116 | 117 | # Unit test / coverage reports 118 | htmlcov/ 119 | .tox/ 120 | .nox/ 121 | .coverage 122 | .coverage.* 123 | .cache 124 | nosetests.xml 125 | coverage.xml 126 | *.cover 127 | .hypothesis/ 128 | .pytest_cache/ 129 | 130 | # Translations 131 | *.mo 132 | *.pot 133 | 134 | # Django stuff: 135 | *.log 136 | local_settings.py 137 | db.sqlite3 138 | 139 | # Flask stuff: 140 | instance/ 141 | .webassets-cache 142 | 143 | # Scrapy stuff: 144 | .scrapy 145 | 146 | # Sphinx documentation 147 | docs/_build/ 148 | 149 | # PyBuilder 150 | target/ 151 | 152 | # Jupyter Notebook 153 | .ipynb_checkpoints 154 | 155 | # IPython 156 | profile_default/ 157 | ipython_config.py 158 | 159 | # pyenv 160 | .python-version 161 | 162 | # celery beat schedule file 163 | celerybeat-schedule 164 | 165 | # SageMath parsed files 166 | *.sage.py 167 | 168 | # Environments 169 | .env 170 | .venv 171 | env/ 172 | venv/ 173 | ENV/ 174 | env.bak/ 175 | venv.bak/ 176 | 177 | # Spyder project settings 178 | .spyderproject 179 | .spyproject 180 | 181 | # Rope project settings 182 | .ropeproject 183 | 184 | # mkdocs documentation 185 | /site 186 | 187 | # mypy 188 | .mypy_cache/ 189 | .dmypy.json 190 | dmypy.json 191 | 192 | ### Python Patch ### 193 | .venv/ 194 | 195 | ### Python.VirtualEnv Stack ### 196 | # Virtualenv 197 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 198 | [Bb]in 199 | [Ii]nclude 200 | [Ll]ib 201 | [Ll]ib64 202 | [Ll]ocal 203 | [Ss]cripts 204 | pyvenv.cfg 205 | pip-selfcheck.json 206 | 207 | # End of https://www.gitignore.io/api/c,ocaml,python 208 | 209 | # dont upload our real life zoo 210 | test/real_world_samples 211 | test/run_real_world_samples.sh 212 | 213 | .project 214 | .pydevproject 215 | 216 | # Plugin files (generated by bapbuild) 217 | *.plugin 218 | 219 | # install files for opam packages (generated by dune) 220 | *.install 221 | 222 | # the documentation build directory 223 | doc/html 224 | 225 | test/artificial_samples/dockcross* 226 | src/cwe_checker_lib/benches/_data/* 227 | !src/cwe_checker_lib/benches/_data/.gitkeep 228 | 229 | .#* 230 | 231 | .sconsign.dblite 232 | 233 | ### Rust ### 234 | # Generated by Cargo 235 | # will have compiled files and executables 236 | /target/ 237 | 238 | # Local backups of files 239 | **/*.bk 240 | 241 | # Libraries for development 242 | ghidra/p_code_extractor/lib/ 243 | 244 | # Compiled class file 245 | *.class 246 | 247 | # Log file 248 | *.log 249 | 250 | # BlueJ files 251 | *.ctxt 252 | 253 | # Mobile Tools for Java (J2ME) 254 | .mtj.tmp/ 255 | 256 | # Package Files # 257 | *.jar 258 | *.war 259 | *.nar 260 | *.ear 261 | *.zip 262 | *.tar.gz 263 | *.rar 264 | 265 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 266 | hs_err_pid* 267 | 268 | # Stuff 269 | notes.md 270 | *.pcode-raw 271 | *.ir-optimized 272 | *.ir-unoptimized 273 | *.ir-opt 274 | *.ir-unopt 275 | *.ir 276 | *.pi 277 | *.cfg 278 | *.warn 279 | _ghidra 280 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # cwe_checker contributors 2 | 3 | - [Thomas Barabosch](https://github.com/tbarabosch) 4 | - Original author, maintainer 2018-2019 5 | 6 | - [Nils-Edvin Enkelmann](https://github.com/Enkelmann) 7 | - Maintainer 2020-2024 8 | 9 | - [Valentin Obst](https://github.com/vobst) 10 | - Maintainer since 2024 11 | 12 | - [Jörg Stucke](https://github.com/jstucke) 13 | 14 | - [Melvin Klimke](https://github.com/mellowCS) 15 | 16 | - [Mauritz van den Bosch](https://github.com/m-rtz) -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["src/cwe_checker_lib", "src/caller", "test", "src/installer"] 3 | resolver = "2" 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.82-alpine3.20 AS builder 2 | 3 | WORKDIR /cwe_checker 4 | 5 | RUN apk add --no-cache musl-dev 6 | 7 | COPY . . 8 | RUN cargo build --target x86_64-unknown-linux-musl --locked 9 | 10 | FROM ghcr.io/fkie-cad/ghidra_headless_base:v11.2 as runtime 11 | 12 | RUN apk add --no-cache bash 13 | 14 | ENV USER cwe 15 | ENV GROUPNAME cwe 16 | ENV UID 1000 17 | ENV GID 1000 18 | 19 | RUN addgroup --gid "$GID" "$GROUPNAME" \ 20 | && adduser \ 21 | --disabled-password \ 22 | --gecos "" \ 23 | --home "/home/cwe" \ 24 | --ingroup "$GROUPNAME" \ 25 | --no-create-home \ 26 | --uid "$UID" \ 27 | $USER 28 | 29 | RUN mkdir -p /home/cwe \ 30 | && mkdir -p /home/cwe/.config/ghidra/${GHIDRA_VERSION_NAME} \ 31 | && chown -R cwe:cwe /home/cwe 32 | 33 | USER cwe 34 | 35 | # Install all necessary files from the builder stage 36 | COPY --chown=${USER} --from=builder /cwe_checker/target/x86_64-unknown-linux-musl/debug/cwe_checker /home/cwe/cwe_checker 37 | COPY --chown=${USER} --from=builder /cwe_checker/src/config.json /home/cwe/.config/cwe_checker/config.json 38 | COPY --chown=${USER} --from=builder /cwe_checker/src/lkm_config.json /home/cwe/.config/cwe_checker/lkm_config.json 39 | COPY --chown=${USER} --from=builder /cwe_checker/src/ghidra/p_code_extractor /home/cwe/.local/share/cwe_checker/ghidra/p_code_extractor 40 | RUN echo "{ \"ghidra_path\": \"/opt/ghidra\" }" | tee /home/cwe/.config/cwe_checker/ghidra.json 41 | 42 | WORKDIR / 43 | 44 | ENV RUST_BACKTRACE=1 45 | ENTRYPOINT ["/home/cwe/cwe_checker"] 46 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GHIDRA_PATH = 2 | 3 | .PHONY: all clean test uninstall docker 4 | all: 5 | cargo build --locked -p cwe_checker_install --release 6 | ./target/release/cwe_checker_install ${GHIDRA_PATH} 7 | 8 | test: 9 | cargo test --locked 10 | if [ ! -d "test/artificial_samples/build" ]; then \ 11 | echo "Acceptance test binaries not found. Please see test/artificial_samples/Readme.md for build instructions."; \ 12 | exit -1; \ 13 | fi 14 | if [ ! -d "test/lkm_samples/build" ]; then \ 15 | echo "Acceptance test LKMs not found. Please see test/lkm_samples/Readme.md for build instructions."; \ 16 | exit -1; \ 17 | fi 18 | cargo test --locked --no-fail-fast -p acceptance_tests_ghidra -F docker -- --show-output --ignored --test-threads 6 19 | 20 | compile_test_files: 21 | pushd test/artificial_samples \ 22 | && docker build -t cross_compiling . \ 23 | && rm -rf build/* \ 24 | && mkdir -p build/ \ 25 | && docker run --rm -v $(shell pwd)/test/artificial_samples/build:/home/cwe/artificial_samples/build cross_compiling sudo /usr/local/bin/scons \ 26 | && popd \ 27 | && pushd test/lkm_samples \ 28 | && ./build.sh 29 | 30 | bench: 31 | if [ ! -d "src/cwe_checker_lib/benches/_data/" ]; then \ 32 | echo "Benchmark binaries not found. Please see src/cwe_checker_lib/benches/benchmarks.rs for instructions."; \ 33 | exit -1; \ 34 | fi 35 | cargo bench --bench "benchmarks" -- --save-baseline __last_make_run 36 | 37 | check: 38 | cargo fmt -- --check 39 | cargo clippy -- -D clippy::all -D missing_docs 40 | cargo clippy -p cwe_checker_lib --bench "benchmarks" -- -D clippy::all 41 | RUSTDOCFLAGS="-Dwarnings" cargo doc --no-deps --document-private-items 42 | 43 | clean: 44 | cargo clean 45 | rm -f -r doc/html 46 | 47 | uninstall: 48 | cargo build --locked -p cwe_checker_install --release 49 | ./target/release/cwe_checker_install --uninstall 50 | 51 | documentation: 52 | cargo doc --open --no-deps 53 | 54 | docker: 55 | make clean 56 | docker build -t cwe_checker . 57 | -------------------------------------------------------------------------------- /bare_metal/stm32f407vg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment": "The CPU architecture of the chip. Valid values are those that Ghidra accepts as processor IDs.", 3 | "processor_id": "ARM:LE:32:v8", 4 | "_comment_1": "The base address, where the contents of the binary would be mapped on the chip, as a hexadecimal number.", 5 | "flash_base_address": "0x08000000", 6 | "_comment_2": "The base address, of the RAM memory region as a hexadecimal number.", 7 | "ram_base_address": "0x20000000", 8 | "_comment_3": "The size of the RAM memory region (in bytes) as a hexadecimal number.", 9 | "ram_size": "0x00030000" 10 | } -------------------------------------------------------------------------------- /cwe_checker_to_ida/CweCheckerParser.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | RED = 0x6666FF 4 | ORANGE = 0x6699FF 5 | YELLOW = 0xC0FFFF 6 | 7 | colors = {'CWE125': RED, 8 | 'CWE134': RED, 9 | 'CWE119': RED, 10 | 'CWE190': YELLOW, 11 | 'CWE215': None, 12 | 'CWE243': None, 13 | 'CWE248': YELLOW, 14 | 'CWE332': None, 15 | 'CWE367': ORANGE, 16 | 'CWE415': RED, 17 | 'CWE416': RED, 18 | 'CWE426': ORANGE, 19 | 'CWE457': YELLOW, 20 | 'CWE467': ORANGE, 21 | 'CWE476': ORANGE, 22 | 'CWE560': YELLOW, 23 | 'CWE676': RED, 24 | 'CWE782': ORANGE, 25 | 'CWE787': RED, 26 | } 27 | 28 | class Cwe: 29 | 30 | def __init__(self, name, address, description): 31 | self.address = '0x'+address 32 | self.comment = description 33 | self.color = self.__get_color(name) 34 | 35 | def __get_color(self,name): 36 | return colors[name] 37 | 38 | class Parser: 39 | 40 | def __init__(self,result_path): 41 | self._result_path = result_path 42 | 43 | def __parse_cwe(self,j): 44 | result = [] 45 | for p in j: 46 | addresses = p['addresses'] 47 | for address in addresses: 48 | element = Cwe( 49 | address=address, 50 | name=p['name'], 51 | description=p['description'], 52 | ) 53 | result.append(element) 54 | 55 | return result 56 | 57 | def parse(self): 58 | with open(self._result_path) as fhandle: 59 | j = json.load(fhandle) 60 | cwe_out = self.__parse_cwe(j) 61 | return cwe_out 62 | -------------------------------------------------------------------------------- /cwe_checker_to_ida/Generator.py: -------------------------------------------------------------------------------- 1 | from CweCheckerParser import Cwe 2 | 3 | class IdaGenerator: 4 | 5 | def __init__(self, results): 6 | self._results = results 7 | 8 | def generate(self): 9 | script = "import sark\nimport idaapi\n" 10 | for res in self._results: 11 | script += "sark.Line(%s).color = %s\n" % (res.address, res.color) 12 | script += "sark.Line(%s).comments.regular = '%s'\n" % (res.address, res.comment) 13 | script += "print('[ %s ] %s')\n" % (res.address, res.comment) 14 | return script 15 | -------------------------------------------------------------------------------- /cwe_checker_to_ida/cwe_checker_to_ida.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import argparse 4 | 5 | from CweCheckerParser import Parser 6 | from Generator import IdaGenerator 7 | 8 | 9 | def parse_args(): 10 | parser = argparse.ArgumentParser( 11 | description='Generates an anotation script for IDA Pro based on CweChecker results.') 12 | parser.add_argument( 13 | '-i', '--cwe_checker_result', type=str, required=True, 14 | help='The path to the JSON output of CweChecker.') 15 | parser.add_argument( 16 | '-o', '--anotation_script_output', type=str, required=True, 17 | help='The output path of the anotation script.') 18 | args = parser.parse_args() 19 | return args 20 | 21 | 22 | def save_generated_script(outpath, generated_script): 23 | with open(outpath, "w") as fhandle: 24 | fhandle.write(generated_script) 25 | 26 | def is_valid_json_file(fpath): 27 | try: 28 | with open(fpath) as fhandle: 29 | json.load(fhandle) 30 | return True 31 | except json.JSONDecodeError: 32 | pass 33 | 34 | return False 35 | 36 | def main(): 37 | args = parse_args() 38 | 39 | if not os.path.isfile(args.cwe_checker_result): 40 | print('Input file does not exist.') 41 | return 1 42 | 43 | if not is_valid_json_file(args.cwe_checker_result): 44 | print('Input file must be formatted as cwe_checker\'s JSON output.') 45 | return 1 46 | 47 | results = Parser(args.cwe_checker_result).parse() 48 | generated_script = IdaGenerator(results).generate() 49 | save_generated_script(args.anotation_script_output, generated_script) 50 | print('Done. Now execute generated script %s with IDAPython (Alt+F7).' 51 | % args.anotation_script_output) 52 | 53 | 54 | if __name__ == '__main__': 55 | main() 56 | -------------------------------------------------------------------------------- /doc/images/cwe_checker_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/doc/images/cwe_checker_logo.png -------------------------------------------------------------------------------- /doc/images/example_ghidra_integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/doc/images/example_ghidra_integration.png -------------------------------------------------------------------------------- /doc/images/example_ida_anotation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/doc/images/example_ida_anotation.png -------------------------------------------------------------------------------- /doc/images/example_usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/doc/images/example_usage.png -------------------------------------------------------------------------------- /doc/images/extern_calls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/doc/images/extern_calls.png -------------------------------------------------------------------------------- /doc/images/internal_function_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/doc/images/internal_function_call.png -------------------------------------------------------------------------------- /doc/images/node_edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/doc/images/node_edge.png -------------------------------------------------------------------------------- /doc/slides/cwe_checker_BlackHatUSA2019.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/doc/slides/cwe_checker_BlackHatUSA2019.pdf -------------------------------------------------------------------------------- /doc/slides/cwe_checker_BlackHatUSA2022.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/doc/slides/cwe_checker_BlackHatUSA2022.pdf -------------------------------------------------------------------------------- /doc/slides/cwe_checker_pts19.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/doc/slides/cwe_checker_pts19.pdf -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1733759999, 6 | "narHash": "sha256-463SNPWmz46iLzJKRzO3Q2b0Aurff3U1n0nYItxq7jU=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "a73246e2eef4c6ed172979932bc80e1404ba2d56", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Nix flake for the cwe_checker with patched Ghidra as a dependency."; 3 | 4 | inputs = { 5 | # Depend on NixOS-unstable for the latest Rust version. 6 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 7 | }; 8 | 9 | outputs = { self, nixpkgs }: 10 | let 11 | pkgs = nixpkgs.legacyPackages."x86_64-linux"; 12 | # Building Ghidra. 13 | ghidra-cwe-checker-plugin = pkgs.ghidra.buildGhidraScripts { 14 | pname = "cwe_checker"; 15 | name = "cwe_checker"; 16 | src = ./ghidra_plugin; 17 | }; 18 | cwe-ghidra = pkgs.ghidra.withExtensions (p: with p; [ ghidra-cwe-checker-plugin ]); 19 | # Path to Java Ghidra plugin. 20 | cwe-checker-ghidra-plugins = pkgs.runCommand 21 | "cwe-checker-ghidra-plugins" { src = ./src/ghidra/p_code_extractor; } 22 | '' 23 | mkdir -p $out/p_code_extractor 24 | cp -rf $src/* $out/p_code_extractor 25 | ''; 26 | # Build Ghidra package with analyzeHeadless in support/ instead of bin/. 27 | # This is where the cwe_checker expects it to be. 28 | cwe-ghidra-path-fix = pkgs.stdenv.mkDerivation { 29 | name = "analyzeHeadless"; 30 | pname = "analyzeHeadless"; 31 | buildInputs = [ cwe-ghidra ]; 32 | src = cwe-ghidra; 33 | buildPhase = '' 34 | mkdir -p $out 35 | cp -rf ${cwe-ghidra} $out 36 | # cwe checker expects 37 | mkdir -p $out/support 38 | cp ${cwe-ghidra}/bin/ghidra-analyzeHeadless $out/support/analyzeHeadless 39 | ''; 40 | }; 41 | # Building cwe_checker. 42 | cwe-checker-bins = pkgs.rustPlatform.buildRustPackage { 43 | pname = "cwe_checker"; 44 | name = "cwe_checker"; 45 | src = ./.; 46 | cargoLock = { 47 | lockFile = ./Cargo.lock; 48 | }; 49 | }; 50 | # Build ghidra.json 51 | cwe-ghidra-json = pkgs.writeTextFile { 52 | name = "GhidraConfigFile"; 53 | text = builtins.toJSON { ghidra_path = ''${cwe-ghidra-path-fix}''; }; 54 | }; 55 | # Creates config dir for cwe_checker. 56 | cwe-checker-configs = pkgs.runCommand "cwe-checker-configs" { src = ./src; } 57 | '' 58 | mkdir -p $out 59 | cp $src/config.json $out 60 | cp $src/lkm_config.json $out 61 | ln -s ${cwe-ghidra-json} $out/ghidra.json 62 | ''; 63 | # Target bin for 'nix run'. 64 | cwe-checker = pkgs.writeScriptBin "cwe-checker" '' 65 | #!/bin/sh 66 | CWE_CHECKER_CONFIGS_PATH=${cwe-checker-configs} \ 67 | CWE_CHECKER_GHIDRA_PLUGINS_PATH=${cwe-checker-ghidra-plugins} \ 68 | ${cwe-checker-bins}/bin/cwe_checker $@; 69 | ''; 70 | in 71 | { 72 | devShell.x86_64-linux = pkgs.mkShell { 73 | buildInputs = with pkgs; [ 74 | rustc 75 | cargo 76 | cwe-ghidra-path-fix 77 | ]; 78 | shellHook = '' 79 | export CWE_CHECKER_CONFIGS_PATH=${cwe-checker-configs} \ 80 | export CWE_CHECKER_GHIDRA_PLUGINS_PATH=${cwe-checker-ghidra-plugins} \ 81 | ''; 82 | }; 83 | packages.x86_64-linux.default = cwe-checker; 84 | }; 85 | } 86 | 87 | -------------------------------------------------------------------------------- /ghidra_plugin/cwe_checker_ghidra_plugin.py: -------------------------------------------------------------------------------- 1 | # Import the results of the cwe_checker as bookmarks and comments into Ghidra. 2 | # 3 | # Usage: 4 | # - Copy this file into the Ghidra scripts folder 5 | # - Run the cwe_checker on a binary and save its output as a json file, e.g. with 6 | # "cwe_checker BINARY --json --out output.json" 7 | # - Open the binary in Ghidra and run this file as a script. Select the generated json file when prompted. 8 | 9 | import json 10 | 11 | 12 | def bookmark_cwe(ghidra_address, text): 13 | previous_bookmarks = getBookmarks(ghidra_address) 14 | for bookmark in previous_bookmarks: 15 | if '[cwe_checker]' == bookmark.getCategory(): 16 | if text not in bookmark.getComment(): 17 | createBookmark(ghidra_address, '[cwe_checker]', bookmark.getComment() + '\n' + text) 18 | return 19 | createBookmark(ghidra_address, '[cwe_checker]', text) 20 | return 21 | 22 | 23 | def comment_cwe_eol(ghidra_address, text): 24 | old_comment = getEOLComment(ghidra_address) 25 | if old_comment is None: 26 | setEOLComment(ghidra_address, text) 27 | elif text not in old_comment: 28 | setEOLComment(ghidra_address, old_comment + '\n' + text) 29 | 30 | 31 | def comment_cwe_pre(ghidra_address, text): 32 | old_comment = getPreComment(ghidra_address) 33 | if old_comment is None: 34 | setPreComment(ghidra_address, text) 35 | elif text not in old_comment: 36 | setPreComment(ghidra_address, old_comment + '\n' + text) 37 | 38 | 39 | def get_cwe_checker_output(): 40 | ghidra_file = askFile('Select json output file of the cwe_checker', 'Open') 41 | with open(ghidra_file.getAbsolutePath()) as json_file: 42 | return json.load(json_file) 43 | 44 | 45 | def main(): 46 | """ 47 | Annotate cwe_checker results in Ghidra as end-of-line 48 | comments and bookmarks to the corresponding addresses. 49 | """ 50 | warnings = get_cwe_checker_output() 51 | for warning in warnings: 52 | if len(warning['addresses']) == 0: 53 | cwe_text = '[' + warning['name'] + '] ' + warning['description'] 54 | ghidra_address = currentProgram.getMinAddress().add(0) 55 | bookmark_cwe(ghidra_address, cwe_text) 56 | comment_cwe_pre(ghidra_address, cwe_text) 57 | else: 58 | address_string = warning['addresses'][0] 59 | ghidra_address = currentProgram.getAddressFactory().getAddress(address_string) 60 | bookmark_cwe(ghidra_address, warning['description']) 61 | comment_cwe_eol(ghidra_address, warning['description']) 62 | 63 | main() 64 | -------------------------------------------------------------------------------- /src/caller/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cwe_checker" 3 | version = "0.9.0" 4 | authors = ["Fraunhofer FKIE "] 5 | edition = "2021" 6 | rust-version = "1.82" 7 | 8 | [dependencies] 9 | clap = { version = "4.0.32", features = ["derive"] } 10 | cwe_checker_lib = { path = "../cwe_checker_lib" } 11 | serde = {version = "1.0", features = ["derive", "rc"]} 12 | serde_json = "1.0" 13 | directories = "5.0.1" 14 | anyhow = "1.0" 15 | -------------------------------------------------------------------------------- /src/caller/src/cfg_stats.rs: -------------------------------------------------------------------------------- 1 | use cwe_checker_lib::analysis::graph::{ 2 | call::{CallGraph, CgNode}, 3 | intraprocedural_cfg::IntraproceduralCfg, 4 | }; 5 | use cwe_checker_lib::intermediate_representation::{ExtFunctionTid, Program}; 6 | 7 | use std::collections::HashMap; 8 | 9 | use serde::Serialize; 10 | 11 | /// Some properties of a program's CFG. 12 | #[derive(Serialize)] 13 | pub struct CfgProperties<'a> { 14 | internal_fns: HashMap, 15 | external_fns: HashMap>, 16 | } 17 | 18 | /// Some properties of an intraprocedural CFG. 19 | #[derive(Serialize, Debug, PartialEq, Eq)] 20 | struct IntFnCfgProperties { 21 | /// Cyclomatic complexity. 22 | cyclomatic_complexity: u32, 23 | /// Control flow flattening score. 24 | flattening_score: u32, 25 | /// Number of basic blocks. 26 | num_bb: u64, 27 | /// Number of instructions. 28 | num_insn: u64, 29 | } 30 | 31 | impl IntFnCfgProperties { 32 | fn new(cfg: &IntraproceduralCfg) -> Self { 33 | Self { 34 | cyclomatic_complexity: cfg.cyclomatic_complexity(), 35 | flattening_score: cfg.flattening_score(), 36 | num_bb: cfg.num_blocks(), 37 | num_insn: cfg.num_insn(), 38 | } 39 | } 40 | } 41 | 42 | /// Some properties of an external function in the CFG. 43 | #[derive(Serialize, Debug, PartialEq, Eq)] 44 | struct ExtFnCfgProperties<'a> { 45 | /// Number of call sites where this function may be called. 46 | num_cs: u64, 47 | /// Name of the function. 48 | name: &'a str, 49 | } 50 | 51 | impl<'a> ExtFnCfgProperties<'a> { 52 | fn new(p: &'a Program, cg: &CallGraph<'_>, f: &ExtFunctionTid) -> Self { 53 | Self { 54 | num_cs: cg.callers(f).map(|(_, edge)| edge.num_cs()).sum(), 55 | name: &p.extern_symbols.get(f).unwrap().name, 56 | } 57 | } 58 | } 59 | 60 | impl<'a> CfgProperties<'a> { 61 | /// Computes some CFG properties of the program `p`. 62 | pub fn new(p: &'a Program) -> Self { 63 | let cg = CallGraph::new_with_full_cfgs(p); 64 | 65 | let mut internal_fns = HashMap::new(); 66 | let mut external_fns = HashMap::new(); 67 | 68 | for f in cg.nodes() { 69 | match f { 70 | CgNode::Function(..) if f.is_artificial() => { 71 | // Exclude artificial functions. 72 | } 73 | CgNode::Function(term, cfg) => { 74 | assert_eq!( 75 | internal_fns.insert(term.tid.to_string(), IntFnCfgProperties::new(cfg)), 76 | None, 77 | "Function TIDs are not unique." 78 | ); 79 | } 80 | CgNode::ExtFunction(tid) => { 81 | assert_eq!( 82 | external_fns.insert(tid.to_string(), ExtFnCfgProperties::new(p, &cg, tid)), 83 | None, 84 | "Function TIDs are not unique." 85 | ); 86 | } 87 | } 88 | } 89 | 90 | Self { 91 | internal_fns, 92 | external_fns, 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cwe_checker_lib" 3 | version = "0.9.0" 4 | authors = ["Fraunhofer FKIE "] 5 | edition = "2021" 6 | rust-version = "1.82" 7 | 8 | [dependencies] 9 | apint = "0.2" 10 | regex = "1.5.5" 11 | serde = {version = "1.0", features = ["derive", "rc"]} 12 | serde_json = "1.0" 13 | serde_yaml = "0.9" 14 | petgraph = { version = "0.6", features = ["default", "serde-1"] } 15 | fnv = "1.0" # a faster hash function for small keys like integers 16 | anyhow = "1.0" # for easy error types 17 | crossbeam-channel = "0.5.4" 18 | derive_more = "0.99" 19 | directories = "5.0.1" 20 | goblin = "0.7.1" 21 | itertools = "0.10.3" 22 | gcd = "2.1.0" 23 | nix = "0.26.1" 24 | 25 | [dev-dependencies] 26 | criterion = { version = "0.5.1", features = ["html_reports"] } 27 | paste = "1.0.14" 28 | 29 | [[bench]] 30 | name = "benchmarks" 31 | harness = false 32 | 33 | [lib] 34 | name = "cwe_checker_lib" 35 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/benches/_data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/src/cwe_checker_lib/benches/_data/.gitkeep -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/abstract_domain/strings.rs: -------------------------------------------------------------------------------- 1 | /// A set of functions that all abstract string domains should implement. 2 | pub trait DomainInsertion { 3 | /// Inserts a string domain at a certain position if order is considered. 4 | fn append_string_domain(&self, string_domain: &Self) -> Self; 5 | /// Creates a string domain with characters that usually appear in an integer value. 6 | fn create_integer_domain() -> Self; 7 | /// Creates a string domain with characters that usually appear in a char value. 8 | fn create_char_domain() -> Self; 9 | /// Creates a string domain with characters that usually appear in a float value. 10 | fn create_float_value_domain() -> Self; 11 | /// Creates a string domain with characters that usually appear in a String value. 12 | fn create_pointer_value_domain() -> Self; 13 | /// Creates a top value of the currently used domain. 14 | fn create_top_value_domain() -> Self; 15 | /// Creates an empty string domain. 16 | fn create_empty_string_domain() -> Self; 17 | } 18 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/backward_interprocedural_fixpoint/mock_context.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::analysis::graph::Graph; 3 | use petgraph::graph::NodeIndex; 4 | use std::collections::HashMap; 5 | 6 | /// Identifier for BlkStart and BlkEnd nodes 7 | #[derive(Clone, PartialEq, Eq, Hash)] 8 | pub enum StartEnd { 9 | Start, 10 | End, 11 | } 12 | 13 | /// A simple mock context, only containing the program cfg 14 | #[derive(Clone)] 15 | pub struct Context<'a> { 16 | pub graph: Graph<'a>, 17 | pub tid_to_node_index: HashMap<(Tid, Tid, StartEnd), NodeIndex>, 18 | } 19 | 20 | impl<'a> Context<'a> { 21 | pub fn new(project: &'a Project) -> Self { 22 | let mut graph = crate::analysis::graph::get_program_cfg(&project.program); 23 | graph.reverse(); 24 | let mut tid_to_node_index: HashMap<(Tid, Tid, StartEnd), NodeIndex> = HashMap::new(); 25 | for node in graph.node_indices() { 26 | let node_value = graph.node_weight(node).unwrap(); 27 | match node_value { 28 | Node::BlkStart { 29 | 0: block, 30 | 1: subroutine, 31 | } => { 32 | tid_to_node_index.insert( 33 | (subroutine.tid.clone(), block.tid.clone(), StartEnd::Start), 34 | node, 35 | ); 36 | } 37 | Node::BlkEnd { 38 | 0: block, 39 | 1: subroutine, 40 | } => { 41 | tid_to_node_index.insert( 42 | (subroutine.tid.clone(), block.tid.clone(), StartEnd::End), 43 | node, 44 | ); 45 | } 46 | _ => (), 47 | } 48 | } 49 | Context { 50 | graph, 51 | tid_to_node_index, 52 | } 53 | } 54 | } 55 | 56 | impl<'a> crate::analysis::backward_interprocedural_fixpoint::Context<'a> for Context<'a> { 57 | type Value = u64; 58 | 59 | fn get_graph(&self) -> &Graph<'a> { 60 | &self.graph 61 | } 62 | 63 | /// Take the minimum of two values when merging 64 | fn merge(&self, val1: &u64, val2: &u64) -> u64 { 65 | std::cmp::min(*val1, *val2) 66 | } 67 | 68 | /// Increase the Def count when parsing one 69 | fn update_def(&self, val: &u64, _def: &Term) -> Option { 70 | let updated_value = *val + 1; 71 | Some(updated_value) 72 | } 73 | 74 | /// Simply copy the value at the jumpsite 75 | fn update_jumpsite( 76 | &self, 77 | value_after_jump: &u64, 78 | _jump: &Term, 79 | _untaken_conditional: Option<&Term>, 80 | _jumpsite: &Term, 81 | ) -> Option { 82 | Some(*value_after_jump) 83 | } 84 | 85 | /// Merge two values at the callsite if both exist 86 | /// If there is only one, simply copy it 87 | fn update_callsite( 88 | &self, 89 | target_value: Option<&u64>, 90 | return_value: Option<&u64>, 91 | _caller_sub: &Term, 92 | _call: &Term, 93 | _return_: &Term, 94 | ) -> Option { 95 | match (target_value, return_value) { 96 | (Some(call), Some(fall)) => Some(self.merge(call, fall)), 97 | (Some(call), _) => Some(*call), 98 | (_, Some(fall)) => Some(*fall), 99 | _ => panic!("No values to merge at callsite!"), 100 | } 101 | } 102 | 103 | /// Simply copy the value 104 | fn split_call_stub(&self, combined_value: &u64) -> Option { 105 | Some(*combined_value) 106 | } 107 | 108 | /// Simply copy the value 109 | fn split_return_stub( 110 | &self, 111 | combined_value: &u64, 112 | _returned_from_sub: &Term, 113 | ) -> Option { 114 | Some(*combined_value) 115 | } 116 | 117 | /// Simply copy the value 118 | fn update_call_stub(&self, value_after_call: &u64, _call: &Term) -> Option { 119 | Some(*value_after_call) 120 | } 121 | 122 | /// Simply copy the value 123 | fn specialize_conditional( 124 | &self, 125 | value_after_jump: &u64, 126 | _condition: &Expression, 127 | _is_true: bool, 128 | ) -> Option { 129 | Some(*value_after_jump) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/function_signature/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::variable; 3 | 4 | /// Mock the abstract location of a global parameter. 5 | fn mock_global_x64(address: u64) -> AbstractLocation { 6 | AbstractLocation::GlobalAddress { 7 | address: address, 8 | size: ByteSize::new(8), 9 | } 10 | } 11 | 12 | impl FunctionSignature { 13 | /// Create a mock x64 function signature with 2 parameters, one of which is accessed mutably, 14 | /// one mutably accessed global variable at address 0x2000 15 | /// and one immutably accessed global variable at address 0x3000. 16 | pub fn mock_x64() -> FunctionSignature { 17 | let mut write_access_pattern = AccessPattern::new(); 18 | write_access_pattern.set_unknown_access_flags(); 19 | let parameters = BTreeMap::from_iter([ 20 | ( 21 | AbstractLocation::from_var(&variable!("RDI:8")).unwrap(), 22 | AccessPattern::new(), 23 | ), 24 | ( 25 | AbstractLocation::from_var(&variable!("RSI:8")).unwrap(), 26 | write_access_pattern, 27 | ), 28 | ]); 29 | FunctionSignature { 30 | parameters, 31 | global_parameters: BTreeMap::from([ 32 | (mock_global_x64(0x2000), AccessPattern::new_unknown_access()), 33 | ( 34 | mock_global_x64(0x3000), 35 | AccessPattern::new().with_dereference_flag(), 36 | ), 37 | ]), 38 | } 39 | } 40 | } 41 | 42 | fn mock_stack_arg(offset: i64, size: u64) -> AbstractLocation { 43 | AbstractLocation::Pointer( 44 | variable!("RSP:8"), 45 | AbstractMemoryLocation::Location { 46 | offset: offset, 47 | size: ByteSize::new(size), 48 | }, 49 | ) 50 | } 51 | 52 | #[test] 53 | fn test_two_parameter_overlapping_merging() { 54 | let proj = Project::mock_x64(); 55 | let mut func_sig = FunctionSignature::mock_x64(); 56 | let stack_parm_1 = mock_stack_arg(0x1000, 8); 57 | let stack_parm_2 = mock_stack_arg(0x1004, 8); 58 | 59 | func_sig 60 | .parameters 61 | .insert(stack_parm_1, AccessPattern::new()); 62 | func_sig 63 | .parameters 64 | .insert(stack_parm_2, AccessPattern::new()); 65 | 66 | assert_eq!( 67 | func_sig.sanitize(&proj), 68 | vec!["Unexpected stack parameter size".to_string()], 69 | ); 70 | let mut expected_function_sig = FunctionSignature::mock_x64(); 71 | let expected_stack_arg = mock_stack_arg(0x1000, 12); 72 | 73 | expected_function_sig 74 | .parameters 75 | .insert(expected_stack_arg, AccessPattern::new()); 76 | assert_eq!(func_sig, expected_function_sig); 77 | } 78 | 79 | #[test] 80 | fn test_merging_multiple_parameters() { 81 | let proj = Project::mock_x64(); 82 | let mut func_sig = FunctionSignature::mock_x64(); 83 | let stack_parm_1 = mock_stack_arg(0x8, 8); 84 | let stack_parm_2 = mock_stack_arg(0x8, 1); 85 | let stack_parm_3 = mock_stack_arg(0xf, 1); 86 | let stack_parm_4 = mock_stack_arg(0x10, 8); 87 | 88 | func_sig.parameters.extend([ 89 | (stack_parm_1.clone(), AccessPattern::new()), 90 | (stack_parm_2, AccessPattern::new()), 91 | (stack_parm_3, AccessPattern::new()), 92 | (stack_parm_4.clone(), AccessPattern::new()), 93 | ]); 94 | let logs = func_sig.sanitize(&proj); 95 | assert_eq!(logs, Vec::::new()); 96 | 97 | let mut expected_function_sig = FunctionSignature::mock_x64(); 98 | expected_function_sig.parameters.extend([ 99 | (stack_parm_1, AccessPattern::new()), 100 | (stack_parm_4, AccessPattern::new()), 101 | ]); 102 | assert_eq!(func_sig, expected_function_sig); 103 | } 104 | #[test] 105 | fn test_log_messages() { 106 | let proj = Project::mock_x64(); 107 | let mut func_sig = FunctionSignature::mock_x64(); 108 | let stack_parm_1 = mock_stack_arg(0x1001, 8); 109 | let stack_parm_2 = mock_stack_arg(0x1007, 4); 110 | 111 | func_sig.parameters.extend([ 112 | (stack_parm_1.clone(), AccessPattern::new()), 113 | (stack_parm_2, AccessPattern::new()), 114 | ]); 115 | 116 | let logs = func_sig.sanitize(&proj); 117 | assert_eq!( 118 | vec![ 119 | "Unexpected stack parameter size".to_string(), 120 | "Unexpected stack parameter alignment".to_string() 121 | ], 122 | logs 123 | ); 124 | } 125 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/graph/algo.rs: -------------------------------------------------------------------------------- 1 | //! Some simple graph algorithms. 2 | 3 | use std::collections::hash_map::{Entry, HashMap}; 4 | 5 | use petgraph::prelude::*; 6 | use petgraph::unionfind::UnionFind; 7 | use petgraph::visit::{IntoEdgeReferences, NodeCompactIndexable}; 8 | 9 | /// Returns the components of the graph `g`. 10 | pub fn components(g: &G) -> Vec> 11 | where 12 | G: IntoEdgeReferences + NodeCompactIndexable, 13 | { 14 | let mut vertex_sets = UnionFind::new(g.node_bound()); 15 | for e in g.edge_references() { 16 | let (h, t) = (e.target(), e.source()); 17 | vertex_sets.union(g.to_index(h), g.to_index(t)); 18 | } 19 | let representatives = vertex_sets.into_labeling(); 20 | let mut sets: HashMap> = HashMap::new(); 21 | for (index, repr) in representatives.iter().enumerate() { 22 | match sets.entry(*repr) { 23 | Entry::Vacant(e) => { 24 | e.insert(vec![g.from_index(index)]); 25 | } 26 | Entry::Occupied(e) => e.into_mut().push(g.from_index(index)), 27 | } 28 | } 29 | 30 | sets.into_values().collect() 31 | } 32 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/graph/intraprocedural_cfg/natural_loops.rs: -------------------------------------------------------------------------------- 1 | //! Natural loops. 2 | use crate::analysis::graph::intraprocedural_cfg::IntraproceduralCfg; 3 | use crate::analysis::graph::{Edge, Graph}; 4 | use crate::intermediate_representation::Tid; 5 | 6 | use std::collections::BTreeSet; 7 | use std::fmt; 8 | 9 | use petgraph::visit::EdgeRef; 10 | 11 | /// A natural loop in the CFG. 12 | pub struct NaturalLoop<'a> { 13 | /// Block that controls the loop. 14 | head: &'a Tid, 15 | /// Blocks contained in the loop. 16 | blocks: BTreeSet<&'a Tid>, 17 | } 18 | 19 | impl fmt::Display for NaturalLoop<'_> { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | write!(f, "head:{}, blocks:", self.head)?; 22 | for b in self.blocks() { 23 | write!(f, "{}, ", b)? 24 | } 25 | 26 | Ok(()) 27 | } 28 | } 29 | 30 | impl<'a> NaturalLoop<'a> { 31 | /// Returns the block that controls the loop. 32 | pub fn head(&self) -> &'a Tid { 33 | self.head 34 | } 35 | 36 | /// Returns the blocks inside the loop. 37 | pub fn blocks<'b>(&'b self) -> impl Iterator + 'b { 38 | self.blocks.iter().cloned() 39 | } 40 | } 41 | 42 | /// An edge from a block to one of its dominators. 43 | /// 44 | /// Such an edge defines a natural loop. 45 | struct BackEdge<'a> { 46 | tail: &'a Tid, 47 | /// Block that controls the loop. 48 | head: &'a Tid, 49 | } 50 | 51 | impl<'a> BackEdge<'a> { 52 | /// Computes the natural loop of this back edge. 53 | fn natural_loop(&self, cfg: &IntraproceduralCfg<'a>, rev_cfg: &Graph<'_>) -> NaturalLoop<'a> { 54 | let mut visited = BTreeSet::new(); 55 | visited.insert(cfg.blk_tid_to_idx(self.head).unwrap().0); 56 | 57 | let mut stack = vec![cfg.blk_tid_to_idx(self.tail).unwrap().1]; 58 | while let Some(idx) = stack.pop() { 59 | visited.insert(idx); 60 | for idx in rev_cfg.neighbors(idx) { 61 | if !visited.contains(&idx) { 62 | stack.push(idx); 63 | } 64 | } 65 | } 66 | 67 | NaturalLoop { 68 | head: self.head, 69 | blocks: visited 70 | .into_iter() 71 | // Also removes artificial nodes. 72 | .filter_map(|idx| cfg.idx_to_blk_tid(idx)) 73 | .collect(), 74 | } 75 | } 76 | } 77 | 78 | /// Returns the natural loops of this CFG. 79 | /// 80 | /// Panics if dominator relation was not computed. 81 | pub fn compute_natural_loops<'a>(cfg: &IntraproceduralCfg<'a>) -> Vec> { 82 | let doms = cfg.get_dominators().unwrap(); 83 | let back_edges: Vec> = cfg 84 | .graph() 85 | .edge_references() 86 | .filter_map(|e| { 87 | let tail = cfg.idx_to_blk_tid(e.source()); 88 | let head = cfg.idx_to_blk_tid(e.target()); 89 | 90 | // Due to the way we split blocks into two nodes each `Block` edge 91 | // would be a back edge. 92 | if matches!(e.weight(), Edge::Block) { 93 | return None; 94 | } 95 | 96 | if let (Some(tail), Some(head)) = (tail, head) { 97 | if doms 98 | .get(tail) 99 | .is_some_and(|tail_doms| tail_doms.contains(head)) 100 | { 101 | Some(BackEdge { head, tail }) 102 | } else { 103 | None 104 | } 105 | } else { 106 | None 107 | } 108 | }) 109 | .collect(); 110 | 111 | let mut rev_cfg = cfg.graph().clone(); 112 | rev_cfg.reverse(); 113 | 114 | back_edges 115 | .into_iter() 116 | .map(|be| be.natural_loop(cfg, &rev_cfg)) 117 | .collect() 118 | } 119 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/graph/intraprocedural_cfg/properties.rs: -------------------------------------------------------------------------------- 1 | //! Some simple CFG properties. 2 | 3 | use crate::analysis::graph::intraprocedural_cfg::IntraproceduralCfg; 4 | 5 | use petgraph::algo::connected_components; 6 | 7 | impl<'a> IntraproceduralCfg<'a> { 8 | /// Returns the cyclomatic complexity of the given CFG. 9 | pub fn cyclomatic_complexity(&self) -> u32 { 10 | let p = connected_components(self.graph()) as i64; 11 | let e = self.graph().edge_count() as i64; 12 | let n = self.graph().node_count() as i64; 13 | 14 | let cc = e - n + 2 * p; 15 | 16 | if cc >= 1 && cc < u32::MAX as i64 { 17 | cc as u32 18 | } else { 19 | panic!( 20 | "CFG with invalid cyclomatic complexity: cc={}, e={}, n={}, p={}", 21 | cc, e, n, p 22 | ) 23 | } 24 | } 25 | 26 | /// Returns a number indicating the likeliness that this CFG was obfuscated 27 | /// by control flow flattening. 28 | /// 29 | /// This works by first finding the natural loop whose header dominates the 30 | /// most other blocks. The flattening score is then defined as the fraction 31 | /// of blocks dominated by this header times the maximum score. 32 | /// 33 | /// See this [blog post] for more information. The score is between 0 and 34 | /// 1_000_000 inclusive. 35 | /// 36 | /// Expects that loops and dominators are computed. 37 | /// 38 | /// [blog post]: https://synthesis.to/2021/03/03/flattening_detection.html 39 | pub fn flattening_score(&self) -> u32 { 40 | const MAX_SCORE: usize = 1_000_000; 41 | 42 | let doms = self.get_dominators().expect("Compute dominators first."); 43 | // Compute the maximum number of blocks dominated by a block that 44 | // controls a natural loop. 45 | let tmp = self 46 | .get_natural_loops() 47 | .expect("Compute loops first.") 48 | .iter() 49 | .map(|l| { 50 | let head = l.head(); 51 | // Get number of nodes dominated by this loop head. 52 | doms.get(head).unwrap().len() 53 | }) 54 | .max() 55 | // Score is 0 if there are no loops. 56 | .unwrap_or(0); 57 | 58 | ((tmp * MAX_SCORE) / self.num_blocks() as usize) as u32 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/interprocedural_fixpoint_generic.rs: -------------------------------------------------------------------------------- 1 | //! Types and functions shared between the implementations 2 | //! of forward and backward interprocedural fixpoint computations. 3 | 4 | use crate::prelude::*; 5 | 6 | /// NodeValue that can either be a single abstract value or a 7 | /// composition of the abstract value computed following an interprocedural call in the graph 8 | /// and of the abstract value before or after the call (depending on the direction of the fixpoint analysis). 9 | /// The CallFlowCombinator then allows for a merge of the values computed over both paths. 10 | /// 11 | /// The call_stub value will either be transferred from the callsite to the return site 12 | /// in a forward analysis or the other way around in a backward analysis. 13 | /// 14 | /// The interprocedural_flow value will either be transferred from the end of the called subroutine 15 | /// to the return site in case of a forward analysis or from the beginning of the called subroutine 16 | /// to the callsite in a backward analysis. 17 | #[derive(PartialEq, Eq, Serialize, Deserialize, Clone, Debug)] 18 | pub enum NodeValue { 19 | /// A single abstract value 20 | Value(T), 21 | /// The value saved at artificial combinator nodes. 22 | CallFlowCombinator { 23 | /// The value flowing through the intraprocedural edge of the corresponding call. 24 | call_stub: Option, 25 | /// The value flowing through the interprocedural edge of the corresponding call, 26 | /// i.e. either between callsite and start of the called function 27 | /// or between end of the called function and the return-to site of the call. 28 | interprocedural_flow: Option, 29 | }, 30 | } 31 | 32 | impl NodeValue { 33 | /// Unwraps the contained value for non-combinator nodes. 34 | /// Panics if given a combinator value of an artificial node. 35 | pub fn unwrap_value(&self) -> &T { 36 | match self { 37 | NodeValue::Value(value) => value, 38 | _ => panic!("Unexpected node value type"), 39 | } 40 | } 41 | } 42 | 43 | /// Helper function to merge to values wrapped in `Option<..>`. 44 | /// Merges `(Some(x), None)` to `Some(x)`. 45 | pub fn merge_option(opt1: &Option, opt2: &Option, merge: F) -> Option 46 | where 47 | F: Fn(&T, &T) -> T, 48 | { 49 | match (opt1, opt2) { 50 | (Some(value1), Some(value2)) => Some(merge(value1, value2)), 51 | (Some(value), None) | (None, Some(value)) => Some(value.clone()), 52 | (None, None) => None, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/mod.rs: -------------------------------------------------------------------------------- 1 | //! Modules necessary for graph-based and fixpoint-based analyses, 2 | //! as well as analyses depending on these modules. 3 | 4 | pub mod backward_interprocedural_fixpoint; 5 | pub mod callgraph; 6 | pub mod fixpoint; 7 | pub mod forward_interprocedural_fixpoint; 8 | pub mod function_signature; 9 | pub mod graph; 10 | pub mod interprocedural_fixpoint_generic; 11 | pub mod pointer_inference; 12 | pub mod string_abstraction; 13 | pub mod taint; 14 | pub mod vsa_results; 15 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/pointer_inference/object/id_manipulation.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use std::collections::BTreeMap; 3 | 4 | impl AbstractObject { 5 | /// Get all abstract IDs that the object may contain pointers to. 6 | /// This yields an overapproximation of possible pointer targets. 7 | pub fn get_referenced_ids_overapproximation(&self) -> &BTreeSet { 8 | &self.inner.pointer_targets 9 | } 10 | 11 | /// Get all abstract IDs for which the object contains pointers to. 12 | /// This yields an underapproximation of pointer targets, 13 | /// since the object may contain pointers that could not be tracked by the analysis. 14 | pub fn get_referenced_ids_underapproximation(&self) -> BTreeSet { 15 | let mut referenced_ids = BTreeSet::new(); 16 | for data in self.inner.memory.values() { 17 | referenced_ids.extend(data.referenced_ids().cloned()) 18 | } 19 | referenced_ids 20 | } 21 | 22 | /// Remove the provided IDs from the target lists of all pointers in the memory object. 23 | /// Also remove them from the pointer_targets list. 24 | /// 25 | /// If this operation would produce an empty value, it replaces it with a `Top` value instead. 26 | pub fn remove_ids(&mut self, ids_to_remove: &BTreeSet) { 27 | let inner = Arc::make_mut(&mut self.inner); 28 | inner.pointer_targets = inner 29 | .pointer_targets 30 | .difference(ids_to_remove) 31 | .cloned() 32 | .collect(); 33 | for value in inner.memory.values_mut() { 34 | value.remove_ids(ids_to_remove); 35 | if value.is_empty() { 36 | *value = value.top(); 37 | } 38 | } 39 | inner.memory.clear_top_values(); // In case the previous operation left *Top* values in the memory struct. 40 | } 41 | 42 | /// Replace all abstract IDs in `self` with the values given by the replacement map. 43 | /// IDs not contained as keys in the replacement map are replaced by `Top` values. 44 | pub fn replace_ids(&mut self, replacement_map: &BTreeMap) { 45 | let inner = Arc::make_mut(&mut self.inner); 46 | for elem in inner.memory.values_mut() { 47 | elem.replace_all_ids(replacement_map); 48 | } 49 | inner.memory.clear_top_values(); 50 | let mut new_pointer_targets = BTreeSet::new(); 51 | for target in &inner.pointer_targets { 52 | if let Some(replacement_value) = replacement_map.get(target) { 53 | for new_target in replacement_value.referenced_ids() { 54 | new_pointer_targets.insert(new_target.clone()); 55 | } 56 | } 57 | } 58 | inner.pointer_targets = new_pointer_targets; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/pointer_inference/object/value_access.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl AbstractObject { 4 | /// Read the value at the given offset of the given size inside the memory 5 | /// region. 6 | pub fn get_value(&self, offset: Bitvector, bytesize: ByteSize) -> Data { 7 | self.inner.memory.get(offset, bytesize) 8 | } 9 | 10 | /// Write a value at the given offset to the memory region. 11 | /// 12 | /// If the abstract object is not unique (i.e. may represent more than one actual object), 13 | /// merge the old value at the given offset with the new value. 14 | pub fn set_value(&mut self, value: Data, offset: &ValueDomain) -> Result<(), Error> { 15 | let inner = Arc::make_mut(&mut self.inner); 16 | inner 17 | .pointer_targets 18 | .extend(value.referenced_ids().cloned()); 19 | if let Ok(concrete_offset) = offset.try_to_bitvec() { 20 | if inner.is_unique { 21 | inner.memory.add(value, concrete_offset); 22 | } else { 23 | let merged_value = inner 24 | .memory 25 | .get(concrete_offset.clone(), value.bytesize()) 26 | .merge(&value); 27 | inner.memory.add(merged_value, concrete_offset); 28 | }; 29 | } else if let Ok((start, end)) = offset.try_to_offset_interval() { 30 | inner 31 | .memory 32 | .mark_interval_values_as_top(start, end, value.bytesize()); 33 | } else { 34 | inner.memory.mark_all_values_as_top(); 35 | } 36 | Ok(()) 37 | } 38 | 39 | /// Merge `value` at position `offset` with the value currently saved at that position. 40 | pub fn merge_value(&mut self, value: Data, offset: &ValueDomain) { 41 | let inner = Arc::make_mut(&mut self.inner); 42 | inner 43 | .pointer_targets 44 | .extend(value.referenced_ids().cloned()); 45 | if let Ok(concrete_offset) = offset.try_to_bitvec() { 46 | let merged_value = inner 47 | .memory 48 | .get(concrete_offset.clone(), value.bytesize()) 49 | .merge(&value); 50 | inner.memory.add(merged_value, concrete_offset); 51 | } else if let Ok((start, end)) = offset.try_to_offset_interval() { 52 | inner 53 | .memory 54 | .mark_interval_values_as_top(start, end, value.bytesize()); 55 | } else { 56 | inner.memory.mark_all_values_as_top(); 57 | } 58 | } 59 | 60 | /// Marks all memory as `Top` and adds the `additional_targets` to the pointer targets. 61 | /// Represents the effect of unknown write instructions to the object 62 | /// which may include writing pointers to targets from the `additional_targets` set to the object. 63 | pub fn assume_arbitrary_writes(&mut self, additional_targets: &BTreeSet) { 64 | let inner = Arc::make_mut(&mut self.inner); 65 | inner.memory.mark_all_values_as_top(); 66 | inner 67 | .pointer_targets 68 | .extend(additional_targets.iter().cloned()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/pointer_inference/object_list/id_manipulation.rs: -------------------------------------------------------------------------------- 1 | //! Methods of [`AbstractObjectList`] related to manipulating abstract IDs. 2 | 3 | use super::*; 4 | 5 | impl AbstractObjectList { 6 | /// Return all IDs that may be referenced by the memory object pointed to by the given ID. 7 | /// The returned set is an overapproximation of the actual referenced IDs. 8 | pub fn get_referenced_ids_overapproximation( 9 | &self, 10 | id: &AbstractIdentifier, 11 | ) -> BTreeSet { 12 | if let Some(object) = self.objects.get(id) { 13 | object.get_referenced_ids_overapproximation().clone() 14 | } else { 15 | BTreeSet::new() 16 | } 17 | } 18 | 19 | /// Return all IDs that get referenced by the memory object pointed to by the given ID. 20 | /// The returned set is an underapproximation of the actual referenced IDs, 21 | /// since only still tracked pointers inside the memory object are used to compute it. 22 | pub fn get_referenced_ids_underapproximation( 23 | &self, 24 | id: &AbstractIdentifier, 25 | ) -> BTreeSet { 26 | if let Some(object) = self.objects.get(id) { 27 | object.get_referenced_ids_underapproximation() 28 | } else { 29 | BTreeSet::new() 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/pointer_inference/object_list/list_manipulation.rs: -------------------------------------------------------------------------------- 1 | //! Methods of [`AbstractObjectList`] that add or remove objects from the object list 2 | //! or provide information about the set of objects in the object list. 3 | 4 | use super::*; 5 | 6 | impl AbstractObjectList { 7 | /// Get a reference to the object corresponding to the given ID. 8 | pub fn get_object(&self, id: &AbstractIdentifier) -> Option<&AbstractObject> { 9 | self.objects.get(id) 10 | } 11 | 12 | /// Add a new abstract object to the object list 13 | /// 14 | /// If an object with the same ID already exists, 15 | /// the object is marked as non-unique and merged with the newly created object. 16 | pub fn add_abstract_object( 17 | &mut self, 18 | object_id: AbstractIdentifier, 19 | generic_address_bytesize: ByteSize, 20 | type_: Option, 21 | ) { 22 | let new_object = AbstractObject::new(type_, generic_address_bytesize); 23 | if let Some(object) = self.objects.get_mut(&object_id) { 24 | // If the identifier already exists, we have to assume that more than one object may be referenced by this identifier. 25 | object.mark_as_not_unique(); 26 | *object = object.merge(&new_object); 27 | } else { 28 | self.objects.insert(object_id, new_object); 29 | } 30 | } 31 | 32 | /// Insert an existing object to the object list. 33 | /// If the object identifier already exists, the object is marked as non-unique 34 | /// and merged with the corresponding object already present in the object list. 35 | pub fn insert(&mut self, id: AbstractIdentifier, object: AbstractObject) { 36 | if let Some(existing_object) = self.objects.get_mut(&id) { 37 | existing_object.mark_as_not_unique(); 38 | *existing_object = existing_object.merge(&object); 39 | } else { 40 | self.objects.insert(id, object); 41 | } 42 | } 43 | 44 | /// For abstract IDs not contained in the provided set of IDs 45 | /// remove the corresponding abstract objects. 46 | /// 47 | /// This function does not remove any pointer targets in the contained abstract objects. 48 | pub fn remove_unused_objects(&mut self, ids_to_keep: &BTreeSet) { 49 | let all_ids: BTreeSet = self.objects.keys().cloned().collect(); 50 | let ids_to_remove = all_ids.difference(ids_to_keep); 51 | for id in ids_to_remove { 52 | self.objects.remove(id); 53 | } 54 | } 55 | 56 | /// Get all object IDs. 57 | pub fn get_all_object_ids(&self) -> BTreeSet { 58 | self.objects.keys().cloned().collect() 59 | } 60 | 61 | /// Get an iterator over the contained abstract objects in `self`. 62 | pub fn iter(&self) -> std::collections::btree_map::Iter { 63 | self.objects.iter() 64 | } 65 | 66 | /// Get an iterator of mutable references over the abstract objects in `self`. 67 | pub fn iter_objects_mut(&mut self) -> impl Iterator { 68 | self.objects.values_mut() 69 | } 70 | 71 | /// Get the number of objects that are currently tracked. 72 | #[cfg(test)] 73 | pub fn get_num_objects(&self) -> usize { 74 | self.objects.len() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/pointer_inference/object_list/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::intermediate_representation::*; 2 | use crate::variable; 3 | 4 | use super::super::ValueDomain; 5 | use super::*; 6 | 7 | fn bv(value: i64) -> ValueDomain { 8 | ValueDomain::from(Bitvector::from_i64(value)) 9 | } 10 | 11 | fn new_id(name: &str) -> AbstractIdentifier { 12 | AbstractIdentifier::new( 13 | Tid::new("time0"), 14 | AbstractLocation::Register(variable!(format!("{name}:8"))), 15 | ) 16 | } 17 | 18 | fn new_global_id() -> AbstractIdentifier { 19 | AbstractIdentifier::new( 20 | Tid::new("time0"), 21 | AbstractLocation::GlobalAddress { 22 | address: 0, 23 | size: ByteSize::new(8), 24 | }, 25 | ) 26 | } 27 | 28 | #[test] 29 | fn abstract_object_list() { 30 | // A new object list has 2 memory objects. 31 | let mut obj_list = AbstractObjectList::from_stack_id(new_id("RSP".into()), ByteSize::new(8)); 32 | assert_eq!(obj_list.objects.len(), 2); 33 | // Test writing to and reading from the stack object 34 | let stack_pointer = DataDomain::from_target(new_id("RSP".into()), bv(8)); 35 | obj_list 36 | .set_value(stack_pointer.clone(), bv(42).into()) 37 | .unwrap(); 38 | assert_eq!( 39 | obj_list.get_value(&stack_pointer, ByteSize::new(8)), 40 | bv(42).into() 41 | ); 42 | // Test writing to and reading from the global memory object 43 | let global_pointer = DataDomain::from_target(new_global_id(), bv(1000)); 44 | obj_list 45 | .set_value(global_pointer.clone(), bv(13).into()) 46 | .unwrap(); 47 | assert_eq!( 48 | obj_list.get_value(&global_pointer, ByteSize::new(8)), 49 | bv(13).into() 50 | ); 51 | 52 | let mut other_obj_list = 53 | AbstractObjectList::from_stack_id(new_id("RSP".into()), ByteSize::new(8)); 54 | let second_pointer = DataDomain::from_target(new_id("RSP".into()), bv(-8)); 55 | other_obj_list 56 | .set_value(stack_pointer.clone(), bv(42).into()) 57 | .unwrap(); 58 | other_obj_list 59 | .set_value(second_pointer.clone(), bv(35).into()) 60 | .unwrap(); 61 | assert_eq!( 62 | other_obj_list.get_value(&second_pointer, ByteSize::new(8)), 63 | bv(35).into() 64 | ); 65 | 66 | other_obj_list.add_abstract_object( 67 | new_id("RAX".into()), 68 | ByteSize::new(8), 69 | Some(ObjectType::Heap), 70 | ); 71 | let heap_pointer = DataDomain::from_target(new_id("RAX".into()), bv(8)); 72 | other_obj_list 73 | .set_value(heap_pointer.clone(), bv(3).into()) 74 | .unwrap(); 75 | 76 | let mut merged = obj_list.merge(&other_obj_list); 77 | assert_eq!( 78 | merged.get_value(&stack_pointer, ByteSize::new(8)), 79 | bv(42).into() 80 | ); 81 | 82 | assert!(merged 83 | .get_value(&second_pointer, ByteSize::new(8)) 84 | .contains_top()); 85 | assert_eq!( 86 | merged.get_value(&heap_pointer, ByteSize::new(8)), 87 | bv(3).into() 88 | ); 89 | assert_eq!(merged.objects.len(), 3); 90 | 91 | merged 92 | .set_value(stack_pointer.merge(&heap_pointer), bv(3).into()) 93 | .unwrap(); 94 | assert_eq!( 95 | merged.get_value(&stack_pointer, ByteSize::new(8)), 96 | IntervalDomain::mock(3, 42).with_stride(39).into() 97 | ); 98 | assert_eq!( 99 | merged.get_value(&heap_pointer, ByteSize::new(8)), 100 | bv(3).into() 101 | ); 102 | assert_eq!(merged.objects.len(), 3); 103 | 104 | other_obj_list 105 | .set_value(stack_pointer.clone(), heap_pointer.clone()) 106 | .unwrap(); 107 | assert_eq!( 108 | other_obj_list 109 | .get_referenced_ids_overapproximation(&new_id("RSP".into())) 110 | .len(), 111 | 1 112 | ); 113 | assert_eq!( 114 | *other_obj_list 115 | .get_referenced_ids_overapproximation(&new_id("RSP".into())) 116 | .iter() 117 | .next() 118 | .unwrap(), 119 | new_id("RAX".into()) 120 | ); 121 | 122 | let mut ids_to_keep = BTreeSet::new(); 123 | ids_to_keep.insert(new_id("RAX".into())); 124 | other_obj_list.remove_unused_objects(&ids_to_keep); 125 | assert_eq!(other_obj_list.objects.len(), 1); 126 | assert_eq!( 127 | other_obj_list.objects.iter().next().unwrap().0, 128 | &new_id("RAX".into()) 129 | ); 130 | } 131 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/pointer_inference/vsa_result_impl.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::analysis::graph::NodeIndex; 3 | use crate::{abstract_domain::AbstractLocation, analysis::vsa_results::VsaResult}; 4 | 5 | /// Implementation of the [`VsaResult`] trait for providing other analyses with an easy-to-use interface 6 | /// to use the value set and points-to analysis results of the pointer inference. 7 | impl<'a> VsaResult for PointerInference<'a> { 8 | type ValueDomain = Data; 9 | 10 | /// Return the value of the address at the given read or store instruction. 11 | fn eval_address_at_def(&self, def_tid: &Tid) -> Option { 12 | self.addresses_at_defs.get(def_tid).cloned() 13 | } 14 | 15 | /// Return the assigned value for store or assignment instructions or the value read for load instructions. 16 | fn eval_value_at_def(&self, def_tid: &Tid) -> Option { 17 | self.values_at_defs.get(def_tid).cloned() 18 | } 19 | 20 | /// Evaluate the value of the given expression at the given jump instruction. 21 | fn eval_at_jmp(&self, jmp_tid: &Tid, expression: &Expression) -> Option { 22 | let state = self.states_at_tids.get(jmp_tid)?; 23 | Some(state.eval(expression)) 24 | } 25 | 26 | /// Evaluate the value of the given parameter at the given jump instruction. 27 | fn eval_parameter_arg_at_call(&self, jmp_tid: &Tid, parameter: &Arg) -> Option { 28 | let state = self.states_at_tids.get(jmp_tid)?; 29 | let context = self.computation.get_context().get_context(); 30 | state 31 | .eval_parameter_arg(parameter, &context.project.runtime_memory_image) 32 | .ok() 33 | } 34 | 35 | /// Evaluate the value of the given parameter at the given jump instruction. 36 | fn eval_parameter_location_at_call( 37 | &self, 38 | jmp_tid: &Tid, 39 | parameter: &AbstractLocation, 40 | ) -> Option { 41 | let state = self.states_at_tids.get(jmp_tid)?; 42 | let context = self.computation.get_context().get_context(); 43 | Some(state.eval_abstract_location(parameter, &context.project.runtime_memory_image)) 44 | } 45 | 46 | fn eval_at_node(&self, node: NodeIndex, expression: &Expression) -> Option { 47 | if let NodeValue::Value(state) = self.get_node_value(node)? { 48 | Some(state.eval(expression)) 49 | } else { 50 | None 51 | } 52 | } 53 | 54 | fn get_call_renaming_map( 55 | &self, 56 | call: &Tid, 57 | ) -> Option<&BTreeMap> { 58 | self.id_renaming_maps_at_calls.get(call) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/string_abstraction/context/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From> Context<'a, T> { 4 | pub fn mock( 5 | project: &'a Project, 6 | string_symbols: HashMap, 7 | format_string_index: HashMap, 8 | pointer_inference_results: &'a PointerInferenceComputation<'a>, 9 | ) -> Self { 10 | let mut extern_symbol_map = HashMap::new(); 11 | for (tid, symbol) in project.program.term.extern_symbols.iter() { 12 | extern_symbol_map.insert(tid.clone(), symbol); 13 | } 14 | 15 | let mut block_start_node_map = HashMap::new(); 16 | let mut block_first_def_set = HashSet::new(); 17 | let mut jmp_to_blk_end_node_map = HashMap::new(); 18 | for (node_id, node) in pointer_inference_results.get_graph().node_references() { 19 | match node { 20 | Node::BlkStart(block, sub) => { 21 | if let Some(def) = block.term.defs.get(0) { 22 | block_start_node_map.insert((def.tid.clone(), sub.tid.clone()), node_id); 23 | block_first_def_set.insert((def.tid.clone(), sub.tid.clone())); 24 | } 25 | } 26 | Node::BlkEnd(block, sub) => { 27 | for jmp in block.term.jmps.iter() { 28 | jmp_to_blk_end_node_map.insert((jmp.tid.clone(), sub.tid.clone()), node_id); 29 | } 30 | } 31 | _ => (), 32 | } 33 | } 34 | 35 | Context { 36 | project, 37 | pointer_inference_results, 38 | string_symbol_map: string_symbols, 39 | extern_symbol_map, 40 | format_string_index_map: format_string_index, 41 | block_start_node_map, 42 | block_first_def_set, 43 | jmp_to_blk_end_node_map: jmp_to_blk_end_node_map, 44 | _phantom_string_domain: PhantomData, 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/taint/state/register_taint.rs: -------------------------------------------------------------------------------- 1 | //! Tracking of taint in registers. 2 | 3 | use crate::abstract_domain::{DomainMap, UnionMergeStrategy}; 4 | use crate::intermediate_representation::Variable; 5 | 6 | use super::Taint; 7 | 8 | /// Represents our knowledge about taint in registers at a particular point in 9 | /// the program. 10 | pub type RegisterTaint = DomainMap; 11 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/analysis/vsa_results/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module provides the [`VsaResult`] trait 2 | //! which defines an interface for the results of analyses similar to a value set analysis. 3 | 4 | use crate::abstract_domain::{AbstractIdentifier, AbstractLocation}; 5 | use crate::analysis::graph::NodeIndex; 6 | use crate::intermediate_representation::{Arg, Expression}; 7 | use crate::prelude::*; 8 | 9 | use std::collections::BTreeMap; 10 | 11 | /// Trait for types that provide access to the result of a value set analysis. 12 | /// 13 | /// The generic type parameter can be used to implement this trait multiple 14 | /// times, i.e., the same type can provide access to VSA results with 15 | /// different value domains. 16 | // NOTE: We can not implement `AsRef` on the type instead since `impl Trait` is 17 | // only allowed in function parameters and return types, not generic type 18 | // parameters or trait bounds. 19 | pub trait HasVsaResult { 20 | /// Converts a reference to `Self` into a reference to a type that implements 21 | /// [`VsaResult`] with [`ValueDomain`] `T`. 22 | /// 23 | /// [`ValueDomain`]: VsaResult::ValueDomain 24 | fn vsa_result(&self) -> &impl VsaResult; 25 | } 26 | 27 | /// A trait providing an interface for accessing the results of a value set analysis. 28 | /// Note that the returned values may be any type of information associated with values at certain program points, 29 | /// i.e. the trait can also be used for other analyses than just value set analyses. 30 | /// 31 | /// Every returned value is wrapped into an `Option<..>`. 32 | /// This should mainly be used to indicate that the analysis did not compute a value at a certain point, 33 | /// e.g. because the code point was deemed to be dead code. 34 | /// If the analysis wants to indicate that no specific information is known about a certain value 35 | /// then this should be encoded in the `ValueDomain` itself instead of returning `None`. 36 | pub trait VsaResult { 37 | /// The type of the returned values. 38 | /// Usually this should be an [`AbstractDomain`](crate::abstract_domain::AbstractDomain), 39 | /// although this is not strictly required. 40 | type ValueDomain; 41 | 42 | /// Return the value stored for write instructions, the value read for read instructions or the value assigned for assignments. 43 | fn eval_value_at_def(&self, def_tid: &Tid) -> Option; 44 | 45 | /// Return the value of the address where something is read or written for read or store instructions. 46 | fn eval_address_at_def(&self, def_tid: &Tid) -> Option; 47 | 48 | /// Return the value of a parameter at the given jump instruction. 49 | fn eval_parameter_arg_at_call(&self, jmp_tid: &Tid, param: &Arg) -> Option; 50 | 51 | /// Return the value of a parameter at the given jump instruction. 52 | fn eval_parameter_location_at_call( 53 | &self, 54 | jmp_tid: &Tid, 55 | param: &AbstractLocation, 56 | ) -> Option; 57 | 58 | /// Evaluate the value of the given expression at the given jump instruction. 59 | fn eval_at_jmp(&self, jmp_tid: &Tid, expression: &Expression) -> Option; 60 | 61 | /// Evaluate the given expression at the given node of the graph that the 62 | /// value set analysis was computed on. 63 | fn eval_at_node(&self, node: NodeIndex, expression: &Expression) -> Option; 64 | 65 | /// Returns the mapping of abstract identfiers in the callee to values in 66 | /// the caller for the given call. 67 | fn get_call_renaming_map( 68 | &self, 69 | _call: &Tid, 70 | ) -> Option<&BTreeMap> { 71 | None 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/checkers.rs: -------------------------------------------------------------------------------- 1 | //! The implemented CWE checks. 2 | //! 3 | //! See their module descriptions for detailed information about each check. 4 | //! 5 | //! Currently the **Memory** check is not contained in this module 6 | //! but directly incorporated into the 7 | //! [`pointer_inference`](crate::analysis::pointer_inference) module. 8 | //! See there for detailed information about this check. 9 | 10 | /// Checkers that are supported for Linux kernel modules. 11 | pub const MODULES_LKM: [&str; 10] = [ 12 | "CWE134", "CWE190", "CWE215", "CWE252", "CWE416", "CWE457", "CWE467", "CWE476", "CWE676", 13 | "CWE789", 14 | ]; 15 | 16 | pub mod cwe_119; 17 | pub mod cwe_134; 18 | pub mod cwe_190; 19 | pub mod cwe_215; 20 | pub mod cwe_243; 21 | pub mod cwe_252; 22 | pub mod cwe_332; 23 | pub mod cwe_337; 24 | pub mod cwe_367; 25 | pub mod cwe_416; 26 | pub mod cwe_426; 27 | pub mod cwe_467; 28 | pub mod cwe_476; 29 | pub mod cwe_560; 30 | pub mod cwe_676; 31 | pub mod cwe_78; 32 | pub mod cwe_782; 33 | pub mod cwe_789; 34 | 35 | pub mod prelude { 36 | //! Prelude imports for CWE checkers. 37 | pub use super::{cwe_module, CweModule, CweModuleFn}; 38 | pub use crate::utils::debug; 39 | pub use crate::utils::log::{CweWarning, DeduplicateCweWarnings, LogMessage, WithLogs}; 40 | } 41 | use prelude::*; 42 | 43 | use crate::pipeline::AnalysisResults; 44 | 45 | /// The generic function signature for the main function of a CWE module 46 | pub type CweModuleFn = 47 | fn(&AnalysisResults, &serde_json::Value, &debug::Settings) -> WithLogs>; 48 | 49 | /// A structure containing general information about a CWE analysis module, 50 | /// including the function to be called to run the analysis. 51 | pub struct CweModule { 52 | /// The name of the CWE check. 53 | pub name: &'static str, 54 | /// The version number of the CWE check. 55 | /// Should be incremented whenever significant changes are made to the check. 56 | pub version: &'static str, 57 | /// The function that executes the check and returns CWE warnings found during the check. 58 | pub run: CweModuleFn, 59 | } 60 | 61 | #[macro_export] 62 | /// Defines a CWE checker module. 63 | macro_rules! cwe_module { 64 | ( 65 | $name:literal, $version:literal, $run:ident, 66 | config: $($(#[doc = $config_doc:expr])*$config_key:ident: $config_type:ty), 67 | *$(,)? 68 | ) => { 69 | cwe_module!($name, $version, $run); 70 | #[doc = "The checker-specific configuration."] 71 | #[derive(serde::Serialize, serde::Deserialize)] 72 | struct Config { 73 | $( 74 | $( 75 | #[doc = $config_doc] 76 | )* 77 | $config_key: $config_type, 78 | )* 79 | } 80 | }; 81 | ($name:literal, $version:literal, $run:ident$(,)?) => { 82 | #[doc = "The checker's name, version, and entry point."] 83 | pub static CWE_MODULE: $crate::checkers::prelude::CweModule = 84 | $crate::checkers::prelude::CweModule { 85 | name: $name, 86 | version: $version, 87 | run: $run, 88 | }; 89 | } 90 | } 91 | pub use cwe_module; 92 | 93 | impl std::fmt::Display for CweModule { 94 | /// Print the module name and its version number. 95 | fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 96 | write!(formatter, r#""{}": "{}""#, self.name, self.version) 97 | } 98 | } 99 | 100 | /// Get a list of all known analysis modules. 101 | pub fn get_modules() -> Vec<&'static CweModule> { 102 | vec![ 103 | &crate::checkers::cwe_78::CWE_MODULE, 104 | &crate::checkers::cwe_119::CWE_MODULE, 105 | &crate::checkers::cwe_134::CWE_MODULE, 106 | &crate::checkers::cwe_190::CWE_MODULE, 107 | &crate::checkers::cwe_215::CWE_MODULE, 108 | &crate::checkers::cwe_243::CWE_MODULE, 109 | &crate::checkers::cwe_252::CWE_MODULE, 110 | &crate::checkers::cwe_332::CWE_MODULE, 111 | &crate::checkers::cwe_337::CWE_MODULE, 112 | &crate::checkers::cwe_367::CWE_MODULE, 113 | &crate::checkers::cwe_416::CWE_MODULE, 114 | &crate::checkers::cwe_426::CWE_MODULE, 115 | &crate::checkers::cwe_467::CWE_MODULE, 116 | &crate::checkers::cwe_476::CWE_MODULE, 117 | &crate::checkers::cwe_560::CWE_MODULE, 118 | &crate::checkers::cwe_676::CWE_MODULE, 119 | &crate::checkers::cwe_782::CWE_MODULE, 120 | &crate::checkers::cwe_789::CWE_MODULE, 121 | &crate::analysis::pointer_inference::CWE_MODULE, 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/checkers/cwe_119/context/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{bitvec, variable}; 3 | use std::collections::BTreeSet; 4 | 5 | impl<'a> Context<'a> { 6 | /// Create a mock context. 7 | /// Note that this function leaks memory! 8 | pub fn mock_x64() -> Context<'static> { 9 | let mut project = Box::new(Project::mock_x64()); 10 | project.program.term.subs = BTreeMap::from([ 11 | (Tid::new("func"), Sub::mock("func")), 12 | (Tid::new("main"), Sub::mock("main")), 13 | ]); 14 | let project = Box::leak(project); 15 | let pointer_inference = Box::new(PointerInference::mock(project)); 16 | let pointer_inference = Box::leak(pointer_inference); 17 | let analysis_results = AnalysisResults::mock_from_project(project); 18 | let analysis_results = 19 | Box::new(analysis_results.with_pointer_inference(Some(pointer_inference))); 20 | let analysis_results = Box::leak(analysis_results); 21 | let (log_collector, _) = crossbeam_channel::unbounded(); 22 | 23 | Context::new(analysis_results, log_collector) 24 | } 25 | } 26 | 27 | #[test] 28 | fn test_compute_size_value_of_malloc_like_call() { 29 | use crate::analysis::pointer_inference::State as PiState; 30 | let project = Project::mock_x64(); 31 | let mut pi_results = PointerInference::mock(&project); 32 | let mut malloc_state = PiState::new(&variable!("RSP:8"), Tid::new("func"), BTreeSet::new()); 33 | malloc_state.set_register(&variable!("RDI:8"), bitvec!("3:8").into()); 34 | *pi_results.get_mut_states_at_tids() = HashMap::from([(Tid::new("malloc_call"), malloc_state)]); 35 | let malloc_symbol = ExternSymbol::mock_x64("malloc"); 36 | 37 | assert_eq!( 38 | compute_size_value_of_malloc_like_call( 39 | &Tid::new("malloc_call"), 40 | &malloc_symbol, 41 | &pi_results 42 | ) 43 | .unwrap(), 44 | bitvec!("3:8").into() 45 | ); 46 | assert!(compute_size_value_of_malloc_like_call( 47 | &Tid::new("other"), 48 | &ExternSymbol::mock_x64("other"), 49 | &pi_results 50 | ) 51 | .is_none()); 52 | } 53 | 54 | #[test] 55 | fn test_malloc_zero_case() { 56 | let mut context = Context::mock_x64(); 57 | let (sender, _receiver) = crossbeam_channel::unbounded(); 58 | context.log_collector = sender; // So that log messages can be sent and received. 59 | 60 | let object_size = IntervalDomain::mock(0, 32); 61 | let object_size = DataDomain::from(object_size); 62 | let object_id = AbstractIdentifier::mock("object_tid", "RAX", 8); 63 | 64 | context 65 | .call_to_caller_fn_map 66 | .insert(object_id.get_tid().clone(), Tid::new("func_tid")); 67 | context 68 | .malloc_tid_to_object_size_map 69 | .insert(object_id.get_tid().clone(), object_size); 70 | // Since zero sizes usually result in implementation-specific behavior for allocators, 71 | // the fallback of taking the maximum object size in `context.compute_size_of_heap_object()` should trigger. 72 | let size_domain = context.compute_size_of_heap_object(&object_id); 73 | assert_eq!(size_domain.try_to_offset().unwrap(), 32); 74 | } 75 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/checkers/cwe_215.rs: -------------------------------------------------------------------------------- 1 | //! This module implements a check for CWE-215: Information Exposure Through 2 | //! Debug Information. 3 | //! 4 | //! Sensitive debugging information can be leveraged to get a better 5 | //! understanding of a binary in less time. 6 | //! 7 | //! See for a detailed 8 | //! description. 9 | //! 10 | //! ## How the check works 11 | //! 12 | //! For ELF binaries we check whether they contain sections containing debug 13 | //! information. Other binary formats are currently not supported by this check. 14 | //! 15 | //! ## False Positives 16 | //! 17 | //! None known. 18 | //! 19 | //! ## False Negatives 20 | //! 21 | //! None known. 22 | use super::prelude::*; 23 | 24 | use crate::prelude::*; 25 | use crate::utils::log::{CweWarning, LogMessage}; 26 | 27 | cwe_module!("CWE215", "0.2", check_cwe); 28 | 29 | /// Run the check. 30 | /// 31 | /// We simply check whether the ELF file still contains sections whose name starts with `.debug`. 32 | /// Binary formats other than ELF files are currently not supported by this check. 33 | pub fn check_cwe( 34 | analysis_results: &AnalysisResults, 35 | _cwe_params: &serde_json::Value, 36 | _debug_settings: &debug::Settings, 37 | ) -> WithLogs> { 38 | let binary = analysis_results.binary; 39 | 40 | let (logs, cwe_warnings) = match goblin::Object::parse(binary) { 41 | Ok(goblin::Object::Elf(elf_binary)) => { 42 | for section_header in elf_binary.section_headers { 43 | if let Some(section_name) = elf_binary.shdr_strtab.get_at(section_header.sh_name) { 44 | if section_name.starts_with(".debug") { 45 | let cwe_warning = CweWarning::new( 46 | CWE_MODULE.name, 47 | CWE_MODULE.version, 48 | "(Information Exposure Through Debug Information) The binary contains debug symbols." 49 | ); 50 | return WithLogs::wrap(vec![cwe_warning]); 51 | } 52 | } 53 | } 54 | (Vec::new(), Vec::new()) 55 | } 56 | Ok(_) => { 57 | let info_log = LogMessage::new_info( 58 | "File type not supported. Currently this check only supports ELF files.", 59 | ) 60 | .source(CWE_MODULE.name); 61 | (vec![info_log], Vec::new()) 62 | } 63 | Err(err) => { 64 | let err_log = LogMessage::new_error(format!("Error while parsing binary: {err}")) 65 | .source(CWE_MODULE.name); 66 | (vec![err_log], Vec::new()) 67 | } 68 | }; 69 | 70 | WithLogs::new(cwe_warnings, logs) 71 | } 72 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/checkers/cwe_332.rs: -------------------------------------------------------------------------------- 1 | //! This module implements a check for CWE-332: Insufficient Entropy in PRNG. 2 | //! 3 | //! This can happen, for instance, if the PRNG is not seeded. A classical 4 | //! example would be calling rand without srand. This could lead to predictable 5 | //! random numbers and could, for example, weaken crypto functionality. 6 | //! 7 | //! See for a detailed 8 | //! description. 9 | //! 10 | //! ## How the check works 11 | //! 12 | //! For pairs of a secure seeding function and a corresponding random number 13 | //! generator function (e.g. the pair `(srand, rand)`, configurable in 14 | //! `config.json`) we check whether the program calls the random number 15 | //! generator without calling the seeding function. 16 | //! 17 | //! ## False Positives 18 | //! 19 | //! None known. 20 | //! 21 | //! ## False Negatives 22 | //! 23 | //! It is not checked whether the seeding function gets called before the 24 | //! random number generator function. 25 | use super::prelude::*; 26 | 27 | use crate::prelude::*; 28 | use crate::utils::symbol_utils::find_symbol; 29 | 30 | cwe_module!( 31 | "CWE332", 32 | "0.1", 33 | check_cwe, 34 | config: 35 | /// Pairs of symbol names. 36 | /// 37 | /// The first name is the name of a seeding function and the second name 38 | /// is the name of a corresponding random number generator access 39 | /// function. It is assumed that a program has to call the seeding 40 | /// function first to ensure that the RNG does not generate predictable 41 | /// random numbers. 42 | pairs: Vec<(String, String)>, 43 | ); 44 | 45 | /// Generate the CWE warning for a detected instance of the CWE. 46 | fn generate_cwe_warning(secure_initializer_func: &str, rand_func: &str) -> CweWarning { 47 | CweWarning::new( 48 | CWE_MODULE.name, 49 | CWE_MODULE.version, 50 | format!( 51 | "(Insufficient Entropy in PRNG) program uses {rand_func} without calling {secure_initializer_func} before"), 52 | ) 53 | } 54 | 55 | /// Run the CWE check. See the module-level description for more information. 56 | pub fn check_cwe( 57 | analysis_results: &AnalysisResults, 58 | cwe_params: &serde_json::Value, 59 | _debug_settings: &debug::Settings, 60 | ) -> WithLogs> { 61 | let project = analysis_results.project; 62 | let config: Config = serde_json::from_value(cwe_params.clone()).unwrap(); 63 | let mut cwe_warnings = Vec::new(); 64 | 65 | for (secure_initializer_func, rand_func) in config.pairs.iter() { 66 | if find_symbol(&project.program, rand_func).is_some() 67 | && find_symbol(&project.program, secure_initializer_func).is_none() 68 | { 69 | cwe_warnings.push(generate_cwe_warning(secure_initializer_func, rand_func)); 70 | } 71 | } 72 | 73 | WithLogs::wrap(cwe_warnings) 74 | } 75 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/checkers/cwe_426.rs: -------------------------------------------------------------------------------- 1 | //! This module implements a check for CWE-426: Untrusted Search Path. 2 | //! 3 | //! Basically, the program searches for critical resources on an untrusted 4 | //! search path that can be adjusted by an adversary. For example, see Nebula 5 | //! Level 1 (). 6 | //! 7 | //! According to the manual page of system() the following problems can arise: 8 | //! "Do not use system() from a program with set-user-ID or set-group-ID 9 | //! privileges, because strange values for some environment variables might be 10 | //! used to subvert system integrity. Use the exec(3) family of functions 11 | //! instead, but not execlp(3) or execvp(3). system() will not, in fact, work 12 | //! properly from programs with set-user-ID or set-group-ID privileges on 13 | //! systems on which /bin/sh is bash version 2, since bash 2 drops privileges on 14 | //! startup. (Debian uses a modified bash which does not do this when invoked 15 | //! as sh.)" 16 | //! 17 | //! See for a detailed 18 | //! description. 19 | //! 20 | //! ## How the check works 21 | //! 22 | //! We check whether a function that calls a privilege-changing function 23 | //! (configurable in config.json) also calls system(). 24 | //! 25 | //! ## False Positives 26 | //! 27 | //! If the call to system() happens before the privilege-changing function, the call 28 | //! may not be used for privilege escalation 29 | //! 30 | //! ## False Negatives 31 | //! 32 | //! If the calls to the privilege-changing function and system() happen in 33 | //! different functions, the calls will not be flagged as a CWE-hit. 34 | //! This check only finds potential privilege escalation bugs, but other types 35 | //! of bugs can also be triggered by untrusted search paths. 36 | use super::prelude::*; 37 | 38 | use crate::intermediate_representation::*; 39 | use crate::prelude::*; 40 | use crate::utils::symbol_utils::{find_symbol, get_calls_to_symbols}; 41 | use std::collections::HashMap; 42 | 43 | cwe_module!( 44 | "CWE426", 45 | "0.1", 46 | check_cwe, 47 | config: 48 | /// Functions that change or drop privileges. 49 | symbols: Vec 50 | ); 51 | 52 | /// Generate the CWE warning for a detected instance of the CWE. 53 | fn generate_cwe_warning(sub: &Term) -> CweWarning { 54 | CweWarning::new( 55 | CWE_MODULE.name, 56 | CWE_MODULE.version, 57 | format!( 58 | "(Untrusted Search Path) sub {} at {} may be vulnerable to PATH manipulation.", 59 | sub.term.name, 60 | sub.tid.address() 61 | ), 62 | ) 63 | .tids(vec![format!("{}", sub.tid)]) 64 | .addresses(vec![sub.tid.address().to_string()]) 65 | .symbols(vec![sub.term.name.clone()]) 66 | } 67 | 68 | /// Run the CWE check. 69 | /// We check whether a function calls both `system(..)` and a privilege changing function. 70 | /// For each such function a CWE warning is generated. 71 | pub fn check_cwe( 72 | analysis_results: &AnalysisResults, 73 | cwe_params: &serde_json::Value, 74 | _debug_settings: &debug::Settings, 75 | ) -> WithLogs> { 76 | let project = analysis_results.project; 77 | let config: Config = serde_json::from_value(cwe_params.clone()).unwrap(); 78 | let mut cwe_warnings = Vec::new(); 79 | let mut privilege_changing_symbols = HashMap::new(); 80 | for symbol in config.symbols.iter() { 81 | if let Some((tid, name)) = find_symbol(&project.program, symbol) { 82 | privilege_changing_symbols.insert(tid, name); 83 | } 84 | } 85 | let mut system_symbol = HashMap::new(); 86 | if let Some((tid, name)) = find_symbol(&project.program, "system") { 87 | system_symbol.insert(tid, name); 88 | } 89 | if !system_symbol.is_empty() && !privilege_changing_symbols.is_empty() { 90 | for sub in project.program.term.subs.values() { 91 | if !get_calls_to_symbols(sub, &system_symbol).is_empty() 92 | && !get_calls_to_symbols(sub, &privilege_changing_symbols).is_empty() 93 | { 94 | cwe_warnings.push(generate_cwe_warning(sub)); 95 | } 96 | } 97 | } 98 | 99 | cwe_warnings.deduplicate_first_address() 100 | } 101 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/checkers/cwe_782.rs: -------------------------------------------------------------------------------- 1 | //! This module implements a check for CWE-782: Exposed IOCTL with Insufficient 2 | //! Access Control. 3 | //! 4 | //! See for a detailed 5 | //! description. 6 | //! 7 | //! How the check works: 8 | //! 9 | //! - Calls to ioctl() get flagged as CWE hits. 10 | //! 11 | //! False Positives: 12 | //! 13 | //! - We cannot check whether the call contains sufficient access control. 14 | //! 15 | //! False Negatives: 16 | //! 17 | //! - There are other ways to expose I/O control without access control. 18 | 19 | use super::prelude::*; 20 | 21 | use crate::prelude::*; 22 | use crate::{ 23 | intermediate_representation::{Program, Sub, Term, Tid}, 24 | utils::symbol_utils::{find_symbol, get_calls_to_symbols}, 25 | }; 26 | 27 | use std::collections::HashMap; 28 | 29 | cwe_module!("CWE782", "0.1", check_cwe); 30 | 31 | /// check whether the ioctl symbol is called by any subroutine. If so, generate the cwe warning. 32 | pub fn handle_sub(sub: &Term, symbol: &HashMap<&Tid, &str>) -> Vec { 33 | let calls: Vec<(&str, &Tid, &str)> = get_calls_to_symbols(sub, symbol); 34 | if !calls.is_empty() { 35 | return generate_cwe_warning(&calls); 36 | } 37 | vec![] 38 | } 39 | 40 | /// generate the cwe warning for CWE 782 41 | pub fn generate_cwe_warning(calls: &[(&str, &Tid, &str)]) -> Vec { 42 | let mut cwe_warnings: Vec = Vec::new(); 43 | for (sub_name, jmp_tid, _) in calls.iter() { 44 | let address = jmp_tid.address(); 45 | let description = format!( 46 | "(Exposed IOCTL with Insufficient Access Control) Program uses ioctl at {sub_name} ({address}). Be sure to double check the program and the corresponding driver."); 47 | let cwe_warning = CweWarning::new( 48 | String::from(CWE_MODULE.name), 49 | String::from(CWE_MODULE.version), 50 | description, 51 | ) 52 | .addresses(vec![address.to_string()]) 53 | .tids(vec![format!("{jmp_tid}")]) 54 | .symbols(vec![String::from(*sub_name)]); 55 | 56 | cwe_warnings.push(cwe_warning); 57 | } 58 | cwe_warnings 59 | } 60 | 61 | /// Iterate through all calls of the program and flag calls to `ioctl()` as CWE warnings. 62 | pub fn check_cwe( 63 | analysis_results: &AnalysisResults, 64 | _cwe_params: &serde_json::Value, 65 | _debug_settings: &debug::Settings, 66 | ) -> WithLogs> { 67 | let project = analysis_results.project; 68 | let prog: &Term = &project.program; 69 | let mut warnings: Vec = Vec::new(); 70 | if let Some((tid, name)) = find_symbol(prog, "ioctl") { 71 | let symbol: &HashMap<&Tid, &str> = &[(tid, name)].iter().cloned().collect(); 72 | prog.term 73 | .subs 74 | .values() 75 | .for_each(|sub| warnings.append(&mut handle_sub(sub, symbol))); 76 | } 77 | 78 | WithLogs::wrap(warnings) 79 | } 80 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/datatype_properties.rs: -------------------------------------------------------------------------------- 1 | //! C data type properties. 2 | 3 | use crate::intermediate_representation::DatatypeProperties as IrDatatypeProperties; 4 | 5 | use std::convert::From; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | /// C data type properties for a given platform. 10 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] 11 | pub struct DatatypeProperties { 12 | char_size: u64, 13 | double_size: u64, 14 | float_size: u64, 15 | integer_size: u64, 16 | long_double_size: u64, 17 | long_long_size: u64, 18 | long_size: u64, 19 | pointer_size: u64, 20 | short_size: u64, 21 | } 22 | 23 | impl From for IrDatatypeProperties { 24 | fn from(datatype_properties: DatatypeProperties) -> Self { 25 | Self { 26 | char_size: datatype_properties.char_size.into(), 27 | double_size: datatype_properties.double_size.into(), 28 | float_size: datatype_properties.float_size.into(), 29 | integer_size: datatype_properties.integer_size.into(), 30 | long_double_size: datatype_properties.long_double_size.into(), 31 | long_long_size: datatype_properties.long_long_size.into(), 32 | long_size: datatype_properties.long_size.into(), 33 | pointer_size: datatype_properties.pointer_size.into(), 34 | short_size: datatype_properties.short_size.into(), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/function/extern_function.rs: -------------------------------------------------------------------------------- 1 | use crate::ghidra_pcode::{PcodeProject, Varnode}; 2 | use crate::intermediate_representation::{ExternSymbol as IrExternSymbol, Tid}; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | mod domain_knowledge; 7 | use domain_knowledge::apply_domain_knowledge_to; 8 | 9 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] 10 | pub struct ExternFunction { 11 | name: String, 12 | calling_convention: String, 13 | parameters: Vec, 14 | return_location: Option, 15 | thunks: Vec, 16 | has_no_return: bool, 17 | has_var_args: bool, 18 | } 19 | 20 | impl ExternFunction { 21 | pub fn to_ir_extern_symbol(&self, pcode_project: &PcodeProject) -> IrExternSymbol { 22 | let ir_expr_sp = pcode_project.stack_pointer_register.to_ir_expr(); 23 | let mut ir_extern_symbol = IrExternSymbol { 24 | tid: Tid::new_external_function(&self.name), 25 | addresses: self.thunks.to_owned(), 26 | name: self.name.clone(), 27 | calling_convention: Some(self.calling_convention.clone()), 28 | parameters: self 29 | .parameters 30 | .iter() 31 | .map(|vn| vn.to_ir_arg(&ir_expr_sp)) 32 | .collect(), 33 | return_values: self 34 | .return_location 35 | .as_ref() 36 | .map(|vn| vec![vn.to_ir_arg(&ir_expr_sp)]) 37 | .unwrap_or_default(), 38 | no_return: self.has_no_return, 39 | has_var_args: self.has_var_args, 40 | }; 41 | 42 | apply_domain_knowledge_to(&mut ir_extern_symbol, pcode_project); 43 | 44 | ir_extern_symbol 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/function/extern_function/domain_knowledge/mod.rs: -------------------------------------------------------------------------------- 1 | mod scanf_sscanf; 2 | 3 | mod prelude { 4 | pub use super::ExternalSymbolDomainKnowledge; 5 | pub use crate::ghidra_pcode::PcodeProject; 6 | pub use crate::intermediate_representation::ExternSymbol as IrExternSymbol; 7 | } 8 | use prelude::*; 9 | 10 | const AVAILABLE_DOMAIN_KNOWLEDGE: [ExternalSymbolDomainKnowledge; 1] = 11 | [scanf_sscanf::DOMAIN_KNOWLEDGE]; 12 | 13 | pub struct ExternalSymbolDomainKnowledge { 14 | applicable_symbols: &'static [&'static str], 15 | apply_domain_knowledge_fp: fn(&mut IrExternSymbol, &PcodeProject) -> bool, 16 | } 17 | 18 | impl ExternalSymbolDomainKnowledge { 19 | fn is_applicable_to(&self, ir_extern_symbol: &IrExternSymbol) -> bool { 20 | self.applicable_symbols 21 | .contains(&ir_extern_symbol.name.as_str()) 22 | } 23 | 24 | fn apply_domain_knowledge_to( 25 | &self, 26 | ir_extern_symbol: &mut IrExternSymbol, 27 | pcode_project: &PcodeProject, 28 | ) -> bool { 29 | let fp = self.apply_domain_knowledge_fp; 30 | 31 | fp(ir_extern_symbol, pcode_project) 32 | } 33 | } 34 | 35 | pub fn apply_domain_knowledge_to( 36 | ir_extern_symbol: &mut IrExternSymbol, 37 | pcode_project: &PcodeProject, 38 | ) { 39 | for domain_knowledge in AVAILABLE_DOMAIN_KNOWLEDGE.iter() { 40 | if !domain_knowledge.is_applicable_to(ir_extern_symbol) { 41 | continue; 42 | } 43 | if domain_knowledge.apply_domain_knowledge_to(ir_extern_symbol, pcode_project) { 44 | break; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/function/extern_function/domain_knowledge/scanf_sscanf.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | use crate::intermediate_representation::Arg as IrArg; 4 | 5 | const APPLICABLE_SYMBOLS: [&str; 4] = ["scanf", "sscanf", "__isoc99_scanf", "__isoc99_sscanf"]; 6 | const SSCANF_SYMBOLS: [&str; 2] = ["sscanf", "__isoc99_sscanf"]; 7 | 8 | pub const DOMAIN_KNOWLEDGE: ExternalSymbolDomainKnowledge = ExternalSymbolDomainKnowledge { 9 | applicable_symbols: &APPLICABLE_SYMBOLS, 10 | apply_domain_knowledge_fp: apply_domain_knowledge_to, 11 | }; 12 | 13 | fn apply_domain_knowledge_to( 14 | ir_extern_symbol: &mut IrExternSymbol, 15 | pcode_project: &PcodeProject, 16 | ) -> bool { 17 | let should_stop = true; 18 | 19 | ir_extern_symbol.no_return = false; 20 | ir_extern_symbol.has_var_args = true; 21 | 22 | if ir_extern_symbol.parameters.is_empty() { 23 | let ir_expr_sp = pcode_project.stack_pointer_register.to_ir_expr(); 24 | let cconv = pcode_project 25 | .calling_conventions 26 | .get(ir_extern_symbol.calling_convention.as_ref().unwrap()) 27 | .unwrap(); 28 | let mut parameters: Vec = Vec::new(); 29 | 30 | // TODO: Test that this is indeed on the stack for x86. 31 | // TODO: Insert domain knowledge about parameter type. 32 | let param0 = cconv 33 | .get_integer_parameter_register(0) 34 | .unwrap() 35 | .to_ir_arg(&ir_expr_sp); 36 | parameters.push(param0); 37 | 38 | if SSCANF_SYMBOLS.contains(&ir_extern_symbol.name.as_str()) { 39 | let param1 = cconv 40 | .get_integer_parameter_register(1) 41 | .unwrap() 42 | .to_ir_arg(&ir_expr_sp); 43 | parameters.push(param1); 44 | } 45 | 46 | ir_extern_symbol.parameters = parameters; 47 | } 48 | 49 | should_stop 50 | } 51 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/function/mod.rs: -------------------------------------------------------------------------------- 1 | use super::Block; 2 | 3 | use crate::intermediate_representation::{Sub as IrFunction, Term as IrTerm, Tid}; 4 | 5 | use std::collections::HashSet; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | pub mod extern_function; 10 | pub use extern_function::ExternFunction; 11 | 12 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] 13 | pub struct Function { 14 | name: String, 15 | address: String, 16 | blocks: Vec, 17 | } 18 | 19 | impl Function { 20 | /// Returns a one-line signature of this funtion. 21 | pub fn summary_string(&self) -> String { 22 | format!("FUNCTION: {} @ {}", self.name, self.address) 23 | } 24 | 25 | /// Returns a reference to the basic block of this funtion. 26 | pub fn blocks(&self) -> &Vec { 27 | &self.blocks 28 | } 29 | 30 | /// 1:1 translation of this function to an IR funtion term. 31 | pub fn to_ir_function_term(&self, jump_targets: &HashSet) -> IrTerm { 32 | let ir_function_term = IrFunction::new::<_, &str>( 33 | &self.name, 34 | self.blocks() 35 | .iter() 36 | .flat_map(|block| block.to_ir_blocks(jump_targets).into_iter()) 37 | .collect(), 38 | None, 39 | ); 40 | 41 | IrTerm::new(Tid::new_function(&self.address), ir_function_term) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/instruction/mod.rs: -------------------------------------------------------------------------------- 1 | //! Assembly instructions. 2 | 3 | use super::Term; 4 | 5 | use crate::intermediate_representation::Tid; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | use std::collections::HashSet; 10 | use std::fmt::{self, Display}; 11 | 12 | /// An assembly instruction. 13 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] 14 | pub struct Instruction { 15 | /// Instruction mnemonic. 16 | mnemonic: String, 17 | /// Address at which the first instruction byte is located. 18 | address: String, 19 | /// Number of bytes that belong to this instruction. 20 | size: u64, 21 | /// Pcode terms that this instruction decomposes into. 22 | terms: Vec, 23 | /// Potential targets of an indirect control flow transfer. 24 | potential_targets: Option>, 25 | /// Fall-through target. 26 | fall_through: Option, 27 | } 28 | 29 | impl Display for Instruction { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | for term in &self.terms { 32 | writeln!(f, "{}", term)?; 33 | } 34 | 35 | Ok(()) 36 | } 37 | } 38 | 39 | impl Instruction { 40 | /// Returns the address of the first byte that belongs to this instruction. 41 | pub fn address(&self) -> &String { 42 | &self.address 43 | } 44 | 45 | /// Returns the mnemonic of this instruction. 46 | pub fn mnemonic(&self) -> &String { 47 | &self.mnemonic 48 | } 49 | 50 | /// Returns true iff this is a NOOP. 51 | pub fn is_nop(&self) -> bool { 52 | self.terms.is_empty() 53 | } 54 | 55 | /// Returns the Pcode terms that this instruction decomposes into. 56 | pub fn terms(&self) -> &Vec { 57 | &self.terms 58 | } 59 | 60 | /// Returns potential targets of an indirect control flow transfer. 61 | pub fn potential_targets(&self) -> Option<&Vec> { 62 | self.potential_targets.as_ref() 63 | } 64 | 65 | /// Returns the fall-through target of this instruction. 66 | pub fn fall_through(&self) -> Option<&String> { 67 | self.fall_through.as_ref() 68 | } 69 | 70 | /// Collects all jump targets of an instruction and returns their [`Tid`]. 71 | /// 72 | /// The id follows the naming convention `blk_
`. If the target is 73 | /// within a pcode sequence and the index is larger 0, `_` is 74 | /// suffixed. 75 | pub fn collect_jmp_and_fall_through_targets( 76 | &self, 77 | _consecutive_instr: Option<&Instruction>, 78 | ) -> HashSet { 79 | let mut jump_targets = HashSet::new(); 80 | 81 | for jmp_term in self.terms().iter().filter(|term| term.is_jump()) { 82 | let targets = jmp_term.collect_jmp_targets(self); 83 | jump_targets.extend(targets); 84 | 85 | if let Some(fall_through) = jmp_term.get_fall_through_target(self) { 86 | jump_targets.insert(fall_through); 87 | } 88 | } 89 | 90 | jump_targets 91 | } 92 | 93 | /// Returns true iff this instruction contains a term with the given index. 94 | pub fn contains_term_index(&self, index: u64) -> bool { 95 | index < (self.terms().len() as u64) 96 | } 97 | 98 | /// Returns true iff this instruction is in a MIPS jump delay slot. 99 | pub fn is_mips_jump_delay_slot(&self) -> bool { 100 | self.mnemonic().starts_with("_") 101 | } 102 | } 103 | 104 | // TODO: Fix tests. 105 | /* 106 | #[cfg(test)] 107 | pub mod tests { 108 | use super::*; 109 | 110 | impl Instruction { 111 | /// Returns `InstructionSimple`, with mnemonic `mock`, size `1`, `potential_targets` and `fall_through` set to `None`. 112 | pub fn mock<'a, T>(address: &'a str, pcode_ops: T) -> Self 113 | where 114 | T: IntoIterator, 115 | T::Item: Into<&'a str>, 116 | { 117 | let mut ops = Vec::new(); 118 | for (index, op) in pcode_ops.into_iter().enumerate() { 119 | ops.push(PcodeOperation::mock(op.into()).with_index(index as u64)); 120 | } 121 | Instruction { 122 | mnemonic: "mock".into(), 123 | address: address.to_string(), 124 | size: 1, 125 | terms: ops, 126 | potential_targets: None, 127 | fall_through: None, 128 | } 129 | } 130 | } 131 | } 132 | */ 133 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/ir_passes/entry_points.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | /// Removes listed entry points that are not defined within the program or are 4 | /// empty. 5 | /// 6 | /// # Guarantees 7 | /// 8 | /// Should not interfere with any other pass. 9 | /// 10 | /// # Postconditions 11 | /// 12 | /// 1. All listed entry points are defined, nonempty functions within the 13 | /// program. 14 | /// 15 | /// # Run After 16 | /// 17 | /// - Empty functions have been removed. [Removing them afterwards might violate 18 | /// Postcondition 1]. (Not necessary but lets be conservative.) 19 | /// - Stubs for external functions have been removed. [Removing them afterwards 20 | /// might violate Postcondition 1]. 21 | pub struct EntryPointsPass; 22 | 23 | impl IrPass for EntryPointsPass { 24 | const NAME: &'static str = "EntryPointsPass"; 25 | const DBG_IR_FORM: debug::IrForm = debug::IrForm::EntryPointsExist; 26 | 27 | type Input = Program; 28 | type ConstructionInput = (); 29 | 30 | fn new(_construction_input: &Self::ConstructionInput) -> Self { 31 | Self 32 | } 33 | 34 | fn run(&mut self, program: &mut Self::Input) -> Vec { 35 | let mut logs = Vec::new(); 36 | 37 | // Keep only entry points that are defined and nonempty. 38 | program.entry_points.retain(|ep_tid| { 39 | let defined_and_nonempty = program 40 | .subs 41 | .get(ep_tid) 42 | .is_some_and(|ep_fn| !ep_fn.blocks.is_empty()); 43 | 44 | if !defined_and_nonempty { 45 | logs.push(LogMessage::new_info(format!( 46 | "{}: Entry point {} undefined or empty.", 47 | Self::NAME, 48 | ep_tid 49 | ))) 50 | } 51 | 52 | defined_and_nonempty 53 | }); 54 | 55 | logs 56 | } 57 | 58 | fn assert_postconditions(_construction_input: &Self::ConstructionInput, program: &Self::Input) { 59 | for ep_tid in program.entry_points.iter() { 60 | let ep_fn = program.subs.get(ep_tid); 61 | 62 | assert!(ep_fn.is_some(), "Entry point {} is undefined.", ep_tid); 63 | assert!( 64 | !ep_fn.unwrap().blocks.is_empty(), 65 | "Entry point {} is empty.", 66 | ep_tid 67 | ); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/ir_passes/fn_start_blocks.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | use crate::intermediate_representation::Tid; 4 | 5 | /// Ensures that the first block of a function is its entry point. 6 | /// 7 | /// # Postconditions 8 | /// 9 | /// 1. Each nonempty function has exactly one block that has the same address as 10 | /// the function. 11 | /// 2. This block is in the first position in the block array. 12 | /// 13 | /// # Run After 14 | pub struct ReorderFnBlocksPass; 15 | 16 | impl ReorderFnBlocksPass { 17 | /// Returns true iff `b` is a start block for `f`. 18 | fn is_fn_start_blk(f: &Tid, b: &Tid) -> bool { 19 | f.address() == b.address() && (b.is_block_without_suffix() || b.is_artificial_sink_block()) 20 | } 21 | } 22 | 23 | impl IrPass for ReorderFnBlocksPass { 24 | const NAME: &'static str = "ReorderFnBlocksPass"; 25 | const DBG_IR_FORM: debug::IrForm = debug::IrForm::FnBlksSorted; 26 | 27 | type Input = Program; 28 | type ConstructionInput = (); 29 | 30 | fn new(_construction_input: &Self::ConstructionInput) -> Self { 31 | Self 32 | } 33 | 34 | fn run(&mut self, program: &mut Self::Input) -> Vec { 35 | let mut logs = Vec::new(); 36 | 37 | for f in program.functions_mut().filter(|f| { 38 | !f.blocks.is_empty() && !Self::is_fn_start_blk(&f.tid, &f.blocks().next().unwrap().tid) 39 | }) { 40 | let (idx, _) = f 41 | .blocks() 42 | .enumerate() 43 | .find(|(_, b)| Self::is_fn_start_blk(&f.tid, &b.tid)) 44 | .unwrap(); 45 | 46 | logs.push(LogMessage::new_info(format!( 47 | "{}: Start block of function {} was at idx {}.", 48 | Self::NAME, 49 | f.tid, 50 | idx 51 | ))); 52 | 53 | f.blocks.swap(0, idx); 54 | } 55 | 56 | logs 57 | } 58 | 59 | fn assert_postconditions(_construction_input: &Self::ConstructionInput, program: &Self::Input) { 60 | for f in program.functions().filter(|f| !f.blocks.is_empty()) { 61 | // 1. Each nonempty function has exactly one block that has the same 62 | // address as the function. 63 | assert_eq!( 64 | f.blocks() 65 | .filter(|b| Self::is_fn_start_blk(&f.tid, &b.tid)) 66 | .count(), 67 | 1, 68 | "Function {} has {} entry blocks: {}", 69 | &f.tid, 70 | f.blocks() 71 | .filter(|b| Self::is_fn_start_blk(&f.tid, &b.tid)) 72 | .count(), 73 | f.blocks() 74 | .filter(|b| Self::is_fn_start_blk(&f.tid, &b.tid)) 75 | .fold(String::new(), |mut a, i| { 76 | a.push_str(format!("{},", i.tid).as_str()); 77 | a 78 | }) 79 | ); 80 | // 2. This block is in the first position in the block array. 81 | assert!(Self::is_fn_start_blk( 82 | &f.tid, 83 | &f.blocks().next().unwrap().tid 84 | )); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/ir_passes/mod.rs: -------------------------------------------------------------------------------- 1 | //! Transforming passes that bring the IR to normal form. 2 | 3 | use crate::utils::debug; 4 | use crate::utils::log::LogMessage; 5 | 6 | mod single_target_indirect_calls; 7 | pub use single_target_indirect_calls::*; 8 | 9 | mod entry_points; 10 | pub use entry_points::*; 11 | 12 | mod remove_empty_functions; 13 | pub use remove_empty_functions::*; 14 | 15 | mod replace_call_to_ext_fn; 16 | pub use replace_call_to_ext_fn::*; 17 | 18 | mod inlining; 19 | pub use inlining::*; 20 | 21 | mod jump_targets; 22 | pub use jump_targets::*; 23 | 24 | mod subregister_substitution; 25 | pub use subregister_substitution::*; 26 | 27 | mod fn_start_blocks; 28 | pub use fn_start_blocks::*; 29 | 30 | mod nonret_ext_functions; 31 | pub use nonret_ext_functions::*; 32 | 33 | pub mod prelude { 34 | //! Prelude imports for IR passes. 35 | pub use super::IrPass; 36 | pub use crate::intermediate_representation::Program; 37 | pub use crate::utils::debug; 38 | pub use crate::utils::log::LogMessage; 39 | } 40 | 41 | /// # Guarantees 42 | /// 43 | /// A list of predicates about the program that are preserved if they are true 44 | /// before the pass. 45 | /// 46 | /// # Postconditions 47 | /// 48 | /// A list of predicates about the program that are always true after the pass. 49 | /// Every following transformation is expected to preserve these predicates, 50 | /// i.e., they are assumed to remain true. 51 | /// [Parts of the Postcondition that are not checked by 52 | /// [`IrPass::assert_postconditions`]] 53 | /// 54 | /// # Run After 55 | /// 56 | /// There is a partial ordering between IR passes. `pass0 < pass1` means that 57 | /// `pass0` always has to run before `pass1`. This section lists all passes that 58 | /// are strictly less than the current pass. 59 | pub trait IrPass { 60 | /// Name of this pass. 61 | // Use std::any::type_name once it is stable as const fn. 62 | const NAME: &'static str; 63 | /// Form of the IR __after__ this pass. 64 | const DBG_IR_FORM: debug::IrForm; 65 | 66 | /// Type of the input that the pass runs on. 67 | type Input; 68 | /// Type of the input that the pass constructor needs. 69 | type ConstructionInput; 70 | 71 | /// Constructs a new instance of this pass. 72 | fn new(construction_input: &Self::ConstructionInput) -> Self; 73 | 74 | /// Runs the pass on the given program. 75 | fn run(&mut self, program: &mut Self::Input) -> Vec; 76 | 77 | /// Asserts that the program satisfies all Postconditions of this pass. 78 | fn assert_postconditions(construction_input: &Self::ConstructionInput, program: &Self::Input); 79 | } 80 | 81 | /// Runs an IR pass on the given program. 82 | #[macro_export] 83 | macro_rules! run_ir_pass { 84 | // Run a sequence of IR passes. 85 | { 86 | $program:expr, 87 | $logs:expr, 88 | $dbg_settings:expr, 89 | $(($construction_input:expr, $pass:ty)),+$(,)? 90 | } => { 91 | $( 92 | run_ir_pass![ 93 | $program, 94 | $construction_input, 95 | $pass, 96 | $logs, 97 | $dbg_settings, 98 | ]; 99 | )+ 100 | }; 101 | // Run a single IR pass where the construction input is the program. 102 | ($program:expr, $pass:ty, $logs:expr, $dbg_settings:expr$(,)?) => { 103 | run_ir_pass![$program, $program, $pass, $logs, $dbg_settings] 104 | }; 105 | [$program:expr, $construction_input:expr, $pass:ty, $logs:expr, $dbg_settings:expr$(,)?] => { 106 | let mut pass = <$pass>::new(&$construction_input); 107 | let mut logs = pass.run(&mut $program); 108 | 109 | if $dbg_settings.verbose() { 110 | println!("[IR-PASSES] Finished pass: {}", <$pass>::NAME); 111 | for msg in logs.iter() { 112 | println!(" {}", msg); 113 | } 114 | } 115 | if !$dbg_settings.quiet() { 116 | $logs.append(&mut logs); 117 | } 118 | 119 | $dbg_settings.print(&$program, $crate::utils::debug::Stage::Ir(<$pass>::DBG_IR_FORM)); 120 | }; 121 | } 122 | pub use run_ir_pass; 123 | 124 | /// Asserts that the postconditions of an IR pass are satisfied by the given 125 | /// program. 126 | /// 127 | /// Only active if debug assertions are enabled. 128 | #[macro_export] 129 | macro_rules! debug_assert_postconditions { 130 | ($program:expr, $pass:ty$(,)?) => { 131 | debug_assert_postconditions![$program, $program, $pass]; 132 | }; 133 | [$program:expr, $construction_input:expr, $pass:ty$(,)?] => { 134 | if cfg!(debug_assertions) { 135 | <$pass>::assert_postconditions(&$construction_input, &$program); 136 | } 137 | } 138 | } 139 | pub use debug_assert_postconditions; 140 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/ir_passes/nonret_ext_functions.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | use crate::intermediate_representation::{Jmp as IrJmp, Tid}; 3 | 4 | use std::collections::HashSet; 5 | 6 | /// Rewrites return targets of calls to from noreturn functions. 7 | /// 8 | /// - Inserts an artificial return target block into every nonempty function. 9 | /// - Rewrites return locations of calls to noreturn ext functions to artificial 10 | /// return target block. 11 | /// 12 | /// # Guarantees 13 | /// 14 | /// - Preserves existence of CFT targets. 15 | /// 16 | /// # Postconditions 17 | /// 18 | /// 1. Every nonempty function has zero or one artificial return target blocks. 19 | /// [All incoming edges are from returns of calls to noreturn functions.] 20 | /// 21 | /// # Run After 22 | /// 23 | /// - Rewriting calls to external functions to skip stubs. [Calls to external 24 | /// functions are assumed to be direct.] 25 | /// [ReplaceCallsToExtFnsPass](super::ReplaceCallsToExtFnsPass) 26 | /// - Inlining. [Might violate 1st Postcondition.] 27 | /// [InliningPass](super::InliningPass) 28 | pub struct NoreturnExtFunctionsPass { 29 | /// TIDS of non returning external functions. 30 | nonret_ext_fn_tids: HashSet, 31 | } 32 | 33 | impl IrPass for NoreturnExtFunctionsPass { 34 | const NAME: &'static str = "NoreturnExtFunctionsPass"; 35 | const DBG_IR_FORM: debug::IrForm = debug::IrForm::NonRetExtFunctionsMarked; 36 | 37 | type Input = Program; 38 | type ConstructionInput = Self::Input; 39 | 40 | fn new(program: &Self::ConstructionInput) -> Self { 41 | Self { 42 | nonret_ext_fn_tids: program 43 | .extern_symbols 44 | .values() 45 | .filter_map(|ext_fn| { 46 | if ext_fn.no_return { 47 | Some(ext_fn.tid.clone()) 48 | } else { 49 | None 50 | } 51 | }) 52 | .collect(), 53 | } 54 | } 55 | 56 | fn run(&mut self, program: &mut Self::Input) -> Vec { 57 | let mut logs = Vec::new(); 58 | 59 | // Add artificial return target blocks to all nonempty functions. 60 | for f in program.functions_mut().filter(|f| !f.blocks.is_empty()) { 61 | f.add_artifical_return_target(); 62 | } 63 | 64 | for (fn_tid, j_tid, return_target) in 65 | program 66 | .jmps_mut_with_fn_tid() 67 | .filter_map(|(fn_tid, j)| match &mut j.term { 68 | IrJmp::Call { 69 | target, 70 | return_: return_target, 71 | } if self.nonret_ext_fn_tids.contains(target) => { 72 | Some((fn_tid, &j.tid, return_target)) 73 | } 74 | _ => None, 75 | }) 76 | { 77 | logs.push(LogMessage::new_info(format!( 78 | "{}: Change return target of nonret call @ {} from {:?} with artificial return target.", 79 | Self::NAME, j_tid, return_target 80 | ))); 81 | 82 | // Rewrite return of call to nonret ext function. 83 | *return_target = Some(Tid::artificial_return_target_for_fn(fn_tid)); 84 | } 85 | 86 | logs 87 | } 88 | 89 | fn assert_postconditions(_construction_input: &Self::ConstructionInput, program: &Self::Input) { 90 | // 1. Every nonempty function has zero or one artificial return target 91 | // blocks. 92 | // [All incoming edges are from returns of calls to noreturn 93 | // functions.] 94 | for f in program.functions() { 95 | let num_artificial_return_target = f 96 | .blocks() 97 | .filter(|b| b.tid.is_artificial_return_target_block()) 98 | .count(); 99 | 100 | assert!(num_artificial_return_target == 0 || num_artificial_return_target == 1); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/ir_passes/remove_empty_functions.rs: -------------------------------------------------------------------------------- 1 | use crate::intermediate_representation::{Jmp, Program, Sub as Function, Term, Tid}; 2 | use std::collections::HashSet; 3 | 4 | use super::prelude::*; 5 | 6 | /// Removes empty functions. 7 | /// 8 | /// - Adds an artificial sink function. 9 | /// - Rewrites calls to empty functions to artificial sink function and replaces 10 | /// return target with artificial return block (as the artificial sink 11 | /// function is noreturn). 12 | /// - Removes empty functions. 13 | /// 14 | /// # Guarantees 15 | /// 16 | /// - Maintains: Existence of all CFT targets. 17 | /// 18 | /// # Postconditions 19 | /// 20 | /// 1. The program has no empty functions. 21 | /// 2. The program has exactly one artificial sink function. 22 | /// 23 | /// Run after: 24 | /// - Adding an artificial return target block to each function. 25 | /// [NoreturnExtFunctionsPass](super::NoreturnExtFunctionsPass) 26 | pub struct RemoveEmptyFunctionsPass { 27 | empty_fn_tids: HashSet, 28 | } 29 | 30 | impl IrPass for RemoveEmptyFunctionsPass { 31 | const NAME: &'static str = "RemoveEmptyFunctionsPass"; 32 | const DBG_IR_FORM: debug::IrForm = debug::IrForm::EmptyFnRemoved; 33 | 34 | type Input = Program; 35 | type ConstructionInput = Self::Input; 36 | 37 | fn new(program: &Self::ConstructionInput) -> Self { 38 | Self { 39 | empty_fn_tids: program 40 | .functions() 41 | .filter_map(|f| { 42 | if f.blocks.is_empty() { 43 | Some(f.tid.clone()) 44 | } else { 45 | None 46 | } 47 | }) 48 | .collect(), 49 | } 50 | } 51 | 52 | fn run(&mut self, program: &mut Self::Input) -> Vec { 53 | let mut logs = Vec::new(); 54 | 55 | // Remove empty functions. 56 | program 57 | .subs 58 | .retain(|fn_tid, _| !self.empty_fn_tids.contains(fn_tid)); 59 | 60 | // Insert an artificial sink function. 61 | program.subs.insert( 62 | Tid::artificial_sink_fn(), 63 | Term::::artificial_sink(), 64 | ); 65 | 66 | // Retarget calls to empty functions to artificial sink and 67 | // rewrite their returns to the artificial return target block for the 68 | // surrounding function. 69 | for (fn_tid, j) in program.jmps_mut_with_fn_tid() { 70 | match &mut j.term { 71 | Jmp::Call { target, return_ } if self.empty_fn_tids.contains(target) => { 72 | logs.push(LogMessage::new_info(format!( 73 | "{}: Rewrite call to empty function '{}' at {}.", 74 | Self::NAME, 75 | target, 76 | j.tid 77 | ))); 78 | 79 | *target = Tid::artificial_sink_fn(); 80 | *return_ = Some(Tid::artificial_return_target_for_fn(fn_tid)); 81 | } 82 | _ => (), 83 | } 84 | } 85 | 86 | logs 87 | } 88 | 89 | fn assert_postconditions(_construction_input: &Self::ConstructionInput, program: &Self::Input) { 90 | // 1. The program has no empty functions. 91 | assert!(program.functions().all(|f| !f.blocks.is_empty())); 92 | // 2. The program has exactly one artificial sink function. 93 | assert!( 94 | program 95 | .functions() 96 | .filter(|f| f.tid.is_artificial_sink_fn()) 97 | .count() 98 | == 1 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/ir_passes/replace_call_to_ext_fn.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | use crate::intermediate_representation::{Jmp as IrJmp, Tid}; 3 | 4 | use std::collections::hash_map::Entry; 5 | use std::collections::HashMap; 6 | 7 | /// Call into external functions directly. 8 | /// 9 | /// - Removes stubs for external functions. 10 | /// - Replaces calls to stubs for external function with call to external 11 | /// function. 12 | /// 13 | /// # Guarantees 14 | /// 15 | /// - Preserves: All CFT targets exist. 16 | /// 17 | /// # Postconditions 18 | /// 19 | /// # Run After 20 | pub struct ReplaceCallsToExtFnsPass { 21 | stub_tid_to_ext_fn_tid_map: HashMap, 22 | } 23 | 24 | impl IrPass for ReplaceCallsToExtFnsPass { 25 | const NAME: &'static str = "ReplaceCallsToExtFnsPass"; 26 | const DBG_IR_FORM: debug::IrForm = debug::IrForm::ExtCallsReplaced; 27 | 28 | type Input = Program; 29 | type ConstructionInput = Self::Input; 30 | 31 | fn new(program: &Self::ConstructionInput) -> Self { 32 | let mut stub_tid_to_ext_fn_tid_map = HashMap::new(); 33 | 34 | for ext_fn in program.extern_symbols.values() { 35 | for stub_tid in ext_fn.addresses.iter().map(Tid::new_function) { 36 | match stub_tid_to_ext_fn_tid_map.entry(stub_tid) { 37 | Entry::Vacant(e) => e.insert(ext_fn.tid.clone()), 38 | Entry::Occupied(_) => { 39 | panic!("Mapping of stubs to external functions is not unique.") 40 | } 41 | }; 42 | } 43 | } 44 | 45 | Self { 46 | stub_tid_to_ext_fn_tid_map, 47 | } 48 | } 49 | 50 | fn run(&mut self, program: &mut Self::Input) -> Vec { 51 | let mut logs = Vec::new(); 52 | 53 | // Rewrite potential targets of indirect calls. 54 | 55 | // Rewrite calls to external function's stubs. 56 | for j in program.jmps_mut() { 57 | let IrJmp::Call { target, .. } = &mut j.term else { 58 | continue; 59 | }; 60 | let Some(ext_fn_tid) = self.stub_tid_to_ext_fn_tid_map.get(target) else { 61 | continue; 62 | }; 63 | 64 | logs.push(LogMessage::new_info(format!( 65 | "{}: Replaced call {} @ {} with call to {}.", 66 | Self::NAME, 67 | target, 68 | j.tid, 69 | ext_fn_tid 70 | ))); 71 | 72 | *target = ext_fn_tid.clone(); 73 | } 74 | 75 | // Remove stubs from the program. 76 | for stub_tid in self.stub_tid_to_ext_fn_tid_map.keys() { 77 | program.subs.remove(stub_tid).unwrap(); 78 | 79 | logs.push(LogMessage::new_info(format!( 80 | "{}: Removed stub {}.", 81 | Self::NAME, 82 | stub_tid 83 | ))); 84 | } 85 | 86 | logs 87 | } 88 | 89 | fn assert_postconditions( 90 | _construction_input: &Self::ConstructionInput, 91 | _program: &Self::Input, 92 | ) { 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/ir_passes/single_target_indirect_calls.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | use crate::intermediate_representation::{Jmp, Term}; 3 | 4 | /// Replaces indirect calls with a single target with a direct call to this 5 | /// target. 6 | /// 7 | /// # Guarantees 8 | /// 9 | /// # Postconditions 10 | /// 11 | /// - A block has `Some` list of indirect control flow targets IFF it ends in 12 | /// an indirect jump or call. The type of the indirect control flow targets 13 | /// fits to the type of the indirect control flow transfer. 14 | /// 15 | /// # Run After 16 | pub struct SingleTargetIndirectCallsPass; 17 | 18 | impl IrPass for SingleTargetIndirectCallsPass { 19 | const NAME: &'static str = "SingleTargetIndirectCallsPass"; 20 | const DBG_IR_FORM: debug::IrForm = debug::IrForm::SingleTargetIndirectCallsReplaced; 21 | 22 | type Input = Program; 23 | type ConstructionInput = (); 24 | 25 | fn new(_construction_input: &Self::ConstructionInput) -> Self { 26 | Self 27 | } 28 | 29 | fn run(&mut self, program: &mut Self::Input) -> Vec { 30 | let mut logs = Vec::new(); 31 | 32 | for b in program.blocks_mut() { 33 | // Filter blocks that end in an indirect call with a single target. 34 | let Some(Term { term: j, tid }) = b.jmps().last() else { 35 | continue; 36 | }; 37 | let Jmp::CallInd { 38 | return_: return_target, 39 | .. 40 | } = j 41 | else { 42 | continue; 43 | }; 44 | let mut indirect_call_targets = b.ind_call_targets().unwrap(); 45 | let Some(first_target) = indirect_call_targets.next().cloned() else { 46 | continue; 47 | }; 48 | if indirect_call_targets.next().is_some() { 49 | continue; 50 | } 51 | std::mem::drop(indirect_call_targets); 52 | 53 | logs.push(LogMessage::new_info(format!( 54 | "{}: Replaced single-target indirect call at {} with direct call to {}.", 55 | Self::NAME, 56 | tid, 57 | first_target 58 | ))); 59 | 60 | // Change to a direct call. 61 | b.jmps_mut().last().unwrap().term = Jmp::Call { 62 | target: first_target, 63 | return_: return_target.clone(), 64 | }; 65 | 66 | // Restore invariant that only blocks with indirect calls or jumps 67 | // have `Some` indirect control flow targets. 68 | b.clear_ind_control_flow_targets(); 69 | } 70 | 71 | logs 72 | } 73 | 74 | fn assert_postconditions(_construction_input: &Self::ConstructionInput, program: &Self::Input) { 75 | for b in program.blocks() { 76 | match b.jmps().last() { 77 | None => assert!(b.ind_control_flow_targets().is_none()), 78 | Some(j) => match &j.term { 79 | Jmp::BranchInd(_) => { 80 | assert!(b.ind_control_flow_targets().is_some()); 81 | assert!(b.ind_jump_targets().is_some()); 82 | } 83 | Jmp::CallInd { .. } => { 84 | assert!(b.ind_control_flow_targets().is_some()); 85 | assert!(b.ind_call_targets().is_some()); 86 | } 87 | _ => assert!(b.ind_control_flow_targets().is_none()), 88 | }, 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/pcode_operation/jumps/mod.rs: -------------------------------------------------------------------------------- 1 | //! Pcode jump operations. 2 | 3 | use super::PcodeOperation; 4 | 5 | use crate::{ 6 | ghidra_pcode::{pcode_opcode::PcodeOpcode, JmpOpcode}, 7 | intermediate_representation::{Jmp as IrJmp, Tid}, 8 | }; 9 | 10 | impl PcodeOperation { 11 | /// Helper function to unwrap the jump opcode of a Pcode operation. 12 | /// 13 | /// Panics if `self` is not a jump. 14 | pub fn unwrap_jmp_opcode(&self) -> &JmpOpcode { 15 | if let PcodeOpcode::Jump(jmp_type) = &self.opcode() { 16 | jmp_type 17 | } else { 18 | panic!("Jump type expected.") 19 | } 20 | } 21 | 22 | /// Returns true iff this is a jump operation. 23 | pub fn is_jump(&self) -> bool { 24 | matches!(self.pcode_mnemonic, PcodeOpcode::Jump(_)) 25 | } 26 | 27 | /// Returns true iff this is a direct jump operation. 28 | pub fn is_direct_jump(&self) -> bool { 29 | matches!( 30 | self.pcode_mnemonic, 31 | PcodeOpcode::Jump(JmpOpcode::BRANCH) 32 | | PcodeOpcode::Jump(JmpOpcode::CBRANCH) 33 | | PcodeOpcode::Jump(JmpOpcode::CALL) 34 | ) 35 | } 36 | 37 | /// Create a branch instruction. 38 | pub fn to_ir_jmp_branch(&self, target: Tid) -> IrJmp { 39 | IrJmp::Branch(target) 40 | } 41 | 42 | /// Create a conditional branch. 43 | pub fn to_ir_jmp_cbranch(&self, target: Tid) -> IrJmp { 44 | IrJmp::CBranch { 45 | target, 46 | condition: self.input1().unwrap().to_ir_expr(), 47 | } 48 | } 49 | 50 | /// Create an indirect branch. 51 | pub fn to_ir_jmp_branch_ind(&self) -> IrJmp { 52 | IrJmp::BranchInd(self.input0().unwrap().to_ir_expr()) 53 | } 54 | 55 | /// Create a call. 56 | pub fn to_ir_jmp_call(&self, return_target: Option) -> IrJmp { 57 | IrJmp::Call { 58 | target: Tid::new_function(self.input0().unwrap().get_ram_address_as_string().unwrap()), 59 | return_: return_target, 60 | } 61 | } 62 | 63 | /// Create an indirect call. 64 | pub fn to_ir_jmp_call_ind(&self, return_target: Option) -> IrJmp { 65 | IrJmp::CallInd { 66 | target: self.input0().unwrap().to_ir_expr(), 67 | return_: return_target, 68 | } 69 | } 70 | 71 | /// Create a `CallOther` instruction. 72 | /// 73 | /// The description is given by the mnemonic of the corresponding assembly 74 | /// instruction 75 | pub fn to_ir_jmp_call_other(&self, return_target: Option, description: &str) -> IrJmp { 76 | // FIXME: The description shown by Ghidra is actually not the mnemonic! 77 | // But it is unclear how one can access the description through Ghidras 78 | // API. Furthermore, we do not encode the optional input varnodes that 79 | // Ghidra allows for CALLOTHER operations. 80 | IrJmp::CallOther { 81 | description: description.to_string(), 82 | return_: return_target, 83 | } 84 | } 85 | 86 | /// Create a return instruction. 87 | pub fn to_ir_jmp_return(&self) -> IrJmp { 88 | IrJmp::Return(self.input0().unwrap().to_ir_expr()) 89 | } 90 | } 91 | 92 | // TODO: Fix tests. 93 | //#[cfg(test)] 94 | //mod tests; 95 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/ghidra_pcode/program.rs: -------------------------------------------------------------------------------- 1 | use super::Block; 2 | use super::Function; 3 | 4 | use crate::intermediate_representation::{Sub as IrFunction, Term as IrTerm, Tid}; 5 | 6 | use std::collections::{BTreeMap, HashSet}; 7 | use std::fmt::{self, Display}; 8 | 9 | use serde::{Deserialize, Serialize}; 10 | 11 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] 12 | pub struct Program { 13 | functions: Vec, 14 | } 15 | 16 | impl Program { 17 | /// Returns a reference to the functions of this program. 18 | pub fn functions(&self) -> &Vec { 19 | &self.functions 20 | } 21 | 22 | /// Returns an iterator over the blocks in this program. 23 | pub fn blocks(&self) -> impl Iterator { 24 | self.functions() 25 | .iter() 26 | .flat_map(|func| func.blocks().iter()) 27 | } 28 | 29 | /// 1:1 translation of the functions in this program to IR function terms. 30 | pub fn to_ir_function_terms_map(&self) -> BTreeMap> { 31 | let jump_targets: HashSet = self 32 | .blocks() 33 | .flat_map(|block| block.collect_jmp_targets()) 34 | .collect(); 35 | 36 | let ret: BTreeMap> = self 37 | .functions() 38 | .iter() 39 | .map(|function| { 40 | let ir_function_term = function.to_ir_function_term(&jump_targets); 41 | 42 | (ir_function_term.tid.clone(), ir_function_term) 43 | }) 44 | .collect(); 45 | 46 | assert_eq!(self.functions.len(), ret.len(), "Duplicate function TID."); 47 | 48 | ret 49 | } 50 | } 51 | 52 | impl Display for Program { 53 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 54 | for func in self.functions() { 55 | writeln!(f, "{}", func.summary_string())?; 56 | for block in func.blocks() { 57 | writeln!(f, " {}", block.summary_string())?; 58 | for insn in block.instructions() { 59 | if !insn.is_nop() { 60 | writeln!( 61 | f, 62 | " {}", 63 | insn.to_string().replace('\n', "\n ").trim_end() 64 | )?; 65 | } 66 | } 67 | writeln!(f)?; 68 | } 69 | } 70 | 71 | Ok(()) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/intermediate_representation/expression/builder.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | /// ## Helper functions for building expressions 4 | impl Expression { 5 | /// Shortcut for creating an `IntAdd`-expression 6 | pub fn plus(self, rhs: Expression) -> Expression { 7 | Expression::BinOp { 8 | lhs: Box::new(self), 9 | op: BinOpType::IntAdd, 10 | rhs: Box::new(rhs), 11 | } 12 | } 13 | 14 | /// Construct an expression that adds a constant value to the given expression. 15 | /// 16 | /// The bytesize of the value is automatically adjusted to the bytesize of the given expression. 17 | pub fn plus_const(self, value: i64) -> Expression { 18 | if value == 0 { 19 | return self; 20 | } 21 | let bytesize = self.bytesize(); 22 | let mut value = Bitvector::from_i64(value); 23 | match u64::from(bytesize) { 24 | size if size > 8 => value.sign_extend(bytesize).unwrap(), 25 | size if size < 8 => value.truncate(bytesize).unwrap(), 26 | _ => (), 27 | } 28 | self.plus(Expression::Const(value)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/intermediate_representation/project/ir_passes/intraprocedural_dead_block_elim.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | /// TODO 4 | pub struct IntraproceduralDeadBlockElimPass; 5 | 6 | impl IrPass for IntraproceduralDeadBlockElimPass { 7 | const NAME: &'static str = "IntraproceduralDeadBlockElimPass"; 8 | const DBG_IR_FORM: debug::IrForm = debug::IrForm::IntraproceduralDeadBlocksElimed; 9 | 10 | type Input = Program; 11 | type ConstructionInput = (); 12 | 13 | fn new(_construction_input: &Self::ConstructionInput) -> Self { 14 | Self 15 | } 16 | 17 | fn run(&mut self, _program: &mut Self::Input) -> Vec { 18 | Vec::new() 19 | } 20 | 21 | fn assert_postconditions( 22 | _construction_input: &Self::ConstructionInput, 23 | _program: &Self::Input, 24 | ) { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/intermediate_representation/project/ir_passes/mod.rs: -------------------------------------------------------------------------------- 1 | //! Transforming passes that optimize the IR. 2 | 3 | mod control_flow_propagation; 4 | pub use control_flow_propagation::*; 5 | 6 | mod dead_variable_elim; 7 | pub use dead_variable_elim::*; 8 | 9 | mod intraprocedural_dead_block_elim; 10 | pub use intraprocedural_dead_block_elim::*; 11 | 12 | mod stack_pointer_alignment_substitution; 13 | pub use stack_pointer_alignment_substitution::*; 14 | 15 | mod trivial_expression_substitution; 16 | pub use trivial_expression_substitution::*; 17 | 18 | mod input_expression_propagation; 19 | pub use input_expression_propagation::*; 20 | 21 | pub use crate::ghidra_pcode::ir_passes::prelude; 22 | pub use crate::ghidra_pcode::ir_passes::{debug_assert_postconditions, run_ir_pass, IrPass}; 23 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/intermediate_representation/project/ir_passes/stack_pointer_alignment_substitution.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | 3 | use crate::intermediate_representation::{Project, Variable}; 4 | 5 | mod legacy; 6 | 7 | /// Substitutes stack pointer alignment operations utilising logical AND with an 8 | /// arithmetic SUB operation. 9 | /// 10 | /// The first basic block of every function is searched for a logical AND 11 | /// operation on the stack pointer. 12 | /// By journaling changes to the stack pointer an offset is calculated which is 13 | /// going to be used to alter the operation into a subtraction. 14 | /// 15 | /// # Log Messages 16 | /// Following cases trigger log messages: 17 | /// - alignment is untypical for the architecture 18 | /// - the argument for the AND operation is not a constant 19 | /// - an operation alters the stack pointer, which can not be journaled. 20 | pub struct StackPointerAlignmentSubstitutionPass { 21 | stack_pointer_register: Variable, 22 | cpu_architecture: String, 23 | } 24 | 25 | impl IrPass for StackPointerAlignmentSubstitutionPass { 26 | const NAME: &'static str = "StackPointerAlignmentSubstitutionPass"; 27 | const DBG_IR_FORM: debug::IrForm = debug::IrForm::StackPointerAlignmentSubstituted; 28 | 29 | type Input = Program; 30 | type ConstructionInput = Project; 31 | 32 | fn new(project: &Self::ConstructionInput) -> Self { 33 | Self { 34 | stack_pointer_register: project.stack_pointer_register.clone(), 35 | cpu_architecture: project.cpu_architecture.clone(), 36 | } 37 | } 38 | 39 | fn run(&mut self, program: &mut Self::Input) -> Vec { 40 | legacy::substitute_and_on_stackpointer( 41 | program, 42 | &self.stack_pointer_register, 43 | &self.cpu_architecture, 44 | ) 45 | } 46 | 47 | fn assert_postconditions( 48 | _construction_input: &Self::ConstructionInput, 49 | _program: &Self::Input, 50 | ) { 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/intermediate_representation/project/ir_passes/trivial_expression_substitution.rs: -------------------------------------------------------------------------------- 1 | use super::prelude::*; 2 | use crate::intermediate_representation::{Def, Jmp}; 3 | 4 | /// For all expressions contained in the project, replace trivially computable 5 | /// subexpressions like `a XOR a` with their result. 6 | pub struct TrivialExpressionSubstitutionPass; 7 | 8 | impl IrPass for TrivialExpressionSubstitutionPass { 9 | const NAME: &'static str = "TrivialExpressionSubstitutionPass"; 10 | const DBG_IR_FORM: debug::IrForm = debug::IrForm::TrivialExpressionsSubstituted; 11 | 12 | type Input = Program; 13 | type ConstructionInput = (); 14 | 15 | fn new(_construction_input: &Self::ConstructionInput) -> Self { 16 | Self 17 | } 18 | 19 | fn run(&mut self, program: &mut Self::Input) -> Vec { 20 | for block in program.blocks_mut() { 21 | for def in block.defs_mut() { 22 | match &mut def.term { 23 | Def::Assign { value: expr, .. } | Def::Load { address: expr, .. } => { 24 | expr.substitute_trivial_operations() 25 | } 26 | Def::Store { address, value } => { 27 | address.substitute_trivial_operations(); 28 | value.substitute_trivial_operations(); 29 | } 30 | } 31 | } 32 | for jmp in block.jmps_mut() { 33 | match &mut jmp.term { 34 | Jmp::Branch(_) | Jmp::Call { .. } | Jmp::CallOther { .. } => (), 35 | Jmp::BranchInd(expr) 36 | | Jmp::CBranch { 37 | condition: expr, .. 38 | } 39 | | Jmp::CallInd { target: expr, .. } 40 | | Jmp::Return(expr) => expr.substitute_trivial_operations(), 41 | } 42 | } 43 | } 44 | 45 | Vec::new() 46 | } 47 | 48 | fn assert_postconditions( 49 | _construction_input: &Self::ConstructionInput, 50 | _program: &Self::Input, 51 | ) { 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/intermediate_representation/variable.rs: -------------------------------------------------------------------------------- 1 | use super::ByteSize; 2 | use crate::prelude::*; 3 | use std::fmt::Display; 4 | 5 | /// A variable represents a register with a known size and name. 6 | /// 7 | /// Variables can be physical or virtual: 8 | /// 9 | /// - Physical variables correspond to CPU registers and are thus read/write 10 | /// able by callee and caller functions. In this sense they are akin to global 11 | /// variables in source languages. 12 | /// - Virtual registers are only used to store intermediate results necessary 13 | /// for representing more complex assembly instructions. In principle they are 14 | /// only valid until the end of the current assembly instruction. However, one 15 | /// assembly instruction may span more than one basic block in the 16 | /// intermediate representation (but never more than one function). In this 17 | /// sense they are akin to local variables. 18 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] 19 | pub struct Variable { 20 | /// The name of the variable. Equals the register name if the variable is a 21 | /// physical register. 22 | pub name: String, 23 | /// The size (in bytes) of the variable. 24 | pub size: ByteSize, 25 | /// Set to `false` for physical registers and to `true` for temporary 26 | /// (virtual) variables. 27 | pub is_temp: bool, 28 | } 29 | 30 | impl Display for Variable { 31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32 | write!(f, "{}:{}", self.name, self.size)?; 33 | if self.is_temp { 34 | write!(f, "(temp)")?; 35 | } 36 | Ok(()) 37 | } 38 | } 39 | 40 | impl Variable { 41 | /// Prefix of variable names that correspond to unnamed subregisters. 42 | pub const UNNAMED_SUBREG_PREFIX: &'static str = "$R_"; 43 | /// Prefix of variable names that correspond to temporary registers. 44 | pub const TMP_REG_PREFIX: &'static str = "$U_"; 45 | 46 | /// Returns true iff the variable is physical. 47 | pub fn is_physical_register(&self) -> bool { 48 | !self.is_temp 49 | } 50 | 51 | /// Returns true iff the variable corresponds to a subregister of a 52 | /// physical register. 53 | /// 54 | /// (Such variables do not appear in the final IR, only during the 55 | /// translation process.) 56 | pub fn is_unnamed_subregister(&self) -> bool { 57 | self.name.starts_with(Self::UNNAMED_SUBREG_PREFIX) 58 | } 59 | 60 | /// Extracts the offset into the register address space. 61 | /// 62 | /// (Only useful during IR generation.) 63 | pub fn name_to_offset(&self) -> Option { 64 | if self.is_unnamed_subregister() || !self.is_physical_register() { 65 | let offset = self.name.split('_').last().unwrap().to_string(); 66 | 67 | Some(u64::from_str_radix(offset.strip_prefix("0x").unwrap(), 16).unwrap()) 68 | } else { 69 | None 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | The main library of the cwe_checker containing all CWE checks and analysis modules. 3 | 4 | # What is the cwe_checker 5 | 6 | The cwe_checker is a tool for finding common bug classes on binaries using static analysis. 7 | These bug classes are formally known as [Common Weakness Enumerations](https://cwe.mitre.org/) (CWEs). 8 | Its main goal is to aid analysts to quickly find potentially vulnerable code paths. 9 | 10 | Currently its main focus are ELF binaries that are commonly found on Linux and Unix operating systems. 11 | The cwe_checker uses [Ghidra](https://ghidra-sre.org/) to disassemble binaries into one common intermediate representation 12 | and implements its own analyses on this IR. 13 | Hence, the analyses can be run on most CPU architectures that Ghidra can disassemble, 14 | which makes the cwe_checker a valuable tool for firmware analysis. 15 | 16 | # Usage 17 | 18 | If the cwe_checker is installed locally, just run 19 | ```sh 20 | cwe_checker BINARY 21 | ``` 22 | If you want to use the official docker image, you have to mount the input binary into the docker container, e.g. 23 | ```sh 24 | docker run --rm -v $(pwd)/BINARY:/input ghcr.io/fkie-cad/cwe_checker /input 25 | ``` 26 | One can modify the behaviour of the cwe_checker through the command line. 27 | Use the `--help` command line option for more information. 28 | One can also provide a custom configuration file to modify the behaviour of each check 29 | through the `--config` command line option. 30 | Start by taking a look at the standard configuration file located at `src/config.json` 31 | and read the [check-specific documentation](crate::checkers) for more details about each field in the configuration file. 32 | 33 | There is _experimental_ support for the analysis of Linux loadable kernel modules 34 | (LKMs). *cwe_checker* will recognize if you pass an LKM and will execute a 35 | subset of the CWE checks available for user-space programs. Analyses are 36 | configurable via a separate configuration file at `src/lkm_config.json`. 37 | 38 | ## For bare-metal binaries 39 | 40 | The cwe_checker offers experimental support for analyzing bare-metal binaries. 41 | For that, one needs to provide a bare metal configuration file via the `--bare-metal-config` command line option. 42 | An example for such a configuration file can be found at `bare_metal/stm32f407vg.json` 43 | (which was created and tested for an STM32F407VG MCU). 44 | 45 | For more information on the necessary fields of the configuration file 46 | and the assumed memory model when analyzing bare metal binaries 47 | see the [configuration struct documentation](crate::utils::binary::BareMetalConfig). 48 | 49 | # Integration into other tools 50 | 51 | ### Integration into Ghidra 52 | 53 | To import the results of the cwe_checker as bookmarks and end-of-line comments into Ghidra, 54 | one can use the Ghidra script located at `ghidra_plugin/cwe_checker_ghidra_plugin.py`. 55 | Detailed usage instructions are contained in the file. 56 | 57 | ### Integration into FACT 58 | 59 | [FACT](https://github.com/fkie-cad/FACT_core) already contains a ready-to-use cwe_checker plugin, 60 | which lets you run the cwe_checker and view its result through the FACT user interface. 61 | 62 | # Further documentation 63 | 64 | You can find out more information about each check, including known false positives and false negatives, 65 | by reading the check-specific module documentation in the [`checkers`] module. 66 | */ 67 | 68 | pub mod abstract_domain; 69 | pub mod analysis; 70 | pub mod checkers; 71 | pub mod ghidra_pcode; 72 | pub mod intermediate_representation; 73 | pub mod pipeline; 74 | pub mod utils; 75 | 76 | mod prelude { 77 | pub use apint::Width; 78 | pub use serde::{Deserialize, Serialize}; 79 | 80 | pub use crate::intermediate_representation::{Bitvector, BitvectorExtended, ByteSize}; 81 | pub use crate::intermediate_representation::{Term, Tid}; 82 | pub use crate::pipeline::AnalysisResults; 83 | 84 | pub use anyhow::Context as _; 85 | pub use anyhow::{anyhow, Error}; 86 | } 87 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/pipeline/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains functions and structs helpful for building a complete analysis pipeline 2 | //! starting from the binary file path. 3 | 4 | mod results; 5 | pub use results::AnalysisResults; 6 | 7 | use crate::intermediate_representation::{Project, RuntimeMemoryImage}; 8 | use crate::prelude::*; 9 | use crate::utils::debug; 10 | use crate::utils::log::WithLogs; 11 | use crate::utils::{binary::BareMetalConfig, ghidra::get_project_from_ghidra}; 12 | use std::path::Path; 13 | 14 | /// Disassemble the given binary and parse it to a [`Project`] struct. 15 | /// 16 | /// If successful, returns the binary file (as a byte vector), the parsed project struct, 17 | /// and a vector of log messages generated during the process. 18 | pub fn disassemble_binary( 19 | binary_file_path: &Path, 20 | bare_metal_config_opt: Option, 21 | debug_settings: &debug::Settings, 22 | ) -> Result<(Vec, WithLogs), Error> { 23 | let binary: Vec = 24 | std::fs::read(binary_file_path).context("Could not read from binary file path {}")?; 25 | let mut project = get_project_from_ghidra( 26 | binary_file_path, 27 | &binary[..], 28 | bare_metal_config_opt.clone(), 29 | debug_settings, 30 | )?; 31 | 32 | // Normalize the project and gather log messages generated from it. 33 | debug_settings.print(&project.program.term, debug::Stage::Ir(debug::IrForm::Raw)); 34 | 35 | project.optimize(debug_settings); 36 | 37 | debug_settings.print( 38 | &project.program.term, 39 | debug::Stage::Ir(debug::IrForm::Optimized), 40 | ); 41 | 42 | // Generate the representation of the runtime memory image of the binary 43 | let mut runtime_memory_image = if let Some(bare_metal_config) = bare_metal_config_opt.as_ref() { 44 | RuntimeMemoryImage::new_from_bare_metal(&binary, bare_metal_config) 45 | .context("Error while generating runtime memory image.")? 46 | } else { 47 | RuntimeMemoryImage::new(&binary).context("Error while generating runtime memory image.")? 48 | }; 49 | if project.program.term.address_base_offset != 0 { 50 | // We adjust the memory addresses once globally 51 | // so that other analyses do not have to adjust their addresses. 52 | runtime_memory_image.add_global_memory_offset(project.program.term.address_base_offset); 53 | } 54 | project.runtime_memory_image = runtime_memory_image; 55 | 56 | Ok((binary, project)) 57 | } 58 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/utils/graph_utils.rs: -------------------------------------------------------------------------------- 1 | //! Helper functions for common tasks utilizing the control flow graph of the binary. 2 | 3 | use crate::analysis::graph::*; 4 | use crate::intermediate_representation::Jmp; 5 | use crate::prelude::*; 6 | use petgraph::graph::NodeIndex; 7 | use petgraph::visit::EdgeRef; 8 | use std::collections::HashSet; 9 | 10 | /// Check whether a call to the `sink_symbol` is reachable from the given `source_node` 11 | /// through a path of intraprocedural edges in the control flow graph. 12 | /// 13 | /// A simple depth-first-search on the graph is used to find such a path. 14 | /// We do not search past subsequent calls to the `source_symbol` 15 | /// since we assume that sink calls after that belong to the new call to the source symbol and not the original one. 16 | /// 17 | /// If a sink is found, the `Tid` of the jump term calling the sink is returned. 18 | pub fn is_sink_call_reachable_from_source_call( 19 | graph: &Graph, 20 | source_node: NodeIndex, 21 | source_symbol: &Tid, 22 | sink_symbol: &Tid, 23 | ) -> Option { 24 | let mut visited_nodes = HashSet::new(); 25 | visited_nodes.insert(source_node); 26 | let mut worklist = vec![source_node]; 27 | 28 | while let Some(node) = worklist.pop() { 29 | for edge in graph.edges(node) { 30 | if let Edge::ExternCallStub(jmp) = edge.weight() { 31 | if let Jmp::Call { target, .. } = &jmp.term { 32 | if target == sink_symbol { 33 | // We found a call to the sink 34 | return Some(jmp.tid.clone()); 35 | } else if target == source_symbol { 36 | // Do not search past another source call, 37 | // since subsequent sink calls probably belong to the new source. 38 | continue; 39 | } 40 | } 41 | } 42 | // Add the target node to the worklist if it was not already visited 43 | // and as long as the edge does not leave the function. 44 | match edge.weight() { 45 | Edge::Block 46 | | Edge::CrCallStub 47 | | Edge::CallCombine(_) 48 | | Edge::ReturnCombine(_) 49 | | Edge::Jump(_, _) 50 | | Edge::ExternCallStub(_) => { 51 | if !visited_nodes.contains(&edge.target()) { 52 | visited_nodes.insert(edge.target()); 53 | worklist.push(edge.target()) 54 | } 55 | } 56 | Edge::Call(_) | Edge::CrReturnStub => (), // These edges would leave the function control flow graph. 57 | } 58 | } 59 | } 60 | None 61 | } 62 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains various utility modules and helper functions. 2 | 3 | pub mod arguments; 4 | pub mod binary; 5 | pub mod debug; 6 | pub mod ghidra; 7 | pub mod graph_utils; 8 | pub mod log; 9 | pub mod symbol_utils; 10 | 11 | use crate::prelude::*; 12 | 13 | use std::{env, fs, path}; 14 | 15 | use anyhow::bail; 16 | 17 | const ENV_CWE_CHECKER_CONFIGS_PATH: &str = "CWE_CHECKER_CONFIGS_PATH"; 18 | const ENV_CWE_CHECKER_GHIDRA_PLUGINS_PATH: &str = "CWE_CHECKER_GHIDRA_PLUGINS_PATH"; 19 | 20 | /// Get the contents of a configuration file. 21 | /// 22 | /// We first search the file in our config directory. Then, we fall back to 23 | /// the CWE_CHECKER_CONFIG environment variable. 24 | pub fn read_config_file(filename: &str) -> Result { 25 | let config_path = if let Some(config_path) = get_config_path_from_project_dir(filename) { 26 | config_path 27 | } else if let Some(config_path) = get_path_from_env(ENV_CWE_CHECKER_CONFIGS_PATH, filename) { 28 | config_path 29 | } else { 30 | bail!("Unable to find configuration file: {}.", filename) 31 | }; 32 | let config_file = fs::read_to_string(config_path) 33 | .context(format!("Could not read configuration file: {}", filename))?; 34 | Ok(serde_json::from_str(&config_file)?) 35 | } 36 | 37 | fn get_config_path_from_project_dir(filename: &str) -> Option { 38 | let project_dirs = directories::ProjectDirs::from("", "", "cwe_checker")?; 39 | let config_dir = project_dirs.config_dir(); 40 | let config_path = config_dir.join(filename); 41 | 42 | if config_path.exists() { 43 | Some(config_path) 44 | } else { 45 | None 46 | } 47 | } 48 | 49 | /// Get the path to a Ghidra plugin that is bundled with the cwe_checker. 50 | /// 51 | /// We first search the plugin in our data directory, then we fall back to 52 | /// the CWE_CHECKER_GHIDRA_PLUGIN_PATH environment variable. 53 | pub fn get_ghidra_plugin_path(plugin_name: &str) -> Result { 54 | if let Some(ghidra_plugin_path) = get_ghidra_plugin_path_from_project_dirs(plugin_name) { 55 | Ok(ghidra_plugin_path) 56 | } else if let Some(ghidra_plugin_path) = 57 | get_path_from_env(ENV_CWE_CHECKER_GHIDRA_PLUGINS_PATH, plugin_name) 58 | { 59 | Ok(ghidra_plugin_path) 60 | } else { 61 | bail!("Unable to find Ghidra plugin: {}", plugin_name) 62 | } 63 | } 64 | 65 | fn get_ghidra_plugin_path_from_project_dirs(plugin_name: &str) -> Option { 66 | let project_dirs = directories::ProjectDirs::from("", "", "cwe_checker")?; 67 | let data_dir = project_dirs.data_dir(); 68 | let plugin_path = data_dir.join("ghidra").join(plugin_name); 69 | 70 | if plugin_path.exists() { 71 | Some(plugin_path) 72 | } else { 73 | None 74 | } 75 | } 76 | 77 | fn get_path_from_env(var: &str, filename: &str) -> Option { 78 | let val = env::var(var).ok()?; 79 | let path = path::PathBuf::from(val).join(filename); 80 | 81 | if path.exists() { 82 | Some(path) 83 | } else { 84 | None 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/cwe_checker_lib/src/utils/symbol_utils.rs: -------------------------------------------------------------------------------- 1 | //! Helper functions for common tasks utilizing extern symbols, 2 | //! e.g. searching for calls to a specific extern symbol. 3 | 4 | use std::collections::{HashMap, HashSet}; 5 | 6 | use crate::intermediate_representation::*; 7 | 8 | /// Find the extern symbol object for a symbol name and return the symbol tid and name. 9 | pub fn find_symbol<'a>(prog: &'a Term, name: &str) -> Option<(&'a Tid, &'a str)> { 10 | let mut symbol: Option<(&'a Tid, &'a str)> = None; 11 | prog.term.extern_symbols.iter().find(|(_tid, sym)| { 12 | if name == sym.name { 13 | symbol = Some((&sym.tid, &sym.name)); 14 | true 15 | } else { 16 | false 17 | } 18 | }); 19 | 20 | symbol 21 | } 22 | 23 | /// Match direct calls' target tids in the program's subroutines 24 | /// with the tids of the external symbols given to the function. 25 | /// When a match was found, add a triple of (caller name, callsite tid, callee name) 26 | /// to a vector. Lastly, return the vector with all callsites of all given external symbols. 27 | pub fn get_calls_to_symbols<'a, 'b>( 28 | sub: &'a Term, 29 | symbols: &'b HashMap<&'a Tid, &'a str>, 30 | ) -> Vec<(&'a str, &'a Tid, &'a str)> { 31 | let mut calls: Vec<(&'a str, &'a Tid, &'a str)> = Vec::new(); 32 | for blk in sub.term.blocks.iter() { 33 | for jmp in blk.term.jmps.iter() { 34 | if let Jmp::Call { target: dst, .. } = &jmp.term { 35 | if symbols.contains_key(dst) { 36 | calls.push((sub.term.name.as_str(), &jmp.tid, symbols.get(dst).unwrap())); 37 | } 38 | } 39 | } 40 | } 41 | calls 42 | } 43 | 44 | /// Get a map from TIDs to the corresponding extern symbol struct. 45 | /// 46 | /// Only symbols with names contained in `symbols_to_find` are contained in the 47 | /// map. 48 | /// 49 | /// This is O(|symbols_to_find| x |extern_symbols|), prefer 50 | /// [`get_symbol_map_fast`] if speed matters. 51 | pub fn get_symbol_map<'a>( 52 | project: &'a Project, 53 | symbols_to_find: &[String], 54 | ) -> HashMap { 55 | let mut tid_map = HashMap::new(); 56 | for symbol_name in symbols_to_find { 57 | if let Some((tid, symbol)) = 58 | project 59 | .program 60 | .term 61 | .extern_symbols 62 | .iter() 63 | .find_map(|(_tid, symbol)| { 64 | if symbol.name == *symbol_name { 65 | Some((symbol.tid.clone(), symbol)) 66 | } else { 67 | None 68 | } 69 | }) 70 | { 71 | tid_map.insert(tid, symbol); 72 | } 73 | } 74 | tid_map 75 | } 76 | 77 | /// Get a map from TIDs to the corresponding extern symbol struct. 78 | /// 79 | /// Only symbols with names contained in `symbols_to_find` are contained in the 80 | /// map. 81 | /// 82 | /// More efficient than [`get_symbol_map`], prefer this if `symbols_to_find` is 83 | /// huge since this is O(|extern_symbols|) and not 84 | /// O(|symbols_to_find|x|extern_symbols|). 85 | pub fn get_symbol_map_fast<'a>( 86 | project: &'a Project, 87 | symbols_to_find: &HashSet, 88 | ) -> HashMap { 89 | project 90 | .program 91 | .term 92 | .extern_symbols 93 | .iter() 94 | .filter_map(|(_tid, symbol)| { 95 | if symbols_to_find.contains(&symbol.name) { 96 | Some((symbol.tid.clone(), symbol)) 97 | } else { 98 | None 99 | } 100 | }) 101 | .collect() 102 | } 103 | 104 | /// Find calls to TIDs contained as keys in the given symbol map. 105 | /// For each match return the block containing the call, 106 | /// the jump term representing the call itself and the symbol corresponding to the TID from the symbol map. 107 | pub fn get_callsites<'a>( 108 | sub: &'a Term, 109 | symbol_map: &HashMap, 110 | ) -> Vec<(&'a Term, &'a Term, &'a ExternSymbol)> { 111 | let mut callsites = Vec::new(); 112 | for blk in sub.term.blocks.iter() { 113 | for jmp in blk.term.jmps.iter() { 114 | if let Jmp::Call { target: dst, .. } = &jmp.term { 115 | if let Some(symbol) = symbol_map.get(dst) { 116 | callsites.push((blk, jmp, *symbol)); 117 | } 118 | } 119 | } 120 | } 121 | callsites 122 | } 123 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/Block.java: -------------------------------------------------------------------------------- 1 | import ghidra.program.model.block.CodeBlock; 2 | import ghidra.program.util.VarnodeContext; 3 | import ghidra.program.model.listing.Listing; 4 | import java.util.ArrayList; 5 | 6 | /** 7 | * Wrapper class for a basic block of pcode instructions. 8 | * 9 | * This class is used for clean and simple serialization. 10 | */ 11 | public class Block { 12 | public String address; 13 | public ArrayList instructions = new ArrayList(); 14 | 15 | public Block(CodeBlock block, VarnodeContext context, Listing listing, DatatypeProperties datatypeProperties) { 16 | this.address = block.getFirstStartAddress().toString(false, false); 17 | for (ghidra.program.model.listing.Instruction instr : listing.getInstructions(block, true)) { 18 | instructions.add(new Instruction(instr, context, datatypeProperties)); 19 | } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/CallingConvention.java: -------------------------------------------------------------------------------- 1 | import ghidra.program.util.VarnodeContext; 2 | import java.util.ArrayList; 3 | 4 | /** 5 | * Wrapper class for a simplified calling convention. 6 | * 7 | * This model is designed for the cwe_checker's calling convention equivalent. 8 | * This class is used for clean and simple serialization. 9 | */ 10 | public class CallingConvention { 11 | private String name; 12 | private ArrayList integer_parameter_register = new ArrayList(); 13 | private ArrayList float_parameter_register = new ArrayList(); 14 | private Varnode integer_return_register = null; 15 | private Varnode float_return_register = null; 16 | private ArrayList unaffected_register = new ArrayList(); 17 | private ArrayList killed_by_call_register = new ArrayList(); 18 | 19 | public CallingConvention(String name, ghidra.program.model.pcode.Varnode[] unaffected_register, ghidra.program.model.pcode.Varnode[] killed_by_call_register, 20 | VarnodeContext context, DatatypeProperties datatypeProperties) { 21 | this.name = name; 22 | for (ghidra.program.model.pcode.Varnode varnode : unaffected_register) { 23 | this.unaffected_register.add(new Varnode(varnode, context, datatypeProperties)); 24 | } 25 | for (ghidra.program.model.pcode.Varnode varnode : killed_by_call_register) { 26 | this.killed_by_call_register.add(new Varnode(varnode, context, datatypeProperties)); 27 | } 28 | } 29 | 30 | public void setIntegerParameterRegister(ArrayList integer_parameter_register) { 31 | this.integer_parameter_register = integer_parameter_register; 32 | } 33 | 34 | public void setFloatParameterRegister(ArrayList float_parameter_register) { 35 | this.float_parameter_register = float_parameter_register; 36 | } 37 | 38 | public void setIntegerReturnRegister(Varnode returnRegister) { 39 | this.integer_return_register = returnRegister; 40 | } 41 | 42 | public void setFloatReturnRegister(Varnode returnRegister) { 43 | this.float_return_register = returnRegister; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/DatatypeProperties.java: -------------------------------------------------------------------------------- 1 | import ghidra.program.model.data.DataOrganization; 2 | 3 | /** 4 | * Wrapper class for datatype properties. 5 | * 6 | * This class is used for clean and simple serialization. 7 | */ 8 | public class DatatypeProperties { 9 | private int char_size; 10 | private int double_size; 11 | private int float_size; 12 | private int integer_size; 13 | private int long_double_size; 14 | private int long_long_size; 15 | private int long_size; 16 | private int pointer_size; 17 | private int short_size; 18 | 19 | public DatatypeProperties(ghidra.program.model.listing.Program currentProgram) { 20 | DataOrganization dataOrga = currentProgram.getDataTypeManager().getDataOrganization(); 21 | 22 | this.char_size = dataOrga.getCharSize(); 23 | this.double_size = dataOrga.getDoubleSize(); 24 | this.float_size = dataOrga.getFloatSize(); 25 | this.integer_size = dataOrga.getIntegerSize(); 26 | this.long_double_size = dataOrga.getLongDoubleSize(); 27 | this.long_long_size = dataOrga.getLongLongSize(); 28 | this.long_size = dataOrga.getLongSize(); 29 | this.pointer_size = dataOrga.getPointerSize(); 30 | this.short_size = dataOrga.getShortSize(); 31 | 32 | } 33 | 34 | public int getPointerSize() { 35 | return this.pointer_size; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/ExternFunction.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.HashMap; 3 | import ghidra.program.model.address.Address; 4 | 5 | /** 6 | * Wrapper class for external Function 7 | * 8 | * 9 | * Thunk functions' addresses are referenced as addresses 10 | */ 11 | public class ExternFunction { 12 | private String name; 13 | private String calling_convention; 14 | private ArrayList parameters = new ArrayList(); 15 | private Varnode return_location; 16 | private ArrayList thunks; 17 | private Boolean has_no_return; 18 | private Boolean has_var_args; 19 | 20 | public ExternFunction(String name, String cconv, ArrayList parameters, 21 | Varnode return_location, Boolean has_no_return, Boolean has_var_args) { 22 | this.name = name; 23 | this.calling_convention = cconv; 24 | this.parameters = parameters; 25 | this.return_location = return_location; 26 | this.has_no_return = has_no_return; 27 | this.has_var_args = has_var_args; 28 | this.thunks = new ArrayList(); 29 | } 30 | 31 | public void add_thunk_function_address(Address address) { 32 | this.thunks.add("0x" + address.toString(false, false)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/Function.java: -------------------------------------------------------------------------------- 1 | import ghidra.util.task.TaskMonitor; 2 | import ghidra.program.util.VarnodeContext; 3 | import ghidra.program.model.block.SimpleBlockModel; 4 | import ghidra.program.model.block.CodeBlock; 5 | import ghidra.program.model.listing.Listing; 6 | import java.util.ArrayList; 7 | import ghidra.util.exception.CancelledException; 8 | 9 | /** 10 | * Wrapper class for Function 11 | * (https://ghidra.re/ghidra_docs/api/ghidra/program/model/listing/Function.html). 12 | * 13 | * This model is designed for the cwe_checker's Sub equivalent. 14 | * This class is used for clean and simple serialization. 15 | */ 16 | public class Function { 17 | public String name; 18 | public String address; 19 | public ArrayList blocks = new ArrayList(); 20 | 21 | public Function(ghidra.program.model.listing.Function function, VarnodeContext context, SimpleBlockModel blockModel, TaskMonitor monitor, 22 | Listing listing, DatatypeProperties datatypeProperties) { 23 | this.address = "0x" + function.getEntryPoint().toString(false, false); 24 | this.name = function.getName(); 25 | try { 26 | for (CodeBlock block : blockModel.getCodeBlocksContaining(function.getBody(), monitor)) { 27 | blocks.add(new Block(block, context, listing, datatypeProperties)); 28 | } 29 | } catch (CancelledException e) { 30 | System.out.printf("Construction of Function object failed"); 31 | e.printStackTrace(); 32 | System.exit(1); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/Instruction.java: -------------------------------------------------------------------------------- 1 | import ghidra.program.util.VarnodeContext; 2 | import ghidra.program.model.symbol.Reference; 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * Wrapper class for a single ISA instruction 7 | * (https://ghidra.re/ghidra_docs/api/ghidra/program/model/listing/Instruction.html). 8 | * 9 | * This model contains the list of pcode instructions representing a single ISA 10 | * instruction. 11 | * This class is used for clean and simple serialization. 12 | */ 13 | public class Instruction { 14 | private String mnemonic; 15 | private String address; 16 | private int size; 17 | private ArrayList terms = new ArrayList(); 18 | private ArrayList potential_targets; 19 | private String fall_through = null; 20 | 21 | 22 | public Instruction(ghidra.program.model.listing.Instruction instruction, VarnodeContext context, DatatypeProperties datatypeProperties) { 23 | this.mnemonic = instruction.toString(); 24 | this.address = "0x" + instruction.getAddressString(false, false); 25 | this.size = instruction.getLength(); 26 | 27 | if (instruction.getFallThrough() != null) { 28 | this.fall_through = "0x" + instruction.getFallThrough().toString(false, false); 29 | } 30 | 31 | 32 | ghidra.program.model.pcode.PcodeOp[] pcodes = instruction.getPcode(true); 33 | for (int i = 0; i < pcodes.length; i++) { 34 | this.terms.add(new Term(this.address, i, pcodes[i], context, datatypeProperties)); 35 | 36 | // add potential targets if instruction contains indirect call or branch. 37 | // Note: All references are put together. Multiple CALLIND or BRANCHIND should not 38 | // occur within a single instruction, but if so, the potential targets are not 39 | // separable. 40 | if ((pcodes[i].getMnemonic() == "CALLIND") || (pcodes[i].getMnemonic() == "BRANCHIND")) { 41 | if (potential_targets == null) { 42 | potential_targets = new ArrayList(); 43 | } 44 | for (Reference ref : instruction.getReferencesFrom()) { 45 | switch (ref.getReferenceType().toString()) { 46 | case "COMPUTED_JUMP": 47 | case "CONDITIONAL_COMPUTED_JUMP": 48 | if (pcodes[i].getMnemonic() == "BRANCHIND") { 49 | potential_targets.add("0x" + ref.getToAddress().toString(false, false)); 50 | } 51 | break; 52 | case "COMPUTED_CALL": 53 | case "CONDITIONAL_COMPUTED_CALL": 54 | if (pcodes[i].getMnemonic() == "CALLIND") { 55 | potential_targets.add("0x" + ref.getToAddress().toString(false, false)); 56 | } 57 | break; 58 | default: 59 | break; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/PcodeOp.java: -------------------------------------------------------------------------------- 1 | import ghidra.program.util.VarnodeContext; 2 | 3 | /** 4 | * Wrapper class for a single pcode operation 5 | * (https://ghidra.re/ghidra_docs/api/ghidra/program/model/pcode/PcodeOp.html). 6 | * 7 | * This model contains all inputs and the output of a single pcode instruction. 8 | * This class is used for clean and simple serialization. 9 | */ 10 | public class PcodeOp { 11 | private int pcode_index; 12 | private String pcode_mnemonic; 13 | private Varnode input0; 14 | private Varnode input1; 15 | private Varnode input2; 16 | private Varnode output; 17 | 18 | public PcodeOp(int pcode_index, ghidra.program.model.pcode.PcodeOp op, VarnodeContext context, DatatypeProperties datatypeProperties) { 19 | this.pcode_index = pcode_index; 20 | this.pcode_mnemonic = op.getMnemonic(); 21 | if (op.getInput(0) != null) { 22 | this.input0 = new Varnode(op.getInput(0), context, datatypeProperties); 23 | } 24 | if (op.getInput(1) != null) { 25 | this.input1 = new Varnode(op.getInput(1), context, datatypeProperties); 26 | } 27 | if (op.getInput(2) != null) { 28 | this.input2 = new Varnode(op.getInput(2), context, datatypeProperties); 29 | } 30 | if (op.getOutput() != null) { 31 | this.output = new Varnode(op.getOutput(), context, datatypeProperties); 32 | } 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/PcodeProject.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.HashMap; 3 | 4 | /** 5 | * Wrapper class for all collected information. 6 | * 7 | * This class is used for clean and simple serialization. 8 | */ 9 | public class PcodeProject { 10 | 11 | private Program program; 12 | private ArrayList register_properties; 13 | private String cpu_arch; 14 | private HashMap external_functions; 15 | private ArrayList entry_points; 16 | private Varnode stack_pointer_register; 17 | private HashMap calling_conventions; 18 | private DatatypeProperties datatype_properties; 19 | private String image_base; 20 | 21 | public PcodeProject(ArrayList functions, 22 | ArrayList register_properties, 23 | String cpu_arch, 24 | HashMap external_functions, 25 | ArrayList entry_points, 26 | Varnode stack_pointer_register, 27 | HashMap calling_conventions, 28 | DatatypeProperties datatype_properties, 29 | String image_base) { 30 | this.program = new Program(functions); 31 | this.register_properties = register_properties; 32 | this.cpu_arch = cpu_arch; 33 | this.external_functions = external_functions; 34 | this.entry_points = entry_points; 35 | this.stack_pointer_register = stack_pointer_register; 36 | this.calling_conventions = calling_conventions; 37 | this.datatype_properties = datatype_properties; 38 | this.image_base = image_base; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/Program.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | 3 | public class Program { 4 | 5 | private ArrayList functions; 6 | 7 | public Program(ArrayList functions) { 8 | this.functions = functions; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/RegisterProperties.java: -------------------------------------------------------------------------------- 1 | import ghidra.program.model.lang.Register; 2 | import ghidra.program.util.VarnodeContext; 3 | import java.util.ArrayList; 4 | import java.lang.Integer; 5 | 6 | public class RegisterProperties { 7 | 8 | private String register_name; 9 | private String base_register; 10 | private String parent_register = null; 11 | private ArrayList children = new ArrayList(); 12 | private long lsbyte_in_base; 13 | private long size; 14 | // Offset into the register address space. 15 | private long address_space_offset; 16 | private long bytes_spanned; 17 | private long bit_length; 18 | private boolean is_zero; 19 | private boolean is_processor_context; 20 | private boolean is_base_register; 21 | private boolean is_big_endian; 22 | 23 | public RegisterProperties(Register register, VarnodeContext context) { 24 | 25 | this.register_name = register.getName(); 26 | this.base_register = register.getBaseRegister().getName(); 27 | if (!register.isBaseRegister()) { 28 | this.parent_register = register.getParentRegister().getName(); 29 | } 30 | for (Register child : register.getChildRegisters()) { 31 | this.children.add(child.getName()); 32 | } 33 | 34 | this.lsbyte_in_base = Integer.toUnsignedLong((int) (register.getLeastSignificantBitInBaseRegister() / 8)); 35 | this.size = Integer.toUnsignedLong(context.getRegisterVarnode(register).getSize()); 36 | 37 | this.address_space_offset = Integer.toUnsignedLong(register.getOffset()); 38 | this.bytes_spanned = Integer.toUnsignedLong(register.getNumBytes()); 39 | this.bit_length = Integer.toUnsignedLong(register.getBitLength()); 40 | 41 | this.is_zero = register.isZero(); 42 | this.is_processor_context = register.isProcessorContext(); 43 | this.is_base_register = register.isBaseRegister(); 44 | this.is_big_endian = register.isBigEndian(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/Term.java: -------------------------------------------------------------------------------- 1 | import ghidra.program.util.VarnodeContext; 2 | 3 | public class Term { 4 | private String address; 5 | private int index; 6 | private PcodeOp operation; 7 | 8 | public Term(String address, 9 | int index, 10 | ghidra.program.model.pcode.PcodeOp operation, 11 | VarnodeContext context, 12 | DatatypeProperties datatypeProperties) { 13 | this.address = address; 14 | this.index = index; 15 | this.operation = new PcodeOp(index, operation, context, datatypeProperties); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ghidra/p_code_extractor/Varnode.java: -------------------------------------------------------------------------------- 1 | import ghidra.program.util.VarnodeContext; 2 | import ghidra.program.model.lang.Register; 3 | 4 | /** 5 | * Wrapper class for Varnode 6 | * (https://ghidra.re/ghidra_docs/api/ghidra/program/model/pcode/Varnode.html). 7 | * 8 | * Varnodes represent registers, stack offset and other values and are used as 9 | * operants for 10 | * pcode instructions. 11 | * This class is used for clean and simple serialization. 12 | */ 13 | public class Varnode { 14 | private int size; 15 | private String address_space; 16 | private String address_space_offset; 17 | private int pointer_size; 18 | private String register_name = null; 19 | private Integer register_size = null; 20 | 21 | public Varnode(ghidra.program.model.pcode.Varnode varnode, VarnodeContext context, DatatypeProperties datatypeProperties) { 22 | this.size = varnode.getSize(); 23 | this.address_space = varnode.getAddress().getAddressSpace().getName(); 24 | this.address_space_offset = varnode.getAddress().toString("0x"); 25 | 26 | this.pointer_size = datatypeProperties.getPointerSize(); 27 | 28 | Register register = context.getRegister(varnode); 29 | if (register != null) { 30 | this.register_name = register.getName(); 31 | this.register_size = context.getRegisterVarnode(register).getSize(); 32 | } 33 | } 34 | 35 | public Varnode(Register register) { 36 | this.size = (int) register.getBitLength() / 8; 37 | this.address_space = register.getAddressSpace().getName(); 38 | this.address_space_offset = register.getAddress().toString("0x"); 39 | // Not needed for register Varnodes. 40 | this.pointer_size = 0; 41 | this.register_name = register.getName(); 42 | this.register_size = this.size; 43 | } 44 | 45 | public String toString() { 46 | return String.format("(%s, %s, %d)", this.address_space, this.address_space_offset, this.size); 47 | } 48 | 49 | public String getRegisterName() { 50 | return this.register_name; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/installer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cwe_checker_install" 3 | version = "0.1.0" 4 | edition = "2021" 5 | rust-version = "1.82" 6 | 7 | [dependencies] 8 | directories = "5.0.1" 9 | walkdir = "2" 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = "1.0" 12 | anyhow = "1.0" # for easy error types 13 | clap = { version = "4.0.32", features = ["derive"] } 14 | -------------------------------------------------------------------------------- /test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "acceptance_tests_ghidra" 3 | version = "0.1.0" 4 | authors = ["Fraunhofer FKIE "] 5 | edition = "2021" 6 | rust-version = "1.82" 7 | 8 | [dependencies] 9 | colored = "2.0" 10 | 11 | [features] 12 | docker = [] 13 | -------------------------------------------------------------------------------- /test/artificial_samples/.dockerignore: -------------------------------------------------------------------------------- 1 | build/*.out 2 | build/*.o 3 | -------------------------------------------------------------------------------- /test/artificial_samples/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | 3 | RUN apt-get -y update \ 4 | && apt-get install -y sudo \ 5 | && useradd -m cwe \ 6 | && echo "cwe:cwe" | chpasswd \ 7 | && adduser cwe sudo \ 8 | && sed -i.bkp -e 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' /etc/sudoers 9 | 10 | USER cwe 11 | 12 | RUN sudo apt-get install python3-pip apt-utils -y 13 | 14 | RUN pip3 install --upgrade pip 15 | 16 | RUN sudo pip3 install scons 17 | 18 | ENV PATH="/home/cwe/.local/bin/:${PATH}" 19 | 20 | COPY --chown=cwe:cwe ./install_cross_compilers.sh . 21 | 22 | RUN ./install_cross_compilers.sh 23 | 24 | COPY --chown=cwe:cwe . /home/cwe/artificial_samples/ 25 | 26 | WORKDIR /home/cwe/artificial_samples/ 27 | -------------------------------------------------------------------------------- /test/artificial_samples/Readme.md: -------------------------------------------------------------------------------- 1 | # Test binaries for the acceptance test suite 2 | 3 | For the acceptance test suite of the *cwe_checker*, 4 | the C-files inside this directory have to be compiled for a variety of CPU architectures and C-compilers. 5 | The provided dockerfile should be used for the build process. 6 | 7 | ## Prerequisites 8 | 9 | - Have Docker or Podman installed on your system 10 | 11 | ## Build commands 12 | 13 | Inside this directory run the following commands: 14 | ```shell 15 | docker build -t cross_compiling . 16 | docker run --rm -v $(pwd)/build:/home/cwe/artificial_samples/build cross_compiling sudo python3 -m SCons 17 | ``` 18 | If instead of Docker you use Podman, it's necessary to add also the `--security-opt label=disable` for mounting the volume: 19 | ```shell 20 | podman build -t cross_compiling . 21 | podman run --rm -v $(pwd)/build:/home/cwe/artificial_samples/build --security-opt label=disable cross_compiling sudo python3 -m SCons 22 | ``` 23 | -------------------------------------------------------------------------------- /test/artificial_samples/arrays.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void heap_based_array(){ 5 | char* a = malloc(20); 6 | 7 | for(int i=0; i<20;i++){ 8 | *(a + i) = 'A'; 9 | } 10 | 11 | free(a); 12 | } 13 | 14 | void stack_based_array(){ 15 | char a[20]; 16 | for(int i=0; i<20;i++){ 17 | a[i] = 'A'; 18 | } 19 | } 20 | 21 | int main(int argc, char *argv[argc]) 22 | { 23 | stack_based_array(); 24 | heap_based_array(); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /test/artificial_samples/c_constructs.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void if_statement(){ 4 | void* bla = malloc(89); 5 | int a = 2; 6 | if (a < 4){ 7 | a = 33; 8 | } 9 | free(bla); 10 | } 11 | 12 | void for_loop(){ 13 | void* bla = malloc(89); 14 | int a = 34; 15 | for(int i = 0;i < 100; i++){ 16 | a += i; 17 | } 18 | free(bla); 19 | } 20 | 21 | void nested_for_loop(){ 22 | void* bla = malloc(89); 23 | int a = 34; 24 | for(int i = 0; i < 100; i++){ 25 | for(int j = 0; j < 200; j++){ 26 | a += i + j; 27 | } 28 | } 29 | free(bla); 30 | } 31 | 32 | int main(){ 33 | if_statement(); 34 | for_loop(); 35 | nested_for_loop(); 36 | } 37 | -------------------------------------------------------------------------------- /test/artificial_samples/check_path.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void simple_check_path_in_function(){ 5 | // read in integer 6 | int myInt; 7 | scanf("%d", &myInt); 8 | 9 | // provoke integer overflow 10 | void *m = malloc(myInt * 8); 11 | 12 | // free data 13 | if (m != NULL) 14 | free(m); 15 | } 16 | 17 | int read_int(){ 18 | int myInt; 19 | scanf("%d", &myInt); 20 | return myInt; 21 | } 22 | 23 | void check_path_across_functions(){ 24 | int i = read_int(); 25 | 26 | // provoke integer overflow 27 | void *m = malloc(i * 8); 28 | 29 | // free data 30 | if (m != NULL) 31 | free(m); 32 | } 33 | 34 | int main(void) 35 | { 36 | simple_check_path_in_function(); 37 | check_path_across_functions(); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_119.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void set_array_elements(int* array) { 5 | for(int i = 0; i<= 10; i++) { 6 | array[i] = i*i; // Out-of-bounds write for arrays that are too small. 7 | } 8 | } 9 | 10 | void print_array_sum(int* array) { 11 | int sum = 0; 12 | for(int i = 0; i<= 10; i++) { 13 | sum += array[i]; // Out-of-bounds read for arrays that are too small. 14 | } 15 | printf("%d\n", sum); 16 | } 17 | 18 | int main() { 19 | int* array = calloc(5, sizeof(int)); 20 | // intraprocedural buffer overflow 21 | for(int i = 0; i<= 10; i++) { 22 | array[i] = i*i; // Out-of-bounds write for arrays that are too small. 23 | } 24 | // interprocedural buffer overflow 25 | set_array_elements(array); 26 | free(array); 27 | 28 | array = malloc(5 * sizeof(int)); 29 | // intraprocedural buffer overflow 30 | int sum = 0; 31 | for(int i = 0; i<= 10; i++) { 32 | sum += array[i]; // Out-of-bounds read for arrays that are too small. 33 | } 34 | printf("%d\n", sum); 35 | // interprocedural buffer overflow 36 | print_array_sum(array); 37 | 38 | puts((void*) array - 1); // Parameter is an out-of-bounds pointer. 39 | free(array); 40 | } -------------------------------------------------------------------------------- /test/artificial_samples/cwe_134.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char **argv){ 4 | char buf[128]; 5 | snprintf(buf,128,argv[1]); 6 | return 0; 7 | } -------------------------------------------------------------------------------- /test/artificial_samples/cwe_190.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define png_t 4242 6 | 7 | // example taken from the book 8 | // "The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities" 9 | // slightly edited 10 | char* make_table(unsigned int width, unsigned int height, char* init_row){ 11 | unsigned int n; 12 | int i; 13 | char* buf; 14 | 15 | n = width * height; 16 | 17 | buf = (char*) malloc(n); 18 | if (!buf) 19 | return NULL; 20 | 21 | for(i=0; i < height; i++){ 22 | memcpy(&buf[i* width], init_row, width); 23 | } 24 | 25 | return buf; 26 | } 27 | 28 | void tassa1(int arg1, int arg2){ 29 | char init_row[] = "init"; 30 | char *res = make_table(arg1, arg2, &init_row); 31 | printf("Table at %p\n", res); 32 | free(res); 33 | } 34 | 35 | int malloc_overflow_get_num_elems(){ 36 | srand(42); 37 | return rand() * 1000000; 38 | } 39 | 40 | 41 | void malloc_overflow(){ 42 | int num_elems = malloc_overflow_get_num_elems(); 43 | void* ptr_elems = malloc(sizeof(png_t) * num_elems); // overflow occurs here 44 | printf("PNG at %p\n", ptr_elems); 45 | free(ptr_elems); 46 | } 47 | 48 | int packet_get_int(){ 49 | return malloc_overflow_get_num_elems(); 50 | } 51 | 52 | char* packet_get_string(){ 53 | return NULL; 54 | } 55 | 56 | // taken from https://cwe.mitre.org/data/definitions/190.html 57 | // slightly edited to make it compile 58 | void overflow_ssh3_1(){ 59 | char** response; 60 | int nresp = packet_get_int(); 61 | if (nresp > 0) { 62 | response = malloc(nresp*sizeof(char*)); 63 | for (int i = 0; i < nresp; i++) 64 | response[i] = packet_get_string(); 65 | free(response); 66 | } 67 | } 68 | 69 | int main(int argc, char *argv[argc]) 70 | { 71 | tassa1(atoi(argv[1]), atoi(argv[2])); 72 | malloc_overflow(); 73 | overflow_ssh3_1(); 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_243.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | 6 | void chroot_fail(){ 7 | chdir("/tmp"); 8 | if (chroot("/tmp") != 0) { 9 | perror("chroot /tmp"); 10 | } 11 | } 12 | 13 | // these are safe according to http://www.unixwiz.net/techtips/chroot-practices.html 14 | void chroot_safe1(){ 15 | chdir("/tmp"); 16 | if (chroot("/tmp") != 0) { 17 | perror("chroot /tmp"); 18 | } 19 | setuid(1077); 20 | } 21 | 22 | void chroot_safe2(){ 23 | chdir("/tmp"); 24 | if (chroot("/tmp") != 0) { 25 | perror("chroot /tmp"); 26 | } 27 | setresuid(1077, 1077, 1077); 28 | } 29 | 30 | void chroot_safe3(){ 31 | chdir("/tmp"); 32 | if (chroot("/tmp") != 0) { 33 | perror("chroot /tmp"); 34 | } 35 | setreuid(1077, 44); 36 | } 37 | 38 | void chroot_safe4(){ 39 | chdir("/tmp"); 40 | if (chroot("/tmp") != 0) { 41 | perror("chroot /tmp"); 42 | } 43 | seteuid(1077); 44 | } 45 | 46 | void chroot_safe5(){ 47 | if (chroot("/tmp") != 0) { 48 | perror("chroot /tmp"); 49 | } 50 | chdir("/"); 51 | } 52 | 53 | int main(void) { 54 | chroot_fail(); 55 | 56 | chroot_safe1(); 57 | chroot_safe2(); 58 | chroot_safe3(); 59 | chroot_safe4(); 60 | chroot_safe5(); 61 | } 62 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_243_clean.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | 6 | // this one is safe according to http://www.unixwiz.net/techtips/chroot-practices.html 7 | void chroot_safe1(){ 8 | chdir("/tmp"); 9 | if (chroot("/tmp") != 0) { 10 | perror("chroot /tmp"); 11 | } 12 | setuid(1077); 13 | } 14 | 15 | void chroot_safe2(){ 16 | chdir("/tmp"); 17 | if (chroot("/tmp") != 0) { 18 | perror("chroot /tmp"); 19 | } 20 | setresuid(1077, 1077, 1077); 21 | } 22 | 23 | void chroot_safe3(){ 24 | chdir("/tmp"); 25 | if (chroot("/tmp") != 0) { 26 | perror("chroot /tmp"); 27 | } 28 | setreuid(1077, 44); 29 | } 30 | 31 | void chroot_safe4(){ 32 | chdir("/tmp"); 33 | if (chroot("/tmp") != 0) { 34 | perror("chroot /tmp"); 35 | } 36 | seteuid(1077); 37 | } 38 | 39 | 40 | int main(void) { 41 | chroot_safe1(); 42 | chroot_safe2(); 43 | chroot_safe3(); 44 | chroot_safe4(); 45 | } 46 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_332.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main () 4 | { 5 | int random_number = rand(); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_337.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]){ 7 | srand(time(NULL)); 8 | return rand(); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_367.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(){ 9 | 10 | if (access("file", W_OK) != 0) { 11 | exit(1); 12 | } 13 | 14 | char* buffer = malloc(6); 15 | if(buffer == NULL){ 16 | exit(1); 17 | } 18 | memset(buffer, 1, 6); 19 | 20 | int fd = open("file", O_WRONLY); 21 | write(fd, buffer, sizeof(buffer)); 22 | 23 | close(fd); 24 | free(buffer); 25 | } 26 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_415.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define BUFSIZE1 512 6 | 7 | void bla(){ 8 | char *buf1R1; 9 | char *buf2R1; 10 | buf1R1 = (char *) malloc(BUFSIZE1); 11 | buf2R1 = (char *) malloc(BUFSIZE1); 12 | free(buf1R1); 13 | free(buf2R1); 14 | free(buf1R1); 15 | } 16 | 17 | int main(int argc, char **argv) { 18 | char *buf1R1; 19 | char *buf2R1; 20 | buf1R1 = (char *) malloc(BUFSIZE1); 21 | buf2R1 = (char *) malloc(BUFSIZE1); 22 | free(buf1R1); 23 | free(buf2R1); 24 | free(buf1R1); 25 | bla(); 26 | } 27 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_416.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define BUFSIZE1 512 6 | 7 | int main(int argc, char **argv) { 8 | char *buf1R1; 9 | char *buf2R1; 10 | buf1R1 = (char *) malloc(BUFSIZE1); 11 | buf2R1 = (char *) malloc(BUFSIZE1); 12 | free(buf1R1); 13 | free(buf2R1); 14 | memset(buf1R1, 0x42, BUFSIZE1); 15 | } 16 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_426.c: -------------------------------------------------------------------------------- 1 | // taken from https://exploit-exercises.com/nebula/level01/ 2 | 3 | #define _GNU_SOURCE 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | void vulnerable_sub(){ 12 | gid_t gid; 13 | uid_t uid; 14 | gid = getegid(); 15 | uid = geteuid(); 16 | 17 | setresgid(gid, gid, gid); 18 | setresuid(uid, uid, uid); 19 | 20 | system("/usr/bin/env echo and now what?"); 21 | } 22 | 23 | int main(int argc, char **argv, char **envp) 24 | { 25 | vulnerable_sub(); 26 | } 27 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_457.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void uninitalized_variable(){ 5 | int a; // never initialized 6 | int b = 7; 7 | int c = a + b; 8 | printf("a is %d, b is %d, c is %d\n", a , b, c); 9 | } 10 | 11 | int main(int argc, char *argv[argc]) 12 | { 13 | uninitalized_variable(); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_467.c: -------------------------------------------------------------------------------- 1 | /* taken from https://cwe.mitre.org/data/definitions/467.html and slightly modified */ 2 | /* Ignore CWE-259 (hard-coded password) and CWE-309 (use of password system for authentication) for this example. */ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define AUTH_SUCCESS 1 9 | #define AUTH_FAIL 0 10 | 11 | char *username = "admin"; 12 | char *pass = "password"; 13 | 14 | int AuthenticateUser(char *inUser, char *inPass) { 15 | printf("Sizeof username = %d\n", sizeof(username)); 16 | printf("Sizeof pass = %d\n", sizeof(pass)); 17 | 18 | if (strncmp(username, inUser, sizeof(username))) { 19 | printf("Auth failure of username using sizeof\n"); 20 | return AUTH_FAIL; 21 | } 22 | /* Because of CWE-467, the sizeof returns 4 on many platforms and architectures. */ 23 | 24 | if (! strncmp(pass, inPass, sizeof(pass))) { 25 | printf("Auth success of password using sizeof\n"); 26 | return AUTH_SUCCESS; 27 | } 28 | else { 29 | printf("Auth fail of password using sizeof\n"); 30 | return AUTH_FAIL; 31 | } 32 | } 33 | 34 | int main (int argc, char **argv) { 35 | int authResult; 36 | 37 | if (argc < 3) { 38 | printf("Usage: Provide a username and password\n"); 39 | exit(1); 40 | } 41 | authResult = AuthenticateUser(argv[1], argv[2]); 42 | if (authResult != AUTH_SUCCESS) { 43 | printf("Authentication failed\n"); 44 | exit(1); 45 | } 46 | else { 47 | printf("Authenticated\n"); 48 | exit(0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_476.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void func1(){ 5 | void* data = malloc(20000); 6 | if (data == NULL){ 7 | exit(42); 8 | } 9 | free(data); 10 | } 11 | 12 | void func2(){ 13 | int* data = malloc(200000); 14 | printf("%i", data[0]); 15 | free(data); 16 | } 17 | 18 | int main() { 19 | 20 | func1(); 21 | func2(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_478.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | #define FAILED 0 6 | #define PASSED 1 7 | 8 | int main(int argc, char *argv[argc]) 9 | { 10 | srand(42); 11 | int result = rand() % 2; 12 | 13 | switch (result) { 14 | case FAILED: 15 | printf("Security check failed!\n"); 16 | exit(-1); 17 | //Break never reached because of exit() 18 | break; 19 | case PASSED: 20 | printf("Security check passed.\n"); 21 | break; 22 | } 23 | 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_560.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void umask_incorrect(){ 9 | umask(0666); 10 | int fd = open("some_random_file", O_CREAT|O_WRONLY, 0666); 11 | close(fd); 12 | } 13 | 14 | void umask_correct(){ 15 | umask(022); 16 | int fd = open("some_random_file", O_CREAT|O_WRONLY, 0666); 17 | close(fd); 18 | } 19 | 20 | int main(){ 21 | umask_correct(); 22 | umask_incorrect(); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_676.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main () 5 | { 6 | char str1[]="Hello World!"; 7 | char str2[40]; 8 | strcpy (str2,str1); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_78.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void constant_system() { 5 | system("ls"); 6 | } 7 | 8 | int main(int argc, char **argv) { 9 | char dest[30] = "usr/bin/cat "; 10 | strcat(dest, argv[1]); 11 | system(dest); 12 | constant_system(); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_782.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main(int argc, char *argv[argc]) 10 | { 11 | 12 | int fd = open("/dev/my_driver", O_WRONLY, O_NONBLOCK); 13 | if (fd == -1){ 14 | printf("Could not open my_driver.\n"); 15 | exit(1); 16 | } 17 | 18 | if (ioctl(fd, 0x42) == -1){ 19 | printf("ioctl failed.\n"); 20 | } 21 | 22 | if (close(fd) == -1){ 23 | printf("Could not properly close my_driver.\n"); 24 | exit(1); 25 | } 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /test/artificial_samples/cwe_789.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char* argv[]){ 6 | char buff[0xF4250]; // dec: 1000016 7 | malloc(0xF4250); 8 | 9 | return 0; 10 | } -------------------------------------------------------------------------------- /test/artificial_samples/install_cross_compilers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | expected_version="18.04" 4 | actual_version=`lsb_release -r | awk '{ print $2 }'` 5 | 6 | echo "Installing cross compiler for Portable Executable x86/x86_64" 7 | sudo apt install -y mingw-w64 8 | 9 | 10 | echo "Installting multilibs for gcc and g++" 11 | sudo apt install -y gcc-multilib g++-multilib 12 | if [ "$expected_version" != "$actual_version" ]; then 13 | echo "Installing cross compiler for ELF x86 architecture." 14 | sudo apt install -y gcc-i686-linux-gnu g++-i686-linux-gnu 15 | fi 16 | echo "Installing cross compiler for ELF ARM architecture." 17 | sudo apt install -y gcc-arm-linux-gnueabi g++-arm-linux-gnueabi 18 | sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu 19 | echo "Installing cross compiler for ELF MIPS architecture." 20 | sudo apt install -y gcc-mips-linux-gnu g++-mips-linux-gnu 21 | sudo apt install -y gcc-mipsel-linux-gnu g++-mipsel-linux-gnu 22 | sudo apt install -y gcc-mips64-linux-gnuabi64 g++-mips64-linux-gnuabi64 23 | sudo apt install -y gcc-mips64el-linux-gnuabi64 g++-mips64el-linux-gnuabi64 24 | echo "Installing cross compiler for ELF PPC architecture." 25 | sudo apt install -y gcc-powerpc-linux-gnu g++-powerpc-linux-gnu 26 | sudo apt install -y gcc-powerpc64-linux-gnu g++-powerpc64-linux-gnu 27 | sudo apt install -y gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu 28 | echo "Installing cross compiler for ELF RISCV architecture." 29 | sudo apt install -y gcc-riscv64-linux-gnu g++-riscv64-linux-gnu 30 | 31 | echo "Installing llvm compiler backend" 32 | sudo apt install -y llvm llvm-10 33 | echo "Installing clang compiler frontend" 34 | sudo apt install -y clang clang-10 35 | 36 | sudo ln -s /usr/include/asm-generic /usr/include/asm 37 | sudo ln -s /usr/riscv64-linux-gnu/include/gnu/stubs-lp64d.h /usr/riscv64-linux-gnu/include/gnu/stubs-lp64.h 38 | 39 | echo "Cleaning package index." 40 | sudo apt-get clean && sudo rm -rf /var/cache/apt/archives /var/lib/apt/lists/* 41 | 42 | echo "Done." 43 | -------------------------------------------------------------------------------- /test/artificial_samples/memory_access.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void uninitalized_variable(){ 5 | int a; // never initialized 6 | int b = 7; 7 | int c = a + b; 8 | printf("a is %d, b is %d, c is %d\n", a , b, c); 9 | } 10 | 11 | int main(int argc, char *argv[argc]) 12 | { 13 | uninitalized_variable(); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /test/bare_metal_samples/test_sample.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fkie-cad/cwe_checker/3c042e6f0a795d32b9dca2591ef035b93281da0a/test/bare_metal_samples/test_sample.bin -------------------------------------------------------------------------------- /test/lkm_samples/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /test/lkm_samples/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:noble as builder 2 | 3 | # This container is used to build the sample kernel modules. 4 | 5 | # Kernel release to build the modules against. When changing the minor or 6 | # major release make sure to update the curl command below. 7 | ARG LX_VER="6.7.6" 8 | 9 | # Install tools required to build a kernel. 10 | RUN set -x && \ 11 | echo 'debconf debconf/frontend select Noninteractive' | \ 12 | debconf-set-selections && \ 13 | apt-get update && \ 14 | apt-get install -y -q apt-utils dialog && \ 15 | apt-get install -y -q \ 16 | bc \ 17 | bison \ 18 | bsdmainutils \ 19 | clang \ 20 | clang-tools \ 21 | curl \ 22 | flex \ 23 | git \ 24 | libelf-dev \ 25 | libncurses5-dev \ 26 | libssl-dev \ 27 | lld \ 28 | llvm \ 29 | make \ 30 | sparse \ 31 | sudo && \ 32 | apt-get clean && \ 33 | rm -rf /var/lib/apt/lists/* 34 | 35 | RUN set -x && \ 36 | useradd -m cwe && \ 37 | echo "cwe:cwe" | \ 38 | chpasswd && \ 39 | adduser cwe sudo && \ 40 | sed -i.bkp -e 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' /etc/sudoers 41 | 42 | USER cwe 43 | 44 | # Download the kernel. 45 | WORKDIR /home/cwe 46 | RUN set -x && \ 47 | curl https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${LX_VER}.tar.gz -o linux.tar.gz && \ 48 | tar xf linux.tar.gz && \ 49 | mv linux-${LX_VER} linux 50 | 51 | # Build a minimal kernel with support for external modules. 52 | WORKDIR /home/cwe/linux 53 | ENV ARCH=arm64 54 | ENV LLVM=1 55 | COPY modules.config.fragment . 56 | COPY debug.config.fragment . 57 | RUN set -x && \ 58 | make allnoconfig && \ 59 | ./scripts/kconfig/merge_config.sh -n -m .config debug.config.fragment modules.config.fragment && \ 60 | make -j$(nproc) Image modules modules_prepare 61 | 62 | # Build our sample modules. 63 | WORKDIR /home/cwe/build 64 | COPY *.c . 65 | COPY Makefile . 66 | ENV KBUILD_VERBOSE=1 67 | RUN set -x && \ 68 | mkdir build && \ 69 | make all && \ 70 | for m in $(find . -name '*.ko'); do cp $m "build/${m%.*}_aarch64_clang.ko"; done 71 | 72 | # Copy into a new Docker image to save space. 73 | FROM scratch 74 | COPY --from=builder /home/cwe/build/build /build 75 | -------------------------------------------------------------------------------- /test/lkm_samples/Makefile: -------------------------------------------------------------------------------- 1 | obj-m += cwe_252.o 2 | obj-m += cwe_467.o 3 | obj-m += cwe_476.o 4 | obj-m += cwe_676.o 5 | 6 | all: 7 | make LLVM=1 ARCH=arm64 -C /home/cwe/linux M=$(PWD) modules 8 | 9 | clean: 10 | make LLVM=1 ARCH=arm64 -C /home/cwe/linux M=$(PWD) clean 11 | -------------------------------------------------------------------------------- /test/lkm_samples/Readme.md: -------------------------------------------------------------------------------- 1 | # Test Linux Kernel Modules for the Acceptance Test Suite 2 | 3 | For the acceptance test suite of the `cwe_checker`, the C-files inside this 4 | directory have to be compiled for a variety of CPU architectures and 5 | C-compilers. The provided script should be used for the build process. 6 | 7 | ## Prerequisites 8 | 9 | - Have Docker installed on your system. 10 | 11 | ## Build commands 12 | 13 | Inside this directory run the following commands: 14 | ```shell 15 | ./build.sh 16 | ``` 17 | -------------------------------------------------------------------------------- /test/lkm_samples/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Script that builds the sample kernel modules and places them in ./build. 4 | 5 | set -xeuo pipefail 6 | 7 | DOCKER="docker" 8 | NAME=lkm_samples 9 | 10 | $DOCKER build --progress plain -t ${NAME} . 11 | 12 | rm -rf build/ 13 | 14 | # Create a dummy container, copy the modules, and delete it. 15 | ID=$($DOCKER create ${NAME} /does/not/exist) 16 | $DOCKER cp ${ID}:/build . 17 | $DOCKER rm ${ID} 18 | chown $(id -u):$(id -g) -R ./build 19 | -------------------------------------------------------------------------------- /test/lkm_samples/cwe_252.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void cwe252_intra_h_lost(void* uptr) 11 | { 12 | char buf[10]; 13 | long *ret_store = kmalloc(8, GFP_KERNEL); 14 | 15 | WRITE_ONCE(*ret_store, strncpy_from_user(buf, uptr, sizeof(buf))); // CWE_WARNING, 1 16 | 17 | pr_info("Call some func\n"); 18 | 19 | WRITE_ONCE(*ret_store, 0); // CWE_WARNING, 2, reason=empty_state 20 | 21 | pr_info("buf: %s\n", buf); 22 | } 23 | 24 | static int __init test_init(void) 25 | { 26 | pr_info("Hello, World\n"); 27 | 28 | return 0; 29 | } 30 | 31 | static void __exit test_exit(void) 32 | { 33 | pr_info("Goodbye, World\n"); 34 | } 35 | 36 | MODULE_LICENSE("GPL v2"); 37 | MODULE_AUTHOR("Valentin Obst"); 38 | 39 | module_init(test_init); 40 | module_exit(test_exit); 41 | -------------------------------------------------------------------------------- /test/lkm_samples/cwe_467.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | const char *long_string = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; 9 | char buf[10]; 10 | 11 | static int simple_sizeof_ptr_02(void) 12 | { 13 | char *ptr = kmalloc(0x10, __GFP_ZERO); 14 | 15 | strncpy(ptr, long_string, sizeof(ptr)); 16 | 17 | return 42; 18 | } 19 | 20 | static int simple_sizeof_ptr_01(void) 21 | { 22 | strncpy(buf, long_string, sizeof(&buf)); 23 | 24 | return 42; 25 | } 26 | 27 | static int __init test_init(void) 28 | { 29 | pr_info("Hello, World\n"); 30 | 31 | simple_sizeof_ptr_01(); 32 | simple_sizeof_ptr_02(); 33 | 34 | return 0; 35 | } 36 | 37 | static void __exit test_exit(void) 38 | { 39 | pr_info("Goodbye, World\n"); 40 | } 41 | 42 | MODULE_LICENSE("GPL v2"); 43 | MODULE_AUTHOR("Valentin Obst"); 44 | 45 | module_init(test_init); 46 | module_exit(test_exit); 47 | -------------------------------------------------------------------------------- /test/lkm_samples/cwe_476.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | static int simple_null_deref(void) 8 | { 9 | char *ptr = kmalloc(0x42, __GFP_ZERO); 10 | 11 | pr_info("%c\n", *ptr); 12 | 13 | return 42; 14 | } 15 | 16 | static int simple_not_null_deref(void) 17 | { 18 | char *ptr = kmalloc(0x42, __GFP_ZERO); 19 | 20 | if (!ptr) 21 | return 1337; 22 | 23 | pr_info("%c\n", *ptr); 24 | 25 | return 42; 26 | } 27 | 28 | static int __init test_init(void) 29 | { 30 | pr_info("Hello, World\n"); 31 | 32 | simple_not_null_deref(); 33 | simple_null_deref(); 34 | 35 | return 0; 36 | } 37 | 38 | static void __exit test_exit(void) 39 | { 40 | pr_info("Goodbye, World\n"); 41 | } 42 | 43 | MODULE_LICENSE("GPL v2"); 44 | MODULE_AUTHOR("Valentin Obst"); 45 | 46 | module_init(test_init); 47 | module_exit(test_exit); 48 | -------------------------------------------------------------------------------- /test/lkm_samples/cwe_676.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static char *simple_strlen_strcpy(const char *msg) 9 | { 10 | char *ptr = kmalloc(strlen(msg), __GFP_ZERO); 11 | 12 | if (!ptr) 13 | return NULL; 14 | 15 | strcpy(ptr, msg); 16 | 17 | return ptr; 18 | } 19 | 20 | static int __init test_init(void) 21 | { 22 | pr_info("Hello, World\n"); 23 | 24 | char *msg = (char *)((unsigned long)THIS_MODULE + 0x1337); 25 | 26 | pr_info("%s\n", simple_strlen_strcpy(msg)); 27 | 28 | return 0; 29 | } 30 | 31 | static void __exit test_exit(void) 32 | { 33 | pr_info("Goodbye, World\n"); 34 | } 35 | 36 | MODULE_LICENSE("GPL v2"); 37 | MODULE_AUTHOR("Valentin Obst"); 38 | 39 | module_init(test_init); 40 | module_exit(test_exit); 41 | -------------------------------------------------------------------------------- /test/lkm_samples/debug.config.fragment: -------------------------------------------------------------------------------- 1 | # 2 | # Compile-time checks and compiler options 3 | # 4 | CONFIG_DEBUG_KERNEL=y 5 | CONFIG_DEBUG_INFO=y 6 | CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y 7 | CONFIG_DEBUG_INFO_REDUCED=n 8 | CONFIG_DEBUG_INFO_COMPRESSED_NONE=y 9 | CONFIG_DEBUG_INFO_SPLIT=n 10 | -------------------------------------------------------------------------------- /test/lkm_samples/modules.config.fragment: -------------------------------------------------------------------------------- 1 | CONFIG_ASM_MODVERSIONS=y 2 | CONFIG_MODULES=y 3 | CONFIG_MODULE_COMPRESS_NONE=y 4 | CONFIG_MODULE_SIG=y 5 | CONFIG_MODULE_SIG_ALL=y 6 | CONFIG_MODULE_SIG_FORCE=y 7 | CONFIG_MODULE_SIG_FORMAT=y 8 | CONFIG_MODULE_SIG_HASH="sha512" 9 | CONFIG_MODULE_SIG_SHA512=y 10 | CONFIG_MODULE_SRCVERSION_ALL=y 11 | CONFIG_MODULE_UNLOAD=y 12 | CONFIG_MODVERSIONS=y 13 | --------------------------------------------------------------------------------