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

Lint to disallow SES polymorphic calls #827

Merged
merged 3 commits into from
Aug 28, 2021

Conversation

kumavis
Copy link
Member

@kumavis kumavis commented Jul 9, 2021

[edit kriskowal 2021-07-15]

This adds an eslint plugin that @kumavis authored for us to help us validate that SES shim captures all of the behavior of intrinsics that it needs during initialization in order to provide a faithful emulation of a native SES implementation, to the extent that’s possible and given that SES runs before any intrinsic is altered away from specified behavior.

Then follows changes by me to get SES shim to obey the lint rule.

@erights and I concluded that console and assert should in general be bound late and their methods dispatched dynamically.

Some compartment code moved around to prevent alteration of the Compartment prototype from affecting the behavior of “native” compartment methods.

@kumavis
Copy link
Member Author

kumavis commented Jul 9, 2021

here's one example. should we be concerned that this could be dangerous?
https://github.com/endojs/endo/blob/master/packages/ses/src/scope-constants.js#L155

✖ 2078 problems (0 errors, 2078 warnings)

@kumavis
Copy link
Member Author

kumavis commented Jul 9, 2021

added a "preview" of the call to the lint message

/home/xyz/Development/endo/packages/ses/src/module-instance.js
   17:5   warning  Polymorphic call: "compartmentPrivateFields.get". May be vulnerable to corruption or trap        @endo/no-polymorphic-call
   26:8   warning  Polymorphic call: "Array.isArray". May be vulnerable to corruption or trap                       @endo/no-polymorphic-call
   27:7   warning  Polymorphic call: "staticModuleRecord.exports.some". May be vulnerable to corruption or trap     @endo/no-polymorphic-call
   33:5   warning  Polymorphic call: "staticModuleRecord.exports.forEach". May be vulnerable to corruption or trap  @endo/no-polymorphic-call
   54:9   warning  Polymorphic call: "updaters.push". May be vulnerable to corruption or trap                       @endo/no-polymorphic-call
   68:9   warning  Polymorphic call: "staticModuleRecord.execute". May be vulnerable to corruption or trap          @endo/no-polymorphic-call
   99:29  warning  Polymorphic call: "privateFields.get". May be vulnerable to corruption or trap                   @endo/no-polymorphic-call
  135:3   warning  Polymorphic call: "[[CallExpression]].forEach". May be vulnerable to corruption or trap          @endo/no-polymorphic-call
  180:11  warning  Polymorphic call: "optUpdaters.push". May be vulnerable to corruption or trap                    @endo/no-polymorphic-call
  205:3   warning  Polymorphic call: "[[CallExpression]].forEach". May be vulnerable to corruption or trap          @endo/no-polymorphic-call
  261:11  warning  Polymorphic call: "updaters.push". May be vulnerable to corruption or trap                       @endo/no-polymorphic-call
  318:47  warning  Polymorphic call: "updateRecord.entries". May be vulnerable to corruption or trap                @endo/no-polymorphic-call
  319:24  warning  Polymorphic call: "importedInstances.get". May be vulnerable to corruption or trap               @endo/no-polymorphic-call
  320:7   warning  Polymorphic call: "instance.execute". May be vulnerable to corruption or trap                    @endo/no-polymorphic-call
  322:44  warning  Polymorphic call: "importUpdaters.entries". May be vulnerable to corruption or trap              @endo/no-polymorphic-call
  333:11  warning  Polymorphic call: "exportAlls.includes". May be vulnerable to corruption or trap                 @endo/no-polymorphic-call
  374:5   warning  Polymorphic call: "[[CallExpression]].forEach". May be vulnerable to corruption or trap          @endo/no-polymorphic-call
  374:5   warning  Polymorphic call: "[[CallExpression]].sort". May be vulnerable to corruption or trap             @endo/no-polymorphic-call
  382:20  warning  Polymorphic call: "compartment.evaluate". May be vulnerable to corruption or trap                @endo/no-polymorphic-call

