#!/usr/bin/env python3 # Requires Python 3.6+. # Author: Chris Ball # Ported from Marc "van Hauser" Heuse's "benchmark.sh". import os import re import shutil import subprocess import sys def colon_value_or_none(filename: str, searchKey: str) -> str | None: with open(filename, "r") as fh: for line in fh: kv = line.split(": ", 1) if kv and len(kv) == 2: (key, value) = kv key = key.strip() value = value.strip() if key == searchKey: return value return None def compile_target(source: str, binary: str) -> None: with open("afl.log", "w") as f: process = subprocess.run( ["afl-cc", "-o", binary, source], stdout=f, stderr=subprocess.STDOUT, env={"AFL_INSTRUMENT": "PCGUARD", "PATH": os.environ["PATH"]} ) if process.returncode != 0: sys.exit("Error: afl-cc is unable to compile") # Check if the necessary files exist and are executable if not ( os.access("../afl-fuzz", os.X_OK) and os.access("../afl-cc", os.X_OK) and os.path.exists("../SanitizerCoveragePCGUARD.so") ): sys.exit("Error: you need to compile AFL++ first, we need afl-fuzz, afl-clang-fast and SanitizerCoveragePCGUARD.so built.") print("Preparing environment") targets = [ {"source": "../test-instr.c", "binary": "test-instr"}, {"source": "../utils/persistent_mode/test-instr.c", "binary": "test-instr-persistent"} ] # Unset AFL_* environment variables for e in list(os.environ.keys()): if e.startswith("AFL_"): os.environ.pop(e) AFL_PATH = os.path.abspath("../") os.environ["PATH"] = AFL_PATH + ":" + os.environ["PATH"] for target in targets: compile_target(target["source"], target["binary"]) # Create input directory and file os.makedirs("in", exist_ok=True) with open("in/in.txt", "wb") as f: f.write(b"\x00" * 10240) print("Ready, starting benchmark - this will take approx 20-30 seconds ...") # Run afl-fuzz env_vars = { "AFL_DISABLE_TRIM": "1", "AFL_NO_UI": "1", "AFL_TRY_AFFINITY": "1", "AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES": "1", "AFL_BENCH_JUST_ONE": "1", } for target in targets: with open(f"afl-{target['binary']}.log", "a") as f: process = subprocess.run( [ "afl-fuzz", "-i", "in", "-o", f"out-{target['binary']}", "-s", "123", "-D", f"./{target['binary']}", ], stdout=f, stderr=subprocess.STDOUT, env={**os.environ, **env_vars}, ) print("Analysis:") # Extract CPUID from afl.log with open(f"afl-test-instr.log", "r") as f: match = re.search(r".*try binding to.*#(\d+)", f.read()) if not match: sys.exit("Couldn't see which CPU# was used in afl.log", 1) cpuid = match.group(1) # Print CPU model model = colon_value_or_none("/proc/cpuinfo", "model name") if model: print(" CPU:", model) # Print CPU frequency cpu_speed = None with open("/proc/cpuinfo", "r") as fh: current_cpu = None for line in fh: kv = line.split(": ", 1) if kv and len(kv) == 2: (key, value) = kv key = key.strip() value = value.strip() if key == "processor": current_cpu = value elif key == "cpu MHz" and current_cpu == cpuid: cpu_speed = value if cpu_speed: print(" Mhz:", cpu_speed) # Print execs_per_sec from fuzzer_stats for target in targets: execs = colon_value_or_none(f"out-{target['binary']}/default/fuzzer_stats", "execs_per_sec") if execs: print(f" {target['binary']} single-core execs/s:", execs) print("\nComparison: (note that values can change by 10-15% per run)") with open("COMPARISON", "r") as f: print(f.read()) # Clean up os.remove("afl.log") shutil.rmtree("in") for target in targets: shutil.rmtree(f"out-{target['binary']}") os.remove(target["binary"])