The demo below simulates a program with three legitimate call targets (A, B, C) and one malicious gadget that the attacker would like to jump to. The CFI policy records which targets are valid for each call site.
<!-- CFI demo: a program with 3 legitimate targets and 1 forbidden gadget -->
<div class="cfi-container">
<div class="policy-panel">
<h3 class="panel-title">CFI Policy (allowed targets)</h3>
<div class="policy-row">
<span class="site-label">call site</span>
<span class="arrow">→</span>
<span class="valid-targets">
<span class="target allowed" id="tgt-A">A</span>
<span class="target allowed" id="tgt-B">B</span>
<span class="target allowed" id="tgt-C">C</span>
<span class="target forbidden" id="tgt-gadget">Gadget</span>
</span>
</div>
<p class="policy-note">Green = allowed. Red = forbidden. The check runs before every indirect jump.</p>
</div>
<div class="controls">
<div class="ctrl-row">
<label class="ctrl-label">Destination:</label>
<select id="target-select">
<option value="A">Function A (legitimate)</option>
<option value="B">Function B (legitimate)</option>
<option value="C">Function C (legitimate)</option>
<option value="gadget">Gadget (malicious)</option>
</select>
</div>
<div class="btn-row">
<button id="btn-legit" type="button">Legitimate call</button>
<button id="btn-hijack" type="button" class="danger">Hijacked jump</button>
<button id="btn-clear" type="button" class="ghost">Clear log</button>
</div>
</div>
<div class="log-panel">
<h3 class="panel-title">Execution log</h3>
<ul id="log-list"></ul>
</div>
</div>
/* Base layout and policy panel styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.cfi-container { display: flex; flex-direction: column; gap: .8rem; }
.panel-title { margin: 0 0 .4rem; font-size: .9rem; font-weight: 700; color: #1d3557; text-transform: uppercase; letter-spacing: .04em; }
.policy-panel { background: #eef2f7; border: 1px solid #cdd9e3; border-radius: 10px; padding: .7rem 1rem; }
.policy-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.site-label { font: 700 .85rem ui-monospace, monospace; background: #1d3557; color: #fff; border-radius: 6px; padding: .15rem .5rem; }
.arrow { font-size: 1.1rem; color: #555; }
.valid-targets { display: flex; gap: .4rem; flex-wrap: wrap; }
.target { font: 700 .85rem ui-monospace, monospace; border-radius: 6px; padding: .2rem .55rem; border: 2px solid transparent; }
.target.allowed { background: #d4edda; color: #155724; border-color: #c3e6cb; }
.target.forbidden { background: #f8d7da; color: #721c24; border-color: #f5c6cb; }
.target.active { outline: 3px solid #f0a500; outline-offset: 2px; }
.policy-note { margin: .5rem 0 0; font-size: .78rem; color: #555; }
/* Controls area styles */
.controls { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 10px; padding: .7rem 1rem; }
.ctrl-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; flex-wrap: wrap; }
.ctrl-label { font-size: .85rem; font-weight: 600; }
select { font-size: .9rem; padding: .3rem .5rem; border-radius: 6px; border: 1px solid #adb5bd; }
.btn-row { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 .85rem system-ui; padding: .4rem .85rem; border-radius: 8px; cursor: pointer; border: 1px solid #1d3557; background: #1d3557; color: #fff; }
button.danger { background: #c92f3c; border-color: #a02535; }
button.ghost { background: #fff; color: #1d3557; }
/* Log panel styles */
.log-panel { background: #fff; border: 1px solid #dee2e6; border-radius: 10px; padding: .7rem 1rem; max-height: 200px; overflow-y: auto; }
#log-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; }
.log-entry { font-size: .82rem; font-family: ui-monospace, monospace; padding: .25rem .5rem; border-radius: 6px; }
.log-entry.pass { background: #d4edda; color: #155724; }
.log-entry.block { background: #f8d7da; color: #721c24; }
.log-entry.info { background: #e2ecf7; color: #1d3557; }
// CFI demo — every indirect jump is checked against the allowed-target set
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// CFI policy: only A, B, C are allowed targets for this call site
const ALLOWED = new Set(['A', 'B', 'C']);
const GADGET_TARGET = 'gadget';
// Append a message to the log panel
function addLog(msg, kind) {
const li = document.createElement('li');
li.className = 'log-entry ' + kind;
li.textContent = msg;
const list = document.getElementById('log-list');
list.prepend(li);
}
// Highlight the chosen target in the policy panel
function highlight(targetId) {
['tgt-A','tgt-B','tgt-C','tgt-gadget'].forEach(id => {
document.getElementById(id).classList.remove('active');
});
const el = document.getElementById('tgt-' + targetId);
if (el) el.classList.add('active');
}
// Simulate the CFI check that fires before every indirect branch
function cfiCheck(dest) {
// CFI passes only if dest is in the allowed set
if (ALLOWED.has(dest)) {
addLog(i18nFmt('CFI CHECK PASS — jumping to %dest% (in allowed set)', { dest: dest }), 'pass');
highlight(dest);
return true;
} else {
addLog(i18nFmt('CFI CHECK BLOCK — %dest% is NOT in the allowed set!', { dest: dest }), 'block');
highlight(dest);
return false;
}
}
document.getElementById('btn-legit').addEventListener('click', function() {
// User clicked the legitimate-call button
const dest = document.getElementById('target-select').value;
if (dest === GADGET_TARGET) {
addLog('Select a legitimate target (A, B or C) for a legitimate call.', 'info');
return;
}
addLog(i18nFmt('Dispatching indirect call → %dest%', { dest: dest }), 'info');
cfiCheck(dest);
});
document.getElementById('btn-hijack').addEventListener('click', function() {
// User simulates an attacker redirecting the pointer
addLog('Attacker redirected pointer → Gadget', 'info');
const blocked = !cfiCheck(GADGET_TARGET);
if (blocked) {
addLog('Process terminated. Gadget chain aborted.', 'block');
}
});
document.getElementById('btn-clear').addEventListener('click', function() {
// Reset the log and clear highlights
document.getElementById('log-list').innerHTML = '';
['tgt-A','tgt-B','tgt-C','tgt-gadget'].forEach(id => {
document.getElementById(id).classList.remove('active');
});
addLog('Log cleared. CFI policy unchanged.', 'info');
});
// Startup message
addLog('CFI policy loaded. Allowed targets: A, B, C.', 'info');
<!-- CFI demo: a program with 3 legitimate targets and 1 forbidden gadget -->
<div class="cfi-container">
<div class="policy-panel">
<h3 class="panel-title">CFI Policy (allowed targets)</h3>
<div class="policy-row">
<span class="site-label">call site</span>
<span class="arrow">→</span>
<span class="valid-targets">
<span class="target allowed" id="tgt-A">A</span>
<span class="target allowed" id="tgt-B">B</span>
<span class="target allowed" id="tgt-C">C</span>
<span class="target forbidden" id="tgt-gadget">Gadget</span>
</span>
</div>
<p class="policy-note">Green = allowed. Red = forbidden. The check runs before every indirect jump.</p>
</div>
<div class="controls">
<div class="ctrl-row">
<label class="ctrl-label">Destination:</label>
<select id="target-select">
<option value="A">Function A (legitimate)</option>
<option value="B">Function B (legitimate)</option>
<option value="C">Function C (legitimate)</option>
<option value="gadget">Gadget (malicious)</option>
</select>
</div>
<div class="btn-row">
<button id="btn-legit" type="button">Legitimate call</button>
<button id="btn-hijack" type="button" class="danger">Hijacked jump</button>
<button id="btn-clear" type="button" class="ghost">Clear log</button>
</div>
</div>
<div class="log-panel">
<h3 class="panel-title">Execution log</h3>
<ul id="log-list"></ul>
</div>
</div>
/* Base layout and policy panel styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.cfi-container { display: flex; flex-direction: column; gap: .8rem; }
.panel-title { margin: 0 0 .4rem; font-size: .9rem; font-weight: 700; color: #1d3557; text-transform: uppercase; letter-spacing: .04em; }
.policy-panel { background: #eef2f7; border: 1px solid #cdd9e3; border-radius: 10px; padding: .7rem 1rem; }
.policy-row { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.site-label { font: 700 .85rem ui-monospace, monospace; background: #1d3557; color: #fff; border-radius: 6px; padding: .15rem .5rem; }
.arrow { font-size: 1.1rem; color: #555; }
.valid-targets { display: flex; gap: .4rem; flex-wrap: wrap; }
.target { font: 700 .85rem ui-monospace, monospace; border-radius: 6px; padding: .2rem .55rem; border: 2px solid transparent; }
.target.allowed { background: #d4edda; color: #155724; border-color: #c3e6cb; }
.target.forbidden { background: #f8d7da; color: #721c24; border-color: #f5c6cb; }
.target.active { outline: 3px solid #f0a500; outline-offset: 2px; }
.policy-note { margin: .5rem 0 0; font-size: .78rem; color: #555; }
/* Controls area styles */
.controls { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 10px; padding: .7rem 1rem; }
.ctrl-row { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; flex-wrap: wrap; }
.ctrl-label { font-size: .85rem; font-weight: 600; }
select { font-size: .9rem; padding: .3rem .5rem; border-radius: 6px; border: 1px solid #adb5bd; }
.btn-row { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 .85rem system-ui; padding: .4rem .85rem; border-radius: 8px; cursor: pointer; border: 1px solid #1d3557; background: #1d3557; color: #fff; }
button.danger { background: #c92f3c; border-color: #a02535; }
button.ghost { background: #fff; color: #1d3557; }
/* Log panel styles */
.log-panel { background: #fff; border: 1px solid #dee2e6; border-radius: 10px; padding: .7rem 1rem; max-height: 200px; overflow-y: auto; }
#log-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; }
.log-entry { font-size: .82rem; font-family: ui-monospace, monospace; padding: .25rem .5rem; border-radius: 6px; }
.log-entry.pass { background: #d4edda; color: #155724; }
.log-entry.block { background: #f8d7da; color: #721c24; }
.log-entry.info { background: #e2ecf7; color: #1d3557; }
// CFI demo — every indirect jump is checked against the allowed-target set
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// CFI policy: only A, B, C are allowed targets for this call site
const ALLOWED = new Set(['A', 'B', 'C']);
const GADGET_TARGET = 'gadget';
// Append a message to the log panel
function addLog(msg, kind) {
const li = document.createElement('li');
li.className = 'log-entry ' + kind;
li.textContent = msg;
const list = document.getElementById('log-list');
list.prepend(li);
}
// Highlight the chosen target in the policy panel
function highlight(targetId) {
['tgt-A','tgt-B','tgt-C','tgt-gadget'].forEach(id => {
document.getElementById(id).classList.remove('active');
});
const el = document.getElementById('tgt-' + targetId);
if (el) el.classList.add('active');
}
// Simulate the CFI check that fires before every indirect branch
function cfiCheck(dest) {
// CFI passes only if dest is in the allowed set
if (ALLOWED.has(dest)) {
addLog(i18nFmt('CFI CHECK PASS — jumping to %dest% (in allowed set)', { dest: dest }), 'pass');
highlight(dest);
return true;
} else {
addLog(i18nFmt('CFI CHECK BLOCK — %dest% is NOT in the allowed set!', { dest: dest }), 'block');
highlight(dest);
return false;
}
}
document.getElementById('btn-legit').addEventListener('click', function() {
// User clicked the legitimate-call button
const dest = document.getElementById('target-select').value;
if (dest === GADGET_TARGET) {
addLog('Select a legitimate target (A, B or C) for a legitimate call.', 'info');
return;
}
addLog(i18nFmt('Dispatching indirect call → %dest%', { dest: dest }), 'info');
cfiCheck(dest);
});
document.getElementById('btn-hijack').addEventListener('click', function() {
// User simulates an attacker redirecting the pointer
addLog('Attacker redirected pointer → Gadget', 'info');
const blocked = !cfiCheck(GADGET_TARGET);
if (blocked) {
addLog('Process terminated. Gadget chain aborted.', 'block');
}
});
document.getElementById('btn-clear').addEventListener('click', function() {
// Reset the log and clear highlights
document.getElementById('log-list').innerHTML = '';
['tgt-A','tgt-B','tgt-C','tgt-gadget'].forEach(id => {
document.getElementById(id).classList.remove('active');
});
addLog('Log cleared. CFI policy unchanged.', 'info');
});
// Startup message
addLog('CFI policy loaded. Allowed targets: A, B, C.', 'info');
Click Legitimate call to dispatch to a valid target — CFI's check passes instantly. Click Hijacked jump to simulate an attacker redirecting the pointer to the gadget — CFI catches the mismatch and terminates the call before the gadget runs. The log shows every check as it happens. Notice that CFI adds only a tiny comparison before each branch; the protection is almost free.
Comments
Loading comments...