test: service worker contextBridge leak (#45852)
* test: contextBridge prototype leak in service workers * test: deep prototype checks
This commit is contained in:
parent
add374ef6a
commit
21ad7cdda5
2 changed files with 81 additions and 1 deletions
|
@ -388,6 +388,13 @@ describe('ServiceWorkerMain module', () => {
|
||||||
const result = await runTest(serviceWorker, { name: 'testEvaluate', args: ['evalConstructorName'] });
|
const result = await runTest(serviceWorker, { name: 'testEvaluate', args: ['evalConstructorName'] });
|
||||||
expect(result).to.equal('ServiceWorkerGlobalScope');
|
expect(result).to.equal('ServiceWorkerGlobalScope');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not leak prototypes', async () => {
|
||||||
|
loadWorkerScript();
|
||||||
|
const serviceWorker = await waitForServiceWorker('running');
|
||||||
|
const result = await runTest(serviceWorker, { name: 'testPrototypeLeak', args: [] });
|
||||||
|
expect(result).to.be.true();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('extensions', () => {
|
describe('extensions', () => {
|
||||||
|
|
75
spec/fixtures/api/preload-realm/preload-tests.js
vendored
75
spec/fixtures/api/preload-realm/preload-tests.js
vendored
|
@ -18,6 +18,76 @@ const tests = {
|
||||||
? contextBridge.executeInMainWorld({ func, args })
|
? contextBridge.executeInMainWorld({ func, args })
|
||||||
: contextBridge.executeInMainWorld({ func });
|
: contextBridge.executeInMainWorld({ func });
|
||||||
return result;
|
return result;
|
||||||
|
},
|
||||||
|
testPrototypeLeak: () => {
|
||||||
|
const checkPrototypes = (value) => {
|
||||||
|
// Get prototype in preload world
|
||||||
|
const prototype = Object.getPrototypeOf(value);
|
||||||
|
const constructorName = prototype.constructor.name;
|
||||||
|
|
||||||
|
const result = contextBridge.executeInMainWorld({
|
||||||
|
func: (value) => {
|
||||||
|
// Deeply check that value prototypes exist in the local world
|
||||||
|
const check = (v) => {
|
||||||
|
if (typeof v === 'undefined' || v === null) return true;
|
||||||
|
const prototype = Object.getPrototypeOf(v);
|
||||||
|
const constructorName = prototype.constructor.name;
|
||||||
|
const localPrototype = globalThis[constructorName].prototype;
|
||||||
|
if (prototype !== localPrototype) return false;
|
||||||
|
if (Array.isArray(v)) return v.every(check);
|
||||||
|
if (typeof v === 'object') return Object.values(v).every(check);
|
||||||
|
if (typeof v === 'function') return check(v());
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
return { protoMatches: check(value), value };
|
||||||
|
},
|
||||||
|
args: [value, constructorName]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Deeply check that value prototypes exist in the local world
|
||||||
|
const check = (v) => {
|
||||||
|
if (typeof v === 'undefined' || v === null) return true;
|
||||||
|
const prototype = Object.getPrototypeOf(v);
|
||||||
|
const constructorName = prototype.constructor.name;
|
||||||
|
const localPrototype = globalThis[constructorName].prototype;
|
||||||
|
if (prototype !== localPrototype) return false;
|
||||||
|
if (Array.isArray(v)) return v.every(check);
|
||||||
|
if (typeof v === 'object') return Object.values(v).every(check);
|
||||||
|
if (typeof v === 'function') return check(v());
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
// Prototype matched in main world
|
||||||
|
result.protoMatches &&
|
||||||
|
// Returned value matches prototype
|
||||||
|
check(result.value)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const values = [
|
||||||
|
123,
|
||||||
|
'string',
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
[123, 'string', true, ['foo']],
|
||||||
|
Symbol('foo'),
|
||||||
|
10n,
|
||||||
|
{},
|
||||||
|
Promise.resolve(),
|
||||||
|
() => {},
|
||||||
|
() => () => null,
|
||||||
|
{ [Symbol('foo')]: 123 }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const value of values) {
|
||||||
|
if (!checkPrototypes(value)) {
|
||||||
|
const constructorName = Object.getPrototypeOf(value).constructor.name;
|
||||||
|
return `${constructorName} (${value}) leaked in service worker preload`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,6 +99,9 @@ ipcRenderer.on('test', async (_event, uuid, name, ...args) => {
|
||||||
ipcRenderer.send(`test-result-${uuid}`, { error: false, result });
|
ipcRenderer.send(`test-result-${uuid}`, { error: false, result });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.debug(`erroring test ${name} for ${uuid}`);
|
console.debug(`erroring test ${name} for ${uuid}`);
|
||||||
ipcRenderer.send(`test-result-${uuid}`, { error: true, result: error.message });
|
ipcRenderer.send(`test-result-${uuid}`, {
|
||||||
|
error: true,
|
||||||
|
result: error.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue