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

Disable Privileged ports for OSX #162662

Merged
merged 1 commit into from Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 20 additions & 1 deletion src/vs/platform/tunnel/common/tunnel.ts
Expand Up @@ -129,6 +129,7 @@ export interface ITunnelService {
closeTunnel(remoteHost: string, remotePort: number): Promise<void>;
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable;
setTunnelFeatures(features: TunnelProviderFeatures): void;
isPortPrivileged(port: number): boolean;
}

export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string; port: number } | undefined {
Expand Down Expand Up @@ -402,14 +403,32 @@ export abstract class AbstractTunnelService implements ITunnelService {
return !!extractLocalHostUriMetaDataForPortMapping(uri);
}

public abstract isPortPrivileged(port: number): boolean;

protected doIsPortPrivileged(port: number, isWindows: boolean, isMacintosh: boolean, osRelease: string): boolean {
if (isWindows) {
return false;
} else if (isMacintosh) {
const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(osRelease);
if (osVersion?.length === 4) {
const major = parseInt(osVersion[1]);
const minor = parseInt(osVersion[2]);
if (((major > 10) || (major === 10 && minor >= 14))) {
return false;
}
}
}
return port < 1024;
}

protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined;

protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel with provider ${remoteHost}:${remotePort} on local port ${localPort}.`);
const key = remotePort;
this._factoryInProgress.add(key);
const preferredLocalPort = localPort === undefined ? remotePort : localPort;
const creationInfo = { elevationRequired: elevateIfNeeded ? isPortPrivileged(preferredLocalPort) : false };
const creationInfo = { elevationRequired: elevateIfNeeded ? this.isPortPrivileged(preferredLocalPort) : false };
const tunnelOptions: TunnelOptions = { remoteAddress: { host: remoteHost, port: remotePort }, localAddressPort: localPort, privacy, public: privacy ? (privacy !== TunnelPrivacyId.Private) : undefined, protocol };
const tunnel = tunnelProvider.forwardPort(tunnelOptions, creationInfo);
if (tunnel) {
Expand Down
6 changes: 6 additions & 0 deletions src/vs/platform/tunnel/node/tunnelService.ts
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as net from 'net';
import * as os from 'os';
import { BROWSER_RESTRICTED_PORTS, findFreePortFaster } from 'vs/base/node/ports';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
Expand All @@ -16,6 +17,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { connectRemoteAgentTunnel, IAddressProvider, IConnectionOptions, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { AbstractTunnelService, isAllInterfaces, ISharedTunnelsService as ISharedTunnelsService, isLocalhost, ITunnelService, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel';
import { ISignService } from 'vs/platform/sign/common/sign';
import { isMacintosh, isWindows } from 'vs/base/common/platform';

async function createRemoteTunnel(options: IConnectionOptions, defaultTunnelHost: string, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
let readyTunnel: NodeRemoteTunnel | undefined;
Expand Down Expand Up @@ -165,6 +167,10 @@ export class BaseTunnelService extends AbstractTunnelService {
return (!settingValue || settingValue === 'localhost') ? '127.0.0.1' : '0.0.0.0';
}

public isPortPrivileged(port: number): boolean {
return this.doIsPortPrivileged(port, isWindows, isMacintosh, os.release());
}

protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/api/browser/mainThreadTunnelService.ts
Expand Up @@ -8,7 +8,7 @@ import { MainThreadTunnelServiceShape, MainContext, ExtHostContext, ExtHostTunne
import { TunnelDtoConverter } from 'vs/workbench/api/common/extHostTunnelService';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged, ProvidedPortAttributes, PortAttributesProvider, TunnelProtocol } from 'vs/platform/tunnel/common/tunnel';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, ProvidedPortAttributes, PortAttributesProvider, TunnelProtocol } from 'vs/platform/tunnel/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
Expand Down Expand Up @@ -111,7 +111,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
if (!this.elevateionRetry
&& (tunnelOptions.localAddressPort !== undefined)
&& (tunnel.tunnelLocalPort !== undefined)
&& isPortPrivileged(tunnelOptions.localAddressPort)
&& this.tunnelService.isPortPrivileged(tunnelOptions.localAddressPort)
&& (tunnel.tunnelLocalPort !== tunnelOptions.localAddressPort)
&& this.tunnelService.canElevate) {

Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
Expand Up @@ -339,7 +339,7 @@ class OnAutoForwardedAction extends Disposable {
choices.push(this.openPreviewChoice(tunnel));
}

if ((tunnel.tunnelLocalPort !== tunnel.tunnelRemotePort) && this.tunnelService.canElevate && isPortPrivileged(tunnel.tunnelRemotePort)) {
if ((tunnel.tunnelLocalPort !== tunnel.tunnelRemotePort) && this.tunnelService.canElevate && this.tunnelService.isPortPrivileged(tunnel.tunnelRemotePort)) {
// Privileged ports are not on Windows, so it's safe to use "superuser"
message += nls.localize('remote.tunnelsView.elevationMessage', "You'll need to run as superuser to use port {0} locally. ", tunnel.tunnelRemotePort);
choices.unshift(this.elevateChoice(tunnel));
Expand Down
16 changes: 8 additions & 8 deletions src/vs/workbench/contrib/remote/browser/tunnelView.ts
Expand Up @@ -35,7 +35,7 @@ import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platfor
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { URI } from 'vs/base/common/uri';
import { isAllInterfaces, isLocalhost, isPortPrivileged, ITunnelService, RemoteTunnel, TunnelPrivacyId, TunnelProtocol } from 'vs/platform/tunnel/common/tunnel';
import { isAllInterfaces, isLocalhost, ITunnelService, RemoteTunnel, TunnelPrivacyId, TunnelProtocol } from 'vs/platform/tunnel/common/tunnel';
import { TunnelPrivacy } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
Expand Down Expand Up @@ -1138,13 +1138,13 @@ export namespace ForwardPortAction {
export const TREEITEM_LABEL = nls.localize('remote.tunnel.forwardItem', "Forward Port");
const forwardPrompt = nls.localize('remote.tunnel.forwardPrompt', "Port number or address (eg. 3000 or 10.10.10.10:2000).");

function validateInput(remoteExplorerService: IRemoteExplorerService, value: string, canElevate: boolean): { content: string; severity: Severity } | null {
function validateInput(remoteExplorerService: IRemoteExplorerService, tunnelService: ITunnelService, value: string, canElevate: boolean): { content: string; severity: Severity } | null {
const parsed = parseAddress(value);
if (!parsed) {
return { content: invalidPortString, severity: Severity.Error };
} else if (parsed.port >= maxPortNumber) {
return { content: invalidPortNumberString, severity: Severity.Error };
} else if (canElevate && isPortPrivileged(parsed.port)) {
} else if (canElevate && tunnelService.isPortPrivileged(parsed.port)) {
return { content: requiresSudoString, severity: Severity.Info };
} else if (mapHasAddressLocalhostOrAllInterfaces(remoteExplorerService.tunnelModel.forwarded, parsed.host, parsed.port)) {
return { content: alreadyForwarded, severity: Severity.Error };
Expand Down Expand Up @@ -1174,7 +1174,7 @@ export namespace ForwardPortAction {
}).then(tunnel => error(notificationService, tunnel, parsed!.host, parsed!.port));
}
},
validationMessage: (value) => validateInput(remoteExplorerService, value, tunnelService.canElevate),
validationMessage: (value) => validateInput(remoteExplorerService, tunnelService, value, tunnelService.canElevate),
placeholder: forwardPrompt
});
};
Expand All @@ -1190,7 +1190,7 @@ export namespace ForwardPortAction {
await viewsService.openView(TunnelPanel.ID, true);
const value = await quickInputService.input({
prompt: forwardPrompt,
validateInput: (value) => Promise.resolve(validateInput(remoteExplorerService, value, tunnelService.canElevate))
validateInput: (value) => Promise.resolve(validateInput(remoteExplorerService, tunnelService, value, tunnelService.canElevate))
});
let parsed: { host: string; port: number } | undefined;
if (value && (parsed = parseAddress(value))) {
Expand Down Expand Up @@ -1436,12 +1436,12 @@ namespace ChangeLocalPortAction {
export const ID = 'remote.tunnel.changeLocalPort';
export const LABEL = nls.localize('remote.tunnel.changeLocalPort', "Change Local Address Port");

function validateInput(value: string, canElevate: boolean): { content: string; severity: Severity } | null {
function validateInput(tunnelService: ITunnelService, value: string, canElevate: boolean): { content: string; severity: Severity } | null {
if (!value.match(/^[0-9]+$/)) {
return { content: invalidPortString, severity: Severity.Error };
} else if (Number(value) >= maxPortNumber) {
return { content: invalidPortNumberString, severity: Severity.Error };
} else if (canElevate && isPortPrivileged(Number(value))) {
} else if (canElevate && tunnelService.isPortPrivileged(Number(value))) {
return { content: requiresSudoString, severity: Severity.Info };
}
return null;
Expand Down Expand Up @@ -1484,7 +1484,7 @@ namespace ChangeLocalPortAction {
}
}
},
validationMessage: (value) => validateInput(value, tunnelService.canElevate),
validationMessage: (value) => validateInput(tunnelService, value, tunnelService.canElevate),
placeholder: nls.localize('remote.tunnelsView.changePort', "New local port")
});
}
Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/services/tunnel/browser/tunnelService.ts
Expand Up @@ -18,6 +18,10 @@ export class TunnelService extends AbstractTunnelService {
super(logService);
}

public isPortPrivileged(_port: number): boolean {
return false;
}

protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
Expand Down
Expand Up @@ -14,6 +14,8 @@ import { ISharedProcessTunnelService } from 'vs/platform/remote/common/sharedPro
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { isMacintosh, isWindows } from 'vs/base/common/platform';

class SharedProcessTunnel extends Disposable implements RemoteTunnel {

Expand Down Expand Up @@ -59,6 +61,7 @@ export class TunnelService extends AbstractTunnelService {
@ISharedProcessTunnelService private readonly _sharedProcessTunnelService: ISharedProcessTunnelService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILifecycleService lifecycleService: ILifecycleService,
@INativeWorkbenchEnvironmentService private readonly _nativeWorkbenchEnvironmentService: INativeWorkbenchEnvironmentService
) {
super(logService);

Expand All @@ -70,6 +73,10 @@ export class TunnelService extends AbstractTunnelService {
});
}

public isPortPrivileged(port: number): boolean {
return this.doIsPortPrivileged(port, isWindows, isMacintosh, this._nativeWorkbenchEnvironmentService.os.release);
}

protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise<RemoteTunnel | undefined> | undefined {
const existing = this.getTunnelFromMap(remoteHost, remotePort);
if (existing) {
Expand Down