The simulated program below has 8 branches (numbered 0–7). Each input is a short byte string. Different inputs hit different branches — some branches only open when several conditions line up just right, including the crash-triggering branch.
<!-- Main layout — controls, info row, branch coverage panel, corpus panel, crash banner -->
<div class="controls">
<button id="btn-random" type="button">Random Fuzzing</button>
<button id="btn-guided" type="button">Coverage-Guided</button>
<button id="btn-step" type="button" class="ghost">Step</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="info-row">
<span id="mode-label" class="mode-badge">—</span>
<span id="iteration-label" class="iter-label"></span>
</div>
<div class="panels">
<div class="panel">
<div class="panel-title">Code Coverage</div>
<div id="branch-grid" class="branch-grid"></div>
<div class="coverage-bar-wrap">
<div id="coverage-bar" class="coverage-bar"></div>
</div>
<div id="coverage-pct" class="coverage-pct">0 / 8 branches hit</div>
</div>
<div class="panel">
<div class="panel-title">Corpus (seeds)</div>
<div id="corpus-list" class="corpus-list"></div>
</div>
</div>
<div id="crash-banner" class="crash-banner hidden">CRASH FOUND! The fuzzer triggered the hidden bug.</div>
<div id="status" class="status"></div>
/* Styles for coverage-guided fuzzing demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.controls { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 13px system-ui; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.info-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .5rem; min-height: 1.6rem; }
.mode-badge { font-size: .78rem; font-weight: 700; padding: .18rem .55rem; border-radius: 99px;
background: #e8eef3; color: #1d3557; letter-spacing: .02em; }
.mode-badge.guided { background: #1d3557; color: #fff; }
.iter-label { font-size: .8rem; color: #555; }
.panels { display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; margin-bottom: .5rem; }
.panel { background: #f4f7fa; border-radius: 8px; padding: .65rem .7rem; }
.panel-title { font-size: .78rem; font-weight: 700; color: #555; text-transform: uppercase;
letter-spacing: .06em; margin-bottom: .4rem; }
.branch-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; margin-bottom: .5rem; }
.branch-cell { height: 32px; border-radius: 6px; background: #dde3ea; display: flex;
align-items: center; justify-content: center; font: 700 12px ui-monospace, monospace;
color: #888; transition: background .2s, color .2s; }
.branch-cell.hit { background: #2a9d8f; color: #fff; }
.branch-cell.crash { background: #e63946; color: #fff; }
.coverage-bar-wrap { background: #dde3ea; border-radius: 99px; height: 7px; margin-bottom: .3rem; overflow: hidden; }
.coverage-bar { height: 100%; background: #2a9d8f; border-radius: 99px; transition: width .3s; width: 0%; }
.coverage-pct { font-size: .8rem; color: #444; font-weight: 600; }
.corpus-list { max-height: 130px; overflow-y: auto; font-family: ui-monospace, monospace; font-size: .78rem; }
.corpus-entry { padding: .15rem .3rem; border-radius: 4px; margin-bottom: 2px; background: #e2eaf2;
color: #1d3557; word-break: break-all; }
.corpus-entry.new { background: #c8f7c5; color: #0a5c1e; }
.crash-banner { background: #e63946; color: #fff; font-weight: 700; font-size: .9rem;
border-radius: 8px; padding: .5rem .8rem; margin-bottom: .4rem; text-align: center; }
.crash-banner.hidden { display: none; }
.status { font-size: .82rem; color: #555; min-height: 1.2em; }
@media (max-width: 420px) { .panels { grid-template-columns: 1fr; } }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simulated program: 8 branches with increasing depth
// Branch conditions — each deeper branch requires all previous ones
// Branch 0: input length >= 1
// Branch 1: first byte > 64 (ascii 'A')
// Branch 2: second byte exists and is even
// Branch 3: branch1 && branch2 && input length >= 3
// Branch 4: branch3 && third byte > 100
// Branch 5: branch4 && fourth byte exists
// Branch 6: branch5 && fourth byte XOR second byte > 50
// Branch 7 (crash): branch6 && fifth byte === 42 (ascii '*')
function evaluateInput(bytes) {
// Evaluate which branches this input reaches, and whether it triggers the crash
const hits = new Set();
if (bytes.length >= 1) hits.add(0);
const b1 = bytes.length >= 2 && bytes[0] > 64;
if (b1) hits.add(1);
const b2 = bytes.length >= 2 && bytes[1] % 2 === 0;
if (b2) hits.add(2);
const b3 = b1 && b2 && bytes.length >= 3;
if (b3) hits.add(3);
const b4 = b3 && bytes[2] > 100;
if (b4) hits.add(4);
const b5 = b4 && bytes.length >= 4;
if (b5) hits.add(5);
const b6 = b5 && ((bytes[3] ^ bytes[1]) > 50);
if (b6) hits.add(6);
const crashed = b6 && bytes.length >= 5 && bytes[4] === 42;
if (crashed) hits.add(7);
return { hits, crashed };
}
// Input mutation helpers — AFL-style bit flips and structure tweaks
function randomBytes(len) {
const b = [];
for (let i = 0; i < len; i++) b.push(Math.floor(Math.random() * 256));
return b;
}
function mutate(bytes) {
// Mutation operators — flip a bit, insert/delete/replace a byte, or use interesting boundary values
const ops = ['flip', 'insert', 'delete', 'replace', 'boundary'];
const op = ops[Math.floor(Math.random() * ops.length)];
const b = [...bytes];
if (op === 'flip' && b.length > 0) {
const i = Math.floor(Math.random() * b.length);
b[i] = b[i] ^ (1 << Math.floor(Math.random() * 8));
} else if (op === 'insert') {
const i = Math.floor(Math.random() * (b.length + 1));
b.splice(i, 0, Math.floor(Math.random() * 256));
} else if (op === 'delete' && b.length > 1) {
b.splice(Math.floor(Math.random() * b.length), 1);
} else if (op === 'replace' && b.length > 0) {
b[Math.floor(Math.random() * b.length)] = Math.floor(Math.random() * 256);
} else if (op === 'boundary') {
// Boundary values — integers near type boundaries that often trigger edge cases
const interesting = [0, 1, 42, 64, 65, 100, 101, 127, 128, 255];
if (b.length > 0) b[Math.floor(Math.random() * b.length)] = interesting[Math.floor(Math.random() * interesting.length)];
else b.push(interesting[Math.floor(Math.random() * interesting.length)]);
}
return b;
}
function hexStr(bytes) { return bytes.map(x => x.toString(16).padStart(2, '0')).join(' '); }
// Global fuzzer state
let globalCoverage = new Set();
let corpus = [];
let iteration = 0;
let crashed = false;
let intervalId = null;
let mode = null;
function renderBranches() {
const grid = document.getElementById('branch-grid');
grid.innerHTML = '';
for (let i = 0; i < 8; i++) {
const cell = document.createElement('div');
const isCrashBranch = i === 7;
cell.className = 'branch-cell' +
(globalCoverage.has(i) ? (isCrashBranch && crashed ? ' crash' : ' hit') : '');
cell.textContent = 'B' + i;
if (isCrashBranch) cell.title = 'Crash branch — only reachable deep in the tree';
grid.appendChild(cell);
}
const pct = Math.round((globalCoverage.size / 8) * 100);
document.getElementById('coverage-bar').style.width = pct + '%';
document.getElementById('coverage-pct').textContent =
globalCoverage.size + ' / 8 branches hit';
}
function renderCorpus(newEntry) {
const list = document.getElementById('corpus-list');
list.innerHTML = '';
[...corpus].reverse().slice(0, 8).forEach((entry, idx) => {
const el = document.createElement('div');
el.className = 'corpus-entry' + (idx === 0 && newEntry ? ' new' : '');
el.textContent = hexStr(entry);
list.appendChild(el);
});
}
function setStatus(t) { document.getElementById('status').textContent = t; }
function setMode(m) {
mode = m;
const badge = document.getElementById('mode-label');
badge.textContent = m === 'random' ? 'Random' : m === 'guided' ? 'Coverage-Guided' : '—';
badge.className = 'mode-badge' + (m === 'guided' ? ' guided' : '');
}
function updateIter() {
document.getElementById('iteration-label').textContent =
iteration > 0 ? i18nFmt('Iteration %n%', { n: iteration }) : '';
}
function resetState() {
globalCoverage = new Set();
corpus = [[65, 200, 102, 155, 42]]; // Start with one seed: a byte sequence that hits all 8 branches
iteration = 0;
crashed = false;
document.getElementById('crash-banner').classList.add('hidden');
renderBranches();
renderCorpus(false);
setStatus('');
updateIter();
}
function runStep(useGuidance) {
if (crashed) return;
iteration++;
updateIter();
let candidate;
if (useGuidance && corpus.length > 0) {
// Coverage-guided — mutate a random seed from the corpus
const parent = corpus[Math.floor(Math.random() * corpus.length)];
candidate = mutate(parent);
} else {
// Pure random — generate a fresh random input
candidate = randomBytes(Math.floor(Math.random() * 6) + 1);
}
const result = evaluateInput(candidate);
const prevSize = globalCoverage.size;
result.hits.forEach(b => globalCoverage.add(b));
let added = false;
if (useGuidance) {
// Keep the input only if it reached at least one NEW branch
if (result.hits.size > 0 && globalCoverage.size > prevSize) {
corpus.push(candidate);
added = true;
}
}
renderBranches();
renderCorpus(added);
if (result.crashed && !crashed) {
crashed = true;
document.getElementById('crash-banner').classList.remove('hidden');
setStatus(i18nFmt('CRASH on input: %hex%', { hex: hexStr(candidate) }));
stopAuto();
return;
}
const msg = useGuidance
? (added
? i18nFmt('New branches found! Coverage: %n%/8', { n: globalCoverage.size })
: i18nFmt('No new branches this time. Coverage: %n%/8', { n: globalCoverage.size }))
: i18nFmt('Random input evaluated. Coverage so far: %n%/8', { n: globalCoverage.size });
setStatus(msg);
}
function stopAuto() {
if (intervalId !== null) { clearInterval(intervalId); intervalId = null; }
document.getElementById('btn-random').disabled = false;
document.getElementById('btn-guided').disabled = false;
document.getElementById('btn-step').disabled = false;
}
function startAuto(useGuidance) {
stopAuto();
setMode(useGuidance ? 'guided' : 'random');
document.getElementById('btn-random').disabled = true;
document.getElementById('btn-guided').disabled = true;
document.getElementById('btn-step').disabled = true;
intervalId = setInterval(() => {
if (crashed || iteration >= 120) { stopAuto(); return; }
runStep(useGuidance);
}, 80);
}
document.getElementById('btn-random').addEventListener('click', () => {
resetState();
setMode('random');
startAuto(false);
});
document.getElementById('btn-guided').addEventListener('click', () => {
resetState();
setMode('guided');
startAuto(true);
});
document.getElementById('btn-step').addEventListener('click', () => {
if (!mode) setMode('guided');
stopAuto();
runStep(mode !== 'random');
});
document.getElementById('btn-reset').addEventListener('click', () => {
stopAuto();
setMode(null);
resetState();
});
resetState();
renderBranches();
renderCorpus(false);
<!-- Main layout — controls, info row, branch coverage panel, corpus panel, crash banner -->
<div class="controls">
<button id="btn-random" type="button">Random Fuzzing</button>
<button id="btn-guided" type="button">Coverage-Guided</button>
<button id="btn-step" type="button" class="ghost">Step</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="info-row">
<span id="mode-label" class="mode-badge">—</span>
<span id="iteration-label" class="iter-label"></span>
</div>
<div class="panels">
<div class="panel">
<div class="panel-title">Code Coverage</div>
<div id="branch-grid" class="branch-grid"></div>
<div class="coverage-bar-wrap">
<div id="coverage-bar" class="coverage-bar"></div>
</div>
<div id="coverage-pct" class="coverage-pct">0 / 8 branches hit</div>
</div>
<div class="panel">
<div class="panel-title">Corpus (seeds)</div>
<div id="corpus-list" class="corpus-list"></div>
</div>
</div>
<div id="crash-banner" class="crash-banner hidden">CRASH FOUND! The fuzzer triggered the hidden bug.</div>
<div id="status" class="status"></div>
/* Styles for coverage-guided fuzzing demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.controls { display: flex; gap: .4rem; flex-wrap: wrap; margin-bottom: .5rem; }
button { font: 600 13px system-ui; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.info-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .5rem; min-height: 1.6rem; }
.mode-badge { font-size: .78rem; font-weight: 700; padding: .18rem .55rem; border-radius: 99px;
background: #e8eef3; color: #1d3557; letter-spacing: .02em; }
.mode-badge.guided { background: #1d3557; color: #fff; }
.iter-label { font-size: .8rem; color: #555; }
.panels { display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; margin-bottom: .5rem; }
.panel { background: #f4f7fa; border-radius: 8px; padding: .65rem .7rem; }
.panel-title { font-size: .78rem; font-weight: 700; color: #555; text-transform: uppercase;
letter-spacing: .06em; margin-bottom: .4rem; }
.branch-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; margin-bottom: .5rem; }
.branch-cell { height: 32px; border-radius: 6px; background: #dde3ea; display: flex;
align-items: center; justify-content: center; font: 700 12px ui-monospace, monospace;
color: #888; transition: background .2s, color .2s; }
.branch-cell.hit { background: #2a9d8f; color: #fff; }
.branch-cell.crash { background: #e63946; color: #fff; }
.coverage-bar-wrap { background: #dde3ea; border-radius: 99px; height: 7px; margin-bottom: .3rem; overflow: hidden; }
.coverage-bar { height: 100%; background: #2a9d8f; border-radius: 99px; transition: width .3s; width: 0%; }
.coverage-pct { font-size: .8rem; color: #444; font-weight: 600; }
.corpus-list { max-height: 130px; overflow-y: auto; font-family: ui-monospace, monospace; font-size: .78rem; }
.corpus-entry { padding: .15rem .3rem; border-radius: 4px; margin-bottom: 2px; background: #e2eaf2;
color: #1d3557; word-break: break-all; }
.corpus-entry.new { background: #c8f7c5; color: #0a5c1e; }
.crash-banner { background: #e63946; color: #fff; font-weight: 700; font-size: .9rem;
border-radius: 8px; padding: .5rem .8rem; margin-bottom: .4rem; text-align: center; }
.crash-banner.hidden { display: none; }
.status { font-size: .82rem; color: #555; min-height: 1.2em; }
@media (max-width: 420px) { .panels { grid-template-columns: 1fr; } }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Simulated program: 8 branches with increasing depth
// Branch conditions — each deeper branch requires all previous ones
// Branch 0: input length >= 1
// Branch 1: first byte > 64 (ascii 'A')
// Branch 2: second byte exists and is even
// Branch 3: branch1 && branch2 && input length >= 3
// Branch 4: branch3 && third byte > 100
// Branch 5: branch4 && fourth byte exists
// Branch 6: branch5 && fourth byte XOR second byte > 50
// Branch 7 (crash): branch6 && fifth byte === 42 (ascii '*')
function evaluateInput(bytes) {
// Evaluate which branches this input reaches, and whether it triggers the crash
const hits = new Set();
if (bytes.length >= 1) hits.add(0);
const b1 = bytes.length >= 2 && bytes[0] > 64;
if (b1) hits.add(1);
const b2 = bytes.length >= 2 && bytes[1] % 2 === 0;
if (b2) hits.add(2);
const b3 = b1 && b2 && bytes.length >= 3;
if (b3) hits.add(3);
const b4 = b3 && bytes[2] > 100;
if (b4) hits.add(4);
const b5 = b4 && bytes.length >= 4;
if (b5) hits.add(5);
const b6 = b5 && ((bytes[3] ^ bytes[1]) > 50);
if (b6) hits.add(6);
const crashed = b6 && bytes.length >= 5 && bytes[4] === 42;
if (crashed) hits.add(7);
return { hits, crashed };
}
// Input mutation helpers — AFL-style bit flips and structure tweaks
function randomBytes(len) {
const b = [];
for (let i = 0; i < len; i++) b.push(Math.floor(Math.random() * 256));
return b;
}
function mutate(bytes) {
// Mutation operators — flip a bit, insert/delete/replace a byte, or use interesting boundary values
const ops = ['flip', 'insert', 'delete', 'replace', 'boundary'];
const op = ops[Math.floor(Math.random() * ops.length)];
const b = [...bytes];
if (op === 'flip' && b.length > 0) {
const i = Math.floor(Math.random() * b.length);
b[i] = b[i] ^ (1 << Math.floor(Math.random() * 8));
} else if (op === 'insert') {
const i = Math.floor(Math.random() * (b.length + 1));
b.splice(i, 0, Math.floor(Math.random() * 256));
} else if (op === 'delete' && b.length > 1) {
b.splice(Math.floor(Math.random() * b.length), 1);
} else if (op === 'replace' && b.length > 0) {
b[Math.floor(Math.random() * b.length)] = Math.floor(Math.random() * 256);
} else if (op === 'boundary') {
// Boundary values — integers near type boundaries that often trigger edge cases
const interesting = [0, 1, 42, 64, 65, 100, 101, 127, 128, 255];
if (b.length > 0) b[Math.floor(Math.random() * b.length)] = interesting[Math.floor(Math.random() * interesting.length)];
else b.push(interesting[Math.floor(Math.random() * interesting.length)]);
}
return b;
}
function hexStr(bytes) { return bytes.map(x => x.toString(16).padStart(2, '0')).join(' '); }
// Global fuzzer state
let globalCoverage = new Set();
let corpus = [];
let iteration = 0;
let crashed = false;
let intervalId = null;
let mode = null;
function renderBranches() {
const grid = document.getElementById('branch-grid');
grid.innerHTML = '';
for (let i = 0; i < 8; i++) {
const cell = document.createElement('div');
const isCrashBranch = i === 7;
cell.className = 'branch-cell' +
(globalCoverage.has(i) ? (isCrashBranch && crashed ? ' crash' : ' hit') : '');
cell.textContent = 'B' + i;
if (isCrashBranch) cell.title = 'Crash branch — only reachable deep in the tree';
grid.appendChild(cell);
}
const pct = Math.round((globalCoverage.size / 8) * 100);
document.getElementById('coverage-bar').style.width = pct + '%';
document.getElementById('coverage-pct').textContent =
globalCoverage.size + ' / 8 branches hit';
}
function renderCorpus(newEntry) {
const list = document.getElementById('corpus-list');
list.innerHTML = '';
[...corpus].reverse().slice(0, 8).forEach((entry, idx) => {
const el = document.createElement('div');
el.className = 'corpus-entry' + (idx === 0 && newEntry ? ' new' : '');
el.textContent = hexStr(entry);
list.appendChild(el);
});
}
function setStatus(t) { document.getElementById('status').textContent = t; }
function setMode(m) {
mode = m;
const badge = document.getElementById('mode-label');
badge.textContent = m === 'random' ? 'Random' : m === 'guided' ? 'Coverage-Guided' : '—';
badge.className = 'mode-badge' + (m === 'guided' ? ' guided' : '');
}
function updateIter() {
document.getElementById('iteration-label').textContent =
iteration > 0 ? i18nFmt('Iteration %n%', { n: iteration }) : '';
}
function resetState() {
globalCoverage = new Set();
corpus = [[65, 200, 102, 155, 42]]; // Start with one seed: a byte sequence that hits all 8 branches
iteration = 0;
crashed = false;
document.getElementById('crash-banner').classList.add('hidden');
renderBranches();
renderCorpus(false);
setStatus('');
updateIter();
}
function runStep(useGuidance) {
if (crashed) return;
iteration++;
updateIter();
let candidate;
if (useGuidance && corpus.length > 0) {
// Coverage-guided — mutate a random seed from the corpus
const parent = corpus[Math.floor(Math.random() * corpus.length)];
candidate = mutate(parent);
} else {
// Pure random — generate a fresh random input
candidate = randomBytes(Math.floor(Math.random() * 6) + 1);
}
const result = evaluateInput(candidate);
const prevSize = globalCoverage.size;
result.hits.forEach(b => globalCoverage.add(b));
let added = false;
if (useGuidance) {
// Keep the input only if it reached at least one NEW branch
if (result.hits.size > 0 && globalCoverage.size > prevSize) {
corpus.push(candidate);
added = true;
}
}
renderBranches();
renderCorpus(added);
if (result.crashed && !crashed) {
crashed = true;
document.getElementById('crash-banner').classList.remove('hidden');
setStatus(i18nFmt('CRASH on input: %hex%', { hex: hexStr(candidate) }));
stopAuto();
return;
}
const msg = useGuidance
? (added
? i18nFmt('New branches found! Coverage: %n%/8', { n: globalCoverage.size })
: i18nFmt('No new branches this time. Coverage: %n%/8', { n: globalCoverage.size }))
: i18nFmt('Random input evaluated. Coverage so far: %n%/8', { n: globalCoverage.size });
setStatus(msg);
}
function stopAuto() {
if (intervalId !== null) { clearInterval(intervalId); intervalId = null; }
document.getElementById('btn-random').disabled = false;
document.getElementById('btn-guided').disabled = false;
document.getElementById('btn-step').disabled = false;
}
function startAuto(useGuidance) {
stopAuto();
setMode(useGuidance ? 'guided' : 'random');
document.getElementById('btn-random').disabled = true;
document.getElementById('btn-guided').disabled = true;
document.getElementById('btn-step').disabled = true;
intervalId = setInterval(() => {
if (crashed || iteration >= 120) { stopAuto(); return; }
runStep(useGuidance);
}, 80);
}
document.getElementById('btn-random').addEventListener('click', () => {
resetState();
setMode('random');
startAuto(false);
});
document.getElementById('btn-guided').addEventListener('click', () => {
resetState();
setMode('guided');
startAuto(true);
});
document.getElementById('btn-step').addEventListener('click', () => {
if (!mode) setMode('guided');
stopAuto();
runStep(mode !== 'random');
});
document.getElementById('btn-reset').addEventListener('click', () => {
stopAuto();
setMode(null);
resetState();
});
resetState();
renderBranches();
renderCorpus(false);
Notice how random fuzzing often stalls: it keeps hitting the same easy branches over and over. Coverage-guided fuzzing keeps only inputs that reach new branches, so the corpus grows richer with every discovery. Coverage climbs steadily — and when it finally hits the deepest branch, a crash appears.
Comments
Loading comments...