@kriskowal kriskowal self-assigned this Jul 10, 2021
@kriskowal kriskowal force-pushed the eslint-plugin/no-polymorphic-call branch from ab9f4d3 to cfc4709 Compare July 15, 2021 06:33
@kriskowal
Copy link
Member

@kumavis I did a rebase and force-pushed with extra commits to cut the warnings down by half. I’m shooting to have most of them addressed with another commit or so today.

One of the interesting ones is using the compartment API itself as intrinsics, which will take some refactoring.

@kriskowal
Copy link
Member

@kumavis Added commits to eliminate the warnings. This is ready for broader review and I’ll look into rebasing and cleanup tomorrow.

@kriskowal kriskowal requested a review from erights July 16, 2021 04:51
@kriskowal kriskowal changed the title eslint plugin - no polymorphic call mvp Lint to disallow SES polymorphic calls Jul 16, 2021
@kriskowal kriskowal marked this pull request as ready for review July 16, 2021 04:56
let propertyHint
if (object.type === 'Identifier') {
objectHint = object.name
} else if (object.type === 'MemberExpression') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it cover a[b]?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This certainly did produce lint errors for a[b]() syntax.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes same member expression but with computed flag set to true

@kriskowal
Copy link
Member

kriskowal commented Jul 19, 2021 via email

@kriskowal kriskowal force-pushed the eslint-plugin/no-polymorphic-call branch from c58c520 to 82c38bf Compare July 21, 2021 23:36
@@ -0,0 +1,72 @@
/// <reference types="ses">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Compartment.evaluate implementation needed to be extracted into a stand-alone module so it could be used from both compartment-shim.js and module-instance.js without dynamic dispatch. The internals communicate purely through their captured private fields.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I (MarkM) did not review this refactoring.

baseConsole[name](...args);
}
};
return [name, freeze(method)];
});
const filteringConsole = fromEntries(methods);
return freeze(filteringConsole);
return /** @type {VirtualConsole} */ (freeze(filteringConsole));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an unrelated change that was necessary to appease the IDE type-checker, but not our TSC lint CI job.

@@ -18,7 +18,9 @@ if (typeof abandon === 'function') {
/** @param {Error} reason */
raise = reason => {
// Check `console` each time `raise` is called.
// eslint-disable-next-line no-restricted-globals
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erights and I expressly decided that lazy binding console and assert and dynamic dispatch on their methods were the desired, albeit exceptional, behavior. The reason being that SES changes these on lockdown. The machinery to have lockdown incidentally change some shared module state is far too complex to warrant.

continue;
}
intrinsics[namePrototype] = intrinsicPrototype;
const addIntrinsics = newIntrinsics => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change below here is merely changing the intrinsicsCollector to a bag of closure functions instead of using concise methods. They were already used as bare closure functions, but this needed to be more explicit to avoid dynamic dispatch.

This is a case where dynamic dispatch would have been just as safe, but it was as easy to refactor this way and avoid lint-rule comment noise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@kriskowal kriskowal force-pushed the eslint-plugin/no-polymorphic-call branch from 82c38bf to fef1e82 Compare July 21, 2021 23:47
Copy link
Member Author

@kumavis kumavis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created a monster 👹
(looks good)

@erights
Copy link
Contributor

erights commented Aug 24, 2021

I think we made a mistake prior to this PR that should be fixed before this PR: commons.js should not be capturing and reexporting things like Error which, if captured this early, would be the original Error constructor that is supposed to be completely inaccessible after repair, even in the start compartment. The original Error constructor should be completely encapsulated within the repaired Error constructors.

Thus, these repair modules should do their own initial capture of these objects and not reexport them. But commons.js, capturing the originals and reexporting them, keeps them accessible rather than safely encapsulated.

Copy link
Contributor

@erights erights left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At #827 (comment) I write:

I think we made a mistake prior to this PR that should be fixed before this PR: commons.js should not be capturing and reexporting things like Error which, if captured this early, would be the original Error constructor that is supposed to be completely inaccessible after repair, even in the start compartment. The original Error constructor should be completely encapsulated within the repaired Error constructors.

Thus, these repair modules should do their own initial capture of these objects and not reexport them. But commons.js, capturing the originals and reexporting them, keeps them accessible rather than safely encapsulated.

I'm posting that as a "request changes" so it is repaired before this PR proceeds.

@erights
Copy link
Contributor

erights commented Aug 24, 2021

Just eyeballing commons.js quickly, the violations that jump out at me:

  • Error
  • RegExp
  • FERAL_EVAL
  • FERAL_FUNCTION

We should either remove all four from commons.js, or we should follow the FERAL_ALL_CAPS convention for all four. The FERAL_ convention is adequate for the safety issue at stake here. All the code that could import commons.js is in the TCB, so the potential problem is really the reviewability hazard.

Note that WeakMap and WeakSet are not violations, even though @FUDCo virtualizes them at a higher level. Chip does that in a new compartment. The start compartment should continue to have the initial ones, and so commons.js should as well.

The thread above already alerts us to the issue of the non-standard powerful SES-shim globals assert and console.

Upcoming dangerous standard globals for us to eventually incorporate: WeakRef and FinalizationRegistery. For both, the powerful ones should be only in the start compartment. Their shared prototypes should be in the whitelist. And the shared prototype's constructor should point at a safe one. Perhaps the safe one should be inert, following the evaluator pattern, or perhaps it should simply be powerless, i.e., strongly pointing, following the SharedDate pattern.

So, for now, unless I missed something, just exporting the original Error and RegExp as FERAL_ERROR and FERAL_REGEXP looks like an adequate fix.

@kriskowal kriskowal force-pushed the eslint-plugin/no-polymorphic-call branch from fef1e82 to 67d0117 Compare August 28, 2021 00:50
@kriskowal kriskowal changed the base branch from master to kris-freeze-handlers August 28, 2021 00:50
Base automatically changed from kris-freeze-handlers to master August 28, 2021 00:51
@kriskowal kriskowal force-pushed the eslint-plugin/no-polymorphic-call branch from 67d0117 to b1123b9 Compare August 28, 2021 03:38
@kriskowal kriskowal requested a review from erights August 28, 2021 03:39
Copy link
Contributor

@erights erights left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FERAL_ naming only works if it occurs rarely enough to set off alarm bells when you see it. Most of the occurrences here are just throw FERAL_ERROR which probably should have been throw TypeError anyway as that is the more usual choice. With those fixed, the problem is much reduced, and the phrase will continue to alarm.

packages/ses/index.js Outdated Show resolved Hide resolved
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { globalThis, Error, assign } from './src/commons.js';
import { globalThis, FERAL_ERROR, assign } from './src/commons.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See note below

Suggested change
import { globalThis, FERAL_ERROR, assign } from './src/commons.js';
import { globalThis, TypeError, assign } from './src/commons.js';

@@ -0,0 +1,72 @@
/// <reference types="ses">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I (MarkM) did not review this refactoring.

packages/ses/src/compartment-shim.js Outdated Show resolved Hide resolved
@@ -194,7 +196,7 @@ export default function enablePropertyOverrides(
break;
}
default: {
throw new Error(`unrecognized overrideTaming ${overrideTaming}`);
throw new FERAL_ERROR(`unrecognized overrideTaming ${overrideTaming}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeError

@@ -163,7 +163,7 @@ export const instantiate = (
resolvedImports,
);
} else {
throw new Error(
throw new FERAL_ERROR(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeError

console.warn(
`getOwnPropertyDescriptor trap on scopeHandler for ${quotedProp}`,
new Error().stack,
new FERAL_ERROR().stack,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any error would do equally well here.

Suggested change
new FERAL_ERROR().stack,
new TypeError().stack,

@@ -39,7 +40,7 @@ const nonLocaleCompare = tamedMethods.localeCompare;

export default function tameLocaleMethods(intrinsics, localeTaming = 'safe') {
if (localeTaming !== 'safe' && localeTaming !== 'unsafe') {
throw new Error(`unrecognized dateTaming ${localeTaming}`);
throw new FERAL_ERROR(`unrecognized dateTaming ${localeTaming}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeError

Error,
RegExp as OriginalRegExp,
FERAL_ERROR,
FERAL_REG_EXP,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the one occurrence I expected

@@ -114,7 +116,9 @@ export default function whitelistIntrinsics(
}

// We can't clean [[prototype]], therefore abort.
throw new Error(`Unexpected intrinsic ${path}.__proto__ at ${protoName}`);
throw new FERAL_ERROR(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeError

@kriskowal kriskowal force-pushed the eslint-plugin/no-polymorphic-call branch from 7e63539 to 52edbbc Compare August 28, 2021 21:26
@kriskowal kriskowal force-pushed the eslint-plugin/no-polymorphic-call branch from 52edbbc to 4c33308 Compare August 28, 2021 21:40
@kriskowal kriskowal force-pushed the eslint-plugin/no-polymorphic-call branch from 4c33308 to 0c72ad6 Compare August 28, 2021 21:40
@kriskowal kriskowal requested a review from erights August 28, 2021 21:48
@kriskowal
Copy link
Member

All your feedback on minimizing invocations of FERAL_* was solid and I’ve incorporated it all. Up for your review again.

Copy link
Contributor

@erights erights left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

Comment on lines 246 to 247
* isError tests whether an object descends from the intrinsic Error
* constructor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what "descends" means here.

Suggested change
* isError tests whether an object descends from the intrinsic Error
* constructor.
* isError tests whether an object inherits from the intrinsic
* `Error.prototype`.

Comment on lines 249 to 250
* signal for reviewers that we are handling an object with excess authority
* that we are carefully hiding from client code, like stack trace inspection.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* signal for reviewers that we are handling an object with excess authority
* that we are carefully hiding from client code, like stack trace inspection.
* signal for reviewers that we are handling an object with excess authority,
* like stack trace inspection, that we are carefully hiding from client code.

avoids a parsing ambiguity.

@@ -214,21 +226,21 @@ const tagError = (err, optErrorName = err.name) => {
*/
const makeError = (
optDetails = redactedDetails`Assert failed`,
ErrorConstructor = Error,
ErrorConstructor = globalThis.Error,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good

@@ -39,7 +39,7 @@ const nonLocaleCompare = tamedMethods.localeCompare;

export default function tameLocaleMethods(intrinsics, localeTaming = 'safe') {
if (localeTaming !== 'safe' && localeTaming !== 'unsafe') {
throw new Error(`unrecognized dateTaming ${localeTaming}`);
throw new TypeError(`unrecognized dateTaming ${localeTaming}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new TypeError(`unrecognized dateTaming ${localeTaming}`);
throw new TypeError(`unrecognized localeTaming ${localeTaming}`);

I'm sure this was my mistake, but is a good time to fix it.

@@ -39,7 +39,7 @@ const nonLocaleCompare = tamedMethods.localeCompare;

export default function tameLocaleMethods(intrinsics, localeTaming = 'safe') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need a special case for Number.toLocaleString, to avoid the same bug that XS had. Yes?

@kriskowal kriskowal force-pushed the eslint-plugin/no-polymorphic-call branch from 0c72ad6 to 0273165 Compare August 28, 2021 23:11
@kriskowal kriskowal merged commit 3a3afdd into master Aug 28, 2021
@kriskowal kriskowal deleted the eslint-plugin/no-polymorphic-call branch August 28, 2021 23:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Host code in start compartment can corrupt SES between initialization and lockdown
4 participants