Enable sandbox and contextIsolation for process explorer when running with --__sandbox (#102924)

* sandbox - allow to enable sandbox and contextIsolation for process explorer

* fix asar lookup
This commit is contained in:
Benjamin Pasero 2020-07-20 10:30:20 +02:00 committed by GitHub
parent 040b60f4d1
commit e5b3ff76ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 202 additions and 84 deletions

View file

@ -14,7 +14,7 @@ const nlsConfig = bootstrap.setupNLS();
// Bootstrap: Loader
loader.config({
baseUrl: bootstrap.uriFromPath(__dirname),
baseUrl: bootstrap.fileUriFromPath(__dirname),
catchError: true,
nodeRequire: require,
nodeMain: __filename,

View file

@ -21,8 +21,9 @@
globalThis.MonacoBootstrapWindow = factory();
}
}(this, function () {
const path = require.__$__nodeRequire('path');
const bootstrap = globalThis.MonacoBootstrap;
const preloadGlobals = globals();
const sandbox = preloadGlobals.context.sandbox;
const safeProcess = sandbox ? preloadGlobals.process : process;
/**
* @param {string[]} modulePaths
@ -43,29 +44,33 @@
const configuration = JSON.parse(args['config'] || '{}') || {};
// Error handler
process.on('uncaughtException', function (error) {
safeProcess.on('uncaughtException', function (error) {
onUnexpectedError(error, enableDeveloperTools);
});
// Developer tools
const enableDeveloperTools = (process.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath;
const enableDeveloperTools = (safeProcess.env['VSCODE_DEV'] || !!configuration.extensionDevelopmentPath) && !configuration.extensionTestsPath;
let developerToolsUnbind;
if (enableDeveloperTools || (options && options.forceEnableDeveloperKeybindings)) {
developerToolsUnbind = registerDeveloperKeybindings(options && options.disallowReloadKeybinding);
}
// Correctly inherit the parent's environment
Object.assign(process.env, configuration.userEnv);
// Correctly inherit the parent's environment (TODO@sandbox non-sandboxed only)
if (!sandbox) {
Object.assign(safeProcess.env, configuration.userEnv);
}
// Enable ASAR support
bootstrap.enableASARSupport(path.join(configuration.appRoot, 'node_modules'));
// Enable ASAR support (TODO@sandbox non-sandboxed only)
if (!sandbox) {
globalThis.MonacoBootstrap.enableASARSupport(configuration.appRoot);
}
if (options && typeof options.canModifyDOM === 'function') {
options.canModifyDOM(configuration);
}
// Get the nls configuration into the process.env as early as possible.
const nlsConfig = bootstrap.setupNLS();
// Get the nls configuration into the process.env as early as possible (TODO@sandbox non-sandboxed only)
const nlsConfig = sandbox ? { availableLanguages: {} } : globalThis.MonacoBootstrap.setupNLS();
let locale = nlsConfig.availableLanguages['*'] || 'en';
if (locale === 'zh-tw') {
@ -76,16 +81,20 @@
window.document.documentElement.setAttribute('lang', locale);
// do not advertise AMD to avoid confusing UMD modules loaded with nodejs
window['define'] = undefined;
// do not advertise AMD to avoid confusing UMD modules loaded with nodejs (TODO@sandbox non-sandboxed only)
if (!sandbox) {
window['define'] = undefined;
}
// replace the patched electron fs with the original node fs for all AMD code
require.define('fs', ['original-fs'], function (originalFS) { return originalFS; });
// replace the patched electron fs with the original node fs for all AMD code (TODO@sandbox non-sandboxed only)
if (!sandbox) {
require.define('fs', ['original-fs'], function (originalFS) { return originalFS; });
}
window['MonacoEnvironment'] = {};
const loaderConfig = {
baseUrl: `${bootstrap.uriFromPath(configuration.appRoot)}/out`,
baseUrl: `${uriFromPath(configuration.appRoot)}/out`,
'vs/nls': nlsConfig,
amdModulesPattern: /^vs\//,
};
@ -150,7 +159,7 @@
* @returns {() => void}
*/
function registerDeveloperKeybindings(disallowReloadKeybinding) {
const ipcRenderer = globals().ipcRenderer;
const ipcRenderer = preloadGlobals.ipcRenderer;
const extractKey = function (e) {
return [
@ -163,9 +172,9 @@
};
// Devtools & reload support
const TOGGLE_DEV_TOOLS_KB = (process.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I
const TOGGLE_DEV_TOOLS_KB = (safeProcess.platform === 'darwin' ? 'meta-alt-73' : 'ctrl-shift-73'); // mac: Cmd-Alt-I, rest: Ctrl-Shift-I
const TOGGLE_DEV_TOOLS_KB_ALT = '123'; // F12
const RELOAD_KB = (process.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R
const RELOAD_KB = (safeProcess.platform === 'darwin' ? 'meta-82' : 'ctrl-82'); // mac: Cmd-R, rest: Ctrl-R
let listener = function (e) {
const key = extractKey(e);
@ -192,7 +201,7 @@
*/
function onUnexpectedError(error, enableDeveloperTools) {
if (enableDeveloperTools) {
const ipcRenderer = globals().ipcRenderer;
const ipcRenderer = preloadGlobals.ipcRenderer;
ipcRenderer.send('vscode:openDevTools');
}
@ -211,6 +220,31 @@
return window.vscode;
}
/**
* TODO@sandbox this should not use the file:// protocol at all
* and be consolidated with the fileUriFromPath() method in
* bootstrap.js.
*
* @param {string} path
* @returns {string}
*/
function uriFromPath(path) {
let pathName = path.replace(/\\/g, '/');
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
pathName = `/${pathName}`;
}
/** @type {string} */
let uri;
if (safeProcess.platform === 'win32' && pathName.startsWith('//')) { // specially handle Windows UNC paths
uri = encodeURI(`file:${pathName}`);
} else {
uri = encodeURI(`file://${pathName}`);
}
return uri.replace(/#/g, '%23');
}
return {
load,
globals

37
src/bootstrap.js vendored
View file

@ -16,7 +16,11 @@
// Browser
else {
globalThis.MonacoBootstrap = factory();
try {
globalThis.MonacoBootstrap = factory();
} catch (error) {
console.warn(error); // expected when e.g. running with sandbox: true (TODO@sandbox eventually consolidate this)
}
}
}(this, function () {
const Module = require('module');
@ -40,10 +44,10 @@
//#region Add support for using node_modules.asar
/**
* @param {string=} nodeModulesPath
* @param {string} appRoot
*/
function enableASARSupport(nodeModulesPath) {
let NODE_MODULES_PATH = nodeModulesPath;
function enableASARSupport(appRoot) {
let NODE_MODULES_PATH = appRoot ? path.join(appRoot, 'node_modules') : undefined;
if (!NODE_MODULES_PATH) {
NODE_MODULES_PATH = path.join(__dirname, '../node_modules');
} else {
@ -83,7 +87,7 @@
* @param {string} _path
* @returns {string}
*/
function uriFromPath(_path) {
function fileUriFromPath(_path) {
let pathName = path.resolve(_path).replace(/\\/g, '/');
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
pathName = `/${pathName}`;
@ -132,7 +136,7 @@
}
const bundleFile = path.join(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`);
readFile(bundleFile).then(function (content) {
fs.promises.readFile(bundleFile, 'utf8').then(function (content) {
const json = JSON.parse(content);
bundles[bundle] = json;
@ -140,7 +144,7 @@
}).catch((error) => {
try {
if (nlsConfig._corruptedFile) {
writeFile(nlsConfig._corruptedFile, 'corrupted').catch(function (error) { console.error(error); });
fs.promises.writeFile(nlsConfig._corruptedFile, 'corrupted', 'utf8').catch(function (error) { console.error(error); });
}
} finally {
cb(error, undefined);
@ -152,23 +156,6 @@
return nlsConfig;
}
/**
* @param {string} file
* @returns {Promise<string>}
*/
function readFile(file) {
return fs.promises.readFile(file, 'utf8');
}
/**
* @param {string} file
* @param {string} content
* @returns {Promise<void>}
*/
function writeFile(file, content) {
return fs.promises.writeFile(file, content, 'utf8');
}
//#endregion
@ -254,6 +241,6 @@
avoidMonkeyPatchFromAppInsights,
configurePortable,
setupNLS,
uriFromPath
fileUriFromPath
};
}));

View file

@ -7,16 +7,13 @@
(function () {
'use strict';
const { ipcRenderer, webFrame, crashReporter } = require('electron');
const { ipcRenderer, webFrame, crashReporter, contextBridge } = require('electron');
// @ts-ignore
window.vscode = {
const globals = {
/**
* A minimal set of methods exposed from ipcRenderer
* to support communication to electron-main
*
* @type {typeof import('../electron-sandbox/globals').ipcRenderer}
* A minimal set of methods exposed from Electron's `ipcRenderer`
* to support communication to main process.
*/
ipcRenderer: {
@ -25,9 +22,9 @@
* @param {any[]} args
*/
send(channel, ...args) {
validateIPC(channel);
ipcRenderer.send(channel, ...args);
if (validateIPC(channel)) {
ipcRenderer.send(channel, ...args);
}
},
/**
@ -35,9 +32,9 @@
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
*/
on(channel, listener) {
validateIPC(channel);
ipcRenderer.on(channel, listener);
if (validateIPC(channel)) {
ipcRenderer.on(channel, listener);
}
},
/**
@ -45,9 +42,9 @@
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
*/
once(channel, listener) {
validateIPC(channel);
ipcRenderer.once(channel, listener);
if (validateIPC(channel)) {
ipcRenderer.once(channel, listener);
}
},
/**
@ -55,16 +52,14 @@
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
*/
removeListener(channel, listener) {
validateIPC(channel);
ipcRenderer.removeListener(channel, listener);
if (validateIPC(channel)) {
ipcRenderer.removeListener(channel, listener);
}
}
},
/**
* Support for methods of webFrame type.
*
* @type {typeof import('../electron-sandbox/globals').webFrame}
* Support for subset of methods of Electron's `webFrame` type.
*/
webFrame: {
@ -72,14 +67,14 @@
* @param {number} level
*/
setZoomLevel(level) {
webFrame.setZoomLevel(level);
if (typeof level === 'number') {
webFrame.setZoomLevel(level);
}
}
},
/**
* Support for methods of crashReporter type.
*
* @type {typeof import('../electron-sandbox/globals').crashReporter}
* Support for subset of methods of Electron's `crashReporter` type.
*/
crashReporter: {
@ -89,9 +84,53 @@
start(options) {
crashReporter.start(options);
}
},
/**
* Support for a subset of access to node.js global `process`.
*/
process: {
platform: process.platform,
env: process.env,
on:
/**
* @param {string} type
* @param {() => void} callback
*/
function (type, callback) {
if (validateProcessEventType(type)) {
process.on(type, callback);
}
}
},
/**
* Some information about the context we are running in.
*/
context: {
sandbox: process.argv.includes('--enable-sandbox')
}
};
// Use `contextBridge` APIs to expose globals to VSCode
// only if context isolation is enabled, otherwise just
// add to the DOM global.
let useContextBridge = process.argv.includes('--context-isolation');
if (useContextBridge) {
try {
contextBridge.exposeInMainWorld('vscode', globals);
} catch (error) {
console.error(error);
useContextBridge = false;
}
}
if (!useContextBridge) {
// @ts-ignore
window.vscode = globals;
}
//#region Utilities
/**
@ -101,6 +140,20 @@
if (!channel || !channel.startsWith('vscode:')) {
throw new Error(`Unsupported event IPC channel '${channel}'`);
}
return true;
}
/**
* @param {string} type
* @returns {type is 'uncaughtException'}
*/
function validateProcessEventType(type) {
if (type !== 'uncaughtException') {
throw new Error(`Unsupported process event '${type}'`);
}
return true;
}
//#endregion

View file

@ -81,3 +81,30 @@ export const crashReporter = (window as any).vscode.crashReporter as {
*/
start(options: CrashReporterStartOptions): void;
};
export const process = (window as any).vscode.process as {
/**
* The process.platform property returns a string identifying the operating system platform
* on which the Node.js process is running.
*/
platform: 'win32' | 'linux' | 'darwin';
/**
* The process.env property returns an object containing the user environment. See environ(7).
*/
env: { [key: string]: string | undefined };
/**
* A listener on the process. Only a small subset of listener types are allowed.
*/
on: (type: string, callback: Function) => void;
};
export const context = (window as any).vscode.context as {
/**
* Wether the renderer runs with `sandbox` enabled or not.
*/
sandbox: boolean;
};

View file

@ -43,12 +43,12 @@ export class SharedProcess implements ISharedProcess {
backgroundColor: this.themeMainService.getBackgroundColor(),
webPreferences: {
preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath,
images: false,
nodeIntegration: true,
webgl: false,
enableWebSQL: false,
enableRemoteModule: false,
nativeWindowOpen: true,
images: false,
webgl: false,
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
}
});

View file

@ -168,10 +168,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
webPreferences: {
preload: URI.parse(this.doGetPreloadUrl()).fsPath,
nodeIntegration: true,
webviewTag: true,
enableWebSQL: false,
enableRemoteModule: false,
nativeWindowOpen: true,
webviewTag: true,
zoomFactor: zoomLevelToZoomFactor(windowConfig?.zoomLevel)
}
};

View file

@ -275,7 +275,7 @@ class ProcessExplorer {
tableHead.innerHTML = `<tr>
<th scope="col" class="cpu">${localize('cpu', "CPU %")}</th>
<th scope="col" class="memory">${localize('memory', "Memory (MB)")}</th>
<th scope="col" class="pid">${localize('pid', "pid")}</th>
<th scope="col" class="pid">${localize('pid', "PID")}</th>
<th scope="col" class="nameLabel">${localize('name', "Name")}</th>
</tr>`;

View file

@ -70,12 +70,13 @@ export interface ParsedArgs {
'file-chmod'?: boolean;
'driver'?: string;
'driver-verbose'?: boolean;
remote?: string;
'remote'?: string;
'disable-user-env-probe'?: boolean;
'force'?: boolean;
'do-not-sync'?: boolean;
'force-user-env'?: boolean;
'sync'?: 'on' | 'off';
'__sandbox'?: boolean;
// chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches
'no-proxy-server'?: boolean;
@ -195,6 +196,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'trace-options': { type: 'string' },
'force-user-env': { type: 'boolean' },
'open-devtools': { type: 'boolean' },
'__sandbox': { type: 'boolean' },
// chromium flags
'no-proxy-server': { type: 'boolean' },

View file

@ -43,6 +43,8 @@ export interface INativeEnvironmentService extends IEnvironmentService {
driverVerbose: boolean;
disableUpdates: boolean;
sandbox: boolean;
}
export class EnvironmentService implements INativeEnvironmentService {
@ -262,6 +264,8 @@ export class EnvironmentService implements INativeEnvironmentService {
get disableTelemetry(): boolean { return !!this._args['disable-telemetry']; }
get sandbox(): boolean { return !!this._args['__sandbox']; }
constructor(private _args: ParsedArgs, private _execPath: string) {
if (!process.env['VSCODE_LOGS']) {
const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '');

View file

@ -250,11 +250,22 @@ export class IssueMainService implements ICommonIssueService {
title: localize('processExplorer', "Process Explorer"),
webPreferences: {
preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath,
nodeIntegration: true,
enableWebSQL: false,
enableRemoteModule: false,
nativeWindowOpen: true,
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel)
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel),
...this.environmentService.sandbox ?
// Sandbox
{
sandbox: true,
contextIsolation: true
} :
// No Sandbox
{
nodeIntegration: true
}
}
});

View file

@ -112,14 +112,14 @@ app.on('ready', () => {
width: 800,
show: false,
webPreferences: {
backgroundThrottling: false,
nodeIntegration: true,
webSecurity: false,
webviewTag: true,
preload: path.join(__dirname, '..', '..', '..', 'src', 'vs', 'base', 'parts', 'sandbox', 'electron-browser', 'preload.js'), // ensure similar environment as VSCode as tests may depend on this
nodeIntegration: true,
enableWebSQL: false,
enableRemoteModule: false,
nativeWindowOpen: true
nativeWindowOpen: true,
webSecurity: false,
webviewTag: true,
backgroundThrottling: false
}
});

View file

@ -32,7 +32,7 @@ function initLoader(opts) {
nodeRequire: require,
nodeMain: __filename,
catchError: true,
baseUrl: bootstrap.uriFromPath(path.join(__dirname, '../../../src')),
baseUrl: bootstrap.fileUriFromPath(path.join(__dirname, '../../../src')),
paths: {
'vs': `../${outdir}/vs`,
'lib': `../${outdir}/lib`,