components/vrp_flags/README.md
This component provides a target for controlled read and controlled write
that can be sent to a renderer when the --vrp-flags argument is provided
to Chrome. See the VRP FAQ for details of how
to submit reports making use of the flags.
//components/vrp_flags is a base-like component that can be consumed
by other components like //components/viz and //content/browser.
Test pages must be named poc.html and are served from a local web server
running on localhost:8000.
For cross-renderer testing, the single poc.html file serves as both the
attacker page and the victim page:
http://localhost:8000/poc.html.http://victim.test:<port_number>/poc.html when
a victim renderer is spawned. The attacker can request an arbitrary
number of unique port numbers from the browser.Victim renderers can be spawned directly by the browser in the foreground
or background, or instead the attacker can spawn the victim renderer
themselves by navigating to http://victim.test:<port_number>/poc.html (e.g.
via window.open(...) or inserting an <iframe>).
Scripts inside poc.html can check window.location.hostname to
differentiate between attacker logic (on localhost) and victim logic (on
victim.test).
// --vrp-flags --no-first-run --enable-blink-features=MojoJS
<div id="output"></div>
<div id="controls"></div>
<script type="module">
import { VrpFlagsFactory, VrpFlagsRemote, VictimDisposition } from './gen/components/vrp_flags/vrp_flags.mojom.m.js';
function log(msg) {
console.log(msg);
const output = document.getElementById('output');
output.innerHTML += msg + '
';
}
function createProcessUI(name, remote, writeLocations, writeValue) {
const container = document.createElement('div');
container.className = 'process-section';
const title = document.createElement('h2');
title.textContent = name;
container.appendChild(title);
const info = document.createElement('p');
info.innerHTML = `Write Locations: ${writeLocations.join(', ')}
Write Value: ${writeValue}`;
container.appendChild(info);
const firstLocation = writeLocations.length > 0 ? writeLocations[0] : 0n;
const btnWrite = document.createElement('button');
btnWrite.textContent = `Attempt Write at ${firstLocation}`;
btnWrite.onclick = async () => {
try {
log(`[${name}] Sending WriteAttempted for location ${firstLocation}...`);
const result = await remote.writeAttempted(firstLocation);
log(`[${name}] WriteAttempted Result: ${result.success}`);
} catch (e) {
log(`[${name}] Write Error: ${e}`);
}
};
container.appendChild(btnWrite);
const btnRead = document.createElement('button');
btnRead.textContent = `Attempt Read (Dummy Token)`;
btnRead.onclick = async () => {
try {
// Get prefix first, then try to read with a dummy token
log(`[${name}] Calling GetReadPrefix...`);
const prefixResult = await remote.getReadPrefix();
log(`[${name}] Prefix received: high=${prefixResult.prefix.high}, low=${prefixResult.prefix.low}`);
const dummyToken = { high: 123456789n, low: 987654321n };
log(`[${name}] Sending ReadAttempted with dummy token...`);
const readResult = await remote.readAttempted(dummyToken);
log(`[${name}] ReadAttempted Result: ${readResult.success}`);
} catch (e) {
log(`[${name}] Read Error: ${e}`);
}
};
container.appendChild(btnRead);
document.getElementById('controls').appendChild(container);
}
async function run() {
// If running in victim process on victim.test, act as the victim
if (window.location.hostname === 'victim.test') {
log('Running inside victim process on ' + window.location.host);
return;
}
try {
log("Binding VrpFlagsFactory...");
const factory = VrpFlagsFactory.getRemote();
log("Successfully bound VrpFlagsFactory.
");
// 1. Browser
try {
const browserVrp = new VrpFlagsRemote();
factory.bindBrowserVrpFlags(browserVrp.$.bindNewPipeAndPassReceiver());
const browserRes = await browserVrp.getWriteLocations();
createProcessUI('Browser', browserVrp, browserRes.locations, browserRes.value);
} catch (e) {
log('<strong>Browser VRP Error:</strong> ' + e);
}
// 2. Network
try {
const networkVrp = new VrpFlagsRemote();
factory.bindNetworkVrpFlags(networkVrp.$.bindNewPipeAndPassReceiver());
const networkRes = await networkVrp.getWriteLocations();
createProcessUI('Network', networkVrp, networkRes.locations, networkRes.value);
} catch (e) {
log('<strong>Network VRP Error:</strong> ' + e);
}
// 3. GPU
try {
const gpuVrp = new VrpFlagsRemote();
factory.bindGpuVrpFlags(gpuVrp.$.bindNewPipeAndPassReceiver());
const gpuRes = await gpuVrp.getWriteLocations();
createProcessUI('GPU', gpuVrp, gpuRes.locations, gpuRes.value);
} catch (e) {
log('<strong>GPU VRP Error:</strong> ' + e);
}
// 4. Victim Renderer
try {
const victimVrp = new VrpFlagsRemote();
// Automatic spawn (foreground tab):
const res = await factory.startRendererForVrpFlags(
VictimDisposition.kSpawnForegroundTab,
victimVrp.$.bindNewPipeAndPassReceiver());
log('Spawned victim renderer on port ' + res.port);
// Alternatively, for manual spawn (e.g. via window.open or iframe):
// const manualVrp = new VrpFlagsRemote();
// const manualRes = await factory.startRendererForVrpFlags(
// VictimDisposition.kManualSpawn,
// manualVrp.$.bindNewPipeAndPassReceiver());
// window.open(`http://victim.test:${manualRes.port}/poc.html`);
const victimRes = await victimVrp.getWriteLocations();
createProcessUI('Victim Renderer', victimVrp, victimRes.locations, victimRes.value);
} catch (e) {
log('<strong>Victim Renderer VRP Error:</strong> ' + e);
}
} catch (e) {
log('<strong style="color:red">Error:</strong> ' + e);
}
}
run();
</script>