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 content script world isolation #17032

Conversation

samuelmaddock
Copy link
Member

@samuelmaddock samuelmaddock commented Feb 18, 2019

Description of Change

Executes Chrome extension content scripts in an isolated world.

When an isolated world script context is created for a content script, content_script_bundle is injected. The bundle contains code to initialize Chrome APIs and Electron setup.

Checklist

Release Notes

Notes: Added world isolation to Chrome extension content scripts.

@samuelmaddock samuelmaddock requested review from zcbenz, miniak and a team February 18, 2019 21:11
Copy link
Member

@MarshallOfSound MarshallOfSound left a comment

Choose a reason for hiding this comment

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

The implementation details of the approach seem OK, I have some questions RE the approach in general. Basically where the isolation boundary we are trying to draw is. See comments below.

BUILD.gn Show resolved Hide resolved
atom/renderer/atom_render_frame_observer.cc Outdated Show resolved Hide resolved
// Numbers for isolated worlds for extensions are set in
// lib/renderer/content-script-injector.js, and are greater than or equal to
// this number.
ISOLATED_WORLD_EXTENSIONS = 1000,
Copy link
Member

Choose a reason for hiding this comment

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

This will have to be explicitly documented in the executeJavaScriptInIsolatedWorld API docs that these numbers are "reserved" / "used" by chrome extensions

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. Since users may have use cases for a large range, perhaps the extensions range should be moved up as well (1000 => 100000?).

Copy link
Member Author

Choose a reason for hiding this comment

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

I've defined the range between [1 << 20, 1 << 29) with more info in the commit message of 8d02015

const chromeAPI = require('@electron/internal/renderer/chrome-api')

// Await initialization with extension ID
window.__init = (extensionId) => {
Copy link
Member

Choose a reason for hiding this comment

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

I feel as though there should be a better way of doing this, it also implies that this initalizer and the users chrome extension aren't isolated?

I may be missing where the isolation boundary is that this PR is trying to create.

E.g.

Chrome Extension |||| Electron Code but this PR appears to be more Chrome Extension + Electron Code ||| User Code which implies a high level of trust in the Chrome Extension code? Maybe I just need some clarification on that

Copy link
Member Author

Choose a reason for hiding this comment

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

The __init function is coupled with Electron code in the content-scripts-injector.ts script. Prior to running a Chrome extension's content script, it will call the __init function with the associated extensionId.

const runContentScript = function (this: any, extensionId: string, url: string, code: string) {
  const sources = [
    // initialize Chrome API in isolated world
    { code: `typeof __init === 'function' && __init('${extensionId}')` },
    // then run content script in isolated world
    { code, url }
  ]

  // ...

  webFrame.executeJavaScriptInIsolatedWorld(worldId, sources)
}

Since __init is called first from Electron code, user code won't be able to access it after it has been removed. The exception would be if the user creates an isolated world within the reserved extension ID range, outside of the content script injector.

This still feels dirty to me, but I'm not yet sure how to initialize the content_script_bundle with the extension ID ahead of time.

Copy link
Member Author

Choose a reason for hiding this comment

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

@MarshallOfSound I found another way to pass the extension ID to the content_script_bundle script using a v8 private. fb42cd0

Now there is a clear isolation between user code and Electron code for initializing the content script isolated world.

@MarshallOfSound
Copy link
Member

I can't seem to tag wg-security in the reviewers list so just cc'ing here

cc @electron/wg-security

@samuelmaddock samuelmaddock changed the title [wip] Add content script world isolation [wip] feat: Add content script world isolation Feb 19, 2019
@samuelmaddock samuelmaddock force-pushed the chrome-extension/isolated-world-staging branch from ea57e9a to 2736e61 Compare February 20, 2019 18:16
@samuelmaddock samuelmaddock requested a review from a team February 20, 2019 21:13
@electron electron deleted a comment Feb 24, 2019
@samuelmaddock samuelmaddock changed the title [wip] feat: Add content script world isolation feat: Add content script world isolation Feb 27, 2019
Copy link
Member

@MarshallOfSound MarshallOfSound left a comment

Choose a reason for hiding this comment

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

This all seems good, there's a few TODO's here but they're all things we didn't have before and are good to track 👍

Would like sign-off from someone else in @electron/wg-security that this is 🆗

@samuelmaddock samuelmaddock force-pushed the chrome-extension/isolated-world-staging branch from fb42cd0 to cf8cdcd Compare March 8, 2019 00:10
1 << 20 was chosen as it provides a sufficiently large range of IDs for extensions, but also provides a large enough buffer for any user worlds in [1000, 1 << 20).

Ultimately this range can be changed if any user application raises it as an issue.
This now avoids a script wrapper to inject the style sheet. This closely matches the code used by chromium in `ScriptInjection::InjectCss`.
@samuelmaddock samuelmaddock force-pushed the chrome-extension/isolated-world-staging branch from cf8cdcd to 882adc7 Compare March 8, 2019 01:22
@MarshallOfSound MarshallOfSound requested a review from a team March 8, 2019 23:54
@MarshallOfSound MarshallOfSound merged commit f943db7 into electron:master Mar 11, 2019
@release-clerk
Copy link

release-clerk bot commented Mar 11, 2019

Release Notes Persisted

Added world isolation to Chrome extension content scripts.

@samuelmaddock samuelmaddock deleted the chrome-extension/isolated-world-staging branch March 12, 2019 03:03
kiku-jw pushed a commit to kiku-jw/electron that referenced this pull request May 16, 2019
* Execute content script in isolated world

* Inject script into newly created extension worlds

* Create new content_script_bundle for extension scripts

* Initialize chrome API in content script bundle

* Define Chrome extension isolated world ID range

1 << 20 was chosen as it provides a sufficiently large range of IDs for extensions, but also provides a large enough buffer for any user worlds in [1000, 1 << 20).

Ultimately this range can be changed if any user application raises it as an issue.

* Insert content script CSS into document

This now avoids a script wrapper to inject the style sheet. This closely matches the code used by chromium in `ScriptInjection::InjectCss`.

* Pass extension ID to isolated world via v8 private
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

Successfully merging this pull request may close these issues.

None yet

3 participants