├── tests ├── corpus │ └── a.txt ├── frida │ ├── target.c │ ├── cov.sh │ ├── fuzz.sh │ └── Makefile ├── gcc │ ├── target.c │ ├── cov.sh │ ├── fuzz.sh │ └── Makefile ├── llvm │ ├── target.c │ ├── cov.sh │ ├── fuzz.sh │ └── Makefile ├── qemu │ ├── target.c │ ├── cov.sh │ ├── fuzz.sh │ └── Makefile ├── .gitignore ├── stdin │ ├── cov.sh │ ├── fuzz.sh │ ├── Makefile │ └── target.c ├── README.md └── target.c ├── requirements.txt ├── .dockerignore ├── .gitignore ├── .gitmodules ├── examples ├── img │ ├── tests-gcc-coverage.png │ ├── tests-frida-coverage.png │ ├── tests-llvm-coverage.png │ ├── tests-qemu-coverage.png │ ├── x509-parser-cert-coverage.png │ └── x509-parser-dummy-coverage.png ├── tests.md └── x509-parser.md ├── Dockerfile ├── afl-cov-fast.py ├── afl-cov-fast-frida.py ├── README.md ├── afl-cov-fast-qemu.py ├── utils.py ├── afl-cov-fast-llvm.py ├── afl-cov-fast-gcc.py └── LICENSE /tests/corpus/a.txt: -------------------------------------------------------------------------------- 1 | AAAAAAAAA -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tqdm>=4.62.1 2 | -------------------------------------------------------------------------------- /tests/frida/target.c: -------------------------------------------------------------------------------- 1 | ../target.c -------------------------------------------------------------------------------- /tests/gcc/target.c: -------------------------------------------------------------------------------- 1 | ../target.c -------------------------------------------------------------------------------- /tests/llvm/target.c: -------------------------------------------------------------------------------- 1 | ../target.c -------------------------------------------------------------------------------- /tests/qemu/target.c: -------------------------------------------------------------------------------- 1 | ../target.c -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /AFLplusplus 2 | /drcov-merge 3 | /tests 4 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.out 2 | *.gcno 3 | *.gcda 4 | *.profraw 5 | output 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | .vscode 4 | .token 5 | /AFLplusplus 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "drcov-merge"] 2 | path = drcov-merge 3 | url = https://github.com/airbus-seclab/drcov-merge.git 4 | -------------------------------------------------------------------------------- /examples/img/tests-gcc-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbus-seclab/afl-cov-fast/HEAD/examples/img/tests-gcc-coverage.png -------------------------------------------------------------------------------- /examples/img/tests-frida-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbus-seclab/afl-cov-fast/HEAD/examples/img/tests-frida-coverage.png -------------------------------------------------------------------------------- /examples/img/tests-llvm-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbus-seclab/afl-cov-fast/HEAD/examples/img/tests-llvm-coverage.png -------------------------------------------------------------------------------- /examples/img/tests-qemu-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbus-seclab/afl-cov-fast/HEAD/examples/img/tests-qemu-coverage.png -------------------------------------------------------------------------------- /examples/img/x509-parser-cert-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbus-seclab/afl-cov-fast/HEAD/examples/img/x509-parser-cert-coverage.png -------------------------------------------------------------------------------- /examples/img/x509-parser-dummy-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbus-seclab/afl-cov-fast/HEAD/examples/img/x509-parser-dummy-coverage.png -------------------------------------------------------------------------------- /tests/gcc/cov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ../../afl-cov-fast.py -m gcc --code-dir '.' --afl-fuzzing-dir 'output' --coverage-cmd './target.cov.out @@' $@ 3 | -------------------------------------------------------------------------------- /tests/stdin/cov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ../../afl-cov-fast.py -m gcc --code-dir '.' --afl-fuzzing-dir 'output' --coverage-cmd './target.cov.out' $@ 3 | -------------------------------------------------------------------------------- /tests/frida/cov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ../../afl-cov-fast.py -m frida --afl-fuzzing-dir 'output' --afl-path '../../AFLplusplus' --coverage-cmd './target.out @@' $@ 3 | -------------------------------------------------------------------------------- /tests/qemu/cov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ../../afl-cov-fast.py -m qemu --afl-fuzzing-dir 'output' --afl-path '../../AFLplusplus' --coverage-cmd './target.out @@' $@ 3 | -------------------------------------------------------------------------------- /tests/llvm/cov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ../../afl-cov-fast.py -m llvm --code-dir '.' --afl-fuzzing-dir 'output' --coverage-cmd './target.cov.out @@' --binary-path './target.cov.out' $@ 3 | -------------------------------------------------------------------------------- /tests/gcc/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export AFL_SKIP_CPUFREQ=1 3 | export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 4 | ../../AFLplusplus/afl-fuzz -i ../corpus -o output $@ -- ./target.out @@ 5 | -------------------------------------------------------------------------------- /tests/llvm/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export AFL_SKIP_CPUFREQ=1 3 | export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 4 | ../../AFLplusplus/afl-fuzz -i ../corpus -o output $@ -- ./target.out @@ 5 | -------------------------------------------------------------------------------- /tests/stdin/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export AFL_SKIP_CPUFREQ=1 3 | export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 4 | ../../AFLplusplus/afl-fuzz -i ../corpus -o output $@ -- ./target.out 5 | -------------------------------------------------------------------------------- /tests/frida/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export AFL_SKIP_CPUFREQ=1 3 | export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 4 | ../../AFLplusplus/afl-fuzz -O -i ../corpus -o output $@ -- ./target.out @@ 5 | -------------------------------------------------------------------------------- /tests/qemu/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export AFL_SKIP_CPUFREQ=1 3 | export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 4 | ../../AFLplusplus/afl-fuzz -Q -i ../corpus -o output $@ -- ./target.out @@ 5 | -------------------------------------------------------------------------------- /tests/qemu/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-O2 -Wall -Wextra -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -fPIE -pie -fPIC -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code 2 | 3 | all: target 4 | 5 | target: target.c 6 | $(CC) $(CFLAGS) -o target.out target.c 7 | 8 | clean: 9 | $(RM) *.out 10 | -------------------------------------------------------------------------------- /tests/frida/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-O2 -Wall -Wextra -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -fPIE -pie -fPIC -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code 2 | 3 | all: target 4 | 5 | target: target.c 6 | $(CC) $(CFLAGS) -o target.out target.c 7 | 8 | clean: 9 | $(RM) *.out 10 | -------------------------------------------------------------------------------- /tests/gcc/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-O2 -Wall -Wextra -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -fPIE -pie -fPIC -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code 2 | 3 | all: target 4 | 5 | target: target.c 6 | gcc $(CFLAGS) --coverage -o target.cov.out target.c 7 | ../../AFLplusplus/afl-gcc-fast $(CFLAGS) -o target.out target.c 8 | 9 | clean: 10 | $(RM) *.out *.gcno *.gcda 11 | -------------------------------------------------------------------------------- /tests/stdin/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-O2 -Wall -Wextra -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -fPIE -pie -fPIC -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code 2 | 3 | all: target 4 | 5 | target: target.c 6 | gcc $(CFLAGS) --coverage -o target.cov.out target.c 7 | ../../AFLplusplus/afl-gcc-fast $(CFLAGS) -o target.out target.c 8 | 9 | clean: 10 | $(RM) *.out *.gcno *.gcda 11 | -------------------------------------------------------------------------------- /tests/llvm/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-O2 -Wall -Wextra -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -fPIE -pie -fPIC -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code 2 | 3 | all: target 4 | 5 | target: target.c 6 | clang $(CFLAGS) -fprofile-instr-generate -fcoverage-mapping -o target.cov.out target.c 7 | ../../AFLplusplus/afl-clang-lto $(CFLAGS) -o target.out target.c 8 | 9 | clean: 10 | $(RM) *.out *.profraw 11 | -------------------------------------------------------------------------------- /tests/stdin/target.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define EXIT_FAILURE -1 8 | #define EXIT_SUCCESS 0 9 | #define MAX_INPUT_SIZE 4096 10 | 11 | typedef void func(void); 12 | 13 | int main(int argc, char **argv) { 14 | unsigned char buf[MAX_INPUT_SIZE]; 15 | int len; 16 | unsigned long addr; 17 | 18 | // Wait for input on stdin 19 | for (len = 0; len == 0; len = read(STDIN_FILENO, buf, sizeof(buf))) { 20 | } 21 | 22 | if (len < 0) { 23 | return len; 24 | } else if (len < 8) { 25 | return EXIT_FAILURE; 26 | } 27 | 28 | addr = *((unsigned long *)&buf); 29 | printf("Got header %p\n", (void *)addr); 30 | if (addr != 0xdeadbeef) { 31 | return EXIT_FAILURE; 32 | } 33 | 34 | func *f = (func *)addr; 35 | f(); 36 | 37 | return EXIT_SUCCESS; 38 | } 39 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:slim-bookworm AS build 2 | 3 | # Install dependencies 4 | RUN apt update && apt install -y --no-install-recommends \ 5 | ca-certificates \ 6 | git 7 | 8 | # Build drcov-merge 9 | WORKDIR /build 10 | RUN git clone --recursive "https://github.com/airbus-seclab/afl-cov-fast.git" \ 11 | && cd afl-cov-fast/drcov-merge \ 12 | && cargo build --release 13 | 14 | FROM debian:bookworm-slim 15 | 16 | VOLUME ["/workdir", "/project"] 17 | WORKDIR /workdir 18 | 19 | # Install dependencies 20 | RUN apt update && apt install -y --no-install-recommends \ 21 | python3 \ 22 | python3-tqdm \ 23 | lcov \ 24 | llvm-16 \ 25 | llvm-16-tools && \ 26 | rm -rf /var/lib/apt/lists/* 27 | 28 | # Copy binaries from build stage 29 | COPY --from=build /build/afl-cov-fast /afl-cov-fast 30 | COPY --from=build /build/afl-cov-fast/drcov-merge/target/release/drcov-merge /usr/bin/drcov-merge 31 | 32 | # Make sure the LLVM directory is in the path so that we can use the tool names 33 | # directly instead of aliases (e.g. llvm-profdata instead of llvm-profdata-16) 34 | ENV PATH="/usr/lib/llvm-16/bin:$PATH" 35 | 36 | # Open shell 37 | ENTRYPOINT ["/bin/bash"] 38 | CMD [] 39 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # afl-cov-fast 2 | 3 | This folder contains simple tests for all backends supported by afl-cov-fast. 4 | For an end-to-end example of how to run them, see the 5 | [associated example](../examples/tests.md). 6 | 7 | ## Setup 8 | 9 | The tests assume that the AFL++ repository has been cloned and built at the root 10 | of the afl-cov-fast repository: 11 | 12 | ```bash 13 | cd /path/to/afl-cov-fast 14 | git clone -b dev https://github.com/AFLplusplus/AFLplusplus.git 15 | cd AFLplusplus 16 | make distrib 17 | cd ../tests 18 | ``` 19 | 20 | ## Target 21 | 22 | A very simple target (in [`target.c`](target.c)) is used to run these tests: it 23 | reads up to 4096 bytes from the file provided as argument and, if the 8 first 24 | bytes are equal to `0xdeadbeef`, they jump to the `0xdeadbeef` address (which 25 | should crash). 26 | 27 | The corpus for this target only contains a dummy file: [`a.txt`](corpus/a.txt), 28 | with some `A`s. 29 | 30 | ## Scripts 31 | 32 | Each backend has a `Makefile` to easily build the target for fuzzing and, if 33 | relevant, build it again with code-coverage options: 34 | 35 | ```bash 36 | make 37 | ``` 38 | 39 | The `fuzz.sh` script can be used to run AFL++ on the built target. For example, 40 | to run it for 120 seconds for a given backend: 41 | 42 | ```bash 43 | ./fuzz.sh -V 120 44 | ``` 45 | 46 | Once AFL++ has been run and the queue has been populated, coverage information 47 | can be generated using the `cov.sh` script: 48 | 49 | ```bash 50 | ./cov.sh -j8 51 | ``` 52 | -------------------------------------------------------------------------------- /tests/target.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define EXIT_FAILURE -1 8 | #define EXIT_SUCCESS 0 9 | #define MAX_FILE_SIZE 4096 10 | 11 | typedef void func(void); 12 | 13 | int read_file(char *path, unsigned char *out, int *out_len) { 14 | FILE *fp; 15 | 16 | fp = fopen(path, "rb"); 17 | if (!fp) { 18 | fprintf(stderr, "Failed to open file at %s: %s\n", path, strerror(errno)); 19 | return EXIT_FAILURE; 20 | } 21 | 22 | *out_len = fread(out, 1, MAX_FILE_SIZE, fp); 23 | if (ferror(fp)) { 24 | perror("Failed to read certificate"); 25 | return EXIT_FAILURE; 26 | } 27 | if (!feof(fp)) { 28 | fprintf(stderr, "Warning: truncating input file to %d\n", MAX_FILE_SIZE); 29 | } 30 | 31 | fclose(fp); 32 | printf("Read %d bytes from %s\n", *out_len, path); 33 | return EXIT_SUCCESS; 34 | } 35 | 36 | int main(int argc, char **argv) { 37 | unsigned char buf[MAX_FILE_SIZE]; 38 | int len, err; 39 | unsigned long addr; 40 | 41 | if (argc != 2) { 42 | printf("Usage: ./target \n"); 43 | return EXIT_FAILURE; 44 | } 45 | 46 | err = read_file(argv[1], buf, &len); 47 | if (err) { 48 | return err; 49 | } 50 | 51 | if (len < 8) { 52 | return EXIT_FAILURE; 53 | } 54 | 55 | addr = *((unsigned long *)&buf); 56 | printf("Got header %p\n", (void *)addr); 57 | if (addr != 0xdeadbeef) { 58 | return EXIT_FAILURE; 59 | } 60 | 61 | func *f = (func *)addr; 62 | f(); 63 | 64 | return EXIT_SUCCESS; 65 | } 66 | -------------------------------------------------------------------------------- /afl-cov-fast.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Copyright (C) 2025 Airbus 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import argparse 21 | import importlib 22 | 23 | import utils 24 | 25 | AFL_COV_MODULES = { 26 | "gcc": importlib.import_module("afl-cov-fast-gcc"), 27 | "llvm": importlib.import_module("afl-cov-fast-llvm"), 28 | "qemu": importlib.import_module("afl-cov-fast-qemu"), 29 | "frida": importlib.import_module("afl-cov-fast-frida"), 30 | } 31 | 32 | 33 | def main(): 34 | # Only parse the "mode" argument here, and disable the -h / --help option to 35 | # allow passing it to each module 36 | parser = argparse.ArgumentParser(add_help=False) 37 | parser.add_argument( 38 | "-m", 39 | "--mode", 40 | required=True, 41 | help="Run mode (depending on the target binary compilation workflow)", 42 | choices=AFL_COV_MODULES.keys(), 43 | ) 44 | args, remaining_args = parser.parse_known_args() 45 | 46 | # Get module to call based on user arguments 47 | try: 48 | module = AFL_COV_MODULES[args.mode.lower()] 49 | except KeyError: 50 | raise ValueError(f"Unhandled mode {args.mode}") 51 | 52 | # Call the selected module's main function, which will handle the rest of 53 | # the arguments 54 | module.main(remaining_args) 55 | 56 | 57 | if __name__ == "__main__": 58 | main() 59 | -------------------------------------------------------------------------------- /examples/tests.md: -------------------------------------------------------------------------------- 1 | # Using afl-cov-fast on provided test targets 2 | 3 | ## Target 4 | 5 | A very simple target used as a test for all supported backends is present in the 6 | [tests directory](../tests). Its goal is to be easy to understand, having a bug 7 | which might not be reached depending on coverage, and to work with all backends. 8 | 9 | ## Setup 10 | 11 | Install the afl-cov-fast dependencies: 12 | 13 | * Python 3 (tested with version 3.11.2); 14 | * The Python `tqdm` package (tested with version 4.64.1); 15 | * `lcov` / `genhtml` (tested with version 1.16); 16 | * `llvm-profdata` (tested with version 14.0.6); 17 | * `llvm-cov` (tested with version 14.0.6). 18 | 19 | Clone and build AFL++: 20 | 21 | ```bash 22 | git clone -b dev https://github.com/AFLplusplus/AFLplusplus.git 23 | cd AFLplusplus 24 | make distrib 25 | ``` 26 | 27 | **Note:** This was tested with commit `4086b93ad799e3cd28968102a2d175b166a31300` from Sep 9, 2024. 28 | 29 | #### GCC 30 | 31 | Build the target and run a fuzzing campaign for 120 seconds: 32 | 33 | ```bash 34 | cd tests/gcc 35 | make 36 | ./fuzz.sh -V 120 37 | ``` 38 | 39 | Generate the coverage information: 40 | 41 | ```bash 42 | ./cov.sh -j8 43 | ``` 44 | 45 | The coverage overview should be present in `output/cov/web/index.html`: 46 | 47 | ![GCC test coverage](img/tests-gcc-coverage.png) 48 | 49 | #### LLVM 50 | 51 | Build the target and run a fuzzing campaign for 120 seconds: 52 | 53 | ```bash 54 | cd tests/llvm 55 | make 56 | ./fuzz.sh -V 120 57 | ``` 58 | 59 | Generate the coverage information: 60 | 61 | ```bash 62 | ./cov.sh -j8 63 | ``` 64 | 65 | The coverage overview should be present in `output/cov/web/index.html`: 66 | 67 | ![LLVM test coverage](img/tests-llvm-coverage.png) 68 | 69 | #### QEMU 70 | 71 | Build the target and run a fuzzing campaign for 120 seconds: 72 | 73 | ```bash 74 | cd tests/qemu 75 | make 76 | ./fuzz.sh -V 120 77 | ``` 78 | 79 | Generate the coverage information: 80 | 81 | ```bash 82 | ./cov.sh -j8 83 | ``` 84 | 85 | The coverage overview should be present in `output/cov/drcov/full.drcov.trace`: 86 | 87 | ![QEMU test coverage](img/tests-qemu-coverage.png) 88 | 89 | #### Frida 90 | 91 | Build the target and run a fuzzing campaign for 120 seconds: 92 | 93 | ```bash 94 | cd tests/frida 95 | make 96 | ./fuzz.sh -V 120 97 | ``` 98 | 99 | Generate the coverage information: 100 | 101 | ```bash 102 | ./cov.sh -j8 103 | ``` 104 | 105 | The coverage overview should be present in `output/cov/drcov/full.drcov.trace`: 106 | 107 | ![Frida test coverage](img/tests-frida-coverage.png) 108 | -------------------------------------------------------------------------------- /examples/x509-parser.md: -------------------------------------------------------------------------------- 1 | # Using afl-cov-fast with an x509 parser 2 | 3 | ## Target 4 | 5 | The objectives is to test afl-cov-fast on a target fuzzing structure inputs to 6 | observe whether the code is well covered or not. The 7 | [x509-parser project from ANSSI](https://github.com/ANSSI-FR/x509-parser) was 8 | chosen as it matches these requirements while being simple to fuzz with AFL++. 9 | 10 | ## Setup 11 | 12 | Install the afl-cov-fast dependencies: 13 | 14 | * Python 3 (tested with version 3.11.2); 15 | * The Python `tqdm` package (tested with version 4.64.1); 16 | * `lcov` / `genhtml` (tested with version 1.16). 17 | 18 | Clone and build AFL++: 19 | 20 | ```bash 21 | git clone -b dev https://github.com/AFLplusplus/AFLplusplus.git 22 | cd AFLplusplus 23 | make distrib 24 | ``` 25 | 26 | **Note:** This was tested with commit `7ad694716bf9772d8db5fbbe3f7aec4be99e61df` from Jan 13, 2025. 27 | 28 | Clone the target project and build it with AFL++: 29 | 30 | ```bash 31 | git clone https://github.com/ANSSI-FR/x509-parser.git 32 | cd x509-parser 33 | CC=../AFLplusplus/afl-gcc-fast make 34 | ``` 35 | 36 | **Note:** This was tested with commit `6f3bae3c52989180df6af46da1acb0329315b82a` from Oct 24, 2022. 37 | 38 | ## Corpus 39 | 40 | Let's prepare two corpora: one with a dummy file, and another with various x509 41 | certificates to observe if there are coverage differences. 42 | 43 | ### Dummy corpus 44 | 45 | Create a single file with arbitrary content: 46 | 47 | ```bash 48 | mkdir dummy_corpus 49 | echo 'AAAAAAAAAAAAA' > dummy_corpus/a.txt 50 | ``` 51 | 52 | ### Certificate corpus 53 | 54 | We can reuse the dataset used by OpenSSL for fuzzing and regression tests: 55 | 56 | ```bash 57 | git clone https://github.com/openssl/fuzz-corpora.git 58 | mkdir cert_corpus 59 | cp fuzz-corpora/*/* cert_corpus/ 60 | rm -rf fuzz-corpora 61 | ``` 62 | 63 | To improve efficiency, we can remove test cases which do not increase coverage: 64 | 65 | ```bash 66 | ../AFLplusplus/afl-cmin -i cert_corpus -o cert_corpus_unique -- ./build/x509-parser @@ 67 | ``` 68 | 69 | ## Fuzzing 70 | 71 | Fuzz the target using each corpus for 300 seconds: 72 | 73 | ```bash 74 | export AFL_SKIP_CPUFREQ=1 75 | ../AFLplusplus/afl-fuzz -i dummy_corpus -o dummy_output -V 300 -- ./build/x509-parser @@ 76 | ../AFLplusplus/afl-fuzz -i cert_corpus_unique -o cert_output -V 300 -- ./build/x509-parser @@ 77 | ``` 78 | 79 | ## Compute coverage 80 | 81 | Now that we have output directories from our fuzzing campaigns, rebuild the 82 | target so that it outputs coverage information: 83 | 84 | ```bash 85 | make clean 86 | CFLAGS="--coverage -fPIC" make 87 | ``` 88 | 89 | Finally, run afl-cov-fast on both outputs to generate an overview of the 90 | coverage: 91 | 92 | ```bash 93 | ../afl-cov-fast/afl-cov-fast.py -m gcc --code-dir . --afl-fuzzing-dir dummy_output --coverage-cmd 'build/x509-parser @@' -j8 94 | ../afl-cov-fast/afl-cov-fast.py -m gcc --code-dir . --afl-fuzzing-dir cert_output --coverage-cmd 'build/x509-parser @@' -j8 95 | ``` 96 | 97 | The coverage overview should be present in `dummy_output/cov/web/index.html`: 98 | 99 | ![Dummy coverage](img/x509-parser-dummy-coverage.png) 100 | 101 | and in `cert_output/cov/web/index.html`: 102 | 103 | ![Certificate coverage](img/x509-parser-cert-coverage.png) 104 | -------------------------------------------------------------------------------- /afl-cov-fast-frida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Copyright (C) 2025 Airbus 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import os 21 | import sys 22 | import glob 23 | import asyncio 24 | import logging 25 | import pathlib 26 | import argparse 27 | import tempfile 28 | from typing import Iterable 29 | 30 | from tqdm import tqdm 31 | from tqdm.asyncio import tqdm_asyncio 32 | from tqdm.contrib.logging import logging_redirect_tqdm 33 | 34 | import utils 35 | 36 | 37 | def create_folder_hierarchy(args: argparse.Namespace): 38 | # Create all required directories 39 | output_dir = utils.init_output_dir(args) 40 | (output_dir / "drcov").mkdir() 41 | 42 | 43 | def perform_env_check(args: argparse.Namespace): 44 | if args.no_env_check: 45 | return 46 | 47 | path = args.afl_path / "afl-frida-trace.so" 48 | if not path.is_file(): 49 | raise ValueError(f"{path} file not found, did you compile AFL-Frida?") 50 | 51 | if not utils.command_exists(args.drcov_merge_path): 52 | raise ValueError( 53 | f"{args.drcov_merge_path} command not found, did you build and install the submodule? If not, try specifying `--drcov-merge-path`" 54 | ) 55 | 56 | 57 | async def generate_coverage( 58 | args: argparse.Namespace, input_file: pathlib.Path, semaphore: asyncio.Semaphore 59 | ) -> pathlib.Path: 60 | async with semaphore: 61 | logging.info("Generating coverage for test case: %s", input_file) 62 | 63 | # Create a new folder for this run 64 | output_file = tempfile.mktemp( 65 | prefix="tmp", suffix=".drcov.trace", dir=args.output_dir / "drcov" 66 | ) 67 | output_file = pathlib.Path(output_file) 68 | 69 | # We use environment variables to enable Frida stalker and write 70 | # coverage to the generated file path 71 | # Note: We keep the original environment variables as afl-frida-trace 72 | # accepts many AFL_* options through there 73 | ld_preload = os.environ.get("AFL_PRELOAD", "") 74 | ld_preload += f":{args.afl_path}/afl-frida-trace.so" 75 | env = { 76 | **os.environ, 77 | **utils.split_env_args(args.env), 78 | "AFL_FRIDA_INST_COVERAGE_FILE": output_file, 79 | "LD_PRELOAD": ld_preload, 80 | } 81 | 82 | # Run the original binary 83 | cmd, stdin = utils.prepare_coverage_cmd(args.coverage_cmd, input_file) 84 | await utils.run_cmd(cmd, env=env, timeout=args.timeout, stdin=stdin) 85 | 86 | # Make sure the output file was properly generated 87 | if not args.no_env_check and not output_file.is_file(): 88 | raise RuntimeError("No coverage information generated during run!") 89 | 90 | return output_file 91 | 92 | 93 | async def merge_tracefiles(args: argparse.Namespace) -> pathlib.Path: 94 | # Merge all drcov files 95 | drcov_files_glob = str(args.output_dir / "drcov") + "/tmp*.drcov.trace" 96 | output_file = args.output_dir / "drcov" / "full.drcov.trace" 97 | 98 | cmd = [ 99 | args.drcov_merge_path, 100 | "-o", 101 | str(output_file), 102 | drcov_files_glob, 103 | ] 104 | await utils.run_cmd(cmd) 105 | 106 | # Cleanup drcov files (if necessary) 107 | if not args.keep_intermediate: 108 | for path in glob.iglob(drcov_files_glob): 109 | pathlib.Path(path).unlink() 110 | 111 | return output_file 112 | 113 | 114 | async def run(args: argparse.Namespace): 115 | loop = asyncio.get_event_loop() 116 | if args.jobs <= 0: 117 | raise ValueError("Number of jobs must be greater than 0") 118 | 119 | # Setup logging 120 | logging.basicConfig( 121 | level=args.log_level, 122 | format="[%(asctime)s] %(levelname)s: %(message)s", 123 | datefmt="%H:%M:%S", 124 | ) 125 | logging.getLogger().setLevel(args.log_level) 126 | 127 | # Check environment is properly configured 128 | perform_env_check(args) 129 | 130 | # Create the output folder and all subfolders 131 | create_folder_hierarchy(args) 132 | 133 | # Collect all input files 134 | queue_files = utils.get_queue_files(args) 135 | 136 | # Create a task for each file for which to collect coverage and use a 137 | # semaphore to limit the number of simultaneous jobs 138 | semaphore = asyncio.Semaphore(args.jobs) 139 | tasks = [ 140 | asyncio.create_task(generate_coverage(args, input_file, semaphore)) 141 | for input_file in queue_files 142 | ] 143 | 144 | # Run the tasks and re-raise any exception 145 | try: 146 | if args.no_progress: 147 | await asyncio.gather(*tasks) 148 | else: 149 | with logging_redirect_tqdm(): 150 | await tqdm_asyncio.gather(*tasks, desc="Generating coverage") 151 | except Exception as e: 152 | for task in tasks: 153 | if not task.done(): 154 | task.cancel() 155 | raise e 156 | 157 | # Merge all trace files 158 | tqdm.write("Merging coverage") 159 | trace_file = await merge_tracefiles(args) 160 | 161 | 162 | def parse_args(argv: list) -> argparse.Namespace: 163 | parser = utils.common_args_parser() 164 | parser.add_argument( 165 | "-a", 166 | "--afl-path", 167 | type=pathlib.Path, 168 | required=True, 169 | help="Path to the AFL++ folder (e.g. './AFLplusplus')", 170 | ) 171 | parser.add_argument( 172 | "--drcov-merge-path", 173 | help="Path to the drcov-merge binary", 174 | default="drcov-merge", 175 | ) 176 | args = parser.parse_args(argv) 177 | utils.normalize_user_paths(args) 178 | return args 179 | 180 | 181 | def main(argv: list): 182 | args = parse_args(argv) 183 | asyncio.run(run(args)) 184 | 185 | 186 | if __name__ == "__main__": 187 | # Allow running this file directly instead of calling afl-cov-fast.py with 188 | # the -m option 189 | main(sys.argv[1:]) 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # afl-cov-fast 2 | 3 | afl-cov-fast is a tool to generate code coverage from AFL test cases. It aims to 4 | efficiently generate a "zero coverage" report of functions and lines never 5 | covered by a fuzzing campaign, both when code is available and in binary-only 6 | mode. 7 | 8 | It is a reimplementation of 9 | [afl-cov](https://github.com/vanhauser-thc/afl-cov)'s "cover-corpus" mode with 10 | additional support for binary-only targets (via the QEMU and Frida backends), 11 | multiprocessing, and reduced Python overhead. 12 | 13 | ## Install 14 | 15 | Start by cloning this repository and its submodules: 16 | 17 | ```bash 18 | $ git clone --recursive https://github.com/airbus-seclab/afl-cov-fast.git 19 | ``` 20 | 21 | ### Requirements 22 | 23 | * Python 3.6 or newer; 24 | * The Python `tqdm` package; 25 | * `lcov` (for the `lcov` and `genhtml` commands); 26 | * `llvm` (for the `llvm-profdata` command); 27 | * `llvm-tools` (for the `llvm-cov` command). 28 | 29 | #### Docker 30 | 31 | A `Dockerfile` is provided with pre-installed dependencies. To use it, the 32 | following commands can be used: 33 | 34 | ```bash 35 | $ docker build -t afl-cov-fast . 36 | $ docker run --rm -it -v "${PWD}:/workdir" -v ":" -u `id -u`:`id -g` --name afl-cov-fast afl-cov-fast 37 | ``` 38 | 39 | **Notes:** 40 | 41 | * You have to mount your project with the same path in the docker container when 42 | in GCC mode so that paths to coverage files are properly resolved. In other 43 | cases, you can use an arbitrary folder (e.g. `/project`); 44 | * AFL++ is purposefully not included in the Docker image so you can use your 45 | exact version when relying on the QEMU or Frida backend. This means that you 46 | will have to build the QEMU TCG plugin yourself (see below) if necessary. 47 | 48 | #### Debian 49 | 50 | When using Debian, dependencies can be installed with the following commands: 51 | 52 | ```bash 53 | $ sudo apt update 54 | $ sudo apt install python3 python3-tqdm lcov llvm-16 llvm-16-tools 55 | ``` 56 | 57 | **Notes:** 58 | 59 | * The `lcov` dependency is only required for the GCC and LLVM backend; 60 | * The `llvm-*` dependencies are only required for the LLVM backend. 61 | 62 | #### venv 63 | 64 | Alternatively, Python dependencies could be installed using `pip` in a virtual 65 | environment like so: 66 | 67 | ```bash 68 | $ python3 -m venv .env 69 | $ source .env/bin/activate 70 | $ pip3 install -r requirements.txt 71 | ``` 72 | 73 | ### QEMU 74 | 75 | For the QEMU backend, coverage is obtained using a QEMU TCG plugin. This is 76 | supported natively by AFL++ since 77 | [this commit](https://github.com/AFLplusplus/AFLplusplus/commit/a4017406dc02e49dbc3820e3eb5bee5e15d7fed1) 78 | present in [v4.10c](https://github.com/AFLplusplus/AFLplusplus/releases/tag/v4.10c). 79 | 80 | The QEMU plugins simply need to be compiled in AFL++: 81 | 82 | ```bash 83 | $ cd /qemu_mode 84 | $ make -C qemuafl plugins 85 | ``` 86 | 87 | ### drcov-merge 88 | 89 | A custom utility is used to merge drcov files into a single "full" coverage file 90 | (for the QEMU and Frida backend), which is included as a submodule of this 91 | repository and must be built separately: 92 | 93 | ```bash 94 | $ cargo install --path drcov-merge 95 | ``` 96 | 97 | ## Usage 98 | 99 | ### Compilation 100 | 101 | When source code is available (for the GCC or LLVM backend), the binary is 102 | instrumented at compile time. Specific options must be added for coverage to be 103 | exported when the target is run. These options are described below. 104 | 105 | #### GCC 106 | 107 | Compile the target with the `--coverage` option (equivalent to 108 | `-fprofile-arcs -ftest-coverage`). 109 | 110 | When run, the output binary will then output coverage information in `gcno` and 111 | `gcda` files. 112 | 113 | #### LLVM 114 | 115 | Compile the target with the `-fprofile-instr-generate -fcoverage-mapping` 116 | options. 117 | 118 | When run, the output binary will then output coverage information in a `profraw` 119 | file. 120 | 121 | ### Running 122 | 123 | When running, the `coverage-command` is used to run the target binary and obtain 124 | coverage. The `@@` and `AFL_FILE` strings in this command will be replaced with 125 | the path to the input file. If none of them is present, the content of the input 126 | file will be written to `stdin` instead. 127 | 128 | #### GCC 129 | 130 | Example usage: 131 | 132 | ```bash 133 | $ afl-cov-fast.py -m gcc --code-dir 'src' --afl-fuzzing-dir 'output' --coverage-cmd './a.out @@' -j8 134 | ``` 135 | 136 | Then, simply open the `output/cov/web/index.html` file. 137 | 138 | #### LLVM 139 | 140 | Example usage: 141 | 142 | ```bash 143 | $ afl-cov-fast.py -m llvm --code-dir 'src' --afl-fuzzing-dir 'output' --coverage-cmd './a.out @@' --binary-path 'a.out' -j8 144 | ``` 145 | 146 | Then, simply open the `output/cov/web/index.html` file. 147 | 148 | #### QEMU 149 | 150 | Example usage: 151 | 152 | ```bash 153 | $ afl-cov-fast.py -m qemu --afl-fuzzing-dir 'output' --afl-path './AFLplusplus' --coverage-cmd './a.out @@' -j8 154 | ``` 155 | 156 | Then, load the aggregated coverage file in `output/cov/drcov/full.drcov.trace` 157 | using [lighthouse](https://github.com/gaasedelen/lighthouse), 158 | [Cartographer](https://github.com/nccgroup/Cartographer), 159 | [lightkeeper](https://github.com/WorksButNotTested/lightkeeper), or 160 | [CutterDRcov](https://github.com/rizinorg/CutterDRcov). 161 | 162 | #### Frida 163 | 164 | Example usage: 165 | 166 | ```bash 167 | $ afl-cov-fast.py -m frida --afl-fuzzing-dir 'output' --afl-path './AFLplusplus' --coverage-cmd './a.out @@' -j8 168 | ``` 169 | 170 | Then, load the aggregated coverage file in `output/cov/drcov/full.drcov.trace` 171 | using [lighthouse](https://github.com/gaasedelen/lighthouse), 172 | [Cartographer](https://github.com/nccgroup/Cartographer), 173 | [lightkeeper](https://github.com/WorksButNotTested/lightkeeper), or 174 | [CutterDRcov](https://github.com/rizinorg/CutterDRcov). 175 | 176 | ## Examples 177 | 178 | Practical usage examples are also provided in the [examples directory](./examples). 179 | 180 | ## Contributing 181 | 182 | Issues and pull requests are welcome! 183 | 184 | Notably, the following features could be added: 185 | 186 | * Support for other backends (e.g. 187 | [Nyx](https://github.com/AFLplusplus/AFLplusplus/tree/dev/nyx_mode), 188 | [Unicorn](https://github.com/AFLplusplus/AFLplusplus/tree/dev/unicorn_mode)), 189 | * Support for in-memory fuzzing instead of providing inputs from a file or 190 | stdin, 191 | * Any other feature you might find useful. 192 | 193 | ## References 194 | 195 | * 196 | * 197 | * 198 | 199 | ## License 200 | 201 | This project is licensed under the GPLv3 License. See the 202 | [LICENSE file](LICENSE) for details. 203 | -------------------------------------------------------------------------------- /afl-cov-fast-qemu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Copyright (C) 2025 Airbus 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import os 21 | import sys 22 | import glob 23 | import shlex 24 | import asyncio 25 | import logging 26 | import pathlib 27 | import argparse 28 | import tempfile 29 | from typing import Iterable 30 | 31 | from tqdm import tqdm 32 | from tqdm.asyncio import tqdm_asyncio 33 | from tqdm.contrib.logging import logging_redirect_tqdm 34 | 35 | import utils 36 | 37 | 38 | def create_folder_hierarchy(args: argparse.Namespace): 39 | # Create all required directories 40 | output_dir = utils.init_output_dir(args) 41 | (output_dir / "drcov").mkdir() 42 | 43 | 44 | def perform_env_check(args: argparse.Namespace): 45 | if args.no_env_check: 46 | return 47 | 48 | path = args.afl_path / "afl-qemu-trace" 49 | if not path.is_file(): 50 | raise ValueError(f"{path} file not found, did you compile AFL-QEMU?") 51 | 52 | path = args.afl_path / "qemu_mode/qemuafl/build/contrib/plugins/libdrcov.so" 53 | if not path.is_file(): 54 | raise ValueError(f"{path} file not found, did you compile QEMU plugins?") 55 | 56 | if not utils.command_exists(args.drcov_merge_path): 57 | raise ValueError( 58 | f"{args.drcov_merge_path} command not found, did you build and install the submodule? If not, try specifying `--drcov-merge-path`" 59 | ) 60 | 61 | 62 | async def generate_coverage( 63 | args: argparse.Namespace, input_file: pathlib.Path, semaphore: asyncio.Semaphore 64 | ) -> pathlib.Path: 65 | async with semaphore: 66 | logging.info("Generating coverage for test case: %s", input_file) 67 | 68 | # Create a new folder for this run 69 | output_file = tempfile.mktemp( 70 | prefix="tmp", suffix=".drcov.trace", dir=args.output_dir / "drcov" 71 | ) 72 | output_file = pathlib.Path(output_file) 73 | 74 | # We use environment variables to enable the trace plugin and write 75 | # coverage to the generated file path 76 | # Note: We keep the original environment variables as afl-qemu-trace 77 | # accepts many AFL_* and QEMU_* options through there 78 | plugin_path = ( 79 | args.afl_path / "qemu_mode/qemuafl/build/contrib/plugins/libdrcov.so" 80 | ) 81 | env = { 82 | **os.environ, 83 | **utils.split_env_args(args.env), 84 | "QEMU_PLUGIN": f"{plugin_path},arg=filename={output_file}", 85 | } 86 | 87 | # Run the original binary 88 | cmd, stdin = utils.prepare_coverage_cmd(args.coverage_cmd, input_file) 89 | cmd = [str(args.afl_path / "afl-qemu-trace"), "--"] + shlex.split(cmd) 90 | await utils.run_cmd(cmd, env=env, timeout=args.timeout, stdin=stdin) 91 | 92 | # Make sure the output file was properly generated 93 | if not args.no_env_check and not output_file.is_file(): 94 | raise RuntimeError("No coverage information generated during run!") 95 | 96 | return output_file 97 | 98 | 99 | async def merge_tracefiles(args: argparse.Namespace) -> pathlib.Path: 100 | # Merge all drcov files 101 | drcov_files_glob = str(args.output_dir / "drcov") + "/tmp*.drcov.trace" 102 | output_file = args.output_dir / "drcov" / "full.drcov.trace" 103 | 104 | cmd = [ 105 | args.drcov_merge_path, 106 | "-o", 107 | str(output_file), 108 | drcov_files_glob, 109 | ] 110 | await utils.run_cmd(cmd) 111 | 112 | # Cleanup drcov files (if necessary) 113 | if not args.keep_intermediate: 114 | for path in glob.iglob(drcov_files_glob): 115 | pathlib.Path(path).unlink() 116 | 117 | return output_file 118 | 119 | 120 | async def run(args: argparse.Namespace): 121 | loop = asyncio.get_event_loop() 122 | if args.jobs <= 0: 123 | raise ValueError("Number of jobs must be greater than 0") 124 | 125 | # Setup logging 126 | logging.basicConfig( 127 | level=args.log_level, 128 | format="[%(asctime)s] %(levelname)s: %(message)s", 129 | datefmt="%H:%M:%S", 130 | ) 131 | logging.getLogger().setLevel(args.log_level) 132 | 133 | # Check environment is properly configured 134 | perform_env_check(args) 135 | 136 | # Create the output folder and all subfolders 137 | create_folder_hierarchy(args) 138 | 139 | # Collect all input files 140 | queue_files = utils.get_queue_files(args) 141 | 142 | # Create a task for each file for which to collect coverage and use a 143 | # semaphore to limit the number of simultaneous jobs 144 | semaphore = asyncio.Semaphore(args.jobs) 145 | tasks = [ 146 | asyncio.create_task(generate_coverage(args, input_file, semaphore)) 147 | for input_file in queue_files 148 | ] 149 | 150 | # Run the tasks and re-raise any exception 151 | try: 152 | if args.no_progress: 153 | await asyncio.gather(*tasks) 154 | else: 155 | with logging_redirect_tqdm(): 156 | await tqdm_asyncio.gather(*tasks, desc="Generating coverage") 157 | except Exception as e: 158 | for task in tasks: 159 | if not task.done(): 160 | task.cancel() 161 | raise e 162 | 163 | # Merge all trace files 164 | tqdm.write("Merging coverage") 165 | trace_file = await merge_tracefiles(args) 166 | 167 | 168 | def parse_args(argv: list) -> argparse.Namespace: 169 | parser = utils.common_args_parser() 170 | parser.add_argument( 171 | "-a", 172 | "--afl-path", 173 | type=pathlib.Path, 174 | required=True, 175 | help="Path to the AFL++ folder (e.g. './AFLplusplus')", 176 | ) 177 | parser.add_argument( 178 | "--drcov-merge-path", 179 | help="Path to the drcov-merge binary", 180 | default="drcov-merge", 181 | ) 182 | args = parser.parse_args(argv) 183 | utils.normalize_user_paths(args) 184 | return args 185 | 186 | 187 | def main(argv: list): 188 | args = parse_args(argv) 189 | asyncio.run(run(args)) 190 | 191 | 192 | if __name__ == "__main__": 193 | # Allow running this file directly instead of calling afl-cov-fast.py with 194 | # the -m option 195 | main(sys.argv[1:]) 196 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Copyright (C) 2025 Airbus 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import os 21 | import shlex 22 | import shutil 23 | import asyncio 24 | import logging 25 | import pathlib 26 | import argparse 27 | import itertools 28 | from typing import Optional, Iterable, Union 29 | 30 | 31 | def normalize_user_paths(args: argparse.Namespace): 32 | # Normalize all paths in arguments provided on the command line 33 | for k, v in vars(args).items(): 34 | if isinstance(v, pathlib.Path): 35 | v = v.expanduser().resolve() 36 | setattr(args, k, v) 37 | 38 | 39 | def command_exists(cmd: Union[str, pathlib.Path]) -> bool: 40 | return shutil.which(cmd) is not None 41 | 42 | 43 | def init_output_dir(args: argparse.Namespace) -> pathlib.Path: 44 | # Handle already existing output directories 45 | args.output_dir = args.output_dir or args.afl_fuzzing_dir / "cov" 46 | if args.output_dir.exists(): 47 | if args.overwrite: 48 | logging.warning("Deleting previous output directory %s", args.output_dir) 49 | shutil.rmtree(args.output_dir) 50 | else: 51 | raise RuntimeError(f"Output directory {args.output_dir} already exists") 52 | 53 | # Create output directory 54 | args.output_dir.mkdir() 55 | return args.output_dir 56 | 57 | 58 | def get_queue_files(args: argparse.Namespace) -> Iterable[pathlib.Path]: 59 | # Check if the user provided the top-level folder or a folder of a single 60 | # instance (e.g. output vs output/default) 61 | logging.info("Collecting queue files") 62 | if (args.afl_fuzzing_dir / "queue").is_dir(): 63 | return args.afl_fuzzing_dir.glob("queue/id:*") 64 | else: 65 | return args.afl_fuzzing_dir.glob("*/queue/id:*") 66 | 67 | 68 | def prepare_coverage_cmd(coverage_cmd: str, input_file: pathlib.Path): 69 | cmd = coverage_cmd.replace("@@", "AFL_FILE") 70 | if "AFL_FILE" in cmd: 71 | cmd = cmd.replace("AFL_FILE", str(input_file)) 72 | stdin = None 73 | else: 74 | with open(input_file, "rb") as f: 75 | stdin = f.read() 76 | return cmd, stdin 77 | 78 | 79 | def split_env_args(env_args: list) -> dict: 80 | out = {} 81 | for var in env_args: 82 | try: 83 | key, value = var.split("=", maxsplit=1) 84 | out[key] = value 85 | except ValueError as e: 86 | logging.warning("Invalid environment variable argument: '%s' (%s)", var, e) 87 | return out 88 | 89 | 90 | async def run_cmd( 91 | cmd: Union[str, list], 92 | env: Optional[dict] = None, 93 | timeout: Optional[float] = None, 94 | stdin: Optional[bytes] = None, 95 | redirect_stdout: Optional[pathlib.Path] = None, 96 | ): 97 | if isinstance(cmd, str): 98 | cmd = shlex.split(cmd) 99 | 100 | # Handle user-specified negative timeout: asyncio expects None to disable it 101 | if timeout is not None and timeout < 0: 102 | timeout = None 103 | 104 | logging.debug("Running '%s' (stdin: %s, env: %s)", cmd, stdin, env) 105 | proc = await asyncio.create_subprocess_exec( 106 | *cmd, 107 | env=env, 108 | stdin=asyncio.subprocess.PIPE, 109 | stdout=asyncio.subprocess.PIPE, 110 | stderr=asyncio.subprocess.PIPE, 111 | ) 112 | 113 | try: 114 | stdout, stderr = await asyncio.wait_for( 115 | proc.communicate(stdin), 116 | timeout=timeout, 117 | ) 118 | 119 | logging.debug("%s:\nstdout: %s\nstderr: %s", cmd, stdout, stderr) 120 | 121 | if redirect_stdout: 122 | with open(redirect_stdout, "wb") as f: 123 | f.write(stdout) 124 | 125 | if stderr and proc.returncode != 0: 126 | logging.warn( 127 | "%s: retcode %d stderr %s", cmd, proc.returncode, stderr.decode("utf-8") 128 | ) 129 | 130 | except asyncio.exceptions.TimeoutError: 131 | logging.warn("%s: timeout", cmd) 132 | try: 133 | proc.kill() 134 | except OSError: 135 | # Ignore 'no such process' error 136 | pass 137 | 138 | 139 | def common_args_parser(*args, **kwargs) -> argparse.ArgumentParser: 140 | parser = argparse.ArgumentParser(*args, **kwargs) 141 | parser.add_argument( 142 | "-e", 143 | "--coverage-cmd", 144 | required=True, 145 | help="Set command to exec (including args, and assumes code coverage support; e.g. './a.out @@')", 146 | ) 147 | parser.add_argument( 148 | "-d", 149 | "--afl-fuzzing-dir", 150 | required=True, 151 | type=pathlib.Path, 152 | help="top level AFL fuzzing directory (e.g. './output' or './output/default')", 153 | ) 154 | parser.add_argument( 155 | "-o", 156 | "--output-dir", 157 | type=pathlib.Path, 158 | help="Output folder in which to write results (default: /cov)", 159 | default=None, 160 | ) 161 | parser.add_argument( 162 | "-O", 163 | "--overwrite", 164 | action="store_true", 165 | help="Overwrite output folder if it already exists (default: %(default)s)", 166 | default=False, 167 | ) 168 | parser.add_argument( 169 | "-k", 170 | "--keep-intermediate", 171 | action="store_true", 172 | help="Keep intermediate files (default: %(default)s)", 173 | default=False, 174 | ) 175 | parser.add_argument( 176 | "-t", 177 | "--timeout", 178 | type=float, 179 | help="Timeout for target program run (in seconds, no timeout if < 0, default: %(default)s)", 180 | default=1, 181 | ) 182 | parser.add_argument( 183 | "-j", 184 | "--jobs", 185 | type=int, 186 | help="Maximum number of concurrent jobs (default: %(default)s)", 187 | default=1, 188 | ) 189 | parser.add_argument( 190 | "-E", 191 | "--env", 192 | action="append", 193 | help="Environment variables to set when running the target (example: 'AFL_PRELOAD=libdesock.so', default: %(default)s)", 194 | default=[], 195 | ) 196 | parser.add_argument( 197 | "-l", 198 | "--log-level", 199 | help="Tool output log level (default: %(default)s)", 200 | choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], 201 | default="WARNING", 202 | ) 203 | parser.add_argument( 204 | "--no-progress", 205 | action="store_true", 206 | help="Disable progress bar output (default: %(default)s)", 207 | default=False, 208 | ) 209 | parser.add_argument( 210 | "--no-env-check", 211 | action="store_true", 212 | help="Disable environment checks (e.g. commands are available and coverage files were properly generated, default: %(default)s)", 213 | default=False, 214 | ) 215 | return parser 216 | -------------------------------------------------------------------------------- /afl-cov-fast-llvm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Copyright (C) 2025 Airbus 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import os 21 | import sys 22 | import shutil 23 | import asyncio 24 | import logging 25 | import pathlib 26 | import argparse 27 | import tempfile 28 | 29 | from tqdm import tqdm 30 | from tqdm.asyncio import tqdm_asyncio 31 | from tqdm.contrib.logging import logging_redirect_tqdm 32 | 33 | import utils 34 | 35 | 36 | def create_folder_hierarchy(args: argparse.Namespace): 37 | # Create all required directories 38 | output_dir = utils.init_output_dir(args) 39 | (output_dir / "profraw").mkdir() 40 | (output_dir / "lcov").mkdir() 41 | (output_dir / "web").mkdir() 42 | 43 | 44 | def perform_env_check(args: argparse.Namespace): 45 | if args.no_env_check: 46 | return 47 | 48 | # The user can either provide a path to the LLVM binaries, or try to find 49 | # them in the PATH 50 | if args.llvm_path: 51 | # If a path was provided, make sure it points to a directory and the 52 | # directory contains the required tools 53 | if not args.llvm_path.is_dir(): 54 | raise ValueError(f"{args.llvm_path} directory not found") 55 | 56 | paths = [args.llvm_path / "llvm-profdata", args.llvm_path / "llvm-cov"] 57 | for path in paths: 58 | if not path.is_file(): 59 | raise ValueError(f"{path} file not found") 60 | else: 61 | # Check if the required tools are available in the PATH, and if not give 62 | # some helpful advice 63 | if not utils.command_exists("llvm-profdata"): 64 | raise ValueError( 65 | "llvm-profdata command not found, try specifying `--llvm-path`" 66 | ) 67 | if not utils.command_exists("llvm-cov"): 68 | raise ValueError("llvm-cov command not found, try specifying `--llvm-path`") 69 | 70 | # Check non-llvm tool availability 71 | commands = [args.genhtml_path] 72 | for cmd in commands: 73 | if not utils.command_exists(cmd): 74 | raise ValueError(f"{cmd} command not found") 75 | 76 | 77 | async def generate_coverage( 78 | args: argparse.Namespace, input_file: pathlib.Path, semaphore: asyncio.Semaphore 79 | ) -> pathlib.Path: 80 | async with semaphore: 81 | logging.info("Generating coverage for test case: %s", input_file) 82 | 83 | # Create a new folder for this run 84 | profraw_dir = tempfile.mkdtemp(dir=args.output_dir / "profraw") 85 | profraw_dir = pathlib.Path(profraw_dir) 86 | 87 | # We use environment variables to write coverage in the dedicated folder 88 | # to allow parallel runs 89 | output_file = profraw_dir / "default-%p.profraw" 90 | env = { 91 | **os.environ, 92 | **utils.split_env_args(args.env), 93 | "LLVM_PROFILE_FILE": str(output_file), 94 | "LD_PRELOAD": os.environ.get("AFL_PRELOAD", ""), 95 | } 96 | 97 | # Run the original binary 98 | cmd, stdin = utils.prepare_coverage_cmd(args.coverage_cmd, input_file) 99 | await utils.run_cmd(cmd, env=env, timeout=args.timeout, stdin=stdin) 100 | 101 | # Make sure the output file was properly generated 102 | if not args.no_env_check and not list(profraw_dir.glob("*.profraw")): 103 | raise RuntimeError( 104 | "No coverage information generated during run, did you compile with `-fprofile-instr-generate -fcoverage-mapping`?" 105 | ) 106 | 107 | return output_file 108 | 109 | 110 | async def merge_tracefiles(args: argparse.Namespace) -> pathlib.Path: 111 | # Get path to llvm commands 112 | llvm_profdata = "llvm-profdata" 113 | llvm_cov = "llvm-cov" 114 | if args.llvm_path: 115 | llvm_profdata = args.llvm_path / llvm_profdata 116 | llvm_cov = args.llvm_path / llvm_cov 117 | 118 | # Merge all profraw files 119 | profdata_file = args.output_dir / "lcov" / "default.profdata" 120 | cmd = [ 121 | str(llvm_profdata), 122 | "merge", 123 | "-sparse", 124 | str(args.output_dir / "profraw"), 125 | "-o", 126 | str(profdata_file), 127 | ] 128 | await utils.run_cmd(cmd) 129 | 130 | # Cleanup profraw files (if necessary) 131 | if not args.keep_intermediate: 132 | for path in (args.output_dir / "profraw").iterdir(): 133 | if path.is_dir(): 134 | shutil.rmtree(path) 135 | 136 | # Export coverage as an lcov file 137 | output_file = args.output_dir / "lcov" / "trace.lcov_total" 138 | cmd = [ 139 | str(llvm_cov), 140 | "export", 141 | "--instr-profile", 142 | str(profdata_file), 143 | "--format", 144 | "lcov", 145 | str(args.binary_path), 146 | ] 147 | await utils.run_cmd(cmd, redirect_stdout=output_file) 148 | return output_file 149 | 150 | 151 | async def generate_report(args: argparse.Namespace, output_file: pathlib.Path): 152 | cmd = [ 153 | args.genhtml_path, 154 | "--prefix", 155 | str(args.code_dir), 156 | "--highlight", 157 | "--ignore-errors", 158 | "source", 159 | "--legend", 160 | "--function-coverage", 161 | str(args.output_dir / "lcov" / "trace.lcov_total"), 162 | "--output-directory", 163 | str(args.output_dir / "web"), 164 | ] 165 | await utils.run_cmd(cmd) 166 | 167 | 168 | async def run(args: argparse.Namespace): 169 | loop = asyncio.get_event_loop() 170 | if args.jobs <= 0: 171 | raise ValueError("Number of jobs must be greater than 0") 172 | 173 | # Setup logging 174 | logging.basicConfig( 175 | level=args.log_level, 176 | format="[%(asctime)s] %(levelname)s: %(message)s", 177 | datefmt="%H:%M:%S", 178 | ) 179 | logging.getLogger().setLevel(args.log_level) 180 | 181 | # Check environment is properly configured 182 | perform_env_check(args) 183 | 184 | # Create the output folder and all subfolders 185 | create_folder_hierarchy(args) 186 | 187 | # Collect all input files 188 | queue_files = utils.get_queue_files(args) 189 | 190 | # Create a task for each file for which to collect coverage and use a 191 | # semaphore to limit the number of simultaneous jobs 192 | semaphore = asyncio.Semaphore(args.jobs) 193 | tasks = [ 194 | asyncio.create_task(generate_coverage(args, input_file, semaphore)) 195 | for input_file in queue_files 196 | ] 197 | 198 | # Run the tasks and re-raise any exception 199 | try: 200 | if args.no_progress: 201 | await asyncio.gather(*tasks) 202 | else: 203 | with logging_redirect_tqdm(): 204 | await tqdm_asyncio.gather(*tasks, desc="Generating coverage") 205 | except Exception as e: 206 | for task in tasks: 207 | if not task.done(): 208 | task.cancel() 209 | raise e 210 | 211 | # Merge all trace files 212 | tqdm.write("Merging coverage") 213 | trace_file = await merge_tracefiles(args) 214 | 215 | # Generate HTML output 216 | tqdm.write("Generating HTML report") 217 | await generate_report(args, trace_file) 218 | 219 | 220 | def parse_args(argv: list) -> argparse.Namespace: 221 | parser = utils.common_args_parser() 222 | parser.add_argument( 223 | "-c", 224 | "--code-dir", 225 | required=True, 226 | type=pathlib.Path, 227 | help="Directory where the code lives (compiled with code coverage support; e.g. './src')", 228 | ) 229 | parser.add_argument( 230 | "-b", 231 | "--binary-path", 232 | required=True, 233 | type=pathlib.Path, 234 | help="Path to the target binary (e.g. './a.out')", 235 | ) 236 | parser.add_argument( 237 | "--genhtml-path", 238 | help="Path to the genhtml binary", 239 | default="genhtml", 240 | ) 241 | parser.add_argument( 242 | "--llvm-path", 243 | type=pathlib.Path, 244 | help="Path to the llvm directory (e.g. '/usr/lib/llvm-14/bin')", 245 | default=None, 246 | ) 247 | args = parser.parse_args(argv) 248 | utils.normalize_user_paths(args) 249 | return args 250 | 251 | 252 | def main(argv: list): 253 | args = parse_args(argv) 254 | asyncio.run(run(args)) 255 | 256 | 257 | if __name__ == "__main__": 258 | # Allow running this file directly instead of calling afl-cov-fast.py with 259 | # the -m option 260 | main(sys.argv[1:]) 261 | -------------------------------------------------------------------------------- /afl-cov-fast-gcc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Copyright (C) 2025 Airbus 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import os 21 | import sys 22 | import shutil 23 | import asyncio 24 | import logging 25 | import pathlib 26 | import argparse 27 | import tempfile 28 | from typing import Iterable 29 | 30 | from tqdm import tqdm 31 | from tqdm.contrib.logging import logging_redirect_tqdm 32 | 33 | import utils 34 | 35 | 36 | def create_folder_hierarchy(args: argparse.Namespace): 37 | # Create all required directories 38 | output_dir = utils.init_output_dir(args) 39 | (output_dir / "gcov").mkdir() 40 | (output_dir / "lcov").mkdir() 41 | (output_dir / "web").mkdir() 42 | 43 | 44 | def perform_env_check(args: argparse.Namespace): 45 | if args.no_env_check: 46 | return 47 | 48 | commands = [args.lcov_path, args.genhtml_path] 49 | for cmd in commands: 50 | if not utils.command_exists(cmd): 51 | raise ValueError(f"{cmd} command not found") 52 | 53 | 54 | async def generate_lcov_zero_coverage(args: argparse.Namespace): 55 | # Reset code coverage counters 56 | cmd = [ 57 | args.lcov_path, 58 | "--no-checksum", 59 | "--zerocounters", 60 | "--directory", 61 | str(args.code_dir), 62 | ] 63 | await utils.run_cmd(cmd) 64 | 65 | # Run baseline lcov 66 | cmd = [ 67 | args.lcov_path, 68 | "--no-checksum", 69 | "--capture", 70 | "--rc", 71 | "lcov_branch_coverage=1", 72 | "--initial", 73 | "--directory", 74 | str(args.code_dir), 75 | "--follow", 76 | "--output-file", 77 | str(args.output_dir / "lcov" / "trace.lcov_base"), 78 | ] 79 | await utils.run_cmd(cmd) 80 | 81 | 82 | async def generate_coverage( 83 | args: argparse.Namespace, output_folder: pathlib.Path, input_file: pathlib.Path 84 | ): 85 | logging.info("Generating coverage for test case: %s", input_file) 86 | 87 | # We use environment variables to write coverage in the dedicated folder 88 | # to allow parallel runs 89 | env = { 90 | **os.environ, 91 | **utils.split_env_args(args.env), 92 | "GCOV_PREFIX": output_folder, 93 | "LD_PRELOAD": os.environ.get("AFL_PRELOAD", ""), 94 | } 95 | 96 | # Run the original binary 97 | cmd, stdin = utils.prepare_coverage_cmd(args.coverage_cmd, input_file) 98 | await utils.run_cmd(cmd, env=env, timeout=args.timeout, stdin=stdin) 99 | 100 | 101 | async def coverage_worker( 102 | args: argparse.Namespace, queue: asyncio.Queue, pbar: tqdm 103 | ) -> pathlib.Path: 104 | # Create a folder for this worker 105 | gcov_dir = tempfile.mkdtemp(dir=args.output_dir / "gcov") 106 | gcov_dir = pathlib.Path(gcov_dir) 107 | logging.debug("Created worker at path %s", gcov_dir) 108 | 109 | # Run until the queue is empty 110 | consumed_items = 0 111 | while not queue.empty(): 112 | path = await queue.get() 113 | consumed_items += 1 114 | await generate_coverage(args, gcov_dir, path) 115 | queue.task_done() 116 | pbar.update(1) 117 | 118 | # If this worker didn't consume anything from the queue, exit early to avoid 119 | # running lcov without any input file 120 | if consumed_items == 0: 121 | logging.debug( 122 | "Worker at %s didn't consume any queue item, exiting early", gcov_dir 123 | ) 124 | shutil.rmtree(gcov_dir) 125 | return None 126 | 127 | # We now need to place the gcno files next to their gcda counterparts 128 | # To avoid copying them, we instead create symbolic links 129 | gcda_file_count = 0 130 | for gcda in gcov_dir.rglob("*.gcda"): 131 | # Count number of gcda files to make sure coverage information was 132 | # properly generated 133 | gcda_file_count += 1 134 | 135 | # Find gcno location based on gcda path 136 | gcno = gcda.relative_to(gcov_dir).with_suffix(".gcno") 137 | gcno = pathlib.Path("/") / gcno 138 | 139 | # Create symlink 140 | gcda.with_suffix(".gcno").symlink_to(gcno) 141 | 142 | # Make sure coverage information was generated 143 | if not args.no_env_check and gcda_file_count == 0: 144 | raise RuntimeError( 145 | "No coverage information generated during run, did you compile with `--coverage`?" 146 | ) 147 | 148 | # We can now run lcov from the main directory to generate a unique 149 | # coverage file for this execution 150 | output_file = args.output_dir / "lcov" / (gcov_dir.stem + ".lcov") 151 | lcov_cmd = [ 152 | args.lcov_path, 153 | "--no-checksum", 154 | "--capture", 155 | "--rc", 156 | "lcov_branch_coverage=1", 157 | "--directory", 158 | str(gcov_dir), 159 | "--follow", 160 | "--output-file", 161 | str(output_file), 162 | ] 163 | await utils.run_cmd(lcov_cmd) 164 | 165 | # Cleanup if necessary and return the generated output file 166 | if not args.keep_intermediate: 167 | shutil.rmtree(gcov_dir) 168 | return output_file 169 | 170 | 171 | async def merge_tracefiles( 172 | args: argparse.Namespace, input_files: Iterable[pathlib.Path] 173 | ) -> pathlib.Path: 174 | # The input_files array might contain some "None" values if some spawned 175 | # workers did not get any input and aborted early 176 | input_files = [path for path in input_files if path is not None] 177 | 178 | # Make sure there is at least one file left 179 | if not args.no_env_check and not input_files: 180 | raise RuntimeError("No coverage file generated, is the AFL++ queue empty?") 181 | 182 | # Create command line with all input file paths 183 | output_file = args.output_dir / "lcov" / f"trace.lcov_total" 184 | cmd = [ 185 | args.lcov_path, 186 | "--output-file", 187 | str(output_file), 188 | "-a", 189 | str(args.output_dir / "lcov" / "trace.lcov_base"), 190 | ] 191 | for path in input_files: 192 | cmd += ["-a", str(path)] 193 | 194 | # Actually run command 195 | logging.info("Merging %d tracefiles into %s", len(input_files), str(output_file)) 196 | await utils.run_cmd(cmd) 197 | 198 | # Cleanup and return generated file 199 | if not args.keep_intermediate: 200 | for path in input_files: 201 | path.unlink() 202 | return output_file 203 | 204 | 205 | async def generate_report(args: argparse.Namespace, input_file: pathlib.Path): 206 | cmd = [ 207 | args.genhtml_path, 208 | "--prefix", 209 | str(args.code_dir), 210 | "--highlight", 211 | "--ignore-errors", 212 | "source", 213 | "--legend", 214 | "--function-coverage", 215 | str(input_file), 216 | "--output-directory", 217 | str(args.output_dir / "web"), 218 | ] 219 | await utils.run_cmd(cmd) 220 | 221 | 222 | async def run(args: argparse.Namespace): 223 | loop = asyncio.get_event_loop() 224 | if args.jobs <= 0: 225 | raise ValueError("Number of jobs must be greater than 0") 226 | 227 | # Setup logging 228 | logging.basicConfig( 229 | level=args.log_level, 230 | format="[%(asctime)s] %(levelname)s: %(message)s", 231 | datefmt="%H:%M:%S", 232 | ) 233 | logging.getLogger().setLevel(args.log_level) 234 | 235 | # Check environment is properly configured 236 | perform_env_check(args) 237 | 238 | # Create the output folder and all subfolders 239 | create_folder_hierarchy(args) 240 | 241 | # Init lcov 242 | tqdm.write("Initializing coverage engine") 243 | await generate_lcov_zero_coverage(args) 244 | 245 | # When running the target, a .gcda file is created or, if it already exists, 246 | # updated with the new coverage 247 | # To handle multiprocessing, we create a queue with all the input files to 248 | # process, and then create 1 worker per job that will have its own output 249 | # folder 250 | queue_files = asyncio.Queue() 251 | for path in utils.get_queue_files(args): 252 | queue_files.put_nowait(path) 253 | 254 | # Create workers 255 | with logging_redirect_tqdm(): 256 | pbar = tqdm(total=queue_files.qsize(), desc="Generating coverage") 257 | tasks = [] 258 | for i in range(args.jobs): 259 | tasks.append(asyncio.create_task(coverage_worker(args, queue_files, pbar))) 260 | 261 | # Wait until all the workers are done 262 | try: 263 | partial_trace_files = await asyncio.gather(*tasks) 264 | except Exception as e: 265 | for task in tasks: 266 | if not task.done(): 267 | task.cancel() 268 | raise e 269 | finally: 270 | pbar.close() 271 | 272 | # Merge all trace files 273 | tqdm.write("Merging coverage") 274 | trace_file = await merge_tracefiles(args, partial_trace_files) 275 | 276 | # Generate HTML output 277 | tqdm.write("Generating HTML report") 278 | await generate_report(args, trace_file) 279 | 280 | 281 | def parse_args(argv: list) -> argparse.Namespace: 282 | parser = utils.common_args_parser() 283 | parser.add_argument( 284 | "-c", 285 | "--code-dir", 286 | required=True, 287 | type=pathlib.Path, 288 | help="Directory where the code lives (compiled with code coverage support; e.g. './src')", 289 | ) 290 | parser.add_argument( 291 | "--genhtml-path", 292 | help="Path to the genhtml binary", 293 | default="genhtml", 294 | ) 295 | parser.add_argument( 296 | "--lcov-path", 297 | help="Path to the lcov binary", 298 | default="lcov", 299 | ) 300 | args = parser.parse_args(argv) 301 | utils.normalize_user_paths(args) 302 | return args 303 | 304 | 305 | def main(argv: list): 306 | args = parse_args(argv) 307 | asyncio.run(run(args)) 308 | 309 | 310 | if __name__ == "__main__": 311 | # Allow running this file directly instead of calling afl-cov-fast.py with 312 | # the -m option 313 | main(sys.argv[1:]) 314 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | --------------------------------------------------------------------------------