dcp-client
Version:
Core libraries for accessing DCP network
512 lines (478 loc) • 14 kB
JavaScript
/**
* @file worker/evaluator-lib/access-lists.js
*
* Applies access lists to the global object.
*
* @author Sam Cantor, sam@kingsds.network
* @date Sept 2020
*/
self.wrapScriptLoading({ scriptName: 'access-lists', ringTransition: true }, function accessLists$$fn(protectedStorage, ring0PostMessage)
{
const ring1PostMessage = self.postMessage;
const global = typeof globalThis === 'undefined' ? self : globalThis;
const allowList = new Set([
// global objects, aggregated from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects#Reflection
'AggregateError',
'Array',
'ArrayBuffer',
'Atomics',
'BigInt',
'BigInt64Array',
'BigUint64Array',
'Boolean',
'constructor',
'CustomEvent',
'DataView',
'Date',
'DOMException',
'decodeURI',
'decodeURIComponent',
'dispatchEvent',
'encodeURI',
'encodeURIComponent',
'Error',
'Event',
'EventTarget',
'EventHandler',
'escape',
'eval',
'EvalError',
'FinalizationRegistry',
'Float32Array',
'Float64Array',
'Function',
'globalThis',
'Infinity',
'Int16Array',
'Int32Array',
'Int8Array',
'isFinite',
'isNaN',
'JSON',
'Map',
'Math',
'module',
'NaN',
'null',
'Number',
'Object',
'onerror',
'onmessage',
'parseFloat',
'parseInt',
'postMessage',
'Promise',
'propertyIsEnumerable',
'Proxy',
'RangeError',
'ReferenceError',
'Reflect',
'RegExp',
'require',
'Response',
'Set',
'SharedArrayBuffer',
'String',
'Symbol',
'SyntaxError',
'TextDecoder',
'TextEncoder',
'toLocaleString',
'toString',
'TypeError',
'URIError',
'URL',
'Uint16Array',
'Uint32Array',
'Uint8Array',
'Uint8ClampedArray',
'undefined',
'unescape',
'valueOf',
'WeakMap',
'WeakSet',
'WeakRef',
'__proto__',
// WorkerGlobalScope symbols, aggregated from https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope
// 'caches',
//'crossOriginIsolated',
'crypto',
//'fonts',
// 'indexedDB',
'isSecureContext',
'location',
'navigator',
//'origin',
'performance',
// 'scheduler',
'self',
'console',
'atob',
'btoa',
'clearInterval',
'clearTimeout',
// 'importScripts',
'queueMicrotask',
'setInterval',
'setTimeout',
'structuredClone',
'reportError',
'WorkerGlobalScope',
// fetch API symbols (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#fetch_interfaces)
'fetch',
'Headers',
'Request',
'Response',
// WebAssembly symbols
'WebAssembly',
'HTMLCanvasElement',
'HTMLVideoElement',
'Navigator',
'WorkerNavigator',
// DCP symbols / chosen web API additions p
'progress',
'work',
'bravojs',
'setImmediate',
'Blob',
'addEventListener',
'removeEventListener',
]);
const navigatorAllowList = new Set([
// Deprecated but kept for compatibility
'appCodeName',
'appName',
'appVersion',
'platform',
'product',
'userAgent',
'deviceMemory',
'hardwareConcurrency',
'onLine',
'userAgentData',
]);
const webGLSymbols = [
'WebGL2RenderingContext',
'WebGLTexture',
'OffscreenCanvas',
];
const webGPUSymbols = [
'WebGPUWindow',
'GPU',
'GPU',
'GPUAdapter',
'GPUAdapterInfo',
'GPUAdapterLimits',
'GPUAdapterProperties',
'GPUAdapterRequestOptions',
'GPUAddressMode',
'GPUAutoLayoutMode',
'GPUBindGroup',
'GPUBindGroupDescriptor',
'GPUBindGroupEntry',
'GPUBindGroupLayout',
'GPUBindGroupLayoutDescriptor',
'GPUBindGroupLayoutEntry',
'GPUBindingCommandsMixin',
'GPUBindingResource',
'GPUBlendComponent',
'GPUBlendFactor',
'GPUBlendOperation',
'GPUBlendState',
'GPUBuffer',
'GPUBufferBinding',
'GPUBufferBindingLayout',
'GPUBufferBindingType',
'GPUBufferDescriptor',
'GPUBufferDynamicOffset',
'GPUBufferMapState',
'GPUBufferUsage',
'GPUBufferUsageFlags',
'GPUCanvasAlphaMode',
'GPUCanvasConfiguration',
'GPUCanvasContext',
'GPUColor',
'GPUColorDict',
'GPUColorTargetState',
'GPUColorWrite',
'GPUColorWriteFlags',
'GPUCommandBuffer',
'GPUCommandBufferDescriptor',
'GPUCommandEncoder',
'GPUCommandEncoderDescriptor',
'GPUCommandsMixin',
'GPUCompareFunction',
'GPUCompilationInfo',
'GPUCompilationMessage',
'GPUCompilationMessageType',
'GPUComputePassDescriptor',
'GPUComputePassEncoder',
'GPUComputePassTimestampLocation',
'GPUComputePassTimestampWrite',
'GPUComputePassTimestampWrites',
'GPUComputePipeline',
'GPUComputePipelineDescriptor',
'GPUCullMode',
'GPUDebugCommandsMixin',
'GPUDepthBias',
'GPUDepthStencilState',
'GPUDevice',
'GPUDeviceDescriptor',
'GPUDeviceLostInfo',
'GPUDeviceLostReason',
'GPUError',
'GPUErrorFilter',
'GPUExtent3D',
'GPUExtent3DDict',
'GPUExtent3DDictStrict',
'GPUExtent3DStrict',
'GPUExternalTexture',
'GPUExternalTextureBindingLayout',
'GPUExternalTextureDescriptor',
'GPUFeatureName',
'GPUFilterMode',
'GPUFlagsConstant',
'GPUFragmentState',
'GPUFrontFace',
'GPUImageCopyBuffer',
'GPUImageCopyExternalImage',
'GPUImageCopyTexture',
'GPUImageCopyTextureTagged',
'GPUImageDataLayout',
'GPUIndex32',
'GPUIndexFormat',
'GPUIntegerCoordinate',
'GPUInternalError',
'GPULoadOp',
'GPUMapMode',
'GPUMapModeFlags',
'GPUMipmapFilterMode',
'GPUMultisampleState',
'GPUObjectBase',
'GPUObjectDescriptorBase',
'GPUOrigin2D',
'GPUOrigin2DDict',
'GPUOrigin2DDictStrict',
'GPUOrigin2DStrict',
'GPUOrigin3D',
'GPUOrigin3DDict',
'GPUOutOfMemoryError',
'GPUPipelineBase',
'GPUPipelineConstantValue',
'GPUPipelineDescriptorBase',
'GPUPipelineError',
'GPUPipelineErrorInit',
'GPUPipelineErrorReason',
'GPUPipelineLayout',
'GPUPipelineLayoutDescriptor',
'GPUPowerPreference',
'GPUPrimitiveState',
'GPUPrimitiveTopology',
'GPUProgrammableStage',
'GPUQuerySet',
'GPUQuerySetDescriptor',
'GPUQueryType',
'GPUQueue',
'GPUQueueDescriptor',
'GPURenderBundle',
'GPURenderBundleDescriptor',
'GPURenderBundleEncoder',
'GPURenderBundleEncoderDescriptor',
'GPURenderCommandsMixin',
'GPURenderPassColorAttachment',
'GPURenderPassDepthStencilAttachment',
'GPURenderPassDescriptor',
'GPURenderPassEncoder',
'GPURenderPassLayout',
'GPURenderPassTimestampLocation',
'GPURenderPassTimestampWrite',
'GPURenderPassTimestampWrites',
'GPURenderPipeline',
'GPURenderPipelineDescriptor',
'GPURequestAdapterOptions',
'GPUSampleMask',
'GPUSampler',
'GPUSamplerBindingLayout',
'GPUSamplerBindingType',
'GPUSamplerDescriptor',
'GPUShaderModule',
'GPUShaderModuleCompilationHint',
'GPUShaderModuleDescriptor',
'GPUShaderStage',
'GPUShaderStageFlags',
'GPUSignedOffset32',
'GPUSize32',
'GPUSize64',
'GPUStencilFaceState',
'GPUStencilOperation',
'GPUStencilValue',
'GPUStorageTextureAccess',
'GPUStorageTextureBindingLayout',
'GPUStoreOp',
'GPUSupportedFeatures',
'GPUSupportedLimits',
'GPUTexture',
'GPUTextureAspect',
'GPUTextureBindingLayout',
'GPUTextureDescriptor',
'GPUTextureDimension',
'GPUTextureFormat',
'GPUTextureSampleType',
'GPUTextureUsage',
'GPUTextureUsageFlags',
'GPUTextureView',
'GPUTextureViewDescriptor',
'GPUTextureViewDimension',
'GPUUncapturedErrorEvent',
'GPUUncapturedErrorEventInit',
'GPUValidationError',
'GPUVertexAttribute',
'GPUVertexBufferLayout',
'GPUVertexFormat',
'GPUVertexState',
'GPUVertexStepMode',
'WGSLLanguageFeatures',
];
// elements to be blocked from the navigator
const navigatorWebGPUSymbols = [
'gpu',
];
/**
* Applies a allow list to an object. After this function, non-allowed properties will
* appear undefined.
* @param {object} obj - The object, which will have the allow list applied to its properties.
* @param {Set} allowList - A set of properties to allow people to access.
*/
function applyAccessLists(obj, allowList) {
if (!obj) { return; }
Object.getOwnPropertyNames(obj).forEach(function (prop) {
if (Object.getOwnPropertyDescriptor(obj, prop)?.configurable) {
if (!allowList.has(prop)) {
let isSet = false;
let propValue;
Object.defineProperty(obj, prop, {
get: function getProtectedProperty() {
if (isSet)
return propValue;
else
return undefined;
},
set: function setProtectedProperty(value) {
propValue = value;
isSet = true;
},
configurable: false
});
}
}
});
}
/**
* Applies the allowList.
* This must be called after the requirements are assigned to the sandbox
* so that symbols only accessible if specific requirements are set (such as webGPU)
* can be added to the allowList.
*/
function applyAllAccessLists() {
// We need to apply the access lists to global, and the entirety of global's prototype chain
// because there's networking-accessing functions inside the chain, like fetch.
var global = typeof globalThis === 'undefined' ? self : globalThis;
for (let g = global; Object.getPrototypeOf(g); g = Object.getPrototypeOf(g))
applyAccessLists(g, allowList);
if (typeof navigator === 'undefined')
global.navigator = { userAgent: 'not a browser' };
/**
* Replace the navigator object with a polyfill that only has symbols we are explicitly allowing access to.
* On web workers, the navigator is an attribute of the WorkerGlobalScope interface, which is inherited by the
* DedicatedWorkerGlobalScope, the global context (globalThis) in the web worker.
* In the native evaluator, the navigator is an attribute of the global object directly.
*
* On all tested platforms, navigator is non-writable, but is configurable, so we can delete & redefine the
* navigator. In the case where navigator is also non-configurable, we will fall back on applying the access list
* to the original navigator, to only allow access to allowed properties.
*/
var owner;
if (Object.getOwnPropertyDescriptor(global, 'navigator')) // native
owner = global;
else // web worker
owner = Object.getPrototypeOf(Object.getPrototypeOf(global));
const navigatorDescriptor = Object.getOwnPropertyDescriptor(owner, 'navigator');
if (!navigatorDescriptor?.configurable)
{
for (let n = navigator; Object.getPrototypeOf(n); n = Object.getPrototypeOf(n))
applyAccessLists(n, navigatorAllowList);
return;
}
const polyNavigator = {};
for (let prop of navigatorAllowList)
polyNavigator[prop] = navigator[prop];
if (!navigatorDescriptor.writable)
delete owner.navigator;
owner.navigator = polyNavigator;
if (!navigator.hasOwnProperty('userAgent'))
navigator.userAgent = 'not-a-browser';
}
// At time of writing, Chrome defines requestAnimationFrame inside web workers, but
// Firefox doesn't.
if (typeof requestAnimationFrame == 'undefined') {
global.requestAnimationFrame = callback => setTimeout(callback, 0);
}
if (protectedStorage.hasWebglSupport()) {
// This deals with Firefox bug 1529995, which causes the tab to crash if fenceSync is called.
if (navigator.userAgent.indexOf('Firefox') >= 0) {
new OffscreenCanvas(640, 480).getContext('webgl2').__proto__.fenceSync = null;
// Note: We can't just do the following, since WebGL2RenderingContext isn't defined
// in Firefox until the first webgl2 context is created.
// WebGL2RenderingContext.prototype.fenceSync = undefined
}
// Make it so that if getContext throws on a given type of context, return null
// instead of throwing an exception. This replicates Chrome's behaviour.
OffscreenCanvas.prototype.oldGetContext = OffscreenCanvas.prototype.getContext;
OffscreenCanvas.prototype.getContext = function getContextPolyfill(type) {
try {
return this.oldGetContext(type);
} catch (e) {
return null;
}
};
}
function allowWorktimeSymbols(symbols)
{
for (let symbol of symbols)
allowList.add(symbol);
}
addEventListener('message', async (event) => {
try {
if (event.request === 'applyRequirements') {
// This event is fired when the worker is initialized with job requirements,
// apply restrictions to the environment based on the requirements.
// Assume the scheduler gave us a nicely-shaped req object.
if (event.offscreenCanvas)
{
for (let symbol of webGLSymbols)
allowList.add(symbol);
}
if (event.webgpu && !protectedStorage.forceDisableWebGPU)
{
for (let symbol of webGPUSymbols)
allowList.add(symbol);
for (let symbol of navigatorWebGPUSymbols)
navigatorAllowList.add(symbol)
}
if (event.worktime && protectedStorage.worktimeGlobals[event.worktime])
allowWorktimeSymbols(protectedStorage.worktimeGlobals[event.worktime]);
applyAllAccessLists();
ring1PostMessage({ request: 'applyRequirementsDone' });
}
} catch (error) {
ring1PostMessage({
request: 'error',
error: {
name: error.name,
message: error.message,
stack: error.stack,
}
});
}
});
});