blob: 795f49d1ce64ae38c2dd30f7357cc8413c6bd1b8 [file] [edit]
<!DOCTYPE html><!-- webkit-test-runner [ IPCTestingAPIEnabled=true IgnoreInvalidMessageWhenIPCTestingAPIEnabled=true ] -->
<html>
<body>
<p>Test that a WebContent process cannot forge a persistent storage-access grant by sending
storageAccessUnderTopFrameDomains via NetworkConnectionToWebProcess::ResourceLoadStatisticsUpdated.</p>
<pre id="log"></pre>
<script>
if (window.testRunner) { testRunner.waitUntilDone(); testRunner.dumpAsText(); }
function out(s) { document.getElementById('log').textContent += s + '\n'; }
function J(v) { return JSON.stringify(v, (k, x) => typeof x === 'bigint' ? x.toString() + 'n' : x); }
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
async function main() {
const { CoreIPC, ArgumentSerializer } = await import('./coreipc.js');
const fix = (o) => {
if (Array.isArray(o))
o.forEach(fix);
else if (o && typeof o === 'object') {
for (const k in o) {
if (o[k] === 'std::monostate')
o[k] = 'std::nullptr_t';
else
fix(o[k]);
}
}
};
fix(CoreIPC.typeInfo);
const so = ArgumentSerializer.serializeOptional;
ArgumentSerializer.serializeOptional = (t, a) => so.call(ArgumentSerializer, t, a ?? { });
const VICTIM = 'victim.example';
const ATTACKER = 'attacker.example';
const frameID = Array.isArray(IPC.frameID) ? IPC.frameID[0] : IPC.frameID;
const pageID = IPC.pageID;
const now = Date.now() / 1000;
const RD = (d) => ({ "string": d });
const WT = (t) => ({ "secondsSinceEpochSeconds": t });
const stat = (dom, storageAccessUnder) => ({
registrableDomain: RD(dom),
lastSeen: WT(now),
hadUserInteraction: true,
mostRecentUserInteractionTime: WT(now),
grandfathered: true,
storageAccessUnderTopFrameDomains: (storageAccessUnder || []).map(RD),
topFrameUniqueRedirectsTo: [],
topFrameUniqueRedirectsToSinceSameSiteStrictEnforcement: [],
topFrameUniqueRedirectsFrom: [],
topFrameLinkDecorationsFrom: [],
gotLinkDecorationFromPrevalentResource: false,
topFrameLoadedThirdPartyScripts: [],
subframeUnderTopFrameDomains: [],
subresourceUnderTopFrameDomains: [],
subresourceUniqueRedirectsTo: [],
subresourceUniqueRedirectsFrom: [],
isPrevalentResource: true,
isVeryPrevalentResource: false,
dataRecordsRemoved: 0,
timesAccessedAsFirstPartyDueToUserInteraction: 0,
timesAccessedAsFirstPartyDueToStorageAccessAPI: 0,
topFrameRegistrableDomainsWhichAccessedWebAPIs: [],
fontsFailedToLoad: [],
fontsSuccessfullyLoaded: [],
canvasActivityRecord: { textWritten: [], wasDataRead: false },
navigatorFunctionsAccessed: 0,
screenFunctionsAccessed: 0,
});
let rlsDone = false;
CoreIPC.Networking.NetworkConnectionToWebProcess.ResourceLoadStatisticsUpdated(0, {
statistics: [stat(ATTACKER), stat(VICTIM, [ATTACKER])]
}, () => { rlsDone = true; });
for (let i = 0; i < 40 && !rlsDone; i++) await sleep(100);
let rsaReply = null;
CoreIPC.Networking.NetworkConnectionToWebProcess.RequestStorageAccess(0, {
subFrameDomain: RD(VICTIM),
topFrameDomain: RD(ATTACKER),
frameID: frameID,
webPageID: pageID,
webPageProxyID: IPC.webPageProxyID,
scope: 1,
hasUserGestureOrNoUserGestureRequired: 1,
}, (r) => { rsaReply = r; });
for (let i = 0; i < 40 && !rsaReply; i++) await sleep(100);
let hsaReply = null;
CoreIPC.Networking.NetworkConnectionToWebProcess.HasStorageAccess(0, {
subFrameDomain: RD(VICTIM),
topFrameDomain: RD(ATTACKER),
frameID: frameID,
pageID: pageID,
}, (r) => { hsaReply = r; });
for (let i = 0; i < 40 && !hsaReply; i++) await sleep(100);
if (hsaReply && hsaReply.hasStorageAccess)
out('FAIL: forged storageAccessUnderTopFrameDomains produced a live storage-access grant');
else
out('PASS: no storage-access grant was created from forged statistics');
}
if (window.IPC)
main().catch(e => out('FAIL: ' + e)).finally(() => testRunner.notifyDone());
else {
out('PASS: no storage-access grant was created from forged statistics');
testRunner?.notifyDone();
}
</script>
</body>
</html>