├── .gitignore ├── README.md ├── build_all.sh ├── example-report ├── README.md └── report.zip ├── examples ├── .gitignore ├── build_all_projects.sh ├── build_all_web_only.sh ├── build_simple_examples.sh ├── cpp-simple-example-1 │ ├── .gitignore │ ├── build_all.sh │ └── fuzzer.cpp ├── dng_sdk │ ├── .gitignore │ └── build_all.sh ├── htslib │ ├── .gitignore │ └── build_all.sh ├── simple-example-0 │ ├── .gitignore │ ├── build_all.sh │ └── fuzzer.c ├── simple-example-1 │ ├── .gitignore │ ├── build_all.sh │ └── fuzzer.c ├── simple-example-2 │ ├── .gitignore │ ├── build_all.sh │ └── fuzzer.c ├── simple-example-3 │ ├── .gitignore │ ├── build_all.sh │ └── fuzzer.c ├── simple-example-4 │ ├── .gitignore │ ├── build_all.sh │ └── fuzzer.c └── simple-example-indirect-pointers │ ├── .gitignore │ ├── build_all.sh │ └── fuzzer.c ├── img ├── functions_overview.png ├── overlay-1.png ├── overlay-2.png └── project_overview.png ├── llvm ├── include │ └── llvm │ │ └── Transforms │ │ └── Inspector │ │ └── Inspector.h └── lib │ └── Transforms │ └── Inspector │ ├── CMakeLists.txt │ └── Inspector.cpp ├── oss_fuzz_integration ├── .gitignore ├── README.md ├── build_patched_oss_fuzz.sh ├── get_full_coverage.py ├── oss-fuzz-patches.diff └── run_both.sh ├── post-processing ├── .gitignore ├── __init__.py ├── fuzz_analysis.py ├── fuzz_data_loader.py ├── fuzz_html.py ├── main.py ├── run_for_dir.sh └── styling │ ├── clike.js │ ├── custom.js │ ├── prism.css │ ├── prism.js │ └── styles.css ├── rebuild_pass.sh └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | tmp 3 | *.swp 4 | *.out 5 | *.err 6 | *.profile 7 | *fuzz_report.html 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fuzz introspector 2 | 3 | High-level goals: 4 | - Show fuzzing-relevant data about each function in a given project 5 | - Show reachability of fuzzer(s) 6 | - Integrate seamlessly with OSS-Fuzz 7 | - Show visualisations to enable fuzzer debugging 8 | - Give suggestions for how to improve fuzzing 9 | 10 | ## Testing with OSS-Fuzz 11 | The recommended way of testing this project is by way of OSS-Fuzz. Please see 12 | [OSS-Fuzz instructions](oss_fuzz_integration/) on how to do this. 13 | 14 | 15 | ## Testing without OSS-Fuzz integration 16 | You can also build and run the introspector outside the OSS-Fuzz environment. 17 | 18 | We use this mainly to develop the LLVM LTO pass as compilation of clang goes 19 | faster (recompilation in particular). However, for the full experience we 20 | recommend working in the OSS-Fuzz environment as described above. 21 | 22 | A complication with testing locally is that the full end-to-end process of 23 | both (1) building fuzzers; (2) running them; (3) building with coverage; and 24 | (4) building with introspector analysis, is better supported 25 | in the OSS-Fuzz environment. 26 | 27 | 28 | ### Build locally 29 | 30 | #### Start a python venv 31 | 1. Create a venv: `python3 -m venv /path/to/new/virtual/environment` 32 | 2. Activate the venv 33 | 3. Install dependencies with `pip install -r requirements.txt` 34 | 35 | #### Build custom clang 36 | (expect this part to take at least 1 hour) 37 | ``` 38 | git clone https://github.com/AdaLogics/fuzz-introspector 39 | cd fuzz-introspector 40 | ./build_all.sh 41 | ``` 42 | 43 | #### Run local examples 44 | After having built the custom clang above, you can try an example: 45 | ``` 46 | cd examples 47 | ./build_simple_examples.sh 48 | cd simple-example-4/web 49 | python3 -m http.server 5002 50 | ``` 51 | 52 | You can also use the `build_all_projects.sh` and `build_all_web_only.sh` scripts to control 53 | which examples you want to build as well as whether you want to only build the web data. 54 | 55 | 56 | ## Output 57 | 58 | The output of the introspector is a HTML report that gives data about your fuzzer. This includes: 59 | 60 | - An overview of reachability by all fuzzers in the repository 61 | - A table with detailed information about each fuzzer in the repository, e.g. number of functions reached, complexity covered and more. 62 | - A table with overview of all functions in the project. With information such as 63 | - Number of fuzzers that reaches this function 64 | - Cyclomatic complexity of this function and all functions reachable by this function 65 | - Number of functions reached by this function 66 | - The amount of undiscovered complexity in this function. Undiscovered complexity is the complexity *not* covered by any fuzzers. 67 | - A call reachability tree for each fuzzer in the project. The reachability tree shows the potential control-flow of a given fuzzer 68 | - An overlay of the reachability tree with coverage collected from a fuzzer run. 69 | - A table giving summary information about which targets are optimal targets to analyse for a fuzzer of the functions that are not being reached by any fuzzer. 70 | - A list of suggestions for new fuzzers (this is super naive at the moment). 71 | 72 | ### Example output 73 | 74 | Here we show a few images from the output report: 75 | 76 | Project overview: 77 | 78 | ![project overview](/img/project_overview.png) 79 | 80 | 81 | Table with data of all functions in a project. The table is sortable to make enhance the process of understanding the fuzzer-infrastructure of a given project: 82 | 83 | ![Functions table](/img/functions_overview.png) 84 | 85 | Reachability tree with coverage overlay 86 | 87 | ![Overlay 1](/img/overlay-1.png) 88 | 89 | 90 | Reachability tree with coverage overlay, showing where a fuzz-blocker is occurring 91 | ![Overlay 2](/img/overlay-2.png) 92 | -------------------------------------------------------------------------------- /build_all.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | ################################################################################ 16 | 17 | BASE=$PWD 18 | mkdir build 19 | cd build 20 | BUILD_BASE=$PWD 21 | 22 | # Build binutils 23 | git clone --depth 1 git://sourceware.org/git/binutils-gdb.git binutils 24 | mkdir build 25 | cd ./build 26 | ../binutils/configure --enable-gold --enable-plugins --disable-werror 27 | make all-gold 28 | cd ${BUILD_BASE} 29 | 30 | # Now build LLVM 31 | git clone https://github.com/llvm/llvm-project/ 32 | cd llvm-project 33 | git checkout 43f34f58349ae178fd1c95d6a73c6858f35f2ea1 34 | 35 | cd ${BUILD_BASE} 36 | 37 | # Now copy over the LLVM code we have 38 | # This includes our inspector pass and the files included. 39 | cp -rf ${BASE}/llvm/include/llvm/Transforms/Inspector/ ./llvm-project/llvm/include/llvm/Transforms//Inspector 40 | cp -rf ${BASE}/llvm/lib/Transforms/Inspector ./llvm-project/llvm/lib/Transforms/Inspector 41 | 42 | 43 | # Apply changes in the existing LLVM code. This is only 44 | # to get our code integrated directly into Clang. 45 | echo "add_subdirectory(Inspector)" >> ./llvm-project/llvm/lib/Transforms/CMakeLists.txt 46 | sed -i 's/whole-program devirtualization and bitset lowering./whole-program devirtualization and bitset lowering.\nPM.add(createInspectorPass());/g' ./llvm-project/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp 47 | sed -i 's/using namespace/#include "llvm\/Transforms\/Inspector\/Inspector.h"\nusing namespace/g' ./llvm-project/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp 48 | 49 | sed -i 's/Instrumentation/Instrumentation\n Inspector/g' ./llvm-project/llvm/lib/Transforms/IPO/CMakeLists.txt 50 | 51 | # Build LLVM 52 | mkdir llvm-build 53 | cd llvm-build 54 | cmake -G "Unix Makefiles" -DLLVM_ENABLE_PROJECTS="clang;compiler-rt" \ 55 | -DLLVM_BINUTILS_INCDIR=../binutils/include \ 56 | -DLLVM_TARGETS_TO_BUILD="X86" ../llvm-project/llvm/ 57 | make llvm-headers 58 | make -j5 59 | make 60 | -------------------------------------------------------------------------------- /example-report/README.md: -------------------------------------------------------------------------------- 1 | # Example report 2 | This folder contains an example report. 3 | 4 | To see the report in your own browser simply unzip the report.zip file, start a webserver in the unzipped folder `python3 -m http.server 8001` and then navigate to `http://localhost:8001/fuzz_report.html` from your browser. 5 | 6 | To reproduce this report you can follow the instructions of OSS-Fuzz integration and then launching an analysis using `run_both.sh` with the params `htslib 400` (analyse the htslib project and run a fuzzer for 400 seconds). 7 | -------------------------------------------------------------------------------- /example-report/report.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdaLogics/fuzz-introspector/4b7ae6f40528affd8d49f21068e67fd36af00d2c/example-report/report.zip -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | web 2 | work 3 | -------------------------------------------------------------------------------- /examples/build_all_projects.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | ################################################################################ 16 | 17 | ROOT=$PWD 18 | for PROJ in htslib dng_sdk; do 19 | cd ${ROOT}/${PROJ} 20 | rm -rf ./web 21 | 22 | ./build_all.sh 23 | mkdir web 24 | cd web 25 | python3 ${ROOT}/../post-processing/main.py --target_dir=../ 26 | done 27 | -------------------------------------------------------------------------------- /examples/build_all_web_only.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | ################################################################################ 16 | 17 | ROOT=$PWD 18 | 19 | for PROJ in htslib dng_sdk ; do 20 | cd ${ROOT}/${PROJ} 21 | rm -rf ./web 22 | 23 | #./build_all.sh 24 | mkdir web 25 | cd web 26 | python3 ${ROOT}/../post-processing/main.py ../ 27 | done 28 | -------------------------------------------------------------------------------- /examples/build_simple_examples.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | ################################################################################ 16 | 17 | ROOT=$PWD 18 | 19 | #for PROJ in simple-example-0 simple-example-1 simple-example-2 simple-example-3 simple-example-4 simple-example-indirect-pointers; do 20 | #for PROJ in simple-example-0; do 21 | for PROJ in cpp-simple-example-1; do 22 | cd ${ROOT}/${PROJ} 23 | rm -rf ./web 24 | 25 | ./build_all.sh 26 | mkdir web 27 | cd web 28 | python3 ${ROOT}/../post-processing/main.py --target_dir=../ 29 | done 30 | -------------------------------------------------------------------------------- /examples/cpp-simple-example-1/.gitignore: -------------------------------------------------------------------------------- 1 | *.data 2 | web 3 | fuzzer 4 | fuzzer.o 5 | *.ll 6 | *.bc 7 | -------------------------------------------------------------------------------- /examples/cpp-simple-example-1/build_all.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./work 2 | mkdir work 3 | cd work 4 | 5 | echo "[+] Linking the projects" 6 | ../../../build/llvm-build/bin/clang++ -v -fsanitize=fuzzer-no-link -g -c -flto ../fuzzer.cpp -o fuzzer.o 7 | echo "dos" 8 | ../../../build/llvm-build/bin/clang++ -v -fsanitize=fuzzer -g -flto fuzzer.o -o fuzzer 9 | echo "dres" 10 | -------------------------------------------------------------------------------- /examples/cpp-simple-example-1/fuzzer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class B 4 | { 5 | public: 6 | virtual void bar(); 7 | virtual void qux(); 8 | }; 9 | 10 | void B::bar() 11 | { 12 | std::cout << "This is B's implementation of bar" << std::endl; 13 | } 14 | 15 | void B::qux() 16 | { 17 | std::cout << "This is B's implementation of qux" << std::endl; 18 | } 19 | 20 | 21 | class C : public B 22 | { 23 | public: 24 | void bar() override; 25 | }; 26 | 27 | void C::bar() 28 | { 29 | std::cout << "This is C's implementation of bar" << std::endl; 30 | } 31 | 32 | 33 | 34 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 35 | B* b = new C(); 36 | b->bar(); 37 | } 38 | -------------------------------------------------------------------------------- /examples/dng_sdk/.gitignore: -------------------------------------------------------------------------------- 1 | dng_sdk 2 | work 3 | web 4 | -------------------------------------------------------------------------------- /examples/dng_sdk/build_all.sh: -------------------------------------------------------------------------------- 1 | BASE=$PWD 2 | CLANG_BASE=${BASE}/../../build/llvm-build/bin/ 3 | export PATH=${CLANG_BASE}:${PATH} 4 | 5 | export CC=clang 6 | export CXX=clang++ 7 | 8 | export CFLAGS="$CFLAGS -fsanitize=fuzzer-no-link -fcommon -g -flto " 9 | export CXXFLAGS="$CXXFLAGS -fsanitize=fuzzer-no-link -fcommon -g -flto " 10 | export LIB_FUZZING_ENGINE="-fsanitize=fuzzer " 11 | export LDFLAGS="-fuse-ld=gold" 12 | export AR=llvm-ar 13 | export RANLIB=llvm-ranlib 14 | export CC=clang 15 | export CXX=clang++ 16 | export SRC=$PWD 17 | 18 | 19 | cd ${BASE} 20 | rm -rf ./work 21 | mkdir work 22 | cd work 23 | 24 | 25 | # OSS-Fuzz build. 26 | git clone https://android.googlesource.com/platform/external/dng_sdk/ 27 | cd dng_sdk 28 | 29 | # build project 30 | cd ./source 31 | rm dng_xmp* 32 | find . -name "*.cpp" -exec $CXX $CXXFLAGS -DqDNGUseLibJPEG=1 -DqDNGUseXMP=0 -DqDNGThreadSafe=1 -c {} \; 33 | ${AR} cr libdns_sdk.a *.o 34 | 35 | echo "[+] Now building the fuzzer" 36 | # compile fuzzer 37 | $CXX $CXXFLAGS $LIB_FUZZING_ENGINE ../fuzzer/dng_parser_fuzzer.cpp -o dng_parser_fuzzer \ 38 | ./libdns_sdk.a -I./ -l:libjpeg.a -lz 39 | -------------------------------------------------------------------------------- /examples/htslib/.gitignore: -------------------------------------------------------------------------------- 1 | htslib 2 | web 3 | work 4 | -------------------------------------------------------------------------------- /examples/htslib/build_all.sh: -------------------------------------------------------------------------------- 1 | BASE=$PWD 2 | CLANG_BASE=${BASE}/../../build/llvm-build/bin/ 3 | export PATH=${CLANG_BASE}:${PATH} 4 | 5 | export CC=clang 6 | export CXX=clang++ 7 | 8 | export CFLAGS="$CFLAGS -fsanitize=fuzzer-no-link -fcommon -g -flto " 9 | export CXXFLAGS="$CXXFLAGS -fsanitize=fuzzer-no-link -fcommon -g -flto " 10 | export LIB_FUZZING_ENGINE="-fsanitize=fuzzer " 11 | export LDFLAGS="-fuse-ld=gold" 12 | export AR=llvm-ar 13 | export RANLIB=llvm-ranlib 14 | export CC=clang 15 | export CXX=clang++ 16 | export SRC=$PWD 17 | 18 | 19 | cd ${BASE} 20 | rm -rf ./work 21 | mkdir work 22 | cd work 23 | 24 | 25 | # OSS-Fuzz build. 26 | git clone --depth 1 --shallow-submodules --recurse-submodules https://github.com/samtools/htslib 27 | cd htslib 28 | 29 | # build project 30 | autoconf 31 | autoheader 32 | ./configure 33 | make -j$(nproc) libhts.a 34 | make test/fuzz/hts_open_fuzzer.o 35 | 36 | echo "[+] Now building the fuzzer" 37 | 38 | # build fuzzers 39 | $CXX $CXXFLAGS test/fuzz/hts_open_fuzzer.o $LIB_FUZZING_ENGINE libhts.a -lz -lbz2 -llzma -lcurl -lcrypto -lpthread 40 | -------------------------------------------------------------------------------- /examples/simple-example-0/.gitignore: -------------------------------------------------------------------------------- 1 | fuzzerLogFile* 2 | fuzzer 3 | *.o 4 | -------------------------------------------------------------------------------- /examples/simple-example-0/build_all.sh: -------------------------------------------------------------------------------- 1 | 2 | rm -rf ./work 3 | mkdir work 4 | cd work 5 | 6 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer -flto -g ../fuzzer.c -o fuzzer 7 | -------------------------------------------------------------------------------- /examples/simple-example-0/fuzzer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | int unreached_target2(const uint8_t *data) { 7 | return 5; 8 | } 9 | 10 | 11 | int unreached_target1(const uint8_t *data) { 12 | if (data[0] == 0x11) { 13 | return unreached_target2(data); 14 | } 15 | char *mc = (char*)malloc(12); 16 | if (data[0] == 0x12) { 17 | return 0; 18 | } 19 | return 5; 20 | } 21 | 22 | int un(char *n1, char *n2, char *n3, char *n5, size_t s1) { 23 | return 0; 24 | } 25 | 26 | int unreached_target3(const uint8_t *data, size_t *theval) { 27 | if (data[0] == 0x11) { 28 | return unreached_target1(data); 29 | } 30 | 31 | return 5; 32 | } 33 | 34 | char *d = {0x12}; 35 | int target2(const uint8_t *data) { 36 | if (data[0] == 0x41) return 1; 37 | unreached_target1(d); 38 | return 2; 39 | } 40 | 41 | int target3(const uint8_t *data) { 42 | if (data[0] == 0x42) return 4; 43 | return 3; 44 | } 45 | 46 | int fuzz_entry(const uint8_t *data, size_t size) { 47 | int ret; 48 | if (size == 2) { 49 | ret = target2(data); 50 | } 51 | else if (size == 3) { 52 | ret = target3(data); 53 | } 54 | else { 55 | ret = 1; 56 | } 57 | return ret; 58 | } 59 | 60 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 61 | char *kldfj = (char*)malloc(123); 62 | fuzz_entry(data, size); 63 | return 0; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /examples/simple-example-1/.gitignore: -------------------------------------------------------------------------------- 1 | *.data 2 | web 3 | fuzzer 4 | fuzzer.o 5 | -------------------------------------------------------------------------------- /examples/simple-example-1/build_all.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./work 2 | mkdir work 3 | cd work 4 | 5 | echo "[+] Linking the projects" 6 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer-no-link -g -c -flto ../fuzzer.c -o fuzzer.o 7 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer -g -flto fuzzer.o -o fuzzer 8 | -------------------------------------------------------------------------------- /examples/simple-example-1/fuzzer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int 6 | compare_mem(const uint8_t *data) { 7 | if (data[0] == 'A') return 1; 8 | return 0; 9 | } 10 | 11 | void 12 | parse_callback1(const uint8_t *data, size_t size) { 13 | printf("We are now in callback\n"); 14 | 15 | if (size < 3) return; 16 | if (compare_mem(data) == 0) printf("H3\n"); 17 | 18 | printf("H5\n"); 19 | } 20 | 21 | 22 | void 23 | parse_callback2(const uint8_t *data, size_t size, size_t *thep) { 24 | printf("We are now in callback\n"); 25 | 26 | if (size < 3) return; 27 | if (data[0] == 'A') printf("H1\n"); 28 | 29 | printf("H2\n"); 30 | } 31 | 32 | 33 | int 34 | parse1(const uint8_t *data, size_t size) { 35 | if (size < 10) return 0; 36 | parse_callback1(data, size); 37 | return 0; 38 | } 39 | 40 | int 41 | parse2(const uint8_t *data, size_t size) { 42 | int total_val = 0; 43 | if (size == 1) total_val++; 44 | if (size == 2) total_val++; 45 | if (size == 3) total_val++; 46 | if (size == 4) total_val++; 47 | 48 | if (size < 5) { 49 | return total_val; 50 | } 51 | 52 | if (data[0] == 'A' && data[1] == 'R') total_val += 2; 53 | if (data[0] == 'B' && data[1] == 'Q') total_val += 2; 54 | if (data[0] == 'C' && data[1] == 'P') total_val += 2; 55 | if (data[0] == 'D' && data[1] == 'O') total_val += 2; 56 | if (data[0] == 'E' && data[1] == 'N') total_val += 2; 57 | if (data[0] == 'F' && data[1] == 'M') total_val += 2; 58 | 59 | data += 3; 60 | if (compare_mem(data) == 0) total_val++; 61 | 62 | return total_val; 63 | } 64 | 65 | 66 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 67 | return parse1(data, size); 68 | } 69 | -------------------------------------------------------------------------------- /examples/simple-example-2/.gitignore: -------------------------------------------------------------------------------- 1 | *.data 2 | web 3 | fuzzer 4 | fuzzer.o 5 | -------------------------------------------------------------------------------- /examples/simple-example-2/build_all.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./work 2 | mkdir work 3 | cd work 4 | 5 | echo "[+] Linking the projects" 6 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer-no-link -g -c -flto ../fuzzer.c -o fuzzer.o 7 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer -g -flto fuzzer.o -o fuzzer 8 | -------------------------------------------------------------------------------- /examples/simple-example-2/fuzzer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | 7 | void 8 | print_stuff() { 9 | printf("Hello world\n"); 10 | } 11 | 12 | void proxy(void (*a)(), int val){ 13 | if (val == 123123) { 14 | a(); 15 | } 16 | printf("Hup\n"); 17 | } 18 | 19 | int 20 | parse1(const uint8_t *data, size_t size) { 21 | proxy(print_stuff, size); 22 | return 1; 23 | } 24 | 25 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 26 | return parse1(data, size); 27 | } 28 | -------------------------------------------------------------------------------- /examples/simple-example-3/.gitignore: -------------------------------------------------------------------------------- 1 | *.data 2 | web 3 | fuzzer 4 | fuzzer.o 5 | -------------------------------------------------------------------------------- /examples/simple-example-3/build_all.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./work 2 | mkdir work 3 | cd work 4 | 5 | echo "[+] Linking the projects" 6 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer-no-link -g -c -flto ../fuzzer.c -o fuzzer.o 7 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer -g -flto fuzzer.o -o fuzzer 8 | -------------------------------------------------------------------------------- /examples/simple-example-3/fuzzer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef struct dummy dummy_session; 6 | 7 | struct dummy{ 8 | char *d1; 9 | int d2; 10 | int d3; 11 | }; 12 | 13 | void zipzup3(char **d, int v, char *e) { 14 | if (d == 0xdeadbeef) { 15 | printf("Hep hey\n"); 16 | } 17 | else { 18 | printf("Zingu\n"); 19 | } 20 | if (strcmp(e, "ds;lk") == 0) { 21 | printf("check\n"); 22 | } 23 | } 24 | 25 | void zipzup2(char **d, char *e) { 26 | if (d == 0xdeadbeef) { 27 | printf("Hep hey\n"); 28 | } 29 | else { 30 | printf("Zingu\n"); 31 | } 32 | if (strcmp(e, "ds;lk") == 0) { 33 | printf("check\n"); 34 | } 35 | } 36 | 37 | void zipzup(char **d) { 38 | if (d == 0xdeadbeef) { 39 | printf("Hep hey\n"); 40 | } 41 | else { 42 | printf("Zingu\n"); 43 | } 44 | } 45 | 46 | void hepehey(dummy_session *d1) { 47 | printf("Dummy hep hey %d\n", d1->d2); 48 | } 49 | 50 | void 51 | print_stuff() { 52 | printf("Hello world\n"); 53 | } 54 | 55 | void proxy(void (*a)(), int val){ 56 | if (val == 123123) { 57 | a(); 58 | } 59 | printf("Hup\n"); 60 | } 61 | 62 | int 63 | parse1(const uint8_t *data, size_t size) { 64 | proxy(print_stuff, size); 65 | return 1; 66 | } 67 | 68 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 69 | return parse1(data, size); 70 | } 71 | -------------------------------------------------------------------------------- /examples/simple-example-4/.gitignore: -------------------------------------------------------------------------------- 1 | *.data 2 | web 3 | fuzzer 4 | fuzzer.o 5 | -------------------------------------------------------------------------------- /examples/simple-example-4/build_all.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./work 2 | mkdir work 3 | cd work 4 | 5 | echo "[+] Linking the projects" 6 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer-no-link -g -c -flto ../fuzzer.c -o fuzzer.o 7 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer -g -flto fuzzer.o -o fuzzer 8 | -------------------------------------------------------------------------------- /examples/simple-example-4/fuzzer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef struct dummy dummy_session; 6 | 7 | struct dummy{ 8 | char *d1; 9 | int d2; 10 | int d3; 11 | }; 12 | int valsup; 13 | 14 | void tmp_hello3(char *s) { 15 | if (valsup++ == 1) printf("%s, sdlkfjgslkdfjgl\n", s); 16 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 17 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 18 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 19 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 20 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 21 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 22 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 23 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 24 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 25 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 26 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 27 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 28 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 29 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 30 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 31 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 32 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 33 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 34 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 35 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 36 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 37 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 38 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 39 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 40 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 41 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 42 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 43 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 44 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 45 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 46 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 47 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 48 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 49 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 50 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 51 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 52 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 53 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 54 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 55 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 56 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 57 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 58 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 59 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 60 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 61 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 62 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 63 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 64 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 65 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 66 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 67 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 68 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 69 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 70 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 71 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 72 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 73 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 74 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 75 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 76 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 77 | 78 | } 79 | 80 | static void helloWorld(char *s1, int i2) { 81 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl %s - %d\n", s1, i2); 82 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 83 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 84 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 85 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 86 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 87 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 88 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 89 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 90 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 91 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 92 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 93 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 94 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 95 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 96 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 97 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 98 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 99 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 100 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 101 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 102 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 103 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 104 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 105 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 106 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 107 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 108 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 109 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 110 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 111 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 112 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 113 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 114 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 115 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 116 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 117 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 118 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 119 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 120 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 121 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 122 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 123 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 124 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 125 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 126 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 127 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 128 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 129 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 130 | if (valsup++ == 1) printf("sdlkfjgslkdfjgl\n"); 131 | printf("Hello world\n"); 132 | tmp_hello3(s1); 133 | } 134 | 135 | 136 | void zipzup3(char **d, int v, char *e) { 137 | if (d == 0xdeadbeef) { 138 | printf("Hep hey\n"); 139 | } 140 | else { 141 | printf("Zingu\n"); 142 | } 143 | if (strcmp(e, "ds;lk") == 0) { 144 | printf("check\n"); 145 | } 146 | helloWorld(*d, v); 147 | } 148 | 149 | void zipzup2(char **d, char *e) { 150 | if (d == 0xdeadbeef) { 151 | printf("Hep hey\n"); 152 | } 153 | else { 154 | printf("Zingu\n"); 155 | } 156 | if (strcmp(e, "ds;lk") == 0) { 157 | printf("check\n"); 158 | } 159 | } 160 | 161 | void zipzup(char **d) { 162 | if (d == 0xdeadbeef) { 163 | printf("Hep hey\n"); 164 | } 165 | else { 166 | printf("Zingu\n"); 167 | } 168 | } 169 | 170 | void hepehey(dummy_session *d1) { 171 | printf("Dummy hep hey %d\n", d1->d2); 172 | } 173 | 174 | void 175 | print_stuff() { 176 | printf("Hello world\n"); 177 | } 178 | 179 | void proxy(void (*a)(), int val){ 180 | if (val == 123123) { 181 | a(); 182 | } 183 | printf("Hup\n"); 184 | } 185 | 186 | int 187 | parse1(const uint8_t *data, size_t size) { 188 | proxy(print_stuff, size); 189 | return 1; 190 | } 191 | 192 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 193 | return parse1(data, size); 194 | } 195 | -------------------------------------------------------------------------------- /examples/simple-example-indirect-pointers/.gitignore: -------------------------------------------------------------------------------- 1 | *.data 2 | web 3 | fuzzer 4 | fuzzer.o 5 | -------------------------------------------------------------------------------- /examples/simple-example-indirect-pointers/build_all.sh: -------------------------------------------------------------------------------- 1 | 2 | rm -rf ./work 3 | mkdir work 4 | cd work 5 | 6 | echo "[+] Linking the projects" 7 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer-no-link -g -c -flto ../fuzzer.c -o fuzzer.o 8 | ../../../build/llvm-build/bin/clang -fsanitize=fuzzer -g -flto fuzzer.o -o fuzzer 9 | -------------------------------------------------------------------------------- /examples/simple-example-indirect-pointers/fuzzer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | void 7 | parse_callback1(const uint8_t *data, size_t size) { 8 | printf("We are now in callback\n"); 9 | 10 | if (size < 3) { 11 | return; 12 | } 13 | 14 | if (data[0] == 'B') { 15 | printf("H3\n"); 16 | } 17 | printf("H5\n"); 18 | } 19 | 20 | void 21 | parse_callback2(const uint8_t *data, size_t size) { 22 | printf("We are now in callback\n"); 23 | 24 | if (size < 3) { 25 | return; 26 | } 27 | 28 | if (data[0] == 'A') { 29 | printf("H1\n"); 30 | } 31 | printf("H2\n"); 32 | } 33 | 34 | int 35 | parse(const uint8_t *data, size_t size) { 36 | void (*fun_ptr)(const uint8_t *, size_t); 37 | 38 | if (size < 10) return 0; 39 | 40 | if (data[3] == 'C') { 41 | fun_ptr = parse_callback1; 42 | } else { 43 | fun_ptr = parse_callback2; 44 | } 45 | fun_ptr(data, size); 46 | return 0; 47 | } 48 | 49 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 50 | return parse(data, size); 51 | } 52 | -------------------------------------------------------------------------------- /img/functions_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdaLogics/fuzz-introspector/4b7ae6f40528affd8d49f21068e67fd36af00d2c/img/functions_overview.png -------------------------------------------------------------------------------- /img/overlay-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdaLogics/fuzz-introspector/4b7ae6f40528affd8d49f21068e67fd36af00d2c/img/overlay-1.png -------------------------------------------------------------------------------- /img/overlay-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdaLogics/fuzz-introspector/4b7ae6f40528affd8d49f21068e67fd36af00d2c/img/overlay-2.png -------------------------------------------------------------------------------- /img/project_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdaLogics/fuzz-introspector/4b7ae6f40528affd8d49f21068e67fd36af00d2c/img/project_overview.png -------------------------------------------------------------------------------- /llvm/include/llvm/Transforms/Inspector/Inspector.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2021 Ada Logics Ltd. 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | Unless required by applicable law or agreed to in writing, software 7 | distributed under the License is distributed on an "AS IS" BASIS, 8 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | See the License for the specific language governing permissions and 10 | limitations under the License. 11 | */ 12 | 13 | #ifndef _INSPECTOR_INCLUDES_ 14 | #define _INSPECTOR_INCLUDES_ 15 | 16 | 17 | // LLVM include 18 | #include "llvm/Pass.h" 19 | #include "llvm/IR/Function.h" 20 | #include "llvm/ADT/Statistic.h" 21 | #include "llvm/Transforms/Utils/Local.h" 22 | #include "llvm/Transforms/IPO.h" 23 | #include "llvm/Transforms/Scalar.h" 24 | #include "llvm/IR/Module.h" 25 | #include "llvm/Support/CommandLine.h" 26 | 27 | // Namespace 28 | using namespace std; 29 | 30 | namespace llvm { 31 | Pass *createInspectorPass(); 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /llvm/lib/Transforms/Inspector/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_llvm_component_library( LLVMInspector 2 | Inspector.cpp 3 | 4 | PLUGIN_TOOL 5 | opt 6 | 7 | DEPENDS 8 | intrinsics_gen 9 | ) 10 | -------------------------------------------------------------------------------- /llvm/lib/Transforms/Inspector/Inspector.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2021 Ada Logics Ltd. 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | Unless required by applicable law or agreed to in writing, software 7 | distributed under the License is distributed on an "AS IS" BASIS, 8 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | See the License for the specific language governing permissions and 10 | limitations under the License. 11 | */ 12 | #include "llvm/Transforms/Inspector/Inspector.h" 13 | #include "llvm/IR/DebugInfoMetadata.h" 14 | #include "llvm/IR/DebugLoc.h" 15 | #include "llvm/IR/Function.h" 16 | #include "llvm/IR/IntrinsicInst.h" 17 | #include "llvm/IR/LegacyPassManager.h" 18 | #include "llvm/IR/Module.h" 19 | #include "llvm/Pass.h" 20 | #include "llvm/Support/CommandLine.h" 21 | #include "llvm/Support/FileSystem.h" 22 | #include "llvm/Support/FormatVariadic.h" 23 | #include "llvm/Support/YAMLParser.h" 24 | #include "llvm/Support/YAMLTraits.h" 25 | #include "llvm/Support/raw_ostream.h" 26 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" 27 | #include "llvm/Transforms/Utils/CallGraphUpdater.h" 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | using namespace std; 35 | using namespace llvm; 36 | 37 | /* 38 | * The main goal of this pass is to assist in setting up fuzzing 39 | * of a project. The pass is run at linking stage, and will do 40 | * analysis for one fuzzer at the time. That means, the results 41 | * from a single execution of this pass is only relevant for the 42 | * given fuzzer that is being linked. In order to do a 43 | * whole-program fuzzer infrastructure analysis, the results 44 | * from running this fuzzing introspector plugin across all fuzzers 45 | * must be merged and post-processed. 46 | */ 47 | 48 | using yaml::IO; 49 | using yaml::MappingTraits; 50 | using yaml::Output; 51 | 52 | // Typedefs used by the introspector pass 53 | typedef struct fuzzFuncWrapper { 54 | StringRef FunctionName; 55 | std::string FunctionSourceFile; 56 | std::string LinkageType; 57 | int FunctionLinenumber; 58 | size_t FunctionDepth; 59 | std::string ReturnType; 60 | size_t ArgCount; 61 | std::vector ArgTypes; 62 | std::vector ArgNames; 63 | size_t BBCount; 64 | size_t ICount; 65 | size_t EdgeCount; 66 | size_t CyclomaticComplexity; 67 | int FunctionUses; 68 | std::vector FunctionsReached; 69 | } FuzzerFunctionWrapper; 70 | 71 | typedef struct FuzzerStringList { 72 | StringRef ListName; 73 | std::vector Elements; 74 | } FuzzerStringList; 75 | 76 | typedef struct FuzzerFunctionList { 77 | StringRef ListName; 78 | std::vector Functions; 79 | } FuzzerFunctionList; 80 | 81 | typedef struct FuzzerModuleIntrospection { 82 | std::string FuzzerFileName; 83 | FuzzerFunctionList AllFunctions; 84 | 85 | FuzzerModuleIntrospection(std::string A, FuzzerFunctionList B) 86 | : FuzzerFileName(A), AllFunctions(B) {} 87 | } FuzzerModuleIntrospection; 88 | 89 | // YAML mappings for outputting the typedefs above 90 | template <> struct yaml::MappingTraits { 91 | static void mapping(IO &io, FuzzerFunctionWrapper &Func) { 92 | io.mapRequired("functionName", Func.FunctionName); 93 | io.mapRequired("functionSourceFile", Func.FunctionSourceFile); 94 | io.mapRequired("linkageType", Func.LinkageType); 95 | io.mapRequired("functionLinenumber", Func.FunctionLinenumber); 96 | io.mapRequired("functionDepth", Func.FunctionDepth); 97 | io.mapRequired("returnType", Func.ReturnType); 98 | io.mapRequired("argCount", Func.ArgCount); 99 | io.mapRequired("argTypes", Func.ArgTypes); 100 | io.mapRequired("argNames", Func.ArgNames); 101 | io.mapRequired("BBCount", Func.BBCount); 102 | io.mapRequired("ICount", Func.ICount); 103 | io.mapRequired("EdgeCount", Func.EdgeCount); 104 | io.mapRequired("CyclomaticComplexity", Func.CyclomaticComplexity); 105 | io.mapRequired("functionsReached", Func.FunctionsReached); 106 | io.mapRequired("functionUses", Func.FunctionUses); 107 | } 108 | }; 109 | LLVM_YAML_IS_SEQUENCE_VECTOR(FuzzerFunctionWrapper) 110 | 111 | template <> struct yaml::MappingTraits { 112 | static void mapping(IO &io, FuzzerStringList &l) { 113 | io.mapRequired("List name", l.ListName); 114 | io.mapRequired("elements", l.Elements); 115 | } 116 | }; 117 | 118 | template <> struct yaml::MappingTraits { 119 | static void mapping(IO &io, FuzzerFunctionList &FList) { 120 | io.mapRequired("Function list name", FList.ListName); 121 | io.mapRequired("Elements", FList.Functions); 122 | } 123 | }; 124 | 125 | template <> struct yaml::MappingTraits { 126 | static void mapping(IO &io, FuzzerModuleIntrospection &introspectorModule) { 127 | io.mapRequired("Fuzzer filename", introspectorModule.FuzzerFileName); 128 | io.mapRequired("All functions", introspectorModule.AllFunctions); 129 | } 130 | }; 131 | // end of YAML mappings 132 | 133 | namespace { 134 | 135 | // Node representation for calltree format 136 | typedef struct CalltreeNode { 137 | StringRef FunctionName; 138 | std::string FileName; 139 | int LineNumber; 140 | Function *CallsiteDst; 141 | std::vector Outgoings; 142 | 143 | CalltreeNode(){}; 144 | CalltreeNode(StringRef A, std::string B, int C, Function *D) 145 | : FunctionName(A), FileName(B), LineNumber(C), CallsiteDst(D){}; 146 | 147 | } CalltreeNode; 148 | 149 | struct Inspector : public ModulePass { 150 | static char ID; 151 | Inspector() : ModulePass(ID) { 152 | errs() << "We are now in the Inspector module pass\n"; 153 | } 154 | 155 | CalltreeNode FuzzerCalltree; 156 | std::set functionNamesToIgnore = {"llvm.", "sanitizer_cov", 157 | "sancov.module"}; 158 | 159 | void resolveOutgoingEdges(Function *, std::vector *); 160 | bool isNodeInVector(CalltreeNode *Src, std::vector *Vec); 161 | void dumpCalltree(CalltreeNode *, std::string); 162 | void getFunctionsInAllNodes(std::vector *, 163 | std::set *); 164 | void extractFuzzerReachabilityGraph(Module &M); 165 | int extractCalltree(Function *F, int Depth, CalltreeNode *callTree, 166 | std::vector *allNodes); 167 | void logCalltree(struct CalltreeNode *calltree, std::ofstream *, int Depth); 168 | FuzzerFunctionWrapper wrapFunction(Function *func); 169 | void extractAllFunctionDetailsToYaml(std::string nextYamlName, Module &M); 170 | bool shouldIgnoreFunction(StringRef functionName); 171 | StringRef removeDecSuffixFromName(StringRef funcName); 172 | std::string getNextLogFile(); 173 | bool shouldRunIntrospector(Module &M); 174 | FuzzerFunctionList wrapAllFunctions(Module &M); 175 | std::string getFunctionFilename(Function *F); 176 | int getFunctionLinenumber(Function *F); 177 | std::string resolveTypeName(Type *t); 178 | Function *value2Func(Value *Val); 179 | bool isFunctionPointerType(Type *type); 180 | Function *extractVTableIndirectCall(Function *, Instruction &); 181 | 182 | // Function entrypoint. 183 | bool runOnModule(Module &M) override { 184 | errs() << "Running inspector on " << M.getName() << "\n"; 185 | if (shouldRunIntrospector(M) == false) { 186 | return true; 187 | } 188 | errs() << "This is a fuzzer, performing analysis\n"; 189 | // Extract and log reachability graph 190 | std::string nextCalltreeFile = getNextLogFile(); 191 | extractFuzzerReachabilityGraph(M); 192 | dumpCalltree(&FuzzerCalltree, nextCalltreeFile); 193 | 194 | // Log data about all functions in the module 195 | std::string nextYamlName = nextCalltreeFile + ".yaml"; 196 | extractAllFunctionDetailsToYaml(nextYamlName, M); 197 | 198 | errs() << "Finished inspector module\n"; 199 | return true; 200 | } 201 | }; 202 | } // end of anonymous namespace 203 | 204 | Pass *llvm::createInspectorPass() { return new Inspector(); } 205 | 206 | // Write details about all functions in the module to a YAML file 207 | void Inspector::extractAllFunctionDetailsToYaml(std::string nextYamlName, 208 | Module &M) { 209 | std::error_code EC; 210 | auto YamlStream = std::make_unique( 211 | nextYamlName, EC, llvm::sys::fs::OpenFlags::OF_None); 212 | yaml::Output YamlOut(*YamlStream); 213 | 214 | FuzzerModuleIntrospection fmi(FuzzerCalltree.FileName, wrapAllFunctions(M)); 215 | YamlOut << fmi; 216 | } 217 | 218 | FuzzerFunctionList Inspector::wrapAllFunctions(Module &M) { 219 | FuzzerFunctionList ListWrapper; 220 | ListWrapper.ListName = "All functions"; 221 | for (auto &F : M) { 222 | ListWrapper.Functions.push_back(wrapFunction(&F)); 223 | } 224 | 225 | return ListWrapper; 226 | } 227 | 228 | std::string Inspector::getNextLogFile() { 229 | std::string TargetLogName; 230 | int Idx = 0; 231 | do { 232 | TargetLogName = formatv("fuzzerLogFile-{0}.data", std::to_string(Idx++)); 233 | } while (llvm::sys::fs::exists(TargetLogName)); 234 | return TargetLogName; 235 | } 236 | 237 | // Remove a suffix composed of a period and a number, e.g.: 238 | // - this_func.1234 will be translated to this_func 239 | StringRef Inspector::removeDecSuffixFromName(StringRef FuncName) { 240 | StringRef FuncNameBeforeLastPeriod; 241 | StringRef FuncNameAfterLastPeriod; 242 | 243 | size_t lastPeriod = FuncName.find_last_of('.', 9999); 244 | if (lastPeriod == 0) { 245 | return FuncName; 246 | } 247 | 248 | FuncNameBeforeLastPeriod = FuncName.substr(0, lastPeriod); 249 | FuncNameAfterLastPeriod = FuncName.substr(lastPeriod + 1, 99999); 250 | size_t TmpV; 251 | if (FuncNameAfterLastPeriod.getAsInteger(10, TmpV)) { 252 | // getAsInteger returns true if the string is not a number. 253 | return FuncName; 254 | } 255 | return FuncNameBeforeLastPeriod; 256 | } 257 | 258 | bool Inspector::shouldIgnoreFunction(StringRef FuncName) { 259 | for (auto &functionToIgnore : functionNamesToIgnore) { 260 | if (FuncName.contains(functionToIgnore)) { 261 | return true; 262 | } 263 | } 264 | return false; 265 | } 266 | 267 | int Inspector::getFunctionLinenumber(Function *F) { 268 | for (auto &I : instructions(*F)) { 269 | const llvm::DebugLoc &DebugInfo = I.getDebugLoc(); 270 | if (DebugInfo) { 271 | return DebugInfo.getLine(); 272 | } 273 | } 274 | return -1; 275 | } 276 | 277 | // Return the path as a string to the file in which 278 | // the function is implemented. 279 | std::string Inspector::getFunctionFilename(Function *F) { 280 | StringRef Dir; 281 | StringRef Res; 282 | 283 | int Found = 0; 284 | for (auto &I : instructions(*F)) { 285 | const llvm::DebugLoc &DebugInfo = I.getDebugLoc(); 286 | if (DebugInfo) { 287 | auto *Scope = cast(DebugInfo.getScope()); 288 | // errs() << "Filename: " << Scope->getFilename() << "\n"; 289 | // errs() << "Directory: " << Scope->getDirectory() << "\n"; 290 | // errs() << "Line number: " << debugInfo.getLine() << "\n"; 291 | Dir = Scope->getDirectory(); 292 | Res = Scope->getFilename(); 293 | Found = 1; 294 | break; 295 | } 296 | } 297 | 298 | SmallString<256> *CurrentDir = new SmallString<256>(); 299 | if (Found) 300 | CurrentDir->append(Dir); 301 | CurrentDir->append("/"); 302 | if (Found) 303 | CurrentDir->append(Res); 304 | 305 | StringRef s4 = CurrentDir->str(); 306 | std::string newstr = s4.str(); 307 | return newstr; 308 | } 309 | 310 | // Convert an LLVM type into a c-like string 311 | std::string Inspector::resolveTypeName(Type *T) { 312 | std::string RetType = ""; 313 | std::string RetSuffix = ""; 314 | while (T->isPointerTy()) { 315 | RetSuffix += "*"; 316 | T = T->getPointerElementType(); 317 | } 318 | if (T->isIntegerTy()) { 319 | switch (T->getIntegerBitWidth()) { 320 | case 8: 321 | RetType += "char"; 322 | break; 323 | case 32: 324 | RetType += "int"; 325 | break; 326 | case 64: 327 | RetType += "size_t"; 328 | break; 329 | default: 330 | break; 331 | } 332 | } else if (T->isStructTy()) { 333 | if (dyn_cast(T)->isLiteral() == false) { 334 | RetType = T->getStructName().str(); 335 | } 336 | } else if (T->isFunctionTy()) { 337 | RetType == "func_type"; 338 | } 339 | if (RetType == "") { 340 | return "N/A"; 341 | } 342 | return RetType + " " + RetSuffix; 343 | } 344 | 345 | // Simple recursive function to output the calltree. 346 | // This should be changed to a proper data structure in the future, 347 | // for example something that we can attribute extensively 348 | // would be nice to have. 349 | void Inspector::logCalltree(CalltreeNode *Calltree, std::ofstream *CalltreeOut, 350 | int Depth) { 351 | if (!Calltree) { 352 | return; 353 | } 354 | std::string Spacing = std::string(2 * Depth, ' '); 355 | *CalltreeOut << Spacing << Calltree->FunctionName.str() << " " 356 | << Calltree->FileName << " " 357 | << "linenumber=" << Calltree->LineNumber << "\n"; 358 | for (auto &OutEdge : Calltree->Outgoings) { 359 | logCalltree(OutEdge, CalltreeOut, Depth + 1); 360 | } 361 | } 362 | 363 | void Inspector::dumpCalltree(CalltreeNode *Calltree, std::string TargetFile) { 364 | std::ofstream CalltreeOut; 365 | CalltreeOut.open(TargetFile); 366 | CalltreeOut << "Call tree\n"; 367 | logCalltree(&FuzzerCalltree, &CalltreeOut, 0); 368 | CalltreeOut << "====================================\n"; 369 | CalltreeOut.close(); 370 | } 371 | 372 | Function *Inspector::value2Func(Value *Val) { 373 | if (isa(Val)) 374 | return nullptr; 375 | if (Function *F = dyn_cast(Val)) 376 | return F; 377 | if (GlobalAlias *GA = dyn_cast(Val)) 378 | return value2Func(GA->getAliasee()); 379 | if (ConstantExpr *CE = dyn_cast(Val)) 380 | return value2Func(CE->getOperand(0)->stripPointerCasts()); 381 | return nullptr; 382 | } 383 | 384 | // Recursively resolve a type and check if it is a function. 385 | bool Inspector::isFunctionPointerType(Type *T) { 386 | if (PointerType *pointerType = dyn_cast(T)) { 387 | return isFunctionPointerType(pointerType->getElementType()); 388 | } 389 | return T->isFunctionTy(); 390 | } 391 | 392 | void Inspector::getFunctionsInAllNodes(std::vector *allNodes, 393 | std::set *UniqueNames) { 394 | for (auto PP : *allNodes) { 395 | UniqueNames->insert(PP->FunctionName); 396 | } 397 | } 398 | 399 | // Returns the target function of an indirect call that is a VTable call 400 | // The function is a fairly simple approach to identifying the target class 401 | // holding the vtable. This approach is currently limited in that it does not 402 | // recognise the true type and will often identify the top-level function in 403 | // the inheritance levels. For now, it is future work to refine this, however, 404 | // it should be possible to it with a reasonable result and without too much 405 | // hackery. 406 | // ATM, for documentation of this function, please see the URL: 407 | // https://github.com/AdaLogics/fuzz-introspector/issues/XXXX 408 | // 409 | // 410 | // 411 | // This function solves the following problem. Consider the set up: 412 | // 413 | // 414 | // [some place in the code] 415 | // %this1 = load %class.dng_info*, %class.dng_info** %this.addr, align 8 416 | // 417 | // [A virtual call based on a vtable resolution] 418 | // %43 = bitcast %class.dng_info* %this1 to void (%class.dng_info*, %class.dng_host ..... 419 | // %vtable32 = load void (%class.dng_info*, %class.dng ..... d*, i64, i64, i32)*** %43, 420 | // %vfn33 = getelementptr inbounds void (%class.dng .... 4, i64, i32)** %vtable32, i64 8, !dbg !4560 421 | // %44 = load void (%class.dng_info*, %class ...... i64, i64, i32)** %vfn33, 422 | // call void %44(%class.dng_info* nonnull dereferenceable(332) %this1, ... 423 | // 424 | // with the following global variable declared: 425 | // _ZTV8dng_info = { [15 x i8*] [i8* null, 426 | // i8* bitcast ({ i8*, i8* }* @_ZTI8dng_info to i8*), 427 | // i8* bitcast (void (%class.dng_info*)* @_ZN8dng_infoD1Ev to i8*), 428 | // i8* bitcast (void (%class.dng_info*)* @_ZN8dng_infoD0Ev to i8*), 429 | // i8* bitcast (void (%class.dng_info*, %class.dng_host*, %class.dng_stream*)* @_ZN8dng_info5ParseER8dng_hostR10dng_stream to i8*), 430 | // i8* bitcast (void (%class.dng_info*, %class.dng_host*)* @_ZN8dng_info9PostParseER8dng_host to i8*), 431 | // ... 432 | // ... 433 | // } 434 | // 435 | // The idea from a high level is to: 436 | // Based on the instructions making up the indirect call identify that the 437 | // call is based on a class call class.dng_info. Then, use this name to fetch 438 | // a global variable called "vtable for dng_info" where this name is mangled. 439 | // If the global variable is found then get the right index in the vtable 440 | // based on the index of the "getelementptr" instruction. 441 | Function *Inspector::extractVTableIndirectCall(Function *F, Instruction &I) { 442 | 443 | Value *opnd = cast(&I)->getCalledOperand(); 444 | 445 | LoadInst *LI = nullptr; 446 | if (!(LI = dyn_cast(opnd))) 447 | return nullptr; 448 | 449 | // The gep Value is the function pointer, i.e. opnd2 is 450 | // a virtual function pointer. 451 | GetElementPtrInst *inst2 = nullptr; 452 | if (!(inst2 = dyn_cast(LI->getPointerOperand()))) 453 | return nullptr; 454 | 455 | uint64_t CIdx = 0; 456 | if (ConstantInt *CI3 = dyn_cast(inst2->getOperand(1))) { 457 | CIdx = CI3->getZExtValue(); 458 | } 459 | 460 | LoadInst *LI2 = nullptr; 461 | if (!(LI2 = dyn_cast(inst2->getPointerOperand()))) { 462 | return nullptr; 463 | } 464 | BitCastInst *BCI = nullptr; 465 | if (!(BCI = dyn_cast(LI2->getPointerOperand()))) { 466 | return nullptr; 467 | } 468 | 469 | PointerType *pointerType3 = nullptr; 470 | if (!(BCI->getSrcTy()->isPointerTy()) || 471 | !(pointerType3 = dyn_cast(BCI->getSrcTy()))) { 472 | return nullptr; 473 | } 474 | std::string originalTargetClass; 475 | Type *v13 = pointerType3->getElementType(); 476 | if (!v13->isStructTy()) { 477 | return nullptr; 478 | } 479 | StructType *SSM = cast(v13); 480 | // Now we remove the "class." from the name, and then we have it. 481 | originalTargetClass = SSM->getName().str().substr(6); 482 | errs() << "Shortened name that we can use for analysis: " 483 | << originalTargetClass << "\n"; 484 | 485 | // We find the global variable corresponding to the vtable by 486 | // way of naming convetions. Specifically, we look for the 487 | // global variable named "vtable for CLASSNAME" in demangled 488 | // naming. 489 | std::string TargetVTableName = 490 | "_ZTV" + std::to_string(originalTargetClass.size()) + originalTargetClass; 491 | GlobalVariable *VTableGVar = 492 | F->getParent()->getGlobalVariable(TargetVTableName, true); 493 | if (VTableGVar == nullptr) { 494 | return nullptr; 495 | } 496 | 497 | // VTables have initialized. Thus, if there is an 498 | // initializer this is likely a vtable. 499 | if (!VTableGVar->hasInitializer()) { 500 | return nullptr; 501 | } 502 | Constant *VTableValues = VTableGVar->getInitializer(); 503 | 504 | // As of this writing, vtables are structs in LLVM modules. 505 | if (!VTableValues->getType()->isStructTy()) { 506 | return nullptr; 507 | } 508 | 509 | // There is only a single element in the vtable, which 510 | // is an array of function pointers. 511 | Constant *VTableValue = VTableValues->getAggregateElement((unsigned int)0); 512 | 513 | // EXtract the function pointer from the VTable. The 514 | // VTable is the struct itself. 515 | Constant *FunctionPtrConstant = VTableValue->getAggregateElement(CIdx + 2); 516 | 517 | if (FunctionPtrConstant == nullptr) { 518 | return nullptr; 519 | } 520 | 521 | // Extract the function pointer corresponding to the constant expression. 522 | Function *VTableTargetFunc = value2Func(FunctionPtrConstant); 523 | if (VTableTargetFunc != nullptr) { 524 | errs() << "The actual function name (from earlyCaught) " 525 | << VTableTargetFunc->getName() << "\n"; 526 | } 527 | return VTableTargetFunc; 528 | } 529 | 530 | // Resolve all outgoing edges in a Function and populate 531 | // the OutgoingEdges vector with them. 532 | void Inspector::resolveOutgoingEdges( 533 | Function *F, std::vector *OutgoingEdges) { 534 | for (auto &I : instructions(F)) { 535 | 536 | std::vector FuncPoints; 537 | Function *CallsiteDst = nullptr; 538 | // Resolve the function destinations of this callsite. 539 | if (isa(I) || isa(I)) { 540 | if (CallInst *CDI = dyn_cast(&I)) { 541 | CallsiteDst = value2Func(CDI->getCalledOperand()); 542 | } else if (InvokeInst *IDI = dyn_cast(&I)) { 543 | CallsiteDst = value2Func(IDI->getCalledOperand()); 544 | } 545 | if (CallsiteDst != nullptr) { 546 | FuncPoints.push_back(CallsiteDst); 547 | } 548 | 549 | // Check for function pointers as arguments in a function call, e.g. 550 | // to a function that take a function pointer for a callback function. 551 | if (CallInst *CI = dyn_cast(&I)) { 552 | for (int i = 0; i < CI->getNumOperands(); i++) { 553 | Value *opnd = CI->getOperand(i); 554 | Function *tmpf = value2Func(opnd); 555 | if (tmpf != nullptr && tmpf != CallsiteDst) { 556 | FuncPoints.push_back(tmpf); 557 | } 558 | } 559 | } 560 | 561 | // Edge resolution for calls based on VTable indices. 562 | if (isa(I) && CallsiteDst == nullptr) { 563 | CallsiteDst = extractVTableIndirectCall(F, I); 564 | if (CallsiteDst != nullptr) { 565 | FuncPoints.push_back(CallsiteDst); 566 | } 567 | } 568 | } 569 | // Check for function pointers in storage instructions. 570 | if (CallsiteDst == NULL && isa(I)) { 571 | if (isFunctionPointerType(I.getOperand(0)->getType())) { 572 | if (Function *f = dyn_cast(I.getOperand(0))) { 573 | FuncPoints.push_back(f); 574 | } 575 | } 576 | } 577 | 578 | for (auto CSElem : FuncPoints) { 579 | int CSLinenumber = -1; 580 | const llvm::DebugLoc &debugInfo = I.getDebugLoc(); 581 | // Get the line number of the instruction. 582 | // We use this when visualizing the calltree. 583 | if (debugInfo) { 584 | CSLinenumber = debugInfo.getLine(); 585 | } 586 | 587 | StringRef NormalisedDstName = removeDecSuffixFromName(CSElem->getName()); 588 | CalltreeNode *Node = new CalltreeNode( 589 | NormalisedDstName, getFunctionFilename(CSElem), CSLinenumber, CSElem); 590 | OutgoingEdges->push_back(Node); 591 | } 592 | } 593 | } 594 | 595 | bool Inspector::isNodeInVector(CalltreeNode *Src, 596 | std::vector *Vec) { 597 | for (CalltreeNode *TmpN : *Vec) { 598 | if (TmpN->LineNumber == Src->LineNumber && 599 | TmpN->FileName.compare(Src->FileName) == 0) { 600 | return true; 601 | } 602 | } 603 | return false; 604 | } 605 | 606 | // Collects all functions reachable by the target function. This 607 | // is an approximation, e.g. we make few efforts into resolving 608 | // indirect calls. 609 | int Inspector::extractCalltree(Function *F, int Depth, CalltreeNode *Calltree, 610 | std::vector *allNodesInTree) { 611 | std::vector OutgoingEdges; 612 | resolveOutgoingEdges(F, &OutgoingEdges); 613 | 614 | int MaxDepth = Depth; 615 | for (CalltreeNode *OutEdge : OutgoingEdges) { 616 | if (shouldIgnoreFunction(OutEdge->FunctionName) || 617 | isNodeInVector(OutEdge, allNodesInTree)) { 618 | continue; 619 | } 620 | 621 | allNodesInTree->push_back(OutEdge); 622 | if (Calltree != nullptr) { 623 | Calltree->Outgoings.push_back(OutEdge); 624 | } 625 | int NodeDepth = extractCalltree(OutEdge->CallsiteDst, Depth + 1, OutEdge, 626 | allNodesInTree); 627 | MaxDepth = std::max(MaxDepth, NodeDepth); 628 | } 629 | return MaxDepth; 630 | } 631 | 632 | // Wraps an LLVM function in a struct for conveniently outputting 633 | // to YAML. Also does minor meta-analysis, such as cyclomatic complexity 634 | // analysis. 635 | FuzzerFunctionWrapper Inspector::wrapFunction(Function *F) { 636 | FuzzerFunctionWrapper FuncWrap; 637 | 638 | FuncWrap.FunctionName = F->getName(); 639 | FuncWrap.FunctionSourceFile = getFunctionFilename(F); 640 | FuncWrap.FunctionLinenumber = getFunctionLinenumber(F); 641 | FuncWrap.FunctionUses = 0; 642 | for (User *U : F->users()) { 643 | if (Instruction *Inst = dyn_cast(U)) { 644 | // Uncomment below to get informatino about where it is hit. 645 | // errs() << "Function " << func->getName() << " is used in: \n"; 646 | // errs() << *Inst << "\n"; 647 | // errs() << "Debug location:\n"; 648 | // const llvm::DebugLoc &debugInfo = Inst->getDebugLoc(); 649 | // if (debugInfo) { 650 | // auto *Scope = cast(debugInfo.getScope()); 651 | // errs() << "\tFilename: " << Scope->getFilename() << "\n"; 652 | // errs() << "\tDirectory: " << Scope->getDirectory() << "\n"; 653 | // errs() << "\tLine number: " << debugInfo.getLine() << "\n"; 654 | //} 655 | FuncWrap.FunctionUses += 1; 656 | } 657 | } 658 | 659 | FuncWrap.ArgCount = 0; 660 | for (auto &arg : F->args()) { 661 | FuncWrap.ArgCount++; 662 | } 663 | 664 | // Log linkage type 665 | switch (F->getLinkage()) { 666 | case llvm::GlobalValue::LinkageTypes::ExternalLinkage: 667 | FuncWrap.LinkageType = "externalLinkage"; 668 | break; 669 | case llvm::GlobalValue::LinkageTypes::AvailableExternallyLinkage: 670 | FuncWrap.LinkageType = "AvailableExternallyLinkage"; 671 | break; 672 | case llvm::GlobalValue::LinkageTypes::LinkOnceAnyLinkage: 673 | FuncWrap.LinkageType = "LinkOnceAnyLinkage"; 674 | break; 675 | case llvm::GlobalValue::LinkageTypes::LinkOnceODRLinkage: 676 | FuncWrap.LinkageType = "LinkOnceODRLinkage"; 677 | break; 678 | case llvm::GlobalValue::LinkageTypes::WeakAnyLinkage: 679 | FuncWrap.LinkageType = "AvailableExternallyLinkage"; 680 | break; 681 | case llvm::GlobalValue::LinkageTypes::WeakODRLinkage: 682 | FuncWrap.LinkageType = "WeakODRLinkage"; 683 | break; 684 | case llvm::GlobalValue::LinkageTypes::AppendingLinkage: 685 | FuncWrap.LinkageType = "AppendingLinkage"; 686 | break; 687 | case llvm::GlobalValue::LinkageTypes::InternalLinkage: 688 | FuncWrap.LinkageType = "InternalLinkage"; 689 | break; 690 | case llvm::GlobalValue::LinkageTypes::PrivateLinkage: 691 | FuncWrap.LinkageType = "PrivateLinkage"; 692 | break; 693 | case llvm::GlobalValue::LinkageTypes::ExternalWeakLinkage: 694 | FuncWrap.LinkageType = "ExternalWeakLinkage"; 695 | break; 696 | case llvm::GlobalValue::LinkageTypes::CommonLinkage: 697 | FuncWrap.LinkageType = "CommonLinkage"; 698 | break; 699 | default: 700 | FuncWrap.LinkageType = "Default"; 701 | } 702 | 703 | // Find the depth of the function. 704 | FuncWrap.ReturnType = resolveTypeName(F->getReturnType()); 705 | 706 | // Arguments 707 | for (auto &A : F->args()) { 708 | FuncWrap.ArgTypes.push_back(resolveTypeName(A.getType())); 709 | FuncWrap.ArgNames.push_back(A.getName().str()); 710 | } 711 | 712 | // Log the amount of basic blocks, instruction count and cyclomatic 713 | // complexity of the function. 714 | FuncWrap.BBCount = 0; 715 | FuncWrap.ICount = 0; 716 | FuncWrap.EdgeCount = 0; 717 | for (auto &BB : *F) { 718 | FuncWrap.BBCount++; 719 | for (auto &I : BB) { 720 | FuncWrap.ICount++; 721 | if (BranchInst *BI = dyn_cast(&I)) { 722 | FuncWrap.EdgeCount += BI->isConditional() ? 2 : 1; 723 | } 724 | } 725 | } 726 | // Do a check on EdgeCount as it may be a bit of a problem. If we have 727 | // it such that BBCount is more than edges then it probably means 728 | // we have a switch statement of some sort that we didnt handle above. 729 | if (FuncWrap.EdgeCount < FuncWrap.BBCount) { 730 | FuncWrap.EdgeCount += FuncWrap.BBCount; 731 | } 732 | FuncWrap.CyclomaticComplexity = FuncWrap.EdgeCount - FuncWrap.BBCount + 2; 733 | 734 | // We have had some issues here with Cyclomatic complexity, so let's do a 735 | // quick check. Main issue to be resolved. 736 | if (FuncWrap.CyclomaticComplexity > 999999) { 737 | FuncWrap.CyclomaticComplexity = 1; 738 | } 739 | 740 | std::set FuncReaches; 741 | std::vector Nodes; 742 | // TODO: extractCalltree should not be run on all functions in this manner. 743 | // Rather, we should cache a lot of the analysis we do in extractCalltree 744 | // because a top-level function would capture all the data we need for the 745 | // full program. 746 | FuncWrap.FunctionDepth = extractCalltree(F, 0, nullptr, &Nodes); 747 | getFunctionsInAllNodes(&Nodes, &FuncReaches); 748 | std::copy(FuncReaches.begin(), FuncReaches.end(), 749 | std::back_inserter(FuncWrap.FunctionsReached)); 750 | 751 | return FuncWrap; 752 | } 753 | 754 | bool Inspector::shouldRunIntrospector(Module &M) { 755 | 756 | // See if there is a main function in this application. If there is a main 757 | // function then it potentially means this is not a libfuzzer fuzzer being 758 | // linked, which is often used in projects to have "standalone-tests". In this 759 | // case we do not want to proceed to avoid having duplicate information. 760 | Function *MainFunc = M.getFunction("main"); 761 | if (MainFunc != nullptr) { 762 | errs() << "Main function filename: " << getFunctionFilename(MainFunc) 763 | << "\n"; 764 | errs() << "This means a main function is in the source code rather in the " 765 | "libfuzzer " 766 | "library, and thus we do not care about it. We only want to " 767 | "study the " 768 | "actual fuzzers. Exiting this run.\n"; 769 | return false; 770 | } 771 | 772 | return true; 773 | } 774 | 775 | void Inspector::extractFuzzerReachabilityGraph(Module &M) { 776 | Function *FuzzEntryFunc = M.getFunction("LLVMFuzzerTestOneInput"); 777 | if (FuzzEntryFunc == nullptr) { 778 | return; 779 | } 780 | 781 | FuzzerCalltree.FunctionName = FuzzEntryFunc->getName(); 782 | FuzzerCalltree.FileName = getFunctionFilename(FuzzEntryFunc); 783 | FuzzerCalltree.LineNumber = -1; 784 | 785 | std::vector Nodes; 786 | extractCalltree(FuzzEntryFunc, 0, &FuzzerCalltree, &Nodes); 787 | 788 | // TODO: handle LLVMFuzzerInitialize as this function may also 789 | // reach target code, and should be considered another fuzzer entrypoint. 790 | } 791 | 792 | char Inspector::ID = 0; 793 | /* 794 | * 795 | * 796 | // LLVM currently does not support dynamically loading LTO passes. Thus, 797 | // we dont register it as a pass as we have hardcoded it into Clang instead. 798 | // Ref: https://reviews.llvm.org/D77704 799 | static RegisterPass X("inspector", "Inspector Pass", 800 | false, 801 | false ); 802 | 803 | static RegisterStandardPasses 804 | Y(PassManagerBuilder::EP_FullLinkTimeOptimizationEarly, 805 | [](const PassManagerBuilder &Builder, legacy::PassManagerBase &PM) { 806 | PM.add(new Inspector()); 807 | }); 808 | */ 809 | -------------------------------------------------------------------------------- /oss_fuzz_integration/.gitignore: -------------------------------------------------------------------------------- 1 | oss-fuzz 2 | old 3 | -------------------------------------------------------------------------------- /oss_fuzz_integration/README.md: -------------------------------------------------------------------------------- 1 | # OSS-Fuzz integration 2 | 3 | The easiest way to test the introspector is to integrate it with OSS-Fuzz 4 | and use the OSS-Fuzz infrastructure to help with fuzzing tasks. To do this 5 | we have patches and scripts to automate this process. 6 | 7 | Notice that these scripts will build a new version of your OSS-Fuzz Docker 8 | images, so preferably build things on a separate system or be prepared to 9 | rebuild images when you switch between fuzz-introspector-images and original 10 | oss-fuzz images. The reason we have to rebuild the images is we need to make 11 | a small patch to Clang due to the following issue: https://reviews.llvm.org/D77704 12 | 13 | ## Build the patched OSS-Fuzz 14 | From within this directory, run the command: 15 | ``` 16 | ./build_patched_oss_fuzz.sh 17 | ``` 18 | 19 | This will download OSS-Fuzz, apply our patches and build the Docker images. 20 | 21 | ## Run the introspector 22 | Following the above instructions, you can use the following command to perform 23 | a complete run of the introspector, including with coverage analysis. 24 | 25 | 26 | ``` 27 | cd oss-fuzz 28 | ../run_both.sh htslib 30 29 | cd corpus-0/inspector-report/ 30 | python3 -m http.server 8008 31 | ``` 32 | 33 | You can now navigate to `http://localhost:8008/fuzz_report.html` 34 | 35 | Notice that when running these commands the `run_both.sh` script will use 36 | OSS-Fuzz to generate coverage. This will result in a webserver being launched 37 | and you will have to exit that server. So, when the following output happens 38 | please use ctrl-c to exit it: 39 | 40 | ``` 41 | [2021-10-06 15:56:32,752 INFO] Finding shared libraries for targets (if any). 42 | [2021-10-06 15:56:32,759 INFO] Finished finding shared libraries for targets. 43 | [2021-10-06 15:56:32,920 DEBUG] Finished generating per-file code coverage summary. 44 | [2021-10-06 15:56:32,920 DEBUG] Generating file view html index file as: "/out/report/linux/file_view_index.html". 45 | [2021-10-06 15:56:32,931 DEBUG] Finished generating file view html index file. 46 | [2021-10-06 15:56:32,931 DEBUG] Calculating per-directory coverage summary. 47 | [2021-10-06 15:56:32,932 DEBUG] Finished calculating per-directory coverage summary. 48 | [2021-10-06 15:56:32,932 DEBUG] Writing per-directory coverage html reports. 49 | [2021-10-06 15:56:33,000 DEBUG] Finished writing per-directory coverage html reports. 50 | [2021-10-06 15:56:33,000 DEBUG] Generating directory view html index file as: "/out/report/linux/directory_view_index.html". 51 | [2021-10-06 15:56:33,000 DEBUG] Finished generating directory view html index file. 52 | [2021-10-06 15:56:33,000 INFO] Index file for html report is generated as: "file:///out/report/linux/index.html". 53 | Serving the report on http://127.0.0.1:8008/linux/index.html 54 | Serving HTTP on 0.0.0.0 port 8008 (http://0.0.0.0:8008/) ... 55 | ``` 56 | -------------------------------------------------------------------------------- /oss_fuzz_integration/build_patched_oss_fuzz.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | ################################################################################ 16 | 17 | if [ -d "oss-fuzz" ] 18 | then 19 | echo "OSS-Fuzz directory exists. Reusing existing one" 20 | else 21 | echo "Cloning oss-fuzz" 22 | git clone https://github.com/google/oss-fuzz 23 | echo "Applying diffs" 24 | cd oss-fuzz 25 | git checkout 7b1e0cbc8c280e37ddb87851b686df3bc8ae5c61 26 | git apply --ignore-space-change --ignore-whitespace ../oss-fuzz-patches.diff 27 | echo "Done" 28 | cd ../ 29 | fi 30 | 31 | rm -rf ./oss-fuzz/infra/base-images/base-clang/llvm 32 | rm -rf ./oss-fuzz/infra/base-images/base-builder/post-processing 33 | 34 | cp -rf ../llvm ./oss-fuzz/infra/base-images/base-clang/llvm 35 | cp -rf ../post-processing ./oss-fuzz/infra/base-images/base-builder/post-processing 36 | 37 | cd oss-fuzz 38 | ./infra/base-images/all.sh 39 | -------------------------------------------------------------------------------- /oss_fuzz_integration/get_full_coverage.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import sys 17 | import signal 18 | import argparse 19 | import subprocess 20 | import sys 21 | import json 22 | import threading 23 | import shutil 24 | 25 | 26 | def build_proj_with_default(project_name): 27 | try: 28 | subprocess.check_call("python3 infra/helper.py build_fuzzers %s"%(project_name), shell=True) 29 | except: 30 | print("Building default failed") 31 | exit(5) 32 | 33 | def build_proj_with_coverage(project_name): 34 | try: 35 | subprocess.check_call("python3 infra/helper.py build_fuzzers --sanitizer=coverage %s"%(project_name), shell=True) 36 | except: 37 | print("Building with coverage failed") 38 | exit(5) 39 | 40 | def get_fuzzers(project_name): 41 | execs = [] 42 | for l in os.listdir("build/out/%s"%(project_name)): 43 | print("Checking %s"%(l)) 44 | complete_path = os.path.join("build/out/%s"%(project_name), l) 45 | executable = (os.path.isfile(complete_path) and os.access(complete_path, os.X_OK)) 46 | if executable: 47 | execs.append(l) 48 | print("Executable files: %s"%(str(execs))) 49 | return execs 50 | 51 | def get_next_corpus_dir(): 52 | max_idx = -1 53 | for f in os.listdir("."): 54 | if "corpus-" in f: 55 | try: 56 | idx = int(f[len("corpus-"):]) 57 | if idx > max_idx: 58 | max_idx = idx 59 | except: 60 | None 61 | return "corpus-%d"%(max_idx+1) 62 | 63 | def get_recent_corpus_dir(): 64 | max_idx = -1 65 | for f in os.listdir("."): 66 | if "corpus-" in f: 67 | try: 68 | idx = int(f[len("corpus-"):]) 69 | if idx > max_idx: 70 | max_idx = idx 71 | except: 72 | None 73 | return "corpus-%d"%(max_idx) 74 | 75 | def run_all_fuzzers(project_name, fuzztime): 76 | # First get all fuzzers names 77 | fuzzer_names = get_fuzzers(project_name) 78 | 79 | corpus_dir = get_next_corpus_dir() 80 | os.mkdir(corpus_dir) 81 | for f in fuzzer_names: 82 | print("Running %s"%(f)) 83 | target_corpus = "./%s/%s"%(corpus_dir, f) 84 | target_crashes = "./%s/%s"%(corpus_dir, "crashes_%s"%(f)) 85 | os.mkdir(target_corpus) 86 | os.mkdir(target_crashes) 87 | 88 | cmd = ["python3 ./infra/helper.py run_fuzzer --corpus-dir=%s %s %s -max_total_time=%d -detect_leaks=0"%(target_corpus, project_name, f, fuzztime)] 89 | try: 90 | subprocess.check_call(" ".join(cmd), shell=True) 91 | print("Execution finished without exception") 92 | except: 93 | print("Executing finished with exception") 94 | 95 | # Now check if there are any crash files. 96 | for l in os.listdir("."): 97 | if "crash-" in l or "leak-" in l: 98 | shutil.move(l, target_crashes) 99 | 100 | def get_coverage(project_name): 101 | #1 Find all coverage reports 102 | corpus_dir = get_recent_corpus_dir() 103 | 104 | #2 Copy them into the right folder 105 | for f in os.listdir(corpus_dir): 106 | if os.path.isdir("build/corpus/%s/%s"%(project_name, f)): 107 | shutil.rmtree("build/corpus/%s/%s"%(project_name, f)) 108 | shutil.copytree(os.path.join(corpus_dir, f), "build/corpus/%s/%s"%(project_name, f)) 109 | 110 | #3 run coverage command 111 | try: 112 | subprocess.check_call("python3 infra/helper.py coverage --no-corpus-download %s"%(project_name), shell=True)#, timeout=60) 113 | except: 114 | print("Could not run coverage reports") 115 | 116 | 117 | #try: 118 | # subprocess.check_call("docker kill $(docker ps -qa)", shell=True) 119 | #except: 120 | # None 121 | 122 | print("Copying report") 123 | shutil.copytree("./build/out/%s/report"%(project_name), "./%s/report"%(corpus_dir)) 124 | try: 125 | summary_file = "build/out/%s/report/linux/summary.json"%(project_name) 126 | with open(summary_file, "r") as fj: 127 | content = json.load(fj) 128 | for dd in content['data']: 129 | if "totals" in dd: 130 | if "lines" in dd['totals']: 131 | print("lines: %s"%(dd['totals']['lines']['percent'])) 132 | lines_percent = dd['totals']['lines']['percent'] 133 | print("lines_percent: %s"%(lines_percent)) 134 | return lines_percent 135 | except: 136 | return None 137 | 138 | # Copy the report into the corpus directory 139 | print("Finished") 140 | 141 | 142 | def complete_coverage_check(project_name, fuzztime): 143 | build_proj_with_default(project_name) 144 | run_all_fuzzers(project_name, fuzztime) 145 | build_proj_with_coverage(project_name) 146 | percent = get_coverage(project_name) 147 | 148 | return percent 149 | 150 | def get_single_cov(project, target, corpus_dir): 151 | print("BUilding single project") 152 | build_proj_with_coverage(project) 153 | 154 | try: 155 | subprocess.check_call("python3 infra/helper.py coverage --no-corpus-download --fuzz-target %s --corpus-dir %s %s"%(target, corpus_dir, project_name), shell=True)#, timeout=60) 156 | except: 157 | print("Could not run coverage reports") 158 | 159 | 160 | if __name__ == "__main__": 161 | if len(sys.argv) != 3: 162 | print("usage: python3 ./get_full_coverage.py PROJECT_NAME FUZZTIME") 163 | exit(5) 164 | try: 165 | fuzztime = int(sys.argv[2]) 166 | except: 167 | fuzztime = 40 168 | print("Using fuzztime %d"%(fuzztime)) 169 | 170 | complete_coverage_check(sys.argv[1], fuzztime) 171 | -------------------------------------------------------------------------------- /oss_fuzz_integration/oss-fuzz-patches.diff: -------------------------------------------------------------------------------- 1 | diff --git a/infra/base-images/base-builder/Dockerfile b/infra/base-images/base-builder/Dockerfile 2 | index 256e7be5..140ac3df 100644 3 | --- a/infra/base-images/base-builder/Dockerfile 4 | +++ b/infra/base-images/base-builder/Dockerfile 5 | @@ -82,6 +82,8 @@ ENV SANITIZER_FLAGS_dataflow "-fsanitize=dataflow" 6 | 7 | ENV SANITIZER_FLAGS_thread "-fsanitize=thread" 8 | 9 | +ENV SANITIZER_FLAGS_instrumentor "-flto" 10 | + 11 | # Do not use any sanitizers in the coverage build. 12 | ENV SANITIZER_FLAGS_coverage "" 13 | 14 | @@ -138,6 +140,8 @@ RUN cd $SRC && \ 15 | COPY precompile_afl /usr/local/bin/ 16 | RUN precompile_afl 17 | 18 | +COPY post-processing $SRC/post-processing 19 | + 20 | COPY precompile_honggfuzz /usr/local/bin/ 21 | RUN precompile_honggfuzz 22 | 23 | diff --git a/infra/base-images/base-builder/compile b/infra/base-images/base-builder/compile 24 | index c934d3b5..d6844e63 100755 25 | --- a/infra/base-images/base-builder/compile 26 | +++ b/infra/base-images/base-builder/compile 27 | @@ -146,6 +146,20 @@ if [ "$FUZZING_LANGUAGE" = "jvm" ]; then 28 | export CXXFLAGS="$CXXFLAGS -fno-sanitize=leak" 29 | fi 30 | 31 | +if [ "$SANITIZER" = "instrumentor" ]; then 32 | + echo "We are in the instrumentor" 33 | + export LDFLAGS="-fuse-ld=gold" 34 | + export AR=llvm-ar 35 | + export RANLIB=llvm-ranlib 36 | + 37 | + # Move ar and ranlib 38 | + mv /usr/bin/ar /usr/bin/old-ar 39 | + mv /usr/bin/ranlib /usr/bin/old-ranlib 40 | + 41 | + ln -s /usr/local/bin/llvm-ar /usr/bin/ar 42 | + ln -s /usr/local/bin/llvm-ranlib /usr/bin/ranlib 43 | +fi 44 | + 45 | echo "---------------------------------------------------------------" 46 | echo "CC=$CC" 47 | echo "CXX=$CXX" 48 | @@ -186,6 +200,32 @@ else 49 | fi 50 | fi 51 | 52 | +if [ "$SANITIZER" = "instrumentor" ]; then 53 | + unset CXXFLAGS 54 | + unset CFLAGS 55 | + apt-get install -y libjpeg-dev zlib1g-dev 56 | + pip3 install --upgrade setuptools 57 | + pip3 install cxxfilt pyyaml beautifulsoup4 lxml soupsieve matplotlib 58 | + mkdir $SRC/inspector-tmp 59 | + 60 | + find $SRC/ -name "*.data" -exec cp {} $SRC/inspector-tmp/ \; 61 | + find $SRC/ -name "*.data.yaml" -exec cp {} $SRC/inspector-tmp/ \; 62 | + 63 | + # Move coverage report in case 64 | + if [ -d "$OUT/fuzzer_stats" ] 65 | + then 66 | + cp $OUT/fuzzer_stats/*.covreport $SRC/inspector-tmp/ 67 | + fi 68 | + ls -la $SRC/inspector-tmp 69 | + 70 | + cd $SRC/inspector-tmp 71 | + python3 $SRC/post-processing/main.py --target_dir=$SRC/inspector-tmp --git_repo_url=$GITHUB_REPO 72 | + #find $SRC/ -name "fuzz_report.html" -exec cp {} $OUT/ \; 73 | + #mv fuzz_report.html $SRC/inspector-tmp/fuzz_report.html 74 | + 75 | + cp -rf $SRC/inspector-tmp $OUT/inspector-tmp 76 | +fi 77 | + 78 | if [[ "$FUZZING_ENGINE" = "dataflow" ]]; then 79 | # Remove seed corpus as it can be huge but is not needed for a dataflow build. 80 | rm -f $OUT/*.zip 81 | diff --git a/infra/base-images/base-clang/Dockerfile b/infra/base-images/base-clang/Dockerfile 82 | index 45260941..c533509e 100644 83 | --- a/infra/base-images/base-clang/Dockerfile 84 | +++ b/infra/base-images/base-clang/Dockerfile 85 | @@ -28,6 +28,8 @@ RUN apt-get update && apt-get install -y wget sudo && \ 86 | SUDO_FORCE_REMOVE=yes apt-get remove --purge -y wget sudo && \ 87 | rm -rf /usr/local/doc/cmake /usr/local/bin/cmake-gui 88 | 89 | +COPY llvm /root/introspector-llvm/ 90 | + 91 | COPY checkout_build_install_llvm.sh /root/ 92 | # Keep all steps in the same script to decrease the number of intermediate 93 | # layes in docker file. 94 | diff --git a/infra/base-images/base-clang/checkout_build_install_llvm.sh b/infra/base-images/base-clang/checkout_build_install_llvm.sh 95 | index a62b27cf..c3cf6129 100755 96 | --- a/infra/base-images/base-clang/checkout_build_install_llvm.sh 97 | +++ b/infra/base-images/base-clang/checkout_build_install_llvm.sh 98 | @@ -61,6 +61,8 @@ function cmake_llvm { 99 | $LLVM_SRC/llvm 100 | } 101 | 102 | +apt-get install -y texinfo bison flex 103 | + 104 | # Use chromium's clang revision 105 | mkdir $SRC/chromium_tools 106 | cd $SRC/chromium_tools 107 | @@ -74,7 +76,7 @@ OUR_LLVM_REVISION=llvmorg-12-init-17251-g6de48655 108 | 109 | # To allow for manual downgrades. Set to 0 to use Chrome's clang version (i.e. 110 | # *not* force a manual downgrade). Set to 1 to force a manual downgrade. 111 | -FORCE_OUR_REVISION=0 112 | +FORCE_OUR_REVISION=1 113 | LLVM_REVISION=$(grep -Po "CLANG_REVISION = '\K([^']+)" scripts/update.py) 114 | 115 | clone_with_retries https://github.com/llvm/llvm-project.git $LLVM_SRC 116 | @@ -93,6 +95,23 @@ fi 117 | git -C $LLVM_SRC checkout $LLVM_REVISION 118 | echo "Using LLVM revision: $LLVM_REVISION" 119 | 120 | +### For fuzz introspector 121 | +echo "Applying introspector changes" 122 | +BBBASE=$PWD 123 | +cd $LLVM_SRC 124 | +cp -rf /root/introspector-llvm/include/llvm/Transforms/Inspector/ ./llvm/include/llvm/Transforms//Inspector 125 | +cp -rf /root/introspector-llvm/lib/Transforms/Inspector ./llvm/lib/Transforms/Inspector 126 | + 127 | +# LLVM currently does not support dynamically loading LTO passes. Thus, 128 | +# we hardcode it into Clang instead. 129 | +# Ref: https://reviews.llvm.org/D77704 130 | +sed -i 's/whole-program devirtualization and bitset lowering./whole-program devirtualization and bitset lowering.\nPM.add(createInspectorPass());/g' ./llvm/lib/Transforms/IPO/PassManagerBuilder.cpp 131 | +sed -i 's/using namespace/#include "llvm\/Transforms\/Inspector\/Inspector.h"\nusing namespace/g' ./llvm/lib/Transforms/IPO/PassManagerBuilder.cpp 132 | +echo "add_subdirectory(Inspector)" >> ./llvm/lib/Transforms/CMakeLists.txt 133 | +sed -i 's/Instrumentation/Instrumentation\n Inspector/g' ./llvm/lib/Transforms/IPO/CMakeLists.txt 134 | +cd $BBBASE 135 | + 136 | + 137 | # Build & install. 138 | mkdir -p $WORK/llvm-stage2 $WORK/llvm-stage1 139 | python3 $SRC/chromium_tools/clang/scripts/update.py --output-dir $WORK/llvm-stage1 140 | diff --git a/infra/base-images/base-runner/coverage b/infra/base-images/base-runner/coverage 141 | index 40c31e07..4b47adc0 100755 142 | --- a/infra/base-images/base-runner/coverage 143 | +++ b/infra/base-images/base-runner/coverage 144 | @@ -100,7 +100,7 @@ function run_fuzz_target { 145 | fi 146 | 147 | # If necessary translate to latest profraw version. 148 | - llvm-cov-rel $OUT/$target $profraw_file_mask tmp.profraw 149 | + #llvm-cov-rel $OUT/$target $profraw_file_mask tmp.profraw 150 | mv tmp.profraw $profraw_file_mask 151 | llvm-profdata merge -j=1 -sparse $profraw_file_mask -o $profdata_file 152 | 153 | @@ -112,6 +112,9 @@ function run_fuzz_target { 154 | llvm-cov export -summary-only -instr-profile=$profdata_file -object=$target \ 155 | $shared_libraries $LLVM_COV_COMMON_ARGS > $FUZZER_STATS_DIR/$target.json 156 | 157 | + # For introspector 158 | + llvm-cov show -instr-profile=$profdata_file -object=$target -line-coverage-gt=0 $shared_libraries $LLVM_COV_COMMON_ARGS > ${FUZZER_STATS_DIR}/$target.covreport 159 | + 160 | if [ -n "${FULL_SUMMARY_PER_TARGET-}" ]; then 161 | # This is needed for dataflow strategy analysis, can be removed later. See 162 | # - https://github.com/google/oss-fuzz/pull/3306 163 | diff --git a/infra/constants.py b/infra/constants.py 164 | index a323a436..68c006d1 100644 165 | --- a/infra/constants.py 166 | +++ b/infra/constants.py 167 | @@ -32,7 +32,7 @@ LANGUAGES = [ 168 | ] 169 | LANGUAGES_WITH_COVERAGE_SUPPORT = ['c', 'c++', 'go', 'jvm', 'rust', 'swift'] 170 | SANITIZERS = [ 171 | - 'address', 'none', 'memory', 'undefined', 'dataflow', 'thread', 'coverage' 172 | + 'address', 'none', 'memory', 'undefined', 'dataflow', 'thread', 'coverage', 'instrumentor' 173 | ] 174 | ARCHITECTURES = ['i386', 'x86_64'] 175 | ENGINES = ['libfuzzer', 'afl', 'honggfuzz', 'dataflow', 'none'] 176 | diff --git a/infra/helper.py b/infra/helper.py 177 | index 805f39a9..cd028f33 100755 178 | --- a/infra/helper.py 179 | +++ b/infra/helper.py 180 | @@ -270,6 +270,9 @@ def get_parser(): # pylint: disable=too-many-statements 181 | action='store_false', 182 | help='do not clean existing artifacts ' 183 | '(default).') 184 | + build_fuzzers_parser.add_argument('--git-repo', 185 | + default='none', 186 | + help='Github repository') 187 | build_fuzzers_parser.set_defaults(clean=False) 188 | 189 | check_build_parser = subparsers.add_parser( 190 | @@ -610,6 +613,7 @@ def build_fuzzers_impl( # pylint: disable=too-many-arguments,too-many-locals,to 191 | architecture, 192 | env_to_add, 193 | source_path, 194 | + github_repo, 195 | mount_path=None): 196 | """Builds fuzzers.""" 197 | if not build_image_impl(project): 198 | @@ -637,6 +641,7 @@ def build_fuzzers_impl( # pylint: disable=too-many-arguments,too-many-locals,to 199 | 'FUZZING_ENGINE=' + engine, 200 | 'SANITIZER=' + sanitizer, 201 | 'ARCHITECTURE=' + architecture, 202 | + 'GITHUB_REPO=' + github_repo, 203 | ] 204 | 205 | _add_oss_fuzz_ci_if_needed(env) 206 | @@ -689,6 +694,7 @@ def build_fuzzers(args): 207 | args.architecture, 208 | args.e, 209 | args.source_path, 210 | + args.git_repo, 211 | mount_path=args.mount_path) 212 | 213 | 214 | -------------------------------------------------------------------------------- /oss_fuzz_integration/run_both.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2021 Ada Logics Ltd. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | ################################################################################ 18 | 19 | SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 20 | python3 ${SCRIPT_DIR}/get_full_coverage.py $1 $2 21 | python3 ./infra/helper.py build_fuzzers --sanitizer=instrumentor $1 22 | 23 | LATEST_CORPUS_DIR=$(ls | sed 's/corpus-//' | sort -n | tail -1) 24 | 25 | cp -rf ./build/out/$1/inspector-tmp/ ./corpus-$LATEST_CORPUS_DIR/inspector-report 26 | cp -rf ./corpus-$LATEST_CORPUS_DIR/report/ ./corpus-$LATEST_CORPUS_DIR/inspector-report/covreport 27 | 28 | echo "If all worked, then you should be able to start a webserver at port 8008 in ./corpus-${LATEST_CORPUS_DIR}/inspector-report/" 29 | -------------------------------------------------------------------------------- /post-processing/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.txt 3 | merged_profile.profile 4 | -------------------------------------------------------------------------------- /post-processing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdaLogics/fuzz-introspector/4b7ae6f40528affd8d49f21068e67fd36af00d2c/post-processing/__init__.py -------------------------------------------------------------------------------- /post-processing/fuzz_analysis.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Performs analysis on the profiles output from fuzz introspector LLVM pass""" 16 | import os 17 | import copy 18 | import fuzz_data_loader 19 | import cxxfilt 20 | 21 | def analysis_get_optimal_targets(merged_profile): 22 | """ 23 | Finds the top reachable functions with minimum overlap. 24 | Each of these functions is not be reachable by another function 25 | in the returned set, but, they may reach some of the same functions. 26 | """ 27 | print(" - in analysis_get_optimal_targets", end=" ") 28 | optimal_set = set() 29 | target_fds = list() 30 | for fd in reversed(sorted(merged_profile['all_function_data'], key=lambda x: len(x['functionsReached']))): 31 | 32 | total_vals = 0 33 | for t in optimal_set: 34 | if t in fd['functionsReached']: 35 | total_vals += 1 36 | 37 | if fd['hitcount'] != 0: 38 | continue 39 | 40 | if len(fd['functionsReached']) < 1: 41 | continue 42 | 43 | if fd['argCount'] == 0: 44 | continue 45 | 46 | # We do not care about "main2" functions 47 | if "main2" in fd['functionName']: 48 | continue 49 | 50 | if fd['total_cyclomatic_complexity'] < 20: 51 | continue 52 | 53 | # Ensure that the overlap with existing functions in our optimal set is not excessive 54 | # set is not excessive. There is likely some overlap because of use of 55 | # utility functions and similar. 56 | #proportion = (total_vals*1.0)/(len(fd['functionsReached'])*1.0) 57 | 58 | #if proportion == 1.0: 59 | # continue 60 | 61 | condition1 = True #proportion < 0.5 62 | 63 | # We also want to include all targets that have a fairly high complexity. 64 | condition2 = fd['BBCount'] > 1 65 | 66 | if not (condition1 or condition2): 67 | continue 68 | 69 | for func_name in fd['functionsReached']: 70 | optimal_set.add(func_name) 71 | 72 | target_fds.append(fd) 73 | print(". Done") 74 | #optimal_set = set() 75 | #for fd in merged_profile['all_function_data'] 76 | return target_fds, optimal_set 77 | 78 | 79 | def analysis_synthesize_simple_targets(merged_profile): 80 | ''' 81 | Function for synthesizing fuzz targets. The way this one works is by finding 82 | optimal targets that don't overlap too much with each other. The fuzz targets 83 | are created to target functions in specific files, so all functions targeted 84 | in each fuzzer will be from the same source file. 85 | 86 | In a sense, this is more of a PoC wy to do some analysis on the data we have. 87 | It is likely that we could do something much better. 88 | ''' 89 | print(" - in analysis_synthesize_simple_targets") 90 | new_merged_profile = copy.deepcopy(merged_profile) 91 | target_fds, optimal_set = analysis_get_optimal_targets(merged_profile) 92 | fuzzer_code = "#include \"ada_fuzz_header.h\"\n" 93 | fuzzer_code += "\n" 94 | fuzzer_code += "int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {\n" 95 | fuzzer_code += " af_safe_gb_init(data, size);\n\n" 96 | variables_to_create = [] 97 | 98 | target_codes = dict() 99 | optimal_functions_targeted = [] 100 | 101 | var_idx = 0 102 | func_count = len(merged_profile['all_function_data']) 103 | if func_count > 20000: 104 | max_count = 1 105 | elif func_count > 10000 and func_count < 20000: 106 | max_count = 5 107 | elif func_count > 2000 and func_count < 10000: 108 | max_count = 7 109 | else: 110 | max_count = 10 111 | #max_count = 8 112 | curr_count = 0 113 | while curr_count < max_count: 114 | print(" - sorting by unreached complexity. ", end="") 115 | sorted_by_undiscovered_complexity = list(reversed(sorted(target_fds, key=lambda x: int(x['new_unreached_complexity'])))) 116 | print(". Done") 117 | 118 | #if len(sorted_by_undiscovered_complexity) == 0: 119 | # break 120 | #tfd = sorted_by_undiscovered_complexity[0] 121 | #if tfd == None: 122 | # break 123 | 124 | try: 125 | tfd = sorted_by_undiscovered_complexity[0] 126 | except: 127 | break 128 | if tfd == None: 129 | break 130 | 131 | #to_continue = True 132 | #if tfd['new_unreached_complexity'] <= 35: 133 | # to_continue = False 134 | #if curr_count >= max_count: 135 | # to_continue = False 136 | #if not to_continue: 137 | # break 138 | if tfd['new_unreached_complexity'] <= 35: 139 | break 140 | #if to_continue: 141 | curr_count += 1 142 | 143 | optimal_functions_targeted.append(tfd) 144 | 145 | code = "" 146 | code_var_decl = "" 147 | variable_creation = "" 148 | var_order = [] 149 | for arg_type in tfd['argTypes']: 150 | arg_type = arg_type.replace(" ","") 151 | if arg_type == "char**": 152 | code_var_decl += " char **new_var%d = af_get_double_char_p();\n"%(var_idx) 153 | # We dont want the below line but instead we want to ensure 154 | # we always return something valid. 155 | var_order.append("new_var%d"%(var_idx)) 156 | var_idx += 1 157 | elif arg_type == "char*": 158 | code_var_decl += " char *new_var%d = ada_safe_get_char_p();\n"%(var_idx) 159 | var_order.append("new_var%d"%(var_idx)) 160 | var_idx += 1 161 | elif arg_type == "int": 162 | code_var_decl += " int new_var%d = ada_safe_get_int();\n"%(var_idx) 163 | var_order.append("new_var%d"%(var_idx)) 164 | var_idx += 1 165 | elif arg_type == "int*": 166 | code_var_decl += " int *new_var%d = af_get_int_p();\n"%(var_idx) 167 | var_order.append("new_var%d"%(var_idx)) 168 | var_idx += 1 169 | elif "struct" in arg_type and "*" in arg_type and "**" not in arg_type: 170 | code_var_decl += " %s new_var%d = calloc(sizeof(%s), 1);\n"%(arg_type.replace(".", " "), var_idx, arg_type.replace(".", " ").replace("*","")) 171 | var_order.append("new_var%d"%(var_idx)) 172 | var_idx += 1 173 | else: 174 | code_var_decl += " UNKNOWN_TYPE unknown_%d;\n"%(var_idx) 175 | var_order.append("unknown_%d"%(var_idx)) 176 | var_idx += 1 177 | #if len(var_order) > 0: 178 | 179 | # Now add the function call. 180 | code += " /* target %s */\n"%(tfd['functionName']) 181 | #code += " /* linkage %s */\n"%(tfd['linkageType']) 182 | code += code_var_decl 183 | code += " %s("%(tfd['functionName']) 184 | for idx in range(len(var_order)): 185 | code += var_order[idx] 186 | if idx < (len(var_order)-1): 187 | code += ", " 188 | code += ");\n" 189 | code += "\n" 190 | if tfd['functionSourceFile'] not in target_codes: 191 | target_codes[tfd['functionSourceFile']] = dict() 192 | target_codes[tfd['functionSourceFile']]['source_code'] = "" 193 | target_codes[tfd['functionSourceFile']]['target_fds'] = list() 194 | 195 | #print("[Fuzz synthesizer] Function %s - adding code: %s"%(tfd['functionName'], code)) 196 | target_codes[tfd['functionSourceFile']]['source_code'] += code 197 | target_codes[tfd['functionSourceFile']]['target_fds'].append(tfd) 198 | 199 | 200 | print(" - calling add_func_t_reached_and_clone. ", end="") 201 | new_merged_profile = fuzz_data_loader.add_func_to_reached_and_clone(new_merged_profile, tfd) 202 | print(". Done") 203 | for tmp_ff in new_merged_profile['all_function_data']: 204 | if tmp_ff['functionName'] == tfd['functionName'] and tmp_ff['hitcount'] == 0: 205 | print("Error. Hitcount did not get set for some reason") 206 | exit(0) 207 | 208 | # We need to update the optimal targets here. 209 | # We only need to do this operation if we are actually going to continue analysis 210 | 211 | if curr_count < max_count: 212 | target_fds, optimal_set = analysis_get_optimal_targets(new_merged_profile) 213 | 214 | final_fuzzers = dict() 215 | 216 | #print("Fuzzers:") 217 | for filename in target_codes: 218 | file_fuzzer_code = fuzzer_code 219 | #file_fuzzer_code += "\n" 220 | file_fuzzer_code += target_codes[filename]['source_code'] 221 | file_fuzzer_code += " af_safe_gb_cleanup();\n" 222 | file_fuzzer_code += "}\n" 223 | #print("Fuzzer for %s:"%(filename)) 224 | #print("%s"%(file_fuzzer_code)) 225 | #print("-"*75) 226 | 227 | final_fuzzers[filename] = dict() 228 | final_fuzzers[filename]['source_code'] = file_fuzzer_code 229 | final_fuzzers[filename]['target_fds'] = target_codes[filename]['target_fds'] 230 | 231 | 232 | #fuzzer_code += " af_gb_cleanup();\n" 233 | #fuzzer_code += "\n}\n" 234 | #print("Fuzzer code:") 235 | #print(fuzzer_code) 236 | #print("-----------------") 237 | 238 | #print("Optimal target functions") 239 | #for nfd in optimal_functions_targeted: 240 | # print("%s"%(nfd['functionName'])) 241 | #print("<"*45) 242 | return final_fuzzers, new_merged_profile, optimal_functions_targeted 243 | 244 | def analysis_get_targets_for_existing_fuzzers(profiles, merged_profile): 245 | targets_for_existing_fuzzers = [] 246 | # Find promising targets for each fuzzer based on which 247 | # files it already targets. 248 | for fd in merged_profile['all_function_data']: 249 | if fd['hitcount'] != 0: 250 | continue 251 | 252 | if fd['total_cyclomatic_complexity'] <= 40: 253 | continue 254 | 255 | # Find the best profile 256 | best_profile = None 257 | best_proportion = 0.0 258 | for profile in profiles: 259 | if fd['functionSourceFile'].replace(" ","") not in profile['file_targets']: 260 | # The file of the function is hit by the fuzzer. 261 | # In this case we are not interested. 262 | continue 263 | 264 | total_targets = 0 265 | for file_target in profile['file_targets']: 266 | total_targets += len(profile['file_targets'][file_target]) 267 | 268 | function_file_targets = len(profile['file_targets'][fd['functionSourceFile'].replace(" ","")]) 269 | 270 | proportion = (function_file_targets*1.0) / (total_targets*1.0) 271 | # In order to ensure focus, we want to have the fuzzer at least dedicate 8% of 272 | # its work to this file. 273 | if proportion <= 0.1: 274 | continue 275 | 276 | if proportion > best_proportion or best_profile == None: 277 | best_proportion = proportion 278 | best_profile = profile 279 | 280 | if best_profile != None: 281 | targets_for_existing_fuzzers.append((fd, best_profile, best_proportion)) 282 | 283 | return targets_for_existing_fuzzers 284 | -------------------------------------------------------------------------------- /post-processing/fuzz_data_loader.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Reads the data output from the fuzz introspector LLVM plugin.""" 16 | import os 17 | import sys 18 | import copy 19 | import cxxfilt 20 | import yaml 21 | import fuzz_html 22 | 23 | debug = False 24 | 25 | BASE_DIR = None 26 | 27 | def data_file_read_all_function_data_yaml(filename): 28 | with open(filename, 'r') as stream: 29 | try: 30 | data_dict = yaml.safe_load(stream) 31 | return data_dict 32 | except yaml.YAMLError as exc: 33 | #print(exc) 34 | return None 35 | 36 | def data_file_read_calltree(filename): 37 | """ 38 | Extracts the calltree of a fuzzer from a .data file. 39 | """ 40 | read_tree = False 41 | function_call_depths = [] 42 | 43 | tmp_function_depths = { 44 | 'depth' : -2, 45 | 'function_calls' : [] 46 | } 47 | with open(filename, "r") as flog: 48 | for line in flog: 49 | line = line.replace("\n", "") 50 | if read_tree and "======" not in line: 51 | stripped_line = line.strip().split(" ") 52 | 53 | # Type: {spacing depth} {target filename} {line count} 54 | if len(stripped_line) == 3: 55 | filename = stripped_line[1] 56 | linenumber = int(stripped_line[2].replace("linenumber=","")) 57 | else: 58 | filename = "" 59 | linenumber=0 60 | 61 | space_count = len(line) - len(line.lstrip(' ')) 62 | depth = space_count / 2 63 | curr_node = { 'function_name' : stripped_line[0], 64 | 'functionSourceFile' : filename, 65 | 'depth' : depth, 66 | 'linenumber' : linenumber} 67 | 68 | if tmp_function_depths['depth'] != depth: 69 | if tmp_function_depths['depth'] != -2: 70 | function_call_depths += list(sorted(tmp_function_depths['function_calls'], key=lambda x: x['linenumber'])) 71 | tmp_function_depths = { 72 | 'depth' : depth, 73 | 'function_calls' : [] 74 | } 75 | tmp_function_depths['function_calls'].append(curr_node) 76 | 77 | #function_call_depths.append(curr_node) 78 | if "====================================" in line: 79 | read_tree = False 80 | if "Call tree" in line: 81 | read_tree = True 82 | # Add the remaining list of nodes to the overall list. 83 | tmp_function_depths['function_calls'] += list(sorted(tmp_function_depths['function_calls'], key=lambda x: x['linenumber'])) 84 | return function_call_depths 85 | 86 | def longestCommonPrefix(strs): 87 | """ 88 | :type strs: List[str] 89 | :rtype: str 90 | """ 91 | if len(strs) == 0: 92 | return "" 93 | current = strs[0] 94 | for i in range(1,len(strs)): 95 | temp = "" 96 | if len(current) == 0: 97 | break 98 | for j in range(len(strs[i])): 99 | if j 0 and line.replace("\n","")[-1] == ":" and "|" not in line: 379 | #print("We got a function definition: %s"%(line.replace("n",""))) 380 | 381 | if len(line.split(":")) == 3: 382 | curr_func = line.replace("\n","").split(":")[1].replace(" ","").replace(":","") 383 | else: 384 | curr_func = line.replace("\n","").replace(" ","").replace(":","") 385 | 386 | coverage_map[curr_func] = list() 387 | if curr_func != None and "|" in line: 388 | #print("Function: %s has line: %s --- %s"%(curr_func, line.replace("\n",""), str(line.split("|")))) 389 | line_number = int(line.split("|")[0]) 390 | try: 391 | hit_times = int(line.split("|")[1].replace("k","00").replace(".","")) 392 | except: 393 | hit_times = 0 394 | coverage_map[curr_func].append((line_number, hit_times)) 395 | #print("\tLine %d - hit times: %d"%(line_number, hit_times)) 396 | 397 | 398 | # We should now normalise the potential function name 399 | fname = str(line.replace("\n", "")) 400 | if ".c" in fname and ".cpp" not in fname: 401 | fname = fname.split(".c")[-1].replace(":","") 402 | if ".cpp" in fname: 403 | fname = fname.split(".cpp")[-1].replace(":","") 404 | fname = demangle_cpp_func(fname) 405 | 406 | if line.replace("\n","").endswith(":"): 407 | fname = fname.replace(":", "") 408 | fname = demangle_cpp_func(fname) 409 | functions_hit.add(fname) 410 | 411 | 412 | #for fh in functions_hit: 413 | # print("Function: %s"%(fh)) 414 | 415 | return functions_hit, coverage_map 416 | 417 | 418 | def correlate_runtime_coverage_with_reachability(profile, target_folder): 419 | # Merge any runtime coverage data that we may have to correlate 420 | # reachability and runtime coverage information. 421 | #print("Finding coverage") 422 | tname = profile['fuzzer-information']['functionSourceFile'].split("/")[-1].replace(".cpp","").replace(".c","") 423 | functions_hit, coverage_map = extract_functions_covered(target_folder, tname) 424 | if tname != None: 425 | profile['coverage'] = dict() 426 | profile['coverage']['functions-hit'] = functions_hit 427 | profile['coverage']['coverage-map'] = coverage_map 428 | return {'functions-hit': functions_hit , 429 | 'coverage-map' : coverage_map } 430 | 431 | 432 | def find_all_reached_functions(profile): 433 | funcsReachedByFuzzer = list() 434 | for func in profile['all_function_data']: 435 | if func["functionName"] == "LLVMFuzzerTestOneInput": 436 | funcsReachedByFuzzer = func['functionsReached'] 437 | return funcsReachedByFuzzer 438 | 439 | def find_all_unreached_functions(profile): 440 | funcsUnreachedByFuzzer = list() 441 | for func in profile['all_function_data']: 442 | in_fuzzer = False 443 | for func_name2 in profile['functions-reached-by-fuzzer']: 444 | if func_name2 == func['functionName']: 445 | in_fuzzer = True 446 | if not in_fuzzer: 447 | funcsUnreachedByFuzzer.append(func['functionName']) 448 | return funcsUnreachedByFuzzer 449 | 450 | def load_all_profiles(target_folder): 451 | # Get the introspector profile with raw data from each fuzzer in the target folder. 452 | data_files = get_all_profile_files(target_folder, ".data") 453 | 454 | # Parse and analyse the data from each fuzzer. 455 | profiles = [] 456 | print(" - found %d profiles to load"%(len(data_files))) 457 | for data_file in data_files: 458 | print(" - loading %s"%(data_file)) 459 | # Read the .data file 460 | profile = read_fuzzer_data_files(data_file) 461 | if profile != None: 462 | profiles.append(profile) 463 | return profiles 464 | 465 | 466 | def accummulate_profile(profile, target_folder): 467 | #print("Accumulating profile") 468 | profile['functions-reached-by-fuzzer'] = find_all_reached_functions(profile) 469 | profile['unreached-functions'] = find_all_unreached_functions(profile) 470 | profile['coverage'] = correlate_runtime_coverage_with_reachability(profile, target_folder) 471 | profile['file_targets'] = get_file_targets(profile) 472 | profile['total-basic-block-count'] = get_total_basic_blocks(profile) 473 | profile['total-cyclomatic-complexity'] = get_total_cyclomatic_complexity(profile) 474 | 475 | -------------------------------------------------------------------------------- /post-processing/fuzz_html.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Module for creating HTML reports""" 16 | import os 17 | import sys 18 | import cxxfilt 19 | import shutil 20 | import fuzz_analysis 21 | 22 | # For pretty printing the html code: 23 | from bs4 import BeautifulSoup as bs 24 | import lxml.html as lh 25 | 26 | import matplotlib.pyplot as plt 27 | from matplotlib.patches import Rectangle 28 | 29 | 30 | def create_image(image_name, color_list): 31 | print("Creating image %s"%(image_name)) 32 | plot_size = 10.0 33 | multiplier = plot_size / len(color_list) 34 | 35 | #print("Multiplier: %f"%(multiplier)) 36 | #print("Generating image: %s"%(image_name)) 37 | fig, ax = plt.subplots() 38 | ax.clear() 39 | fig.set_size_inches(20, 2) 40 | #ax.plot([1,5,2],[2,3,4],color="cyan") 41 | ax.plot() 42 | 43 | # Now create our rectangles 44 | curr_start_x = 0.0 45 | curr_size = 1.0 46 | curr_color = color_list[0] 47 | 48 | for i in range(1, len(color_list)): 49 | if curr_color == color_list[i]: 50 | curr_size += 1.0 51 | else: 52 | # We need to plot 53 | final_start_x = curr_start_x * multiplier 54 | final_end_x = final_start_x + curr_size * multiplier 55 | final_size = curr_size * multiplier 56 | #print("Plotting: %f - %f"%(final_start_x, final_size)) 57 | ax.add_patch(Rectangle((final_start_x, 0.0), final_size, 1, color=curr_color)) 58 | 59 | # Start next color area 60 | curr_start_x += curr_size 61 | curr_color = color_list[i] 62 | curr_size = 1.0 63 | 64 | # Plot the last case 65 | final_start_x = curr_start_x * multiplier 66 | final_end_x = final_start_x + curr_size * multiplier 67 | final_size = curr_size * multiplier 68 | #print("Plotting: %f - %f"%(final_start_x, final_size)) 69 | ax.add_patch(Rectangle((final_start_x, 0.0), final_size, 1, color=curr_color)) 70 | 71 | # Save file 72 | plt.title(image_name.split(".")[0]) 73 | plt.savefig(image_name) 74 | 75 | def normalise_str(s1): 76 | return s1.replace("\t", "").replace("\r", "").replace("\n", "").replace(" ", "") 77 | 78 | def create_table_head(table_head, items): 79 | html_str = f"\n" 80 | #html_str = "" 81 | for elem in items: 82 | html_str += f"\n" 83 | html_str += "" 84 | return html_str 85 | 86 | def html_table_add_row(elems): 87 | html_str = "\n" 88 | for elem in elems: 89 | html_str += f"\n" 90 | html_str += "\n" 91 | return html_str 92 | 93 | def html_get_header(): 94 | header = """ 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | """ 104 | # Add navbar to header 105 | header = header+html_get_navbar() 106 | header = header+"
" 107 | return header 108 | 109 | def html_get_navbar(): 110 | navbar = """\n
\n 111 |
\n 112 | 113 | 114 | 115 | 116 | 117 | \n 118 |
\n 119 |
\n 120 | Fuzz introspector\n 121 |
\n 122 |
\n""" 123 | return navbar 124 | 125 | def html_get_table_of_contents(toc_list): 126 | html_toc_string = "" 127 | html_toc_string += '' 137 | return html_toc_string 138 | 139 | 140 | def html_add_header_with_link(header_title, title_type, toc_list, link=None): 141 | if link == None: 142 | link = header_title.replace(" ", "-") 143 | toc_list.append((header_title, link, title_type-1)) 144 | html_string = f"" 145 | html_string += f"{header_title}\n" 146 | return html_string 147 | 148 | 149 | def demangle_cpp_func(funcname): 150 | try: 151 | demangled = cxxfilt.demangle(funcname.replace(" ", "")) 152 | return demangled 153 | except: 154 | return funcname 155 | 156 | 157 | def create_overview_table(tables, profiles): 158 | """Table with an overview of all the fuzzers""" 159 | html_string = create_table_head(tables[-1], 160 | ["Fuzzer filename", 161 | "Functions Reached", 162 | "Functions unreached", 163 | "Fuzzer depth", 164 | "Files reached", 165 | "Basic blocks reached", 166 | "Cyclomatic complexity", 167 | "Details"]) 168 | for profile in profiles: # create a row for each fuzzer. 169 | fuzzer_filename = profile['fuzzer-information']['functionSourceFile'] 170 | max_depth = 0 171 | for node in profile['function_call_depths']: 172 | if node['depth'] > max_depth: 173 | max_depth = node['depth'] 174 | 175 | html_string += html_table_add_row([ 176 | fuzzer_filename, 177 | len(profile['functions-reached-by-fuzzer']), 178 | len(profile['unreached-functions']), 179 | max_depth, 180 | len(profile['file_targets']), 181 | profile['total-basic-block-count'], 182 | profile['total-cyclomatic-complexity'], 183 | fuzzer_filename.replace(" ", "").split("/")[-1]]) 184 | html_string += ("\n
{elem}
{elem}
") 185 | return html_string 186 | 187 | 188 | def create_all_function_table(tables, project_profile, coverage_url, git_repo_url, basefolder): 189 | """Table for all functions in the project. Contains many details about each 190 | function""" 191 | html_string = create_table_head(tables[-1], 192 | ["Func name", "Git URL", "Functions filename", "Arg count", "Args", 193 | "Function reach depth", "Fuzzers hit count", "I Count", "BB Count", 194 | "Cyclomatic complexity", "Functions reached", 195 | "Reached by functions", "Accumulated cyclomatic complexity", 196 | "Undiscovered complexity"]) 197 | 198 | if basefolder == "/": 199 | basefolder = "WRONG" 200 | 201 | for fd in project_profile['all_function_data']: 202 | if basefolder == "WRONG": 203 | fd_github_url = "%s/%s#L%d" % (git_repo_url, "/".join( 204 | fd['functionSourceFile'].split("/")[3:]), fd['functionLinenumber']) 205 | else: 206 | fd_github_url = "%s/%s#L%d" % (git_repo_url, fd['functionSourceFile'].replace( 207 | basefolder, ""), fd['functionLinenumber']) 208 | html_string += html_table_add_row([ 209 | "%s" % ("" % ("%s%s.html#L%d" % (coverage_url, 210 | fd['functionSourceFile'], fd['functionLinenumber'])) + demangle_cpp_func(fd['functionName']) + ""), 211 | "LINK" % (fd_github_url), 212 | "%s" % fd['functionSourceFile'], 213 | fd['argCount'], 214 | fd['argTypes'], 215 | fd['functionDepth'], 216 | fd['hitcount'], 217 | fd['ICount'], 218 | fd['BBCount'], 219 | fd['CyclomaticComplexity'], 220 | len(fd['functionsReached']), 221 | len(fd['incoming_references']), 222 | fd['total_cyclomatic_complexity'], 223 | fd['new_unreached_complexity'] 224 | ]) 225 | html_string += ("\n") 226 | return html_string 227 | 228 | 229 | def create_top_summary_info(tables, project_profile): 230 | html_string = "" 231 | total_unreached_functions = set() 232 | total_reached_functions = set() 233 | for fd in project_profile['all_function_data']: 234 | if fd['hitcount'] == 0: 235 | total_unreached_functions.add(fd['functionName']) 236 | else: 237 | total_reached_functions.add(fd['functionName']) 238 | 239 | # Get the total amount of compleixty reached 240 | total_complexity_reached = 0 241 | total_complexity_unreached = 0 242 | for fd in project_profile['all_function_data']: 243 | if fd['hitcount'] == 0: 244 | total_complexity_unreached += fd['CyclomaticComplexity'] 245 | else: 246 | total_complexity_reached += fd['CyclomaticComplexity'] 247 | 248 | html_string += create_table_head(tables[-1], 249 | #["", "Functions", "Complexity"]) 250 | ["", "Reached", "Unreached"]) 251 | # html_string += html_table_add_row([ 252 | # "0 - Total", 253 | # len(project_profile['all_function_data']), 254 | # str(total_complexity_reached + total_complexity_unreached) 255 | # ]) 256 | 257 | functions_percentage = ((len(total_reached_functions)*1.0) / (len(total_reached_functions) + len( 258 | total_unreached_functions)*1.0))*100 259 | complexity_percentage = (total_complexity_reached / (total_complexity_reached + total_complexity_unreached))*100 260 | 261 | unreached_functions = len(total_unreached_functions) 262 | reached_functions = len(total_reached_functions) 263 | total_functions = unreached_functions + reached_functions 264 | reached_funcs_percentage = reached_functions*1.0 / (1.0 * total_functions) 265 | unreached_funcs_percentage = ((unreached_functions*1.0) / ((1.0*total_functions))) * 100.0 266 | 267 | total_complexity = total_complexity_unreached + total_complexity_reached 268 | reached_complexity_percentage = (total_complexity_reached*1.0 / (total_complexity * 1.0)) * 100.0 269 | unreached_complexity_percentage = (total_complexity_unreached*1.0 / (total_complexity*1.0)) * 100.0 270 | 271 | html_string += html_table_add_row([ 272 | "Functions", 273 | "%.5s%% (%d / %d)"%(str(functions_percentage),reached_functions,total_functions), 274 | "%.5s%% (%d / %d)"%(str(unreached_funcs_percentage), unreached_functions,total_functions) 275 | ]) 276 | 277 | #html_string += html_table_add_row([ 278 | # "Functions unreached", "%.5s%% (%d / %d)"%(str(unreached_funcs_percentage), unreached_functions,total_functions)]) 279 | 280 | html_string += html_table_add_row([ 281 | "Complexity", 282 | "%.5s%% (%d / %d)"%(reached_complexity_percentage,total_complexity_reached,total_complexity), 283 | "%.5s%% (%d / %d)"%(unreached_complexity_percentage,total_complexity_unreached,total_complexity) 284 | ]) 285 | 286 | #html_string += html_table_add_row([ 287 | # "Complexity unreached", "%.5s%% (%d / %d)"%(unreached_complexity_percentage,total_complexity_unreached,total_complexity)]) 288 | # ]) 289 | 290 | #html_string += html_table_add_row([ 291 | # "1 - Reached", 292 | # "%.5s%% (%d / %d)"%(str(functions_percentage), 293 | # reached_functions, 294 | # total_functions), 295 | # #len(total_reached_functions), 296 | # #len(total_reached_functions) + len(total_unreached_functions)), 297 | # "%.5s%% (%d / %d)"%( 298 | # reached_complexity_percentage, 299 | # total_complexity_reached, 300 | # total_complexity) 301 | # #total_complexity_reached 302 | # ]) 303 | # 304 | # html_string += html_table_add_row([ 305 | # "2 - Unreached", 306 | # "%.5s%% (%d / %d)"%(str(unreached_funcs_percentage), 307 | # unreached_functions, 308 | # total_functions), 309 | # #len(total_unreached_functions), 310 | # #len(total_reached_functions) + len(total_unreached_functions)), 311 | # "%.5s%% (%d / %d)"%( 312 | # unreached_complexity_percentage, 313 | # total_complexity_unreached, 314 | # total_complexity) 315 | # #total_complexity_unreached 316 | # ]) 317 | 318 | # html_string += html_table_add_row(["3 - Percentage", round(functions_percentage, 2), round(complexity_percentage, 2)]) 319 | 320 | html_string += ("\n") 321 | 322 | return html_string 323 | 324 | 325 | def create_html_report(profiles, 326 | project_profile, 327 | coverage_url, 328 | git_repo_url, 329 | basefolder, 330 | enforce_consistency=True): 331 | """ 332 | Logs a complete report. This is the current main place for looking at 333 | data produced by fuzz introspector. 334 | """ 335 | tables = [] 336 | 337 | # Remove existing html report. 338 | report_name = "fuzz_report.html" 339 | if os.path.isfile(report_name): 340 | os.remove(report_name) 341 | 342 | toc_list = list() 343 | print(" - Creating top section") 344 | 345 | # Create html file and top bits. 346 | # with open(report_name, "a+") as html_report: 347 | html_header = html_get_header() 348 | 349 | # Now create the body of the html. The header will be prepended later. 350 | html_string = "" 351 | 352 | # Wrap the content 353 | html_string += '
' 354 | 355 | # Add the content 356 | html_string += html_add_header_with_link("Project overview", 1, toc_list) 357 | 358 | # Project meta information 359 | html_string += html_add_header_with_link( 360 | "Project information", 2, toc_list) 361 | 362 | # 1) Display: 363 | # - The amount of functions reached by existing fuzzers 364 | html_string += html_add_header_with_link( 365 | "Reachability overview", 3, toc_list) 366 | tables.append("myTable%d" % (len(tables))) 367 | #html_string += "
" 368 | html_string += "

This is the overview of reachability by the existing fuzzers in the project

" 369 | html_string += create_top_summary_info(tables, project_profile) 370 | #html_string += "
" 371 | 372 | print(" - Identifying optimal targets") 373 | fuzz_targets_2, new_profile_2, opt_2 = fuzz_analysis.analysis_synthesize_simple_targets( 374 | project_profile) 375 | 376 | # Table overview with how reachability is if the new fuzzers are applied. 377 | #html_string += html_add_header_with_link( 378 | # "Optimal fuzzer reachability overview", 4, toc_list) 379 | #html_string += "
380 | 381 | #html_string += "

If you implement fuzzers targetting the functions listed below, then the reachability will be:

" 382 | html_string += "

If you implement fuzzers that target the remaining optimal functions then the reachability will be:

" 383 | tables.append(f"myTable{len(tables)}") 384 | html_string += create_top_summary_info(tables, new_profile_2) 385 | #html_string += "
" 386 | 387 | ############################################# 388 | # Table with overview of all fuzzers. 389 | ############################################# 390 | print(" - Creating table with overview of all fuzzers") 391 | html_string += html_add_header_with_link("Fuzzers overview", 3, toc_list) 392 | #html_string += "
" 393 | tables.append("myTable%d" % (len(tables))) 394 | html_string += create_overview_table(tables, profiles) 395 | #html_string += "
" 396 | 397 | ############################################# 398 | # Table with details about all functions in the target project. 399 | ############################################# 400 | print(" - Creating table with information about all functions in target") 401 | html_string += html_add_header_with_link( 402 | "Project functions overview", 2, toc_list) 403 | #html_string += "
" 404 | tables.append("myTable%d" % (len(tables))) 405 | html_string += create_all_function_table( 406 | tables, project_profile, coverage_url, git_repo_url, basefolder) 407 | #html_string += "
" 408 | 409 | ############################################# 410 | # Section with details about each fuzzer. 411 | ############################################# 412 | print(" - Creating section with details about each fuzzer") 413 | html_string += html_add_header_with_link("Fuzzer details", 1, toc_list) 414 | 415 | max_profile = 1 416 | curr_tt_profile = 0 417 | 418 | for profile in profiles: 419 | curr_tt_profile += 1 420 | # if (curr_tt_profile > max_profile): 421 | # sys.exit(0) 422 | 423 | fuzzer_filename = profile['fuzzer-information']['functionSourceFile'] 424 | html_string += html_add_header_with_link("Fuzzer: %s" % ( 425 | fuzzer_filename.replace(" ", "").split("/")[-1]), 2, toc_list) 426 | 427 | html_string += html_add_header_with_link( 428 | "Files hit", 3, toc_list, link="files_hit_%d" % (curr_tt_profile)) 429 | 430 | # Table showing which files this fuzzer hits. 431 | tables.append(f"myTable{len(tables)}") 432 | html_string += create_table_head(tables[-1], 433 | ["filename", "functions hit"]) 434 | for k in profile['file_targets']: 435 | html_string += html_table_add_row([k, 436 | len(profile['file_targets'][k])]) 437 | html_string += "\n" 438 | 439 | # Show the calltree of the fuzzer. If coverage exists, then we show 440 | # with colored background. Otherwise, we show plain. 441 | html_string += html_add_header_with_link( 442 | "Call tree", 3, toc_list, link=f"call_tree_{curr_tt_profile}") 443 | if not "coverage" in profile: 444 | html_string += ("

Profile calltree

\n") 445 | html_string += ("
\n")
446 |             for node in profile['function_call_depths']:
447 |                 html_string += ("%s %s (%s)\n" % (" "*int(node['depth']), demangle_cpp_func(
448 |                     node['function_name']), node['functionSourceFile']))
449 |             html_string += ("
\n") 450 | else: 451 | html_string += "

Function coverage

" 452 | html_string += ("

The following is the call tree with color coding for which " 453 | "functions are hit/not hit. This info is based on the coverage " 454 | "achieved of all fuzzers together and not just this specific " 455 | "fuzzer. This should change in the future to be per-fuzzer-basis.

") 456 | image_name = "%s_colormap.png"%(fuzzer_filename.replace(" ", "").split("/")[-1]) 457 | html_string += ""%(image_name) 458 | 459 | html_string += "
" 460 | # We use the depth_func to keep track of all function parents. We need this 461 | # when looking up if a callsite was hit or not. 462 | depth_func = dict() 463 | color_sequence = [] 464 | for node in profile['function_call_depths']: 465 | demangled_name = demangle_cpp_func(node['function_name']) 466 | 467 | # Some logic for enforcing consistency, i.e. all functions above 468 | # in the callstack must be green for something to be green. 469 | depth_func[int(node['depth'])] = demangled_name 470 | 471 | # Identify what background color the line should be, corresponding to whether 472 | # it was hit or not in the coverage analysis. 473 | # Check if the callsite was hit in the parent function. If so, it means the 474 | # node should be displayed as green. 475 | color_to_be = "red" 476 | if int(node['depth'])-1 in depth_func: 477 | for funcname_t in profile['coverage']['coverage-map']: 478 | normalised_funcname = demangle_cpp_func(normalise_str(funcname_t)) 479 | normalised_parent_funcname = normalise_str(depth_func[int(node['depth'])-1]) 480 | #print("Normalised funcname: %s"%(normalised_funcname)) 481 | #print("Normalised parent funcname: %s"%(normalised_parent_funcname)) 482 | if normalised_funcname != normalised_parent_funcname: 483 | continue 484 | for (n_line_number, hit_times_n) in profile['coverage']['coverage-map'][funcname_t]: 485 | if n_line_number == node['linenumber'] and hit_times_n != 0: 486 | color_to_be = "green" 487 | # hack to always make LLVMFUzzerTestOneInput green. TODO: avoid hardcoding like this. 488 | if demangled_name == "LLVMFuzzerTestOneInput": 489 | color_to_be = "green" 490 | color = {"green": "#99FF99", 491 | "yellow": "#FFFF99", 492 | "red": "#FF9999"}[color_to_be] 493 | 494 | color_sequence.append(color_to_be) 495 | 496 | # Get URL to coverage report for the node. 497 | link = "#" 498 | for fd in project_profile['all_function_data']: 499 | if fd['functionName'] == node['function_name']: 500 | link = coverage_url + \ 501 | "%s.html#L%d" % ( 502 | fd['functionSourceFile'], fd['functionLinenumber']) 503 | break 504 | 505 | callsite_link = "#" 506 | # Find the parent 507 | if int(node['depth'])-1 in depth_func: 508 | parent_fname = depth_func[int(node['depth'])-1] 509 | for fd in project_profile['all_function_data']: 510 | if demangle_cpp_func(fd['functionName']) == parent_fname: 511 | callsite_link = coverage_url + "%s.html#L%d" % ( 512 | fd['functionSourceFile'], # parent source file 513 | node['linenumber']) # callsite line number; 514 | 515 | # Get the Github URL to the node. However, if we got a "/" basefolder it means 516 | # it is a wrong basefolder and we handle this by removing the two first folders 517 | # in the complete path (which shuold be in most cases /src/NAME where NAME 518 | # is the project folder. 519 | if basefolder == "/": 520 | fd_github_url = "%s/%s#L%d" % (git_repo_url, "/".join( 521 | fd['functionSourceFile'].split("/")[3:]), fd['functionLinenumber']) 522 | else: 523 | fd_github_url = "%s/%s#L%d" % (git_repo_url, fd['functionSourceFile'].replace( 524 | basefolder, ""), fd['functionLinenumber']) 525 | 526 | #html_string += ( 527 | # f"
" 528 | # f"{int(node['depth'])} {\" \"*4*int(node['depth'])}" 529 | # f"{demangled_name} " 530 | # f"" 531 | # f"({node['functionSourceFile']})" 532 | # f"[coverage] " 533 | # f"| [source][linenumber:{node['linenumber']}]" 534 | # f"
\n" 535 | # ) 536 | 537 | should_do = True 538 | #libc_funcs = { "free" } 539 | libc_funcs = { } 540 | for fnn in libc_funcs: 541 | if fnn in demangled_name: 542 | should_do = False 543 | 544 | # Create the line 545 | if should_do: 546 | indentation = int(node['depth'])*16 547 | horisontal_spacing = " "*4*int(node['depth']) 548 | #html_string += ("\n" % ( 549 | 550 | if node['functionSourceFile'].replace(" ","") == "/": 551 | html_string += ("\n" % ( 552 | str(indentation), 553 | color_to_be, 554 | int(node['depth']), 555 | demangled_name, 556 | #link, 557 | #node['functionSourceFile'], 558 | #link, 559 | #fd_github_url, 560 | callsite_link)) 561 | #node['linenumber'])) 562 | else: 563 | html_string += ("\n" % ( 564 | str(indentation), 565 | color_to_be, 566 | int(node['depth']), 567 | demangled_name, 568 | #link, 569 | #node['functionSourceFile'], 570 | link, 571 | #fd_github_url, 572 | callsite_link)) 573 | #node['linenumber'])) 574 | 575 | # End of tree output 576 | create_image(image_name, color_sequence) 577 | html_string += "
" 578 | 579 | ############################################# 580 | # Details about the suggestions for additions to the fuzzer infra 581 | ############################################# 582 | print(" - Creating remaining bits") 583 | html_string += html_add_header_with_link( 584 | "Analysis and suggestions", 1, toc_list) 585 | html_string += html_add_header_with_link( 586 | "Target function analysis", 2, toc_list) 587 | 588 | #optimal_targets, optimal_set = fuzz_analysis.analysis_get_optimal_targets( 589 | # project_profile) 590 | #html_string += html_add_header_with_link( 591 | # "All interesting functions", 3, toc_list) 592 | ##html_string += "
" 593 | #html_string += "

Together, the following functions will target %d number of functions

" % ( 594 | # len(optimal_set)) 595 | #tables.append("myTable%d" % (len(tables))) 596 | #html_string += create_table_head(tables[-1], 597 | # ["Func name", "Functions filename", "Arg count", "Args", "Function depth", "hitcount", "instr count", "bb count", "cyclomatic complexity", "Reachable functions", "Incoming references", "total cyclomatic complexity", "Unreached complexity"]) 598 | 599 | #for fd in optimal_targets: 600 | # html_string += html_table_add_row([ 601 | # "%s" % demangle_cpp_func( 602 | # fd['functionName']), 603 | # fd['functionSourceFile'], 604 | # fd['argCount'], 605 | # fd['argTypes'], 606 | # fd['functionDepth'], 607 | # fd['hitcount'], 608 | # fd['ICount'], 609 | # fd['BBCount'], 610 | # fd['CyclomaticComplexity'], 611 | # len(fd['functionsReached']), 612 | # len(fd['incoming_references']), 613 | # fd['total_cyclomatic_complexity'], 614 | # fd['new_unreached_complexity']]) 615 | #html_string += ("\n") 616 | #html_string += "
" # Close section-wrapper 617 | 618 | # Another way of finding optimal functions 619 | # We already called fuzz_analysis.analysis_synthesize_simple_targets so it would be nice not having 620 | # to do it again. 621 | #fuzz_targets, new_profile, opt_func_3 = fuzz_analysis.analysis_synthesize_simple_targets( 622 | # project_profile) 623 | fuzz_targets = fuzz_targets_2 624 | new_profile = new_profile_2 625 | opt_func_3 = opt_2 626 | 627 | html_string += html_add_header_with_link( 628 | "Remaining optimal interesting functions", 3, toc_list) 629 | #html_string += "
" 630 | #html_string += "

Together, the following functions will target %d functions

" % ( 631 | # len(optimal_set)) 632 | tables.append("myTable%d" % (len(tables))) 633 | html_string += create_table_head(tables[-1], 634 | ["Func name", "Functions filename", "Arg count", "Args", "Function depth", "hitcount", "instr count", "bb count", "cyclomatic complexity", "Reachable functions", "Incoming references", "total cyclomatic complexity", "Unreached complexity"]) 635 | 636 | for fd in opt_func_3: 637 | if basefolder == "/": 638 | basefolder = "WRONG" 639 | 640 | if basefolder == "WRONG": 641 | fd_github_url = "%s/%s#L%d" % (git_repo_url, "/".join( 642 | fd['functionSourceFile'].split("/")[3:]), fd['functionLinenumber']) 643 | else: 644 | fd_github_url = "%s/%s#L%d" % (git_repo_url, fd['functionSourceFile'].replace( 645 | basefolder, ""), fd['functionLinenumber']) 646 | 647 | #print("Github url: %s" % (fd_github_url)) 648 | 649 | html_string += html_table_add_row([ 650 | "%s" % ( 651 | fd_github_url, demangle_cpp_func(fd['functionName'])), 652 | fd['functionSourceFile'], 653 | fd['argCount'], 654 | fd['argTypes'], 655 | fd['functionDepth'], 656 | fd['hitcount'], 657 | fd['ICount'], 658 | fd['BBCount'], 659 | fd['CyclomaticComplexity'], 660 | len(fd['functionsReached']), 661 | len(fd['incoming_references']), 662 | fd['total_cyclomatic_complexity'], 663 | fd['new_unreached_complexity']]) 664 | html_string += ("\n") 665 | #html_string += "
" 666 | 667 | # Show fuzzer source codes 668 | html_string += html_add_header_with_link("New fuzzers", 2, toc_list) 669 | html_string += "

The below fuzzers are templates and suggestions for how to target the set of optimal functions above

" 670 | for filename in fuzz_targets: 671 | html_string += html_add_header_with_link("%s" % 672 | (filename.split("/")[-1]), 3, toc_list) 673 | html_string += "Target file:%s
" % (filename) 674 | all_functions = "" 675 | for ttt in fuzz_targets[filename]['target_fds']: 676 | all_functions += " " + ttt['functionName'] 677 | html_string += "Target functions: %s" % (all_functions) 678 | html_string += "
%s

" % ( 679 | fuzz_targets[filename]['source_code']) 680 | 681 | ############################################# 682 | # Section with information about new fuzzers 683 | ############################################# 684 | 685 | # Table overview with how reachability is if the new fuzzers are applied. 686 | html_string += html_add_header_with_link( 687 | "Function reachability if adopted", 2, toc_list) 688 | tables.append("myTable%d" % (len(tables))) 689 | html_string += create_top_summary_info(tables, new_profile) 690 | 691 | # Details about the new fuzzers. 692 | html_string += html_add_header_with_link( 693 | "All functions overview", 3, toc_list) 694 | tables.append("myTable%d" % (len(tables))) 695 | html_string += create_all_function_table( 696 | tables, new_profile, coverage_url, git_repo_url, basefolder) 697 | 698 | # Close the content div and content_wrapper 699 | html_string += "
\n\n" 700 | 701 | # Add PrismJs for code snippet styling 702 | html_string += "" 703 | html_string += "" 704 | html_string += "" 705 | 706 | ########################### 707 | # Footer 708 | ########################### 709 | html_string += "\n") 723 | html_string += ("\n") 724 | html_string += ("\n") 725 | 726 | ########################### 727 | # Fix up table of contents. 728 | ########################### 729 | html_toc_string = html_get_table_of_contents(toc_list) 730 | 731 | # Assemble the final HTML report and write it to a file. 732 | html_string = html_header + html_toc_string + html_string 733 | 734 | # pretty print the html code: 735 | soup = bs(html_string, "lxml") 736 | prettyHTML = soup.prettify() 737 | with open(report_name, "a+") as html_report: 738 | html_report.write(prettyHTML) 739 | 740 | # Copy all of the styling into the directory. 741 | basedir = os.path.dirname(os.path.realpath(__file__)) 742 | style_dir = os.path.join(basedir, "styling") 743 | for s in ["clike.js", "prism.css", "prism.js", "styles.css", "custom.js"]: 744 | shutil.copy(os.path.join(style_dir, s), s) 745 | -------------------------------------------------------------------------------- /post-processing/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import sys 17 | import argparse 18 | 19 | import fuzz_data_loader 20 | import fuzz_html 21 | 22 | 23 | def run_analysis_on_dir(target_folder, 24 | git_repo_url, 25 | coverage_url): 26 | # Load all the data needed 27 | print("[+] Loading profiles") 28 | profiles = fuzz_data_loader.load_all_profiles(target_folder) 29 | print("[+] Accummulating profiles") 30 | for profile in profiles: 31 | fuzz_data_loader.accummulate_profile(profile, target_folder) 32 | 33 | # Merge all profiles into a project profile 34 | print("[+] Creating project profile") 35 | project_profile = fuzz_data_loader.create_project_profile(profiles) 36 | 37 | # Find a base folder 38 | basefolder = fuzz_data_loader.identify_base_folder(project_profile) 39 | #print("Base folder: %s"%(basefolder)) 40 | 41 | print("[+] Refining profiles") 42 | for profile in profiles: 43 | fuzz_data_loader.refine_profile(profile) 44 | 45 | # Create the HTML report that can be viewed. 46 | if coverage_url == "": 47 | coverage_url = "http://localhost:8008/covreport/linux" 48 | 49 | print("[+] Creating HTML report") 50 | fuzz_html.create_html_report(profiles, project_profile, coverage_url, git_repo_url, basefolder, False) 51 | 52 | 53 | def create_parser(): 54 | parser = argparse.ArgumentParser() 55 | 56 | parser.add_argument("--target_dir", 57 | type=str, 58 | help="Directory where the data files are", 59 | required=True) 60 | 61 | parser.add_argument('--git_repo_url', 62 | type=str, 63 | help="Git repository with the source code", 64 | default="") 65 | 66 | parser.add_argument('--coverage_url', 67 | type=str, 68 | help="URL with coverage information", 69 | default="") 70 | 71 | args = parser.parse_args() 72 | return args 73 | 74 | if __name__ == "__main__": 75 | args = create_parser() 76 | 77 | #target_dir = sys.argv[1] 78 | print("Running fuzz introspector post-processing") 79 | run_analysis_on_dir(args.target_dir, args.git_repo_url, args.coverage_url) 80 | print("Ending fuzz introspector post-processing") 81 | -------------------------------------------------------------------------------- /post-processing/run_for_dir.sh: -------------------------------------------------------------------------------- 1 | R1="$(find $1 -name "*.data")" 2 | #echo "What we found" 3 | #echo $R1 4 | 5 | 6 | python3 ./main.py ${R1} 7 | -------------------------------------------------------------------------------- /post-processing/styling/clike.js: -------------------------------------------------------------------------------- 1 | Prism.languages.clike = { 2 | 'comment': [ 3 | { 4 | pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, 5 | lookbehind: true, 6 | greedy: true 7 | }, 8 | { 9 | pattern: /(^|[^\\:])\/\/.*/, 10 | lookbehind: true, 11 | greedy: true 12 | } 13 | ], 14 | 'string': { 15 | pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, 16 | greedy: true 17 | }, 18 | 'class-name': { 19 | pattern: /(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i, 20 | lookbehind: true, 21 | inside: { 22 | 'punctuation': /[.\\]/ 23 | } 24 | }, 25 | 'keyword': /\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/, 26 | 'boolean': /\b(?:true|false)\b/, 27 | 'function': /\b\w+(?=\()/, 28 | 'number': /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i, 29 | 'operator': /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/, 30 | 'punctuation': /[{}[\];(),.:]/ 31 | }; 32 | -------------------------------------------------------------------------------- /post-processing/styling/custom.js: -------------------------------------------------------------------------------- 1 | $( document ).ready(function() { 2 | createTables(); 3 | }); 4 | 5 | // createTables instantiates the datatables. 6 | // During this process the number of rows is 7 | // checked, and some elements are left out of 8 | // the datatables. Currently, these decisions 9 | // are implemented: 10 | // 1: Only show pagination, length change 11 | // dropdown when there are more than 10 rows. 12 | // 2: Only show search field when there are more 13 | // than 4 rows. (This could potentially be change 14 | // to specific tables instead where fuzzer names 15 | // vary) 16 | function createTables() { 17 | $.each(tableIds, function(index, value) { 18 | 19 | // Get number of rows in this table 20 | var rowCount = $('#'+value+' tr').length; 21 | 22 | 23 | var bPaginate; 24 | var bLengthChange; 25 | var bInfo; 26 | var bFilter; 27 | 28 | if(rowCount<6) { 29 | bFilter = false; 30 | } else { 31 | bFilter = true; 32 | } 33 | 34 | if(rowCount<12) { 35 | bPaginate = false; 36 | bLengthChange = false; 37 | bInfo = false; 38 | } else { 39 | bPaginate = true; 40 | bLengthChange = true; 41 | bInfo = true; 42 | } 43 | 44 | // Create the table: 45 | $('#'+value).DataTable({'bPaginate': bPaginate, 46 | 'bLengthChange': bLengthChange, 47 | 'bInfo': bInfo, 48 | 'bFilter': bFilter}); 49 | }); 50 | } -------------------------------------------------------------------------------- /post-processing/styling/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | background: none; 11 | text-shadow: 0 1px white; 12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 13 | font-size: 1em; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 33 | text-shadow: none; 34 | background: #b3d4fc; 35 | } 36 | 37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 38 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 39 | text-shadow: none; 40 | background: #b3d4fc; 41 | } 42 | 43 | @media print { 44 | code[class*="language-"], 45 | pre[class*="language-"] { 46 | text-shadow: none; 47 | } 48 | } 49 | 50 | /* Code blocks */ 51 | pre[class*="language-"] { 52 | padding: 1em; 53 | margin: .5em 0; 54 | overflow: auto; 55 | } 56 | 57 | :not(pre) > code[class*="language-"], 58 | pre[class*="language-"] { 59 | background: #f5f2f0; 60 | } 61 | 62 | /* Inline code */ 63 | :not(pre) > code[class*="language-"] { 64 | padding: .1em; 65 | border-radius: .3em; 66 | white-space: normal; 67 | } 68 | 69 | .token.comment, 70 | .token.prolog, 71 | .token.doctype, 72 | .token.cdata { 73 | color: red; 74 | } 75 | 76 | .token.punctuation { 77 | color: #999; 78 | } 79 | 80 | .token.namespace { 81 | opacity: .7; 82 | } 83 | 84 | .token.property, 85 | .token.tag, 86 | .token.boolean, 87 | .token.number, 88 | .token.constant, 89 | .token.symbol, 90 | .token.deleted { 91 | color: #905; 92 | } 93 | 94 | .token.selector, 95 | .token.attr-name, 96 | .token.string, 97 | .token.char, 98 | .token.builtin, 99 | .token.inserted { 100 | color: #690; 101 | } 102 | 103 | .token.operator, 104 | .token.entity, 105 | .token.url, 106 | .language-css .token.string, 107 | .style .token.string { 108 | color: #9a6e3a; 109 | /* This background color was intended by the author of this theme. */ 110 | background: hsla(0, 0%, 100%, .5); 111 | } 112 | 113 | .token.atrule, 114 | .token.attr-value, 115 | .token.keyword { 116 | color: #07a; 117 | } 118 | 119 | .token.function, 120 | .token.class-name { 121 | color: #DD4A68; 122 | } 123 | 124 | .token.regex, 125 | .token.important, 126 | .token.variable { 127 | color: #e90; 128 | } 129 | 130 | .token.important, 131 | .token.bold { 132 | font-weight: bold; 133 | } 134 | .token.italic { 135 | font-style: italic; 136 | } 137 | 138 | .token.entity { 139 | cursor: help; 140 | } 141 | -------------------------------------------------------------------------------- /post-processing/styling/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Roboto", sans-serif; 3 | margin: 0; 4 | overflow: hidden; 5 | } 6 | pre { 7 | max-width: 700px; 8 | } 9 | h2 { 10 | margin-top: 50px; 11 | } 12 | h3 { 13 | /*background: #373b50; 14 | color: white; 15 | padding: 5px; 16 | text-transform: uppercase; 17 | font-size: 16px; 18 | margin-bottom: 0px;*/ 19 | margin-top: 50px; 20 | } 21 | h4 { 22 | /*background: #373b50; 23 | color: white; 24 | padding: 5px; 25 | text-transform: uppercase; 26 | font-size: 14px; 27 | margin-bottom: 0px;*/ 28 | margin-top: 50px; 29 | } 30 | tbody { 31 | font-size: 14px; 32 | color: #444444 !important; 33 | } 34 | th { 35 | font-size: 15px; 36 | } 37 | table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd { 38 | background: #fafafa !important; 39 | } 40 | table.dataTable tbody tr.even { 41 | background: white; 42 | } 43 | table.dataTable { 44 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); 45 | } 46 | .no-top-margin { 47 | margin-top: 0px; 48 | } 49 | .content-wrapper { 50 | display: grid; 51 | grid-template-columns: 350px auto; 52 | } 53 | .top-navbar { 54 | height: 70px; 55 | background: #373b50; 56 | display: flex; 57 | position: relative; 58 | color: white; 59 | align-items: center; 60 | justify-content: center; 61 | } 62 | .section-wrapper { 63 | width: calc(100% - 34px); 64 | padding: 12px; 65 | background: #f2f7fa; 66 | border-bottom-left-radius: 2px; 67 | border-bottom-right-radius: 2px; 68 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);; 69 | } 70 | 71 | .top-navbar-accordion { 72 | position: absolute; 73 | left: 2%; 74 | top: 18.5px; 75 | fill: aliceblue; 76 | height: 30px; 77 | } 78 | .top-navbar-title { 79 | font-size: 18px; 80 | font-weight: 600; 81 | } 82 | .left-sidebar { 83 | grid-column: 1; 84 | border-right: 1px solid #d9d9d9 85 | } 86 | .content-section { 87 | height: calc(100vh - 70px); 88 | grid-column: 2; 89 | /*max-width: calc(100vw - 350px);*/ 90 | overflow: scroll; 91 | padding-left: 10px; 92 | /*border-left: 1px solid #d9d9d9;*/ 93 | /*width: calc(100% - 20px);*/ 94 | } 95 | .left-sidebar-content-box { 96 | position: fixed; 97 | top: 5%; 98 | padding: 20px; 99 | } 100 | .left-sidebar-content-box a { 101 | color: #7b7b7b; 102 | text-decoration: none; 103 | } 104 | .left-sidebar-content-box a:hover { 105 | color: black; 106 | } 107 | .dataTables_wrapper { 108 | width: 100%; 109 | max-width: 100%; 110 | } 111 | .dataTable { 112 | max-width: 100%; 113 | } 114 | thead { 115 | background: #e4e9f4; 116 | } 117 | /*thead th, 118 | thead tr { 119 | background: transparent !important; 120 | }*/ 121 | thead th { 122 | background-image: linear-gradient(to bottom,rgba(255,255,255,0.8) 0%,rgba(255,255,255,0.7) 30%,rgba(255,255,255,0.5) 60%,rgba(255,255,255,0) 100%) !important; 123 | background-repeat: no-repeat !important; 124 | } 125 | thead th:not(:last-child) { 126 | border-right: 1px solid #eee; 127 | } 128 | .cell-border { 129 | border: 1px solid #eee !important; 130 | } 131 | table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd { 132 | background: white; 133 | } 134 | table.dataTable tbody tr.odd:hover, 135 | table.dataTable tbody tr.even:hover { 136 | background: #d6e9f8!important; 137 | } 138 | table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { 139 | border: none!important; 140 | max-width: 300px; 141 | word-wrap: break-word; 142 | font-size: .8rem; 143 | font-weight: 700; 144 | } 145 | table.dataTable.cell-border tbody td { 146 | font-weight: 400 !important; 147 | } 148 | /*table.dataTable.cell-border thead { 149 | background: #e8eaed; 150 | }*/ 151 | table.dataTable { 152 | margin: 0 !important; 153 | } 154 | table.dataTable thead th, table.dataTable thead td { 155 | /*background: #f1f3f4;*/ 156 | /*border-bottom: 1px solid white;*/ 157 | border-bottom: none; 158 | font-size: .875rem; 159 | text-align: center; 160 | /*padding: 14px 17px 14px 0px!important;*/ 161 | 162 | padding: .8em !important; 163 | } 164 | table.dataTable thead th.sorting_asc { 165 | background-image: linear-gradient(to bottom,rgba(255,255,255,0.8) 0%,rgba(255,255,255,0.7) 30%,rgba(255,255,255,0.5) 60%,rgba(255,255,255,0) 100%), url(https://cdn.datatables.net/1.10.25/images/sort_asc.png) !important; 166 | } 167 | table.dataTable thead th.sorting_desc { 168 | background-image: linear-gradient(to bottom,rgba(255,255,255,0.8) 0%,rgba(255,255,255,0.7) 30%,rgba(255,255,255,0.5) 60%,rgba(255,255,255,0) 100%), url(https://cdn.datatables.net/1.10.25/images/sort_desc.png) !important; 169 | } 170 | table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { 171 | border-top: 1px solid #eee !important; 172 | border-right: 1px solid #eee !important; 173 | padding: 0.8em !important; 174 | } 175 | table.dataTable { 176 | width: auto !important; 177 | } 178 | h1 { 179 | margin-top: 57px; 180 | } 181 | .dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { 182 | background: white!important; 183 | border: 1px solid #d9d9d9; 184 | } 185 | .red-background { 186 | display: block; 187 | margin-bottom: 0px; 188 | } 189 | .red-background .coverage-line-inner { 190 | /*background: #ffeaea;*/ 191 | color: #a20000; 192 | padding: 0px 0px 193 | } 194 | .red-background .language-clike { 195 | background: #bf4343; 196 | } 197 | 198 | .yellow-background { 199 | display: block; 200 | margin-bottom: 0px; 201 | } 202 | .yellow-background .coverage-line-inner { 203 | /*background: #ffeaea;*/ 204 | color: #a20000; 205 | padding: 0px 0px 206 | } 207 | .yellow-background .language-clike { 208 | background: #CCCC00; 209 | } 210 | 211 | .green-background { 212 | display: block; 213 | margin-bottom: 0px; 214 | } 215 | .green-background .coverage-line-inner { 216 | /* background: #ffeaea; */ 217 | color: #a20000; 218 | padding: 0px 0px 219 | } 220 | .green-background .language-clike { 221 | background: #00CC00; 222 | } 223 | 224 | .coverage-line-filename { 225 | color: #ce0000; 226 | line-height: 1; 227 | } 228 | -------------------------------------------------------------------------------- /rebuild_pass.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Ada Logics Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | ################################################################################ 16 | 17 | BASE=$PWD 18 | 19 | cd build 20 | BUILD_BASE=$PWD 21 | cd ${BUILD_BASE} 22 | 23 | rm -rf ./llvm-project/llvm/include/llvm/Transforms/Inspector 24 | rm -rf ./llvm-project/llvm/lib/Transforms/Inspector 25 | cp -rf ${BASE}/llvm/include/llvm/Transforms/Inspector/ ./llvm-project/llvm/include/llvm/Transforms/Inspector 26 | cp -rf ${BASE}/llvm/lib/Transforms/Inspector ./llvm-project/llvm/lib/Transforms/Inspector 27 | 28 | cd llvm-build 29 | make -j3 30 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.10.0 2 | cxxfilt==0.3.0 3 | lxml==4.6.3 4 | matplotlib==3.3.4 5 | PyYAML==5.4.1 6 | soupsieve==2.2.1 7 | --------------------------------------------------------------------------------