Power Schedules

# afl++’s power schedules based on AFLfast

Power schedules implemented by Marcel Böhme <marcel.boehme@acm.org>. AFLFast is an extension of AFL which is written and maintained by Michal Zalewski <lcamtuf@google.com>.

AFLfast has helped in the success of Team Codejitsu at the finals of the DARPA Cyber Grand Challenge where their bot Galactica took 2nd place in terms of #POVs proven (see red bar at https://www.cybergrandchallenge.com/event#results). AFLFast exposed several previously unreported CVEs that could not be exposed by AFL in 24 hours and otherwise exposed vulnerabilities significantly faster than AFL while generating orders of magnitude more unique crashes.

Essentially, we observed that most generated inputs exercise the same few “high-frequency” paths and developed strategies to gravitate towards low-frequency paths, to stress significantly more program behavior in the same amount of time. We devised several search strategies that decide in which order the seeds should be fuzzed and power schedules that smartly regulate the number of inputs generated from a seed (i.e., the time spent fuzzing a seed). We call the number of inputs generated from a seed, the seed’s energy.

We find that AFL’s exploitation-based constant schedule assigns too much energy to seeds exercising high-frequency paths (e.g., paths that reject invalid inputs) and not enough energy to seeds exercising low-frequency paths (e.g., paths that stress interesting behaviors). Technically, we modified the computation of a seed’s performance score (`calculate_score`), which seed is marked as favourite (`update_bitmap_score`), and which seed is chosen next from the circular queue (`main`). We implemented the following schedules (in the order of their effectiveness, best first):

AFL flag Power Schedule
`-p explore` $p(i)=\frac{\alpha(i)}{\beta}$
`-p fast` (default) $p(i)=\min\left(\frac{\alpha(i)}{\beta}\cdot\frac{2^{s(i)}}{f(i)},M\right)$
`-p coe` $p(i)=\begin{cases} 0 & \text{ if } f(i) > \mu\\ \min\left(\frac{\alpha(i)}{\beta}\cdot 2^{s(i)}, M\right) & \text{ otherwise.} \end{cases}$
`-p quad` $p(i) = \min\left(\frac{\alpha(i)}{\beta}\cdot\frac{s(i)^2}{f(i)},M\right)$
`-p lin` $p(i) = \min\left(\frac{\alpha(i)}{\beta}\cdot\frac{s(i)}{f(i)},M\right)$
`-p exploit` (AFL) $p(i) = \alpha(i)$
`-p mmopt` Experimental: `explore` with no weighting to runtime and increased weighting on the last 5 queue entries
`-p rare` Experimental: `rare` puts focus on queue entries that hit rare edges
`-p seek` Experimental: `seek` is EXPLORE but ignoring the runtime of the queue input and less focus on the size
where α(i) is the performance score that AFL uses to compute for the seed input i, β(i)>1 is a constant, s(i) is the number of times that seed i has been chosen from the queue, f(i) is the number of generated inputs that exercise the same path as seed i, and μ is the average number of generated inputs exercising a path.

More details can be found in the paper that was accepted at the 23rd ACM Conference on Computer and Communications Security (CCS'16).

PS: In parallel mode (several instances with shared queue), we suggest to run the main node using the exploit schedule (-p exploit) and the secondary nodes with a combination of cut-off-exponential (-p coe), exponential (-p fast; default), and explore (-p explore) schedules. In single mode, the default settings will do. EDIT: In parallel mode, AFLFast seems to perform poorly because the path probability estimates are incorrect for the imported seeds. Pull requests to fix this issue by syncing the estimates across instances are appreciated :)