mirror of
https://github.com/Microsoft/vscode
synced 2024-09-19 18:48:00 +00:00
Enable sandbox, contextIsolation and vscode-file for process explorer and issue reporter (#111304)
* Enable sandbox for issue reporter and process explorer (fix #101834) * sandbox - enable vscode-file protocol for sandboxed renderers * issues - stop setting nodeCachedDataDir * address feedback and also use vscode-file for shared process if sandbox is on
This commit is contained in:
parent
545332f793
commit
37e9cceddc
9
src/bootstrap-window.js
vendored
9
src/bootstrap-window.js
vendored
|
@ -88,9 +88,14 @@
|
|||
|
||||
window['MonacoEnvironment'] = {};
|
||||
|
||||
const baseUrl = sandbox ?
|
||||
`${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out` :
|
||||
`${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32' })}/out`;
|
||||
|
||||
const loaderConfig = {
|
||||
baseUrl: `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32' })}/out`,
|
||||
'vs/nls': nlsConfig
|
||||
baseUrl,
|
||||
'vs/nls': nlsConfig,
|
||||
preferScriptTags: sandbox
|
||||
};
|
||||
|
||||
// Enable loading of node modules:
|
||||
|
|
|
@ -134,6 +134,15 @@ protocol.registerSchemesAsPrivileged([
|
|||
corsEnabled: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
scheme: 'vscode-file',
|
||||
privileges: {
|
||||
secure: true,
|
||||
standard: true,
|
||||
supportFetchAPI: true,
|
||||
corsEnabled: true
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
// Global app listeners
|
||||
|
|
|
@ -78,6 +78,12 @@ export namespace Schemas {
|
|||
* Scheme used for extension pages
|
||||
*/
|
||||
export const extension = 'extension';
|
||||
|
||||
/**
|
||||
* Scheme used as a replacement of `file` scheme to load
|
||||
* files with our custom protocol handler (desktop only).
|
||||
*/
|
||||
export const vscodeFileResource = 'vscode-file';
|
||||
}
|
||||
|
||||
class RemoteAuthoritiesImpl {
|
||||
|
@ -132,6 +138,8 @@ export const RemoteAuthorities = new RemoteAuthoritiesImpl();
|
|||
|
||||
class FileAccessImpl {
|
||||
|
||||
private readonly FALLBACK_AUTHORITY = 'vscode-app';
|
||||
|
||||
/**
|
||||
* Returns a URI to use in contexts where the browser is responsible
|
||||
* for loading (e.g. fetch()) or when used within the DOM.
|
||||
|
@ -143,13 +151,43 @@ class FileAccessImpl {
|
|||
asBrowserUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
const uri = this.toUri(uriOrModule, moduleIdToUrl);
|
||||
|
||||
// Handle remote URIs via `RemoteAuthorities`
|
||||
if (uri.scheme === Schemas.vscodeRemote) {
|
||||
return RemoteAuthorities.rewrite(uri);
|
||||
}
|
||||
|
||||
// Only convert the URI if we are in a native context and it has `file:` scheme
|
||||
if (platform.isElectronSandboxed && platform.isNative && uri.scheme === Schemas.file) {
|
||||
return this.toCodeFileUri(uri);
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO@bpasero remove me eventually when vscode-file is adopted everywhere
|
||||
*/
|
||||
_asCodeFileUri(uri: URI): URI;
|
||||
_asCodeFileUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI;
|
||||
_asCodeFileUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
const uri = this.toUri(uriOrModule, moduleIdToUrl);
|
||||
|
||||
return this.toCodeFileUri(uri);
|
||||
}
|
||||
|
||||
private toCodeFileUri(uri: URI): URI {
|
||||
return uri.with({
|
||||
scheme: Schemas.vscodeFileResource,
|
||||
// We need to provide an authority here so that it can serve
|
||||
// as origin for network and loading matters in chromium.
|
||||
// If the URI is not coming with an authority already, we
|
||||
// add our own
|
||||
authority: uri.authority || this.FALLBACK_AUTHORITY,
|
||||
query: null,
|
||||
fragment: null
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the `file` URI to use in contexts where node.js
|
||||
* is responsible for loading.
|
||||
|
@ -159,6 +197,19 @@ class FileAccessImpl {
|
|||
asFileUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI {
|
||||
const uri = this.toUri(uriOrModule, moduleIdToUrl);
|
||||
|
||||
// Only convert the URI if it is `vscode-file:` scheme
|
||||
if (uri.scheme === Schemas.vscodeFileResource) {
|
||||
return uri.with({
|
||||
scheme: Schemas.file,
|
||||
// Only preserve the `authority` if it is different from
|
||||
// our fallback authority. This ensures we properly preserve
|
||||
// Windows UNC paths that come with their own authority.
|
||||
authority: uri.authority !== this.FALLBACK_AUTHORITY ? uri.authority : null,
|
||||
query: null,
|
||||
fragment: null
|
||||
});
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ export interface INodeProcess {
|
|||
versions?: {
|
||||
electron?: string;
|
||||
};
|
||||
sandboxed?: boolean; // Electron
|
||||
type?: string;
|
||||
cwd(): string;
|
||||
}
|
||||
|
@ -59,6 +60,7 @@ if (typeof process !== 'undefined') {
|
|||
}
|
||||
|
||||
const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer';
|
||||
export const isElectronSandboxed = isElectronRenderer && nodeProcess?.sandboxed;
|
||||
|
||||
// Web environment
|
||||
if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||
|
|
70
src/vs/base/test/common/network.test.ts
Normal file
70
src/vs/base/test/common/network.test.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FileAccess, Schemas } from 'vs/base/common/network';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { isElectronSandboxed } from 'vs/base/common/platform';
|
||||
|
||||
suite('network', () => {
|
||||
const enableTest = isElectronSandboxed;
|
||||
|
||||
(!enableTest ? test.skip : test)('FileAccess: URI (native)', () => {
|
||||
|
||||
// asCodeUri() & asFileUri(): simple, without authority
|
||||
let originalFileUri = URI.file('network.test.ts');
|
||||
let browserUri = FileAccess.asBrowserUri(originalFileUri);
|
||||
assert.ok(browserUri.authority.length > 0);
|
||||
let fileUri = FileAccess.asFileUri(browserUri);
|
||||
assert.equal(fileUri.authority.length, 0);
|
||||
assert(isEqual(originalFileUri, fileUri));
|
||||
|
||||
// asCodeUri() & asFileUri(): with authority
|
||||
originalFileUri = URI.file('network.test.ts').with({ authority: 'test-authority' });
|
||||
browserUri = FileAccess.asBrowserUri(originalFileUri);
|
||||
assert.equal(browserUri.authority, originalFileUri.authority);
|
||||
fileUri = FileAccess.asFileUri(browserUri);
|
||||
assert(isEqual(originalFileUri, fileUri));
|
||||
});
|
||||
|
||||
(!enableTest ? test.skip : test)('FileAccess: moduleId (native)', () => {
|
||||
const browserUri = FileAccess.asBrowserUri('vs/base/test/node/network.test', require);
|
||||
assert.equal(browserUri.scheme, Schemas.vscodeFileResource);
|
||||
|
||||
const fileUri = FileAccess.asFileUri('vs/base/test/node/network.test', require);
|
||||
assert.equal(fileUri.scheme, Schemas.file);
|
||||
});
|
||||
|
||||
(!enableTest ? test.skip : test)('FileAccess: query and fragment is dropped (native)', () => {
|
||||
let originalFileUri = URI.file('network.test.ts').with({ query: 'foo=bar', fragment: 'something' });
|
||||
let browserUri = FileAccess.asBrowserUri(originalFileUri);
|
||||
assert.equal(browserUri.query, '');
|
||||
assert.equal(browserUri.fragment, '');
|
||||
});
|
||||
|
||||
(!enableTest ? test.skip : test)('FileAccess: query and fragment is kept if URI is already of same scheme (native)', () => {
|
||||
let originalFileUri = URI.file('network.test.ts').with({ query: 'foo=bar', fragment: 'something' });
|
||||
let browserUri = FileAccess.asBrowserUri(originalFileUri.with({ scheme: Schemas.vscodeFileResource }));
|
||||
assert.equal(browserUri.query, 'foo=bar');
|
||||
assert.equal(browserUri.fragment, 'something');
|
||||
|
||||
let fileUri = FileAccess.asFileUri(originalFileUri);
|
||||
assert.equal(fileUri.query, 'foo=bar');
|
||||
assert.equal(fileUri.fragment, 'something');
|
||||
});
|
||||
|
||||
(!enableTest ? test.skip : test)('FileAccess: web', () => {
|
||||
const originalHttpsUri = URI.file('network.test.ts').with({ scheme: 'https' });
|
||||
const browserUri = FileAccess.asBrowserUri(originalHttpsUri);
|
||||
assert.equal(originalHttpsUri.toString(), browserUri.toString());
|
||||
});
|
||||
|
||||
test('FileAccess: remote URIs', () => {
|
||||
const originalRemoteUri = URI.file('network.test.ts').with({ scheme: Schemas.vscodeRemote });
|
||||
const browserUri = FileAccess.asBrowserUri(originalRemoteUri);
|
||||
assert.notEqual(originalRemoteUri.scheme, browserUri.scheme);
|
||||
});
|
||||
});
|
|
@ -35,6 +35,7 @@ import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSe
|
|||
import product from 'vs/platform/product/common/product';
|
||||
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
|
||||
import { ProxyAuthHandler2 } from 'vs/code/electron-main/auth2';
|
||||
import { FileProtocolHandler } from 'vs/code/electron-main/protocol';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
@ -412,6 +413,9 @@ export class CodeApplication extends Disposable {
|
|||
this.logService.error(error);
|
||||
}
|
||||
|
||||
// Setup Protocol Handler
|
||||
const fileProtocolHandler = this._register(this.instantiationService.createInstance(FileProtocolHandler));
|
||||
|
||||
// Create Electron IPC Server
|
||||
const electronIpcServer = new ElectronIPCServer();
|
||||
|
||||
|
@ -457,7 +461,7 @@ export class CodeApplication extends Disposable {
|
|||
}
|
||||
|
||||
// Open Windows
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient, fileProtocolHandler));
|
||||
|
||||
// Post Open Windows Tasks
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
|
||||
|
@ -583,7 +587,7 @@ export class CodeApplication extends Disposable {
|
|||
});
|
||||
}
|
||||
|
||||
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>): ICodeWindow[] {
|
||||
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<Client<string>>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] {
|
||||
|
||||
// Register more Main IPC services
|
||||
const launchMainService = accessor.get(ILaunchMainService);
|
||||
|
@ -645,8 +649,10 @@ export class CodeApplication extends Disposable {
|
|||
electronIpcServer.registerChannel('logger', loggerChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
|
||||
|
||||
// ExtensionHost Debug broadcast service
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
|
||||
fileProtocolHandler.injectWindowsMainService(windowsMainService);
|
||||
|
||||
// ExtensionHost Debug broadcast service
|
||||
electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ElectronExtensionHostDebugBroadcastChannel(windowsMainService));
|
||||
|
||||
// Signal phase: ready (services set)
|
||||
|
|
93
src/vs/code/electron-main/protocol.ts
Normal file
93
src/vs/code/electron-main/protocol.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { FileAccess, Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { session } from 'electron';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
|
||||
type ProtocolCallback = { (result: string | Electron.FilePathWithHeaders | { error: number }): void };
|
||||
|
||||
export class FileProtocolHandler extends Disposable {
|
||||
|
||||
private readonly validRoots = TernarySearchTree.forUris<boolean>(() => !isLinux);
|
||||
|
||||
constructor(
|
||||
@INativeEnvironmentService environmentService: INativeEnvironmentService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
|
||||
const { defaultSession } = session;
|
||||
|
||||
// Define an initial set of roots we allow loading from
|
||||
// - appRoot : all files installed as part of the app
|
||||
// - extensions : all files shipped from extensions
|
||||
this.validRoots.set(URI.file(environmentService.appRoot), true);
|
||||
this.validRoots.set(URI.file(environmentService.extensionsPath), true);
|
||||
|
||||
// Register vscode-file:// handler
|
||||
defaultSession.protocol.registerFileProtocol(Schemas.vscodeFileResource, (request, callback) => this.handleResourceRequest(request, callback as unknown as ProtocolCallback));
|
||||
|
||||
// Cleanup
|
||||
this._register(toDisposable(() => {
|
||||
defaultSession.protocol.unregisterProtocol(Schemas.vscodeFileResource);
|
||||
}));
|
||||
}
|
||||
|
||||
injectWindowsMainService(windowsMainService: IWindowsMainService): void {
|
||||
this._register(windowsMainService.onWindowReady(window => {
|
||||
if (window.config?.extensionDevelopmentPath || window.config?.extensionTestsPath) {
|
||||
const disposables = new DisposableStore();
|
||||
disposables.add(Event.any(window.onClose, window.onDestroy)(() => disposables.dispose()));
|
||||
|
||||
// Allow access to extension development path
|
||||
if (window.config.extensionDevelopmentPath) {
|
||||
for (const extensionDevelopmentPath of window.config.extensionDevelopmentPath) {
|
||||
disposables.add(this.addValidRoot(URI.file(extensionDevelopmentPath)));
|
||||
}
|
||||
}
|
||||
|
||||
// Allow access to extension tests path
|
||||
if (window.config.extensionTestsPath) {
|
||||
disposables.add(this.addValidRoot(URI.file(window.config.extensionTestsPath)));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private addValidRoot(root: URI): IDisposable {
|
||||
if (!this.validRoots.get(root)) {
|
||||
this.validRoots.set(root, true);
|
||||
|
||||
return toDisposable(() => this.validRoots.delete(root));
|
||||
}
|
||||
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
private async handleResourceRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) {
|
||||
const uri = URI.parse(request.url);
|
||||
|
||||
// Restore the `vscode-file` URI to a `file` URI so that we can
|
||||
// ensure the root is valid and properly tell Chrome where the
|
||||
// resource is at.
|
||||
const fileUri = FileAccess.asFileUri(uri);
|
||||
if (this.validRoots.findSubstr(fileUri)) {
|
||||
return callback({
|
||||
path: fileUri.fsPath
|
||||
});
|
||||
}
|
||||
|
||||
this.logService.error(`${Schemas.vscodeFileResource}: Refused to load resource ${fileUri.fsPath}}`);
|
||||
callback({ error: -3 /* ABORTED */ });
|
||||
}
|
||||
}
|
|
@ -60,8 +60,9 @@ export class SharedProcess implements ISharedProcess {
|
|||
windowId: this.window.id
|
||||
};
|
||||
|
||||
const windowUrl = FileAccess
|
||||
.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require)
|
||||
const windowUrl = (this.environmentService.sandbox ?
|
||||
FileAccess._asCodeFileUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require) :
|
||||
FileAccess.asBrowserUri('vs/code/electron-browser/sharedProcess/sharedProcess.html', require))
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` });
|
||||
this.window.loadURL(windowUrl.toString(true));
|
||||
|
||||
|
|
|
@ -848,10 +848,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
|||
workbench = 'vs/code/electron-browser/workbench/workbench.html';
|
||||
}
|
||||
|
||||
return FileAccess
|
||||
.asBrowserUri(workbench, require)
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` })
|
||||
.toString(true);
|
||||
const browserUri = this.environmentService.sandbox ?
|
||||
FileAccess._asCodeFileUri(workbench, require) :
|
||||
FileAccess.asBrowserUri(workbench, require);
|
||||
|
||||
return browserUri.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` }).toString(true);
|
||||
}
|
||||
|
||||
serializeWindowState(): IWindowState {
|
||||
|
|
|
@ -209,18 +209,8 @@ export class IssueMainService implements ICommonIssueService {
|
|||
spellcheck: false,
|
||||
nativeWindowOpen: true,
|
||||
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel),
|
||||
...this.environmentService.sandbox ?
|
||||
|
||||
// Sandbox
|
||||
{
|
||||
sandbox: true,
|
||||
contextIsolation: true
|
||||
} :
|
||||
|
||||
// No Sandbox
|
||||
{
|
||||
nodeIntegration: true
|
||||
}
|
||||
sandbox: true,
|
||||
contextIsolation: true
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -275,18 +265,8 @@ export class IssueMainService implements ICommonIssueService {
|
|||
spellcheck: false,
|
||||
nativeWindowOpen: true,
|
||||
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel),
|
||||
...this.environmentService.sandbox ?
|
||||
|
||||
// Sandbox
|
||||
{
|
||||
sandbox: true,
|
||||
contextIsolation: true
|
||||
} :
|
||||
|
||||
// No Sandbox
|
||||
{
|
||||
nodeIntegration: true
|
||||
}
|
||||
sandbox: true,
|
||||
contextIsolation: true
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -294,7 +274,6 @@ export class IssueMainService implements ICommonIssueService {
|
|||
|
||||
const windowConfiguration = {
|
||||
appRoot: this.environmentService.appRoot,
|
||||
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
|
||||
windowId: this._processExplorerWindow.id,
|
||||
userEnv: this.userEnv,
|
||||
machineId: this.machineId,
|
||||
|
@ -422,7 +401,6 @@ export class IssueMainService implements ICommonIssueService {
|
|||
|
||||
const windowConfiguration = {
|
||||
appRoot: this.environmentService.appRoot,
|
||||
nodeCachedDataDir: this.environmentService.nodeCachedDataDir,
|
||||
windowId: this._issueWindow.id,
|
||||
machineId: this.machineId,
|
||||
userEnv: this.userEnv,
|
||||
|
@ -458,7 +436,7 @@ function toWindowUrl<T>(modulePathToHtml: string, windowConfiguration: T): strin
|
|||
}
|
||||
|
||||
return FileAccess
|
||||
.asBrowserUri(modulePathToHtml, require)
|
||||
._asCodeFileUri(modulePathToHtml, require)
|
||||
.with({ query: `config=${encodeURIComponent(JSON.stringify(config))}` })
|
||||
.toString(true);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue