Ensemble fuzzing runs multiple fuzzers on a target while a scheduler allocates CPU time among them. Existing schedulers base these decisions on compact summaries of past performance and rules fixed before a campaign. Our measurements reveal two limitations. First, past-reward summaries do not reliably capture performance evolution: after accounting for estimation noise, agreement between consecutive-window rankings is statistically indistinguishable from within-window self-agreement. Second, predictive signals vary across targets: on eight of nine targets, a weighting learned from the other eight predicts reward worse than one learned on the current target. We introduce CAROL, an online scheduler that uses each fuzzer's current context. Already available to the dispatch loop, this context describes reward trends, waiting and plateau time, reached code, and estimation uncertainty. CAROL uses context in two ways: a domain-guided method detects whether a fuzzer is rising or rotting and applies a phase-specific learning rule, while a learned method predicts reward from 15 context signals and uses predictive uncertainty for online selection. Across nine Magma targets, CAROL triggers more unique bugs than each of three ensemble-scheduling baselines whenever their results differ, and fewer on none. Compared with the strongest baseline for each target, CAROL gains 11.8% and surpasses an oracle that retrospectively selects the best single fuzzer per target. Removing context eliminates the gain, and the additional bugs are concentrated among those the baselines trigger rarely or never. Run unchanged on five widely used C++ programs, CAROL finds 120 previously unknown crashing defects, deduplicated by site, fault, and entry point; all were reported to maintainers through the projects' stated disclosure channels.
Figures & tables
Figure 1: Left , the shape § 2.2 describes: mopt on poppler rises as it covers code its peers reached, then rots. Right , a case it does not: darwin on libxml2 never rises. Each curve is one fuzzer’s reward, averaged over that target’s campaigns and over each tenth of its own turns; Method I assumes the left panel. Below , the fall across the suite: the median (campaign, fuzzer) pair’s raw per-turn rate over a campaign’s second half, as a percentage of its first, latched before any scaling.
Figure 2: Method I in the dispatch loop. A worker requests a fuzzer from the phase-aware scheduler, executes its turn, and returns the resulting reward through the shared evaluator.
this paper
baseline
gain
target
context-aware
phase-aware
BandFuzz
autofz
Legion
Δ
pH
libpng
4.00
3.90
3.40
3.20
3.60
+0.40
0.235
libsndfile
7.00
7.00
7.00
7.00
7.00
+0.00
1.000
libtiff
5.00
4.30
3.70
4.00
3.60
+1.00
0.0001
libxml2
3.20
2.70
2.10
2.60
2.20
+0.60
0.317
lua
1.60
1.30
1.10
1.20
1.40
+0.20
1.000
Table 1: Mean unique Magma bugs over ten 24-hour campaigns per cell. The three right-hand schedulers are the baselines, each reimplemented from its own paper with its own constants and run in this harness on the same six fuzzers. Δ is what the context-aware scheduler makes over whichever of the three is strongest on that target, and pH a two-sided Mann–Whitney U test of it after Holm correction over the family of nine targets. Bold marks the highest mean in a row and additionally marks Method I when it exceeds all three baselines. In the gain columns, bold marks a Δ at or above what its own pair resolves (§ 7 ) and pH<0.05 . Each fuzzer alone is Table 12 , standard deviations Table 13 , edge coverage Table 14 and every pair’s effect size Table 11 .
Figure 3: Unique bugs over the campaign on the four targets where the schedulers separate, as the mean over each cell’s ten campaigns, with a band of one standard deviation across them for the context-aware scheduler. The separation opens late: the curves run together through the first hours and are apart by the last.
target
bug
BandFuzz
autofz
Legion
context-aware (this paper)
libpng
PNG003
10
10
10
10
PNG006
10
10
10
10
PNG007
10
10
10
10
PNG001
4
2
6
10
mean
3.40
3.20
3.60
4.00
libtiff
TIF007
10
10
10
10
Table 2: Per-bug campaign counts for libpng, libtiff, and sqlite3: the two targets with resolved Method II gains and the target on which every Method II campaign triggers the same four bugs. Rows list every bug triggered by any scheduler in the table, and each entry is the number of that cell’s ten campaigns that trigger it. Dividing a column sum by ten gives the corresponding mean in Table 1 . new marks a bug triggered by no baseline campaign.
Figure 4: Method II against two post-campaign composites. For each target, the baseline bar selects the highest mean among the three baselines, and the standalone-fuzzer bar selects the highest mean among the six fuzzers run alone. The legend reports the sums across targets; Table 12 gives every standalone-fuzzer cell.
Figure 5: Ablations of Method II. Each bar reports the change in mean unique bugs per target when one setting is replaced, with each pair run at full campaign length under the protocol of § 7 . Explore removes the Gaussian uncertainty term. Context replaces all 15 signals with an intercept and therefore also makes the context-derived confidence width identical across fuzzers. Form uses a linear instead of random-Fourier representation; rank uses a rank-3 projection; models shares one model across fuzzers; and ridge increases λ from 10 to 100. The colored bars mark context and exploration, the only components whose removal produces a difference resolved by at least one full-campaign pair. The remaining full-campaign replacements stay within their pairs’ resolution. Discounting is evaluated by held-out reward prediction and therefore carries no bar (§ A.24 ).
Appendix figures & tables15 assets
Supplementary material from the paper’s appendix.
Appendix
name
how it is computed
recent productivity
win_mean
mean reward over the window
win_var
variance of the reward there
cov_velocity
1−e−ρ , ρ the edges this fuzzer newly covered in the last 120 s of wall clock over the span of that window in seconds
slope
least-squares slope of the reward window
trend and horizon
Appendix
Table 3: The 15 context features, one vector per fuzzer per decision. Every one is computed from quantities the dispatch loop already maintains. None requires a bug oracle, a source annotation or a benchmark harness. Every window and code-property feature is latched when a fuzzer’s turn ends , so nothing computed from a turn’s outcome depends on the turn whose reward the decision is predicting. time_since_run and elapsed_frac are clocks and advance while a fuzzer waits, which is what makes the first a freshness signal.
target
harness
injected
reachable
libpng
libpng_read
7
6
libsndfile
sndfile
18
8
libtiff
tiff_rgba
14
9
libxml2
xml_reader
17
10
lua
lua
4
4
openssl
asn1
20
4
Appendix
Table 4: What was available to be found. injected is the number of bugs Magma re-introduces into the target; reachable is how many of those the Magma oracle records as reached through the single harness we fuzz. The second column bounds every number in Table 1 .
baseline
context-aware
target
bug
camp
med
camp
med
sqlite3
SQL013
0
—
3
—
poppler
PDF021
0
—
1
—
sqlite3
SQL015
0
—
1
—
libtiff
TIF001
1
—
3
—
sqlite3
SQL012
1
—
3
—
Appendix
Table 5: Time to trigger, over every bug the context-aware scheduler and the baselines do not trigger equally often, hardest first. baseline is the strongest of the three on that bug, taken as the one that triggers it in the most campaigns and then the one that reaches its median soonest. camp is how many of that cell’s ten campaigns trigger the bug with a sampled trigger time, med the Kaplan–Meier median, — that fewer than half trigger it so no median falls in budget.
class
weakness
defects
CWE-476
null pointer dereference
54
CWE-125
out-of-bounds read
34
CWE-129
improper validation of an array index
11
CWE-787
out-of-bounds write
6
CWE-369
divide by zero
3
CWE-789
memory allocation with an excessive size
3
Appendix
Table 6: Weakness classes of the 120 real-world defects, taken from the class each defect document assigns from its sanitizer evidence and the source at the fault site. Five reports carry no class of their own and are counted as such.
program
CWE
file : line
function
fault
entry
MNN
125
liteConverter.cpp:421
readModel
wild pointer
tflite
125
liteConverter.cpp:421
readModel
heap read
tflite
476
Program.cpp:122
create
wild pointer
–
125
liteConverter.cpp:209
tflite2MNNNet
heap read
tflite
476
liteConverter.cpp:235
tflite2MNNNet
wild pointer
tflite
125
liteConverter.cpp:264
tflite2MNNNet
wild pointer
tflite
Appendix
Table 7: Where each defect is, part 1 of 2. Each row gives the class its report carries, the file and line the sanitizer names, the function where it names one, the fault it reports, and the entry point that reached it. A dash in the class column marks a defect whose report carries no class, and in the entry column one whose trace does not name the format being parsed. Two rows carry a file without a line, function or fault, because the sanitizer attributes the fault to a translation unit rather than a statement; both are counted in Table 6 under the class their report carries. The last four fields are the deduplication rule of § 6.5 , and every row differs from every other in at least one of them. We print no trigger and no reproducing input.
program
CWE
file : line
function
fault
entry
129
ShapeConcat.cpp:62
onComputeSize
wild pointer
–
129
DepthwiseConv2DTflite.cpp:83
run
wild pointer
tflite
843
CustomTflite.cpp:111
run
wild pointer
tflite
–
ConvolutionFloatFactory.cpp:269
create
arith
onnx
129
FuseDupOp.cpp:122
onExecute
wild pointer
tflite
–
ShapeConvolution.cpp:86
onComputeSize
arith
onnx
Appendix
Table 8: Where each defect is, part 2 of 2. Each row gives the class its report carries, the file and line the sanitizer names, the function where it names one, the fault it reports, and the entry point that reached it. A dash in the class column marks a defect whose report carries no class, and in the entry column one whose trace does not name the format being parsed. Two rows carry a file without a line, function or fault, because the sanitizer attributes the fault to a translation unit rather than a statement; both are counted in Table 6 under the class their report carries. The last four fields are the deduplication rule of § 6.5 , and every row differs from every other in at least one of them. We print no trigger and no reproducing input. The 42 defects in Arm NN are absent because the report we sent that vendor undertakes to publish nothing about them until they agree, and a row here would be publishing.
target
coverage_interval
frontier_branch
rarity_density
libpng
4.00
3.40
3.40
libtiff
5.00
4.20
4.40
openssl
2.00
1.40
1.60
poppler
4.20
3.60
3.60
sqlite3
4.20
3.40
4.00
Appendix
Table 9: The reward the scheduler optimizes, swept on the context-aware scheduler. coverage_interval is BandFuzz’s definition and the one every other result in this paper uses. frontier_branch credits a turn for branches at the edge of the shared map, and rarity_density for the rarity of the edges it covers. Mean unique bugs over ten campaigns per cell, read against the context-aware row of Table 1 . No alternative leads on any target. The other schedulers were not re-run under them.
Figure 6: Allocation concentration by campaign quarter. Each point is the share of scheduling decisions received by the most-selected fuzzer in that quarter, averaged over the ten campaigns in the corresponding cell of Table 1 ; the fuzzer at the peak may change between quarters. Method I’s allocation follows its first rotting verdict, while Method II’s follows the context-to-reward relationship learned during the campaign (§ 4.3 ).
feature
libxml2
libtiff
libpng
php
libsndfile
lua
g_rarity
20.7
62.4
51.5
59.3
20.9
36.4
mk_z
17.1
47.8
46.9
55.4
14.5
7.9
rounds_since_improve
6.9
20.1
56.3
42.2
25.3
3.4
g_sec
3.7
8.6
14.3
37.0
44.8
10.6
Appendix
Table 10: How far each of the four strongest context features governs the decision, named as in Table 3 : the percentage of logged decisions whose selected fuzzer changes when that one feature is replaced by its mean. Bold marks each target’s own strongest signal. Neither its identity nor how far it governs the decision is constant across targets, which is why the weighting is learned per target.
context-aware scheduler
phase-aware scheduler
vs BandFuzz
vs autofz
vs Legion
vs BandFuzz
vs autofz
vs Legion
target
A^12
pH
A^12
pH
A^12
pH
A^12
pH
A^12
pH
A^12
pH
libpng
0.80
0.025
0.90
0.0031
0.70
0.202
0.75
0.226
0.85
0.023
0.65
1.000
libsndfile
0.50
1.000
0.50
1.000
0.50
1.000
0.50
1.000
0.50
1.000
0.50
1.000
libtiff
1.00
0.0004
1.00
0.0001
1.00
0.0004
0.70
0.595
0.60
1.000
0.74
0.385
libxml2
0.96
0.0012
0.74
0.264
0.92
0.0035
0.67
0.775
0.52
1.000
0.63
1.000
Appendix
Table 11: Each of this paper’s schedulers against each baseline, per target. A^12 is the Vargha–Delaney effect size, the probability that a randomly chosen campaign of ours triggers more bugs than a randomly chosen campaign of that scheduler, ties counted as half. pH is a two-sided Mann–Whitney U test after Holm correction over the family of nine targets, one family per pair. Bold marks pH<0.05 .
fuzzer
libpng
libsndfile
libtiff
libxml2
lua
openssl
php
poppler
sqlite3
sum
AFL++
2.20
7.00
4.40
2.80
1.40
2.00
2.40
3.00
3.20
28.40
DARWIN
1.60
7.00
3.00
2.40
1.00
1.60
3.00
3.00
1.20
23.80
honggfuzz
4.00
1.20
3.00
2.00
1.00
1.60
2.80
2.20
2.00
19.80
laf-intel
2.20
7.00
4.00
2.60
1.20
2.00
3.00
2.80
3.00
27.80
MOPT
2.60
7.00
3.20
2.00
1.00
2.00
3.00
3.00
2.80
26.60
Radamsa
1.40
6.40
1.60
1.20
1.40
1.00
3.00
4.20
1.60
21.80
Appendix
Table 12: Every fuzzer alone, mean unique Magma bugs over ten 24-hour campaigns per cell, on the same harness, seeds, cores and budget as every scheduler in Table 1 . The last row is the strongest of them on each target, a choice made after the campaigns have run. Bold marks the highest mean in a column.
scheduler or fuzzer
libpng
libsndfile
libtiff
libxml2
lua
openssl
php
poppler
sqlite3
context-aware
0.00
0.00
0.00
0.42
0.52
0.00
0.00
0.79
0.42
phase-aware
0.32
0.00
0.82
0.95
0.67
0.42
0.00
0.63
0.48
BandFuzz
0.52
0.00
0.48
0.32
0.32
0.52
0.00
0.57
0.52
autofz
0.42
0.00
0.00
0.84
0.42
0.00
0.00
0.79
0.00
Legion
0.52
0.00
0.52
0.42
0.52
0.52
0.00
0.84
0.00
AFL++
0.42
0.00
0.52
0.79
0.52
0.00
1.26
0.00
0.42
Appendix
Table 13: Standard deviation of unique bugs across the ten campaigns of each cell of Table 1 . Bold marks the largest spread in a column.
scheduler or fuzzer
libpng
libsndfile
libtiff
libxml2
lua
openssl
php
poppler
sqlite3
context-aware
1,469
2,496
3,889
8,954
5,126
8,500
3,945
22,242
13,101
phase-aware
1,467
2,578
3,834
8,452
5,159
8,450
3,944
22,189
11,903
BandFuzz
1,465
2,509
3,729
8,810
5,118
8,468
3,945
22,132
12,272
autofz
1,467
2,462
3,714
9,210
5,127
8,469
3,944
22,089
11,597
Legion
1,463
2,439
3,669
8,276
5,144
8,452
3,944
22,201
11,236
AFL++
1,194
2,527
3,881
8,544
5,103
8,477
3,740
22,180
13,269
Appendix
Table 14: Mean edge coverage for every cell of Table 1 , reported for reference and never used to decide a comparison. Bold marks the highest mean in a column.
scheduler or fuzzer
libpng
libsndfile
libtiff
libxml2
lua
openssl
php
poppler
sqlite3
context-aware
6.00
8.00
7.90
8.50
4.00
4.00
5.00
16.00
12.10
phase-aware
6.00
8.00
7.40
8.40
4.00
4.00
5.00
15.90
11.20
BandFuzz
6.00
8.00
6.90
8.10
4.00
4.00
5.00
15.60
11.40
autofz
6.00
8.00
6.80
8.20
4.00
4.00
5.00
15.60
10.60
Legion
6.00
8.00
6.80
8.20
4.00
4.00
5.00
16.00
10.20
AFL++
6.00
8.00
7.20
8.60
4.00
4.00
4.00
16.00
11.20
Appendix
Table 15: Mean injected bugs reached per campaign for every cell of Table 1 , from the Magma oracle’s own reached record. A bug is reached when a campaign executed its site, whether or not the input that got there met the condition that trips it, so this column bounds the bug counts of Table 1 from above and Table 4 bounds it in turn. Bold marks the highest mean in a column.
program
version
found
crash
withheld
MNN
19af6da
62
33
29
Arm NN
v26.07
42
31
11
Krita
6.0.3
14
8
6
ncnn
c189d88
1
1
0
FreeCAD
1.1.3
1
0
1
total
120
73
47
Appendix
Table 16: The 120 real-world defects, by program. Found counts distinct faulting sites under the rule of § 6.5 . Every one crashes a build with no sanitizer and no coverage instrumentation, on five runs out of five. The two right-hand columns give the class we assigned at triage, from the worst outcome we observed across a defect’s runs. Crash is a fault at an unmappable address or an arithmetic fault. Withheld is memory corruption or information disclosure. Every defect went to its maintainers through the channel that project’s policy prescribes. Of the 73 in the crash column, 42 carry an issue on the project’s own tracker with a working reproducer. Arm NN’s 31 do not, because that vendor asked that any public disclosure of its defects be coordinated with it, and we agreed. No defect in the withheld column carries a reproducer here.