Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add regenerator runtime taming #2383

Merged
merged 10 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/ses/src/commons.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ export const { prototype: generatorPrototype } = getPrototypeOf(
// eslint-disable-next-line no-empty-function, func-names
function* () {},
);
export const iteratorPrototype = getPrototypeOf(
// eslint-disable-next-line @endo/no-polymorphic-call
getPrototypeOf(arrayPrototype.values()),
Jack-Works marked this conversation as resolved.
Show resolved Hide resolved
);

export const typedArrayPrototype = getPrototypeOf(Uint8Array.prototype);

Expand Down
12 changes: 12 additions & 0 deletions packages/ses/src/lockdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { makeCompartmentConstructor } from './compartment.js';
import { tameHarden } from './tame-harden.js';
import { tameSymbolConstructor } from './tame-symbol-constructor.js';
import { tameFauxDataProperties } from './tame-faux-data-properties.js';
import { tameRegeneratorRuntime } from './tame-regenerator-runtime.js';

/** @import {LockdownOptions} from '../types.js' */

Expand Down Expand Up @@ -180,12 +181,20 @@ export const repairIntrinsics = (options = {}) => {
/** @param {string} debugName */
debugName => debugName !== '',
),
legacyRegeneratorRuntimeTaming = getenv(
'LOCKDOWN_LEGACY_REGENERATOR_RUNTIME_TAMING',
'safe',
),
__hardenTaming__ = getenv('LOCKDOWN_HARDEN_TAMING', 'safe'),
dateTaming = 'safe', // deprecated
mathTaming = 'safe', // deprecated
...extraOptions
} = options;

legacyRegeneratorRuntimeTaming === 'safe' ||
legacyRegeneratorRuntimeTaming === 'unsafe-ignore' ||
Fail`lockdown(): non supported option legacyRegeneratorRuntimeTaming: ${q(legacyRegeneratorRuntimeTaming)}`;

evalTaming === 'unsafeEval' ||
evalTaming === 'safeEval' ||
evalTaming === 'noEval' ||
Expand Down Expand Up @@ -412,6 +421,9 @@ export const repairIntrinsics = (options = {}) => {
// clear yet which is better.
// @ts-ignore enablePropertyOverrides does its own input validation
enablePropertyOverrides(intrinsics, overrideTaming, overrideDebug);
if (legacyRegeneratorRuntimeTaming === 'unsafe-ignore') {
tameRegeneratorRuntime();
}

// Finally register and optionally freeze all the intrinsics. This
// must be the operation that modifies the intrinsics.
Expand Down
29 changes: 29 additions & 0 deletions packages/ses/src/tame-regenerator-runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
defineProperty,
iteratorPrototype,
iteratorSymbol,
objectHasOwnProperty,
} from './commons.js';

export const tameRegeneratorRuntime = () => {
const iter = iteratorPrototype[iteratorSymbol];
defineProperty(iteratorPrototype, iteratorSymbol, {
configurable: true,
get() {
return iter;
},
set(value) {
// ignore the assignment on IteratorPrototype
if (this === iteratorPrototype) return;
if (objectHasOwnProperty(this, iteratorSymbol)) {
this[iteratorSymbol] = value;
}
defineProperty(this, iteratorSymbol, {
value,
writable: true,
enumerable: true,
configurable: true,
});
},
});
};
45 changes: 45 additions & 0 deletions packages/ses/test/tame-legacy-regenerator-helper.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import test from 'ava';
import '../index.js';

lockdown({ legacyRegeneratorRuntimeTaming: 'unsafe-ignore' });

test('lockdown Iterator.prototype[@@iterator] is tamed', t => {
const IteratorProto = Object.getPrototypeOf(
Object.getPrototypeOf([].values()),
);
const desc = Object.getOwnPropertyDescriptor(IteratorProto, Symbol.iterator);
if (!desc || !desc.get || !desc.set) throw new Error('unreachable');
t.is(desc.configurable || desc.enumerable, false);
t.is(desc.value, undefined);

const { get } = desc;

const child = Object.create(IteratorProto);
child[Symbol.iterator] = 'foo'; // override test
t.is(child[Symbol.iterator], 'foo');

const native = get();
Jack-Works marked this conversation as resolved.
Show resolved Hide resolved
IteratorProto[Symbol.iterator] = function f() {
return this;
};
t.is(get(), native);
t.is(
Function.prototype.toString.call(native),
'function [Symbol.iterator]() { [native code] }',
);
Jack-Works marked this conversation as resolved.
Show resolved Hide resolved
});

test('lockdown Iterator.prototype[@@iterator] is tamed in Compartments', t => {
const c = new Compartment();
const compartmentIteratorProto = Object.getPrototypeOf(
Object.getPrototypeOf(c.globalThis.Array().values()),
);
t.is(
compartmentIteratorProto,
Object.getPrototypeOf(Object.getPrototypeOf([].values())),
);
const parentFunction = /** @type {any} */ (
Object.getOwnPropertyDescriptor(compartmentIteratorProto, Symbol.iterator)
).get.constructor;
t.throws(() => Reflect.construct(parentFunction, ['return globalThis']));
});
6 changes: 6 additions & 0 deletions packages/ses/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ export interface RepairOptions {
overrideTaming?: 'moderate' | 'min' | 'severe';
overrideDebug?: Array<string>;
domainTaming?: 'safe' | 'unsafe';
/**
* safe (default): do nothing.
*
* unsafe-ignore: make %IteratorPrototype%[@@iterator] to a funky accessor which ignores all assignments.
*/
legacyRegeneratorRuntimeTaming?: 'safe' | 'unsafe-ignore';
__hardenTaming__?: 'safe' | 'unsafe';
}

Expand Down
Loading