Skip to content

Commit a1bc9ee

Browse files
authored
feat(ses): Synchronize error stack repair mechanism (#3004)
Refs: #2990 ## Description This change synchronizes the error repair mechanism in SES with changes made in #2990 to pass-style to gratutiously improve the resilience of that mechanism in the face of scripts that ran before SES. ### Security Considerations This reduces SES vulnerability to corruption from code that runs before SES. Replacing the TypeError constructor cannot confuse SES with regard to whether the platform produces type errors with own stack properties. ### Scaling Considerations None. ### Documentation Considerations None. ### Testing Considerations We cover versions of Node.js with and without own stack properties in CI and existing tests should be sufficient. ### Compatibility Considerations Programs that previously deceived SES may now fail when initializing SES. We consider such programs compromised and that incompatibility is a feature. ### Upgrade Considerations None.
2 parents a5a0f5c + 350891c commit a1bc9ee

File tree

2 files changed

+40
-9
lines changed

2 files changed

+40
-9
lines changed

packages/pass-style/src/error.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ const hardenIsFake = () => {
2828
return desc?.writable === true;
2929
};
3030

31+
// The error repair mechanism is very similar to code in ses/src/commons.js
32+
// and these implementations should be kept in sync.
33+
3134
/**
3235
* Pass-style must defend its own integrity under a number of configurations.
3336
*

packages/ses/src/commons.js

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -349,12 +349,39 @@ export const noEvalEvaluate = () => {
349349

350350
// ////////////////// FERAL_STACK_GETTER FERAL_STACK_SETTER ////////////////////
351351

352-
const er1StackDesc = getOwnPropertyDescriptor(Error('er1'), 'stack');
353-
const er2StackDesc = getOwnPropertyDescriptor(TypeError('er2'), 'stack');
352+
// The error repair mechanism is very similar to code in
353+
// pass-style/src/error.js and these implementations should be kept in sync.
354+
355+
/**
356+
* We gratuitiously construct a TypeError instance using syntax in order to
357+
* obviate the possibility that code that ran before SES (for which we are
358+
* irreducable vulnerable) may have replaced the global TypeError constructor.
359+
* We treat the nature of this error instance as the source of truth for the
360+
* nature of runtime constructed errors on the platform, particularly whether
361+
* such errors will have an own "stack" property with getters and setters.
362+
* At time of writing (2025) we know of no comparable mechanism for obtaining a
363+
* host-generated base Error instance, but we corroborate the nature of the
364+
* global Error constructor's instances and refuse to initialize SES in an
365+
* environment where the syntactic TypeError and global Error produce
366+
* inconsistent "stack" properties.
367+
* @returns {TypeError}
368+
*/
369+
const makeTypeError = () => {
370+
try {
371+
// @ts-expect-error deliberate TypeError
372+
null.null;
373+
throw TypeError('obligatory'); // To convince the type flow inferrence.
374+
} catch (error) {
375+
return error;
376+
}
377+
};
378+
379+
const errorStackDesc = getOwnPropertyDescriptor(Error('obligatory'), 'stack');
380+
const typeErrorStackDesc = getOwnPropertyDescriptor(makeTypeError(), 'stack');
354381

355382
let feralStackGetter;
356383
let feralStackSetter;
357-
if (er1StackDesc && er2StackDesc && er1StackDesc.get) {
384+
if (typeErrorStackDesc && typeErrorStackDesc.get) {
358385
// We should only encounter this case on v8 because of its problematic
359386
// error own stack accessor behavior.
360387
// Note that FF/SpiderMonkey, Moddable/XS, and the error stack proposal
@@ -365,16 +392,17 @@ if (er1StackDesc && er2StackDesc && er1StackDesc.get) {
365392
// accessor property, but within the same realm, all these accessor
366393
// properties have the same getter and have the same setter.
367394
// This is therefore the case that we repair.
368-
typeof er1StackDesc.get === 'function' &&
369-
er1StackDesc.get === er2StackDesc.get &&
370-
typeof er1StackDesc.set === 'function' &&
371-
er1StackDesc.set === er2StackDesc.set
395+
errorStackDesc &&
396+
typeof typeErrorStackDesc.get === 'function' &&
397+
typeErrorStackDesc.get === errorStackDesc.get &&
398+
typeof typeErrorStackDesc.set === 'function' &&
399+
typeErrorStackDesc.set === errorStackDesc.set
372400
) {
373401
// Otherwise, we have own stack accessor properties that are outside
374402
// our expectations, that therefore need to be understood better
375403
// before we know how to repair them.
376-
feralStackGetter = freeze(er1StackDesc.get);
377-
feralStackSetter = freeze(er1StackDesc.set);
404+
feralStackGetter = freeze(typeErrorStackDesc.get);
405+
feralStackSetter = freeze(typeErrorStackDesc.set);
378406
} else {
379407
// See https://github.com/endojs/endo/blob/master/packages/ses/error-codes/SES_UNEXPECTED_ERROR_OWN_STACK_ACCESSOR.md
380408
throw TypeError(

0 commit comments

Comments
 (0)