Skip to content

Commit 6a995c4

Browse files
authored
unc - adopt setting and handling of allow list (#5)
* unc - adopt setting and handling of allow list * unc - set allow list on server too * unc - pick our patched node.js for now * bump electron * unc - ignore sync is not needed with machine scope * unc - use process set directly * 🆙 22.5.1
1 parent 0591d3e commit 6a995c4

File tree

17 files changed

+203
-22
lines changed

17 files changed

+203
-22
lines changed

build/gulpfile.reh.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ function nodejs(platform, arch) {
164164

165165
if (platform === 'win32') {
166166
if (product.nodejsRepository) {
167-
return assetFromGithub(product.nodejsRepository, nodeVersion, name => name === `win-${arch}-node.exe`)
167+
return assetFromGithub(product.nodejsRepository, nodeVersion, name => name === `win-${arch}-patched-node.exe`)
168168
.pipe(rename('node.exe'));
169169
}
170170

build/lib/electron.js

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/lib/electron.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ function darwinBundleDocumentTypes(types: { [name: string]: string | string[] },
9191
}
9292

9393
export const config = {
94-
version: product.electronRepository ? '22.4.8' : util.getElectronVersion(),
94+
version: product.electronRepository ? '22.5.1' : util.getElectronVersion(),
9595
productAppName: product.nameLong,
9696
companyName: 'Microsoft Corporation',
9797
copyright: 'Copyright (C) 2023 Microsoft. All rights reserved',
@@ -212,7 +212,7 @@ function getElectron(arch: string): () => NodeJS.ReadWriteStream {
212212
}
213213

214214
async function main(arch = process.arch): Promise<void> {
215-
const version = product.electronRepository ? '22.4.8' : util.getElectronVersion();
215+
const version = product.electronRepository ? '22.5.1' : util.getElectronVersion();
216216
const electronPath = path.join(root, '.build', 'electron');
217217
const versionFile = path.join(electronPath, 'version');
218218
const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`;

src/vs/base/common/errorMessage.ts

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ function stackToString(stack: string[] | string | undefined): string | undefined
2626

2727
function detectSystemErrorMessage(exception: any): string {
2828

29+
// Custom node.js error from us
30+
if (exception.code === 'ERR_UNC_HOST_NOT_ALLOWED') {
31+
return `${exception.message}. Please update the 'security.allowedUNCHosts' setting if you want to allow this host.`;
32+
}
33+
2934
// See https://nodejs.org/api/errors.html#errors_class_system_error
3035
if (typeof exception.code === 'string' && typeof exception.errno === 'number' && typeof exception.syscall === 'string') {
3136
return nls.localize('nodeExceptionMessage', "A system error occurred ({0})", exception.message);

src/vs/base/node/unc.ts

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { isWindows } from 'vs/base/common/platform';
7+
8+
function processUNCHostAllowlist(): Set<string> | undefined {
9+
10+
// The property `process.uncHostAllowlist` is not available in official node.js
11+
// releases, only in our own builds, so we have to probe for availability
12+
13+
const processWithUNCHostAllowlist = process as typeof process & { readonly uncHostAllowlist?: Set<string> };
14+
15+
return processWithUNCHostAllowlist.uncHostAllowlist;
16+
}
17+
18+
export function setUNCHostAllowlist(allowedHosts: string[]): void {
19+
if (!isWindows) {
20+
return;
21+
}
22+
23+
const allowlist = processUNCHostAllowlist();
24+
if (allowlist) {
25+
allowlist.clear();
26+
27+
for (const allowedHost of allowedHosts) {
28+
allowlist.add(allowedHost);
29+
}
30+
}
31+
}
32+
33+
export function getUNCHostAllowlist(): string[] {
34+
const allowlist = processUNCHostAllowlist();
35+
if (allowlist) {
36+
return Array.from(allowlist);
37+
}
38+
39+
return [];
40+
}
41+
42+
export function addUNCHostToAllowlist(allowedHost: string): void {
43+
if (!isWindows) {
44+
return;
45+
}
46+
47+
const allowlist = processUNCHostAllowlist();
48+
if (allowlist) {
49+
allowlist.add(allowedHost);
50+
}
51+
}
52+
53+
export function toUNCHostAllowlist(arg0: unknown): string[] {
54+
const allowedUNCHosts = new Set<string>();
55+
56+
if (Array.isArray(arg0)) {
57+
for (const host of arg0) {
58+
if (typeof host === 'string') {
59+
allowedUNCHosts.add(host);
60+
}
61+
}
62+
}
63+
64+
return Array.from(allowedUNCHosts);
65+
}

src/vs/code/electron-main/app.ts

+9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { app, BrowserWindow, dialog, protocol, session, Session, systemPreferences, WebFrameMain } from 'electron';
7+
import { setUNCHostAllowlist, toUNCHostAllowlist } from 'vs/base/node/unc';
78
import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain';
89
import { hostname, release } from 'os';
910
import { VSBuffer } from 'vs/base/common/buffer';
@@ -311,6 +312,14 @@ export class CodeApplication extends Disposable {
311312
}
312313

313314
//#endregion
315+
316+
//#region UNC Host Allowlist (Windows)
317+
318+
if (isWindows) {
319+
setUNCHostAllowlist(toUNCHostAllowlist(this.configurationService.getValue<unknown>('security.allowedUNCHosts')));
320+
}
321+
322+
//#endregion
314323
}
315324

316325
private registerListeners(): void {

src/vs/code/node/cli.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ import { getStdinFilePath, hasStdinWithoutTty, readFromStdin, stdinDataListener
2121
import { createWaitMarkerFileSync } from 'vs/platform/environment/node/wait';
2222
import product from 'vs/platform/product/common/product';
2323
import { CancellationTokenSource } from 'vs/base/common/cancellation';
24-
import { randomPath } from 'vs/base/common/extpath';
24+
import { isUNC, randomPath } from 'vs/base/common/extpath';
2525
import { Utils } from 'vs/platform/profiling/common/profiling';
2626
import { FileAccess } from 'vs/base/common/network';
2727
import { cwd } from 'vs/base/common/process';
28+
import { addUNCHostToAllowlist } from 'vs/base/node/unc';
29+
import { URI } from 'vs/base/common/uri';
2830

2931
function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean {
3032
return !!argv['install-source']
@@ -116,6 +118,16 @@ export async function main(argv: string[]): Promise<any> {
116118
const source = args._[0];
117119
const target = args._[1];
118120

121+
// Windows: set the paths as allowed UNC paths given
122+
// they are explicitly provided by the user as arguments
123+
if (isWindows) {
124+
for (const path of [source, target]) {
125+
if (isUNC(path)) {
126+
addUNCHostToAllowlist(URI.file(path).authority);
127+
}
128+
}
129+
}
130+
119131
// Validate
120132
if (
121133
!source || !target || source === target || // make sure source and target are provided and are not the same

src/vs/platform/files/node/diskFileSystemProvider.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
707707
return error; // avoid double conversion
708708
}
709709

710+
let resultError: Error | string = error;
710711
let code: FileSystemProviderErrorCode;
711712
switch (error.code) {
712713
case 'ENOENT':
@@ -725,11 +726,15 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
725726
case 'EACCES':
726727
code = FileSystemProviderErrorCode.NoPermissions;
727728
break;
729+
case 'ERR_UNC_HOST_NOT_ALLOWED':
730+
resultError = `${error.message}. Please update the 'security.allowedUNCHosts' setting if you want to allow this host.`;
731+
code = FileSystemProviderErrorCode.Unknown;
732+
break;
728733
default:
729734
code = FileSystemProviderErrorCode.Unknown;
730735
}
731736

732-
return createFileSystemProviderError(error, code);
737+
return createFileSystemProviderError(resultError, code);
733738
}
734739

735740
private async toFileSystemProviderWriteError(resource: URI | undefined, error: NodeJS.ErrnoException): Promise<FileSystemProviderError> {

src/vs/platform/utilityProcess/electron-main/utilityProcess.ts

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
1616
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
1717
import { removeDangerousEnvVariables } from 'vs/base/common/processes';
1818
import { deepClone } from 'vs/base/common/objects';
19+
import { isWindows } from 'vs/base/common/platform';
20+
import { getUNCHostAllowlist } from 'vs/base/node/unc';
1921

2022
export interface IUtilityProcessConfiguration {
2123

@@ -259,6 +261,9 @@ export class UtilityProcess extends Disposable {
259261
env['VSCODE_CRASH_REPORTER_SANDBOXED_HINT'] = '1'; // TODO@bpasero remove me once sandbox is final
260262
}
261263
env['VSCODE_CRASH_REPORTER_PROCESS_TYPE'] = configuration.type;
264+
if (isWindows) {
265+
env['NODE_UNC_HOST_ALLOWLIST'] = getUNCHostAllowlist().join('\\');
266+
}
262267

263268
// Remove any environment variables that are not allowed
264269
removeDangerousEnvVariables(env);

src/vs/platform/windows/electron-main/windowsMainService.ts

+35-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { app, BrowserWindow, WebContents } from 'electron';
6+
import { app, BrowserWindow, WebContents, shell } from 'electron';
77
import { Promises } from 'vs/base/node/pfs';
8+
import { addUNCHostToAllowlist } from 'vs/base/node/unc';
89
import { hostname, release } from 'os';
910
import { coalesce, distinct, firstOrDefault } from 'vs/base/common/arrays';
1011
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -1013,7 +1014,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
10131014
return openable.fileUri;
10141015
}
10151016

1016-
private async doResolveFilePath(path: string, options: IPathResolveOptions): Promise<IPathToOpen<ITextEditorOptions> | undefined> {
1017+
private async doResolveFilePath(path: string, options: IPathResolveOptions, skipHandleUNCError?: boolean): Promise<IPathToOpen<ITextEditorOptions> | undefined> {
10171018

10181019
// Extract line/col information from path
10191020
let lineNumber: number | undefined;
@@ -1083,6 +1084,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
10831084
};
10841085
}
10851086
} catch (error) {
1087+
1088+
if (error.code === 'ERR_UNC_HOST_NOT_ALLOWED' && !skipHandleUNCError) {
1089+
return this.onUNCHostNotAllowed(path, options);
1090+
}
1091+
10861092
const fileUri = URI.file(path);
10871093

10881094
// since file does not seem to exist anymore, remove from recent
@@ -1101,6 +1107,33 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
11011107
return undefined;
11021108
}
11031109

1110+
private async onUNCHostNotAllowed(path: string, options: IPathResolveOptions): Promise<IPathToOpen<ITextEditorOptions> | undefined> {
1111+
const uri = URI.file(path);
1112+
1113+
const { response } = await this.dialogMainService.showMessageBox({
1114+
type: 'warning',
1115+
buttons: [
1116+
localize({ key: 'yes', comment: ['&& denotes a mnemonic'] }, "&&Yes"),
1117+
localize({ key: 'no', comment: ['&& denotes a mnemonic'] }, "&&No"),
1118+
localize({ key: 'learnMore', comment: ['&& denotes a mnemonic'] }, "&&Learn More"),
1119+
],
1120+
message: localize('confirmOpenMessage', "The host '{0}' was not found in the list of allowed hosts. Do you want to open it anyway?", uri.authority),
1121+
detail: localize('confirmOpenDetail', "The path '{0}' uses a host that is not allowed. Unless you trust the host, you should press 'No'", getPathLabel(uri, { os: OS, tildify: this.environmentMainService }))
1122+
});
1123+
1124+
if (response === 0) {
1125+
addUNCHostToAllowlist(uri.authority);
1126+
1127+
return this.doResolveFilePath(path, options, true /* do not handle UNC error again */);
1128+
}
1129+
1130+
if (response === 2) {
1131+
shell.openExternal('https://aka.ms/vscode-windows-unc');
1132+
}
1133+
1134+
return undefined;
1135+
}
1136+
11041137
private doResolveRemotePath(path: string, options: IPathResolveOptions): IPathToOpen<ITextEditorOptions> | undefined {
11051138
const first = path.charCodeAt(0);
11061139
const remoteAuthority = options.remoteAuthority;

src/vs/server/node/remoteExtensionHostAgentCli.ts

+8
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement
4949
import { LogService } from 'vs/platform/log/common/logService';
5050
import { LoggerService } from 'vs/platform/log/node/loggerService';
5151
import { localize } from 'vs/nls';
52+
import { setUNCHostAllowlist, toUNCHostAllowlist } from 'vs/base/node/unc';
5253

5354
class CliMain extends Disposable {
5455

@@ -66,7 +67,14 @@ class CliMain extends Disposable {
6667
async run(): Promise<void> {
6768
const instantiationService = await this.initServices();
6869
await instantiationService.invokeFunction(async accessor => {
70+
const configurationService = accessor.get(IConfigurationService);
6971
const logService = accessor.get(ILogService);
72+
73+
// On Windows, configure the UNC allow list based on settings
74+
if (process.platform === 'win32') {
75+
setUNCHostAllowlist(toUNCHostAllowlist(configurationService.getValue<unknown>('security.allowedUNCHosts')));
76+
}
77+
7078
try {
7179
await this.doRun(instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(logService.getLevel(), false)));
7280
} catch (error) {

src/vs/server/node/remoteExtensionHostAgentServer.ts

+11
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ import { createRegExp, escapeRegExpCharacters } from 'vs/base/common/strings';
2323
import { URI } from 'vs/base/common/uri';
2424
import { generateUuid } from 'vs/base/common/uuid';
2525
import { findFreePort } from 'vs/base/node/ports';
26+
import { setUNCHostAllowlist, toUNCHostAllowlist } from 'vs/base/node/unc';
2627
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
2728
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
29+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2830
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
2931
import { ILogService } from 'vs/platform/log/common/log';
3032
import { IProductService } from 'vs/platform/product/common/productService';
@@ -712,6 +714,15 @@ export async function createServer(address: string | net.AddressInfo | null, arg
712714
initUnexpectedErrorHandler((error: any) => logService.error(error));
713715
});
714716

717+
// On Windows, configure the UNC allow list based on settings
718+
instantiationService.invokeFunction((accessor) => {
719+
const configurationService = accessor.get(IConfigurationService);
720+
721+
if (process.platform === 'win32') {
722+
setUNCHostAllowlist(toUNCHostAllowlist(configurationService.getValue<unknown>('security.allowedUNCHosts')));
723+
}
724+
});
725+
715726
//
716727
// On Windows, exit early with warning message to users about potential security issue
717728
// if there is node_modules folder under home drive or Users folder.

src/vs/workbench/browser/workbench.contribution.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
77
import { localize } from 'vs/nls';
88
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
99
import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform';
10-
import { ConfigurationMigrationWorkbenchContribution, workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
10+
import { ConfigurationMigrationWorkbenchContribution, securityConfigurationNodeBase, workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
1111
import { isStandalone } from 'vs/base/browser/browser';
1212
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
1313
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
@@ -680,4 +680,21 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
680680
}
681681
}
682682
});
683+
684+
// Security
685+
registry.registerConfiguration({
686+
...securityConfigurationNodeBase,
687+
'properties': {
688+
'security.allowedUNCHosts': {
689+
'type': 'array',
690+
'items': {
691+
'type': 'string'
692+
},
693+
'default': [],
694+
'markdownDescription': localize('security.allowedUNCHosts', 'A set of UNC host names to allow without user confirmation. If a UNC host is being accessed that is not allowed via this setting or has not been acknowledged via user confirmation, an error will occur and the operation stopped. A restart is required when changing this setting. Find out more about this setting at https://aka.ms/vscode-windows-unc.'),
695+
'included': isWindows,
696+
'scope': ConfigurationScope.MACHINE
697+
}
698+
}
699+
});
683700
})();

0 commit comments

Comments
 (0)