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

Compartmentalizing non-ECMA host behaviour #40

Closed
Jamesernator opened this issue May 10, 2021 · 1 comment
Closed

Compartmentalizing non-ECMA host behaviour #40

Jamesernator opened this issue May 10, 2021 · 1 comment
Assignees

Comments

@Jamesernator
Copy link

Jamesernator commented May 10, 2021

This is an issue raised in response to this issue on Realms particularly with regards to this suggestion.

Summarizing, there's quite a bit of interest in exposing non-ecma globals in realms, current suggestions restrict things to safe deterministic APIs, however some use cases may want to restrict further, other use cases might actually want new copies of UNSAFE APIs.

As a couple of example use cases:

  • SES and similar use cases must remove all non-deterministic APIs. Depending on specifics, they may even want to hide determinstic ones that expose information about the environment
  • Testing use cases (e.g. test runners) probably want hosts to be able to insert many APIs including potentially unsafe ones such as Worker (e.g. new Worker("some-url") triggers a fetch, enables spectre attacks, etc), having to shim every non-deterministic global, and worse keep those shims up to date is a highly cumbersome task that we'd probably want to avoid (especially when hosts can install it directly)
  • Some plugin APIs may wish to expose some degree of unsafe surface without exposing all of it, e.g. perhaps exposing indexedDB but not exposing fetch
  • ...and so on

Now these problems could be somewhat solved at the Realms level by filtering globals, but in practice some APIs have mixed levels of safety, for example Worker triggers a fetch to load the script/module, but for the most part it's relatively safe. Even ECMAScript itself has mixed safety APIs, for example Date with Date.now().

As such I'd like to propose a way for allowing hosts to compartmentalize their own behaviour (ideally with a note that any APIs exposed as a global on new Realms must be sufficiently compartmentalized). In particular I would like to suggest we allow hosts to expose additional hooks for any non-deterministic behaviour just as ECMAScript does for it's own sources of non-determinism.

As an example of what this could look like in a API surface:

const compartment = new Compartment({}, {}, {
  randomHook: () => 0.5, // ECMAScript hook
  fetchWorkerSourceHook: async (url) => { // Host provided hook
    if (isSafeURL(url)) {
      return await fetch(url);
    }
    throw new Error("UNSAFE");
  },
});

compartment.evaluate(`
  const worker = new Worker("./myPluginWorker.js");
`);

Now in practice a lot of host APIs are probably not ready for arbitrary user code to run in various steps, for example it's likely many parts of fetch run on another thread, as such not all behaviours might be compartmentilizable. As such we should allow hosts to provide simplified hooks that simply disable the ability to use the API altogether (perhaps by simply forwarding a few arguments to the hook for validation), for example:

const compartment = new Compartment({}, {}, {
  preConstructWorkerHook: (url) => {
    if (!isSafeURL(url)) {
      throw new Error("UNSAFE");
    }
    // Note we can't configure fetch steps, only prevent the worker triggering a fetch
    // these sort've hooks should be a lot simpler for hosts to expose than
    // providing completely hookable behaviour, for example in Worker this could be implemented
    // roughly like just adding a simple call to the hook immediately on construction:
    // class Worker extends EventTarget {
    //   constructor(workerSrc) {
    //     // Ensure it's safe to proceed with constructing this worker
    //     [[Realm]].preConstructWorkerHook(workerSrc);
    //     // Host behaviour can run as normal after simple pre check, arbitrary JS
    //     // code probably isn't insertable in here as much of the setup code is
    //     // probably running on another thread
    //     InstantiateWorkerAsPerNormal();
    //   }
    // }
  },
});

Now to ensure safety of things like SES, we need to assert that hosts must hook any unsafe behaviour for any globals exposed, within something like SES, we can simply provide safe versions of host APIs, for unrecognised APIs we could provide a proxy e.g.:

const compartment = new Compartment({}, {}, new Proxy({
  randomHook: () => 0.5,
  preWorkerConstructHook: (url) => {
    if (!isSafeURL(url)) { throw new Error("Not allowed") };
  },
}, {
  get(target, propName, receiver) {
    return Reflect.get(target, propName, receiver) ?? () => {
       throw new Error(`Unrecognized unsafe API used: ${ propName }`);
    });
  }
});

compartment.evaluate(`const worker = new Worker("./foo.js"); // OK`);
compartment.evaluate(`const req = indexedDB.open("some-store")`); // BLOCKED

The above could even be exposed a factory method on Compartments themselves, so that we don't need to manually create proxies e.g.:

const compartment = Compartment.safe({}, {}, {
  randomHook: () => 0.5,
  preWorkerConstructHook: (url) => {
    if (!isSafeURL(url)) { throw new Error("Not allowed") };
  },
});

compartment.evaluate(`const worker = new Worker("./foo.js");`); // OK (if isSafeURL(url))
compartment.evaluate(`indexedDB.open("some-store")`); // BLOCKED, unimplemented hooks automatically throw
@Jamesernator
Copy link
Author

With the latest revisions to the compartments proposal, this issue no longer applies as hooks for non-module loading behaviour are no longer part of the compartments proposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants