Comparing version 0.14.3 to 0.14.4
@@ -6,2 +6,18 @@ # Change Log | ||
### [0.14.4](https://github.com/endojs/endo/compare/ses@0.14.3...ses@0.14.4) (2021-10-15) | ||
### Features | ||
* **ses:** lazily create evaluate ([f1cf92a](https://github.com/endojs/endo/commit/f1cf92a3b8e23aab3894f8431d8b65b4f75daa77)) | ||
### Bug Fixes | ||
* **ses:** Add test and warning about the `has` hazard ([9066c97](https://github.com/endojs/endo/commit/9066c97b41b35a4e37fd12256a0802fb656af755)) | ||
* **ses:** more detailed `has` hazard test ([f010a9e](https://github.com/endojs/endo/commit/f010a9ebe09dadc72a8f22bd54caed8aa3787243)) | ||
* **ses:** Refactor Compartment to use shared evaluator ([dc0bad6](https://github.com/endojs/endo/commit/dc0bad6c1f963ff379e45f320852218228203050)) | ||
### [0.14.3](https://github.com/endojs/endo/compare/ses@0.14.2...ses@0.14.3) (2021-09-18) | ||
@@ -8,0 +24,0 @@ |
16
NEWS.md
User-visible changes in SES: | ||
# 0.14.4 (2021-10-14) | ||
- Fixes a defect in the per-compartment `Function` and `eval` functions, such | ||
that these environments did not have the compartment's `globalLexicals`. | ||
There is no known environment depending on this invariant for security | ||
reasons, but such a scenario would be a program arranging a translator that | ||
introduces run-time security checks, like metering, that depend on the | ||
existence of a named global lexical. | ||
[#898](https://github.com/endojs/endo/issues/898) | ||
- The above fix incidentally improved the performance of compartment evaluation | ||
for cases that do not require special global lexicals, by sharing a single | ||
per-compartment evaluator. | ||
# 0.14.3 (2021-09-18) | ||
- Due to a peculiar bit of error handling code in Node 14, as explained at | ||
[Hardened JavaScript interferes with Node.js 14 Error construction](https://github.com/endojs/endo/issues/868), | ||
[Hardened JavaScript interferes with Node.js 14 Error | ||
construction](https://github.com/endojs/endo/issues/868), | ||
we have added more overrides to the default `overrideTaming: 'moderate'` | ||
@@ -8,0 +22,0 @@ setting. At this setting, assigning to the `name` property of a mutable error |
{ | ||
"name": "ses", | ||
"version": "0.14.3", | ||
"version": "0.14.4", | ||
"description": "Hardened JavaScript for Fearless Cooperation", | ||
@@ -62,6 +62,6 @@ "keywords": [ | ||
"devDependencies": { | ||
"@endo/compartment-mapper": "^0.5.3", | ||
"@endo/eslint-config": "^0.3.15", | ||
"@endo/static-module-record": "^0.6.3", | ||
"@endo/test262-runner": "^0.1.9", | ||
"@endo/compartment-mapper": "^0.5.4", | ||
"@endo/eslint-config": "^0.3.16", | ||
"@endo/static-module-record": "^0.6.4", | ||
"@endo/test262-runner": "^0.1.10", | ||
"ava": "^3.12.1", | ||
@@ -183,3 +183,3 @@ "babel-eslint": "^10.0.3", | ||
}, | ||
"gitHead": "619005298bde41e3013b8d704db543c0888a239f" | ||
"gitHead": "e92151ee140769c4de6d8e8533cf9abaac6781e9" | ||
} |
@@ -14,4 +14,56 @@ /// <reference types="ses"> | ||
} from './transforms.js'; | ||
import { performEval } from './evaluate.js'; | ||
import { makeSafeEvaluator } from './make-safe-evaluator.js'; | ||
export const provideCompartmentEvaluator = (compartmentFields, options) => { | ||
const { | ||
sloppyGlobalsMode = false, | ||
__moduleShimLexicals__ = undefined, | ||
} = options; | ||
let safeEvaluate; | ||
if (__moduleShimLexicals__ === undefined && !sloppyGlobalsMode) { | ||
({ safeEvaluate } = compartmentFields); | ||
} else { | ||
// The scope proxy or global lexicals are different from the | ||
// shared evaluator so we need to build a new one | ||
let { globalTransforms } = compartmentFields; | ||
const { | ||
globalObject, | ||
globalLexicals, | ||
knownScopeProxies, | ||
} = compartmentFields; | ||
let localObject = globalLexicals; | ||
if (__moduleShimLexicals__ !== undefined) { | ||
// When using `evaluate` for ESM modules, as should only occur from the | ||
// module-shim's module-instance.js, we do not reveal the SES-shim's | ||
// module-to-program translation, as this is not standardizable behavior. | ||
// However, the `localTransforms` will come from the `__shimTransforms__` | ||
// Compartment option in this case, which is a non-standardizable escape | ||
// hatch so programs designed specifically for the SES-shim | ||
// implementation may opt-in to use the same transforms for `evaluate` | ||
// and `import`, at the expense of being tightly coupled to SES-shim. | ||
globalTransforms = undefined; | ||
localObject = create(null, getOwnPropertyDescriptors(globalLexicals)); | ||
defineProperties( | ||
localObject, | ||
getOwnPropertyDescriptors(__moduleShimLexicals__), | ||
); | ||
} | ||
({ safeEvaluate } = makeSafeEvaluator({ | ||
globalObject, | ||
localObject, | ||
globalTransforms, | ||
sloppyGlobalsMode, | ||
knownScopeProxies, | ||
})); | ||
} | ||
return { safeEvaluate }; | ||
}; | ||
export const compartmentEvaluate = (compartmentFields, source, options) => { | ||
@@ -28,4 +80,2 @@ // Perform this check first to avoid unecessary sanitizing. | ||
transforms = [], | ||
sloppyGlobalsMode = false, | ||
__moduleShimLexicals__ = undefined, | ||
__evadeHtmlCommentTest__ = false, | ||
@@ -46,30 +96,10 @@ __evadeImportExpressionTest__ = false, | ||
let { globalTransforms } = compartmentFields; | ||
const { globalObject, globalLexicals, knownScopeProxies } = compartmentFields; | ||
const { safeEvaluate } = provideCompartmentEvaluator( | ||
compartmentFields, | ||
options, | ||
); | ||
let localObject = globalLexicals; | ||
if (__moduleShimLexicals__ !== undefined) { | ||
// When using `evaluate` for ESM modules, as should only occur from the | ||
// module-shim's module-instance.js, we do not reveal the SES-shim's | ||
// module-to-program translation, as this is not standardizable behavior. | ||
// However, the `localTransforms` will come from the `__shimTransforms__` | ||
// Compartment option in this case, which is a non-standardizable escape | ||
// hatch so programs designed specifically for the SES-shim | ||
// implementation may opt-in to use the same transforms for `evaluate` | ||
// and `import`, at the expense of being tightly coupled to SES-shim. | ||
globalTransforms = undefined; | ||
localObject = create(null, getOwnPropertyDescriptors(globalLexicals)); | ||
defineProperties( | ||
localObject, | ||
getOwnPropertyDescriptors(__moduleShimLexicals__), | ||
); | ||
} | ||
return performEval(source, globalObject, localObject, { | ||
globalTransforms, | ||
return safeEvaluate(source, { | ||
localTransforms, | ||
sloppyGlobalsMode, | ||
knownScopeProxies, | ||
}); | ||
}; |
@@ -23,3 +23,6 @@ // @ts-check | ||
} from './commons.js'; | ||
import { initGlobalObject } from './global-object.js'; | ||
import { | ||
setGlobalObjectConstantProperties, | ||
setGlobalObjectMutableProperties, | ||
} from './global-object.js'; | ||
import { isValidIdentifierName } from './scope-constants.js'; | ||
@@ -32,2 +35,3 @@ import { sharedGlobalPropertyNames } from './whitelist.js'; | ||
import { compartmentEvaluate } from './compartment-evaluate.js'; | ||
import { makeSafeEvaluator } from './make-safe-evaluator.js'; | ||
@@ -211,3 +215,3 @@ const { quote: q } = assert; | ||
__shimTransforms__ = [], | ||
globalLexicals = {}, | ||
globalLexicals: globalLexicalsOption = {}, | ||
resolveHook, | ||
@@ -250,19 +254,4 @@ importHook, | ||
const globalObject = {}; | ||
initGlobalObject( | ||
globalObject, | ||
intrinsics, | ||
sharedGlobalPropertyNames, | ||
targetMakeCompartmentConstructor, | ||
this.constructor.prototype, | ||
{ | ||
globalTransforms, | ||
markVirtualizedNativeFunction, | ||
}, | ||
); | ||
assign(globalObject, endowments); | ||
const invalidNames = arrayFilter( | ||
getOwnPropertyNames(globalLexicals), | ||
getOwnPropertyNames(globalLexicalsOption), | ||
identifier => !isValidIdentifierName(identifier), | ||
@@ -278,5 +267,40 @@ ); | ||
} | ||
// The caller continues to own the globalLexicals object they passed to | ||
// the compartment constructor, but the compartment only respects the | ||
// original values and they are constants in the scope of evaluated | ||
// programs and executed modules. | ||
// This shallow copy captures only the values of enumerable own | ||
// properties, erasing accessors. | ||
// The snapshot is frozen to ensure that the properties are immutable | ||
// when transferred-by-property-descriptor onto local scope objects. | ||
const globalLexicals = freeze({ ...globalLexicalsOption }); | ||
const globalObject = {}; | ||
// We must initialize all constant properties first because | ||
// `makeSafeEvaluator` may use them to create optimized bindings | ||
// in the evaluator. | ||
// TODO: consider merging into a single initialization if internal | ||
// evaluator is no longer eagerly created | ||
setGlobalObjectConstantProperties(globalObject); | ||
const knownScopeProxies = new WeakSet(); | ||
const { safeEvaluate } = makeSafeEvaluator({ | ||
globalObject, | ||
localObject: globalLexicals, | ||
globalTransforms, | ||
sloppyGlobalsMode: false, | ||
knownScopeProxies, | ||
}); | ||
setGlobalObjectMutableProperties(globalObject, { | ||
intrinsics, | ||
newGlobalPropertyNames: sharedGlobalPropertyNames, | ||
makeCompartmentConstructor: targetMakeCompartmentConstructor, | ||
safeEvaluate, | ||
markVirtualizedNativeFunction, | ||
}); | ||
assign(globalObject, endowments); | ||
weakmapSet(privateFields, this, { | ||
@@ -287,11 +311,4 @@ name, | ||
knownScopeProxies, | ||
// The caller continues to own the globalLexicals object they passed to | ||
// the compartment constructor, but the compartment only respects the | ||
// original values and they are constants in the scope of evaluated | ||
// programs and executed modules. | ||
// This shallow copy captures only the values of enumerable own | ||
// properties, erasing accessors. | ||
// The snapshot is frozen to ensure that the properties are immutable | ||
// when transferred-by-property-descriptor onto local scope objects. | ||
globalLexicals: freeze({ ...globalLexicals }), | ||
globalLexicals, | ||
safeEvaluate, | ||
resolveHook, | ||
@@ -298,0 +315,0 @@ importHook, |
@@ -7,25 +7,10 @@ import { defineProperty, objectHasOwnProperty, entries } from './commons.js'; | ||
/** | ||
* initGlobalObject() | ||
* Create new global object using a process similar to ECMA specifications | ||
* (portions of SetRealmGlobalObject and SetDefaultGlobalBindings). | ||
* `newGlobalPropertyNames` should be either `initialGlobalPropertyNames` or | ||
* `sharedGlobalPropertyNames`. | ||
* setGlobalObjectConstantProperties() | ||
* Initializes a new global object using a process similar to ECMA specifications | ||
* (SetDefaultGlobalBindings). This process is split between this function and | ||
* `setGlobalObjectMutableProperties`. | ||
* | ||
* @param {Object} globalObject | ||
* @param {Object} intrinsics | ||
* @param {Object} newGlobalPropertyNames | ||
* @param {Function} makeCompartmentConstructor | ||
* @param {Object} compartmentPrototype | ||
* @param {Object} [options] | ||
* @param {Array<Transform>} [options.globalTransforms] | ||
* @param {(Object) => void} [options.markVirtualizedNativeFunction] | ||
*/ | ||
export const initGlobalObject = ( | ||
globalObject, | ||
intrinsics, | ||
newGlobalPropertyNames, | ||
makeCompartmentConstructor, | ||
compartmentPrototype, | ||
{ globalTransforms, markVirtualizedNativeFunction }, | ||
) => { | ||
export const setGlobalObjectConstantProperties = globalObject => { | ||
for (const [name, constant] of entries(constantProperties)) { | ||
@@ -39,3 +24,29 @@ defineProperty(globalObject, name, { | ||
} | ||
}; | ||
/** | ||
* setGlobalObjectMutableProperties() | ||
* Create new global object using a process similar to ECMA specifications | ||
* (portions of SetRealmGlobalObject and SetDefaultGlobalBindings). | ||
* `newGlobalPropertyNames` should be either `initialGlobalPropertyNames` or | ||
* `sharedGlobalPropertyNames`. | ||
* | ||
* @param {Object} globalObject | ||
* @param {Object} param1 | ||
* @param {Object} param1.intrinsics | ||
* @param {Object} param1.newGlobalPropertyNames | ||
* @param {Function} param1.makeCompartmentConstructor | ||
* @param {(string, Object?) => any} param1.safeEvaluate | ||
* @param {(Object) => void} param1.markVirtualizedNativeFunction | ||
*/ | ||
export const setGlobalObjectMutableProperties = ( | ||
globalObject, | ||
{ | ||
intrinsics, | ||
newGlobalPropertyNames, | ||
makeCompartmentConstructor, | ||
safeEvaluate, | ||
markVirtualizedNativeFunction, | ||
}, | ||
) => { | ||
for (const [name, intrinsicName] of entries(universalPropertyNames)) { | ||
@@ -65,8 +76,4 @@ if (objectHasOwnProperty(intrinsics, intrinsicName)) { | ||
globalThis: globalObject, | ||
eval: makeEvalFunction(globalObject, { | ||
globalTransforms, | ||
}), | ||
Function: makeFunctionConstructor(globalObject, { | ||
globalTransforms, | ||
}), | ||
eval: makeEvalFunction(safeEvaluate), | ||
Function: makeFunctionConstructor(safeEvaluate), | ||
}; | ||
@@ -73,0 +80,0 @@ |
@@ -27,3 +27,7 @@ // Copyright (C) 2018 Agoric | ||
import tameLocaleMethods from './tame-locale-methods.js'; | ||
import { initGlobalObject } from './global-object.js'; | ||
import { | ||
setGlobalObjectConstantProperties, | ||
setGlobalObjectMutableProperties, | ||
} from './global-object.js'; | ||
import { makeSafeEvaluator } from './make-safe-evaluator.js'; | ||
import { initialGlobalPropertyNames } from './whitelist.js'; | ||
@@ -293,12 +297,14 @@ import { tameFunctionToString } from './tame-function-tostring.js'; | ||
// start compartment, from the intrinsics. | ||
initGlobalObject( | ||
globalThis, | ||
setGlobalObjectConstantProperties(globalThis); | ||
const { safeEvaluate } = makeSafeEvaluator({ globalObject: globalThis }); | ||
setGlobalObjectMutableProperties(globalThis, { | ||
intrinsics, | ||
initialGlobalPropertyNames, | ||
newGlobalPropertyNames: initialGlobalPropertyNames, | ||
makeCompartmentConstructor, | ||
compartmentPrototype, | ||
{ | ||
markVirtualizedNativeFunction, | ||
}, | ||
); | ||
safeEvaluate, | ||
markVirtualizedNativeFunction, | ||
}); | ||
@@ -305,0 +311,0 @@ /** |
@@ -1,9 +0,7 @@ | ||
import { performEval } from './evaluate.js'; | ||
/* | ||
* makeEvalFunction() | ||
* A safe version of the native eval function which relies on | ||
* the safety of performEval for confinement. | ||
* the safety of safeEvaluate for confinement. | ||
*/ | ||
export const makeEvalFunction = (globalObject, options = {}) => { | ||
export const makeEvalFunction = safeEvaluate => { | ||
// We use the the concise method syntax to create an eval without a | ||
@@ -22,3 +20,3 @@ // [[Construct]] behavior (such that the invocation "new eval()" throws | ||
} | ||
return performEval(source, globalObject, {}, options); | ||
return safeEvaluate(source); | ||
}, | ||
@@ -25,0 +23,0 @@ }.eval; |
@@ -8,3 +8,2 @@ import { | ||
} from './commons.js'; | ||
import { performEval } from './evaluate.js'; | ||
import { assert } from './error/assert.js'; | ||
@@ -15,5 +14,5 @@ | ||
* A safe version of the native Function which relies on | ||
* the safety of performEval for confinement. | ||
* the safety of safeEvaluate for confinement. | ||
*/ | ||
export const makeFunctionConstructor = (globaObject, options = {}) => { | ||
export const makeFunctionConstructor = safeEvaluate => { | ||
// Define an unused parameter to ensure Function.length === 1 | ||
@@ -58,3 +57,3 @@ const newFunction = function Function(_body) { | ||
const src = `(function anonymous(${parameters}\n) {\n${bodyText}\n})`; | ||
return performEval(src, globaObject, {}, options); | ||
return safeEvaluate(src); | ||
}; | ||
@@ -61,0 +60,0 @@ |
@@ -42,3 +42,3 @@ import { | ||
* ScopeHandler manages a Proxy which serves as the global scope for the | ||
* performEval operation (the Proxy is the argument of a 'with' binding). | ||
* safeEvaluate operation (the Proxy is the argument of a 'with' binding). | ||
* As described in createSafeEvaluator(), it has several functions: | ||
@@ -156,5 +156,16 @@ * - allow the very first (and only the very first) use of 'eval' to map to | ||
// a property to globalObject instead of throwing a ReferenceError. | ||
// !!!!! WARNING: DANGER ZONE !!!!!! | ||
// The order of the conditions in the `||` expression below is of the | ||
// utmost importance. Under no circumstances should `eval` be checked | ||
// after `globalObject`. The prototype of the global object is under | ||
// full control of user code and may be replaced by a proxy with a | ||
// `has` trap. If we allow that trap to trigger while the | ||
// `allowNextEvalToBeUnsafe` flag is down, it could allow user code | ||
// to get a hold of `FERAL_EVAL`, resulting in a complete escape of | ||
// the compartment. | ||
// !!!!! WARNING: DANGER ZONE !!!!!! | ||
return ( | ||
sloppyGlobalsMode || | ||
prop === 'eval' || | ||
(allowNextEvalToBeUnsafe && prop === 'eval') || | ||
prop in localObject || | ||
@@ -161,0 +172,0 @@ prop in globalObject || |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2858299
58981