Skip to content

Commit

Permalink
Use a nonce to verify si commands
Browse files Browse the repository at this point in the history
Fixes #181522
  • Loading branch information
Tyriar committed May 4, 2023
1 parent 2fa219d commit e738d82
Show file tree
Hide file tree
Showing 19 changed files with 92 additions and 20 deletions.
20 changes: 19 additions & 1 deletion src/vs/platform/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export interface IPtyHostAttachTarget {
isFeatureTerminal?: boolean;
type?: TerminalType;
hasChildProcesses: boolean;
shellIntegrationNonce: string;
}

export interface IReconnectionProperties {
Expand Down Expand Up @@ -483,7 +484,23 @@ export interface IShellLaunchConfig {
* This is a terminal that attaches to an already running terminal.
*/
attachPersistentProcess?: {
id: number; findRevivedId?: boolean; pid: number; title: string; titleSource: TitleEventSource; cwd: string; icon?: TerminalIcon; color?: string; hasChildProcesses?: boolean; fixedDimensions?: IFixedTerminalDimensions; environmentVariableCollections?: ISerializableEnvironmentVariableCollections; reconnectionProperties?: IReconnectionProperties; type?: TerminalType; waitOnExit?: WaitOnExitValue; hideFromUser?: boolean; isFeatureTerminal?: boolean;
id: number;
findRevivedId?: boolean;
pid: number;
title: string;
titleSource: TitleEventSource;
cwd: string;
icon?: TerminalIcon;
color?: string;
hasChildProcesses?: boolean;
fixedDimensions?: IFixedTerminalDimensions;
environmentVariableCollections?: ISerializableEnvironmentVariableCollections;
reconnectionProperties?: IReconnectionProperties;
type?: TerminalType;
waitOnExit?: WaitOnExitValue;
hideFromUser?: boolean;
isFeatureTerminal?: boolean;
shellIntegrationNonce: string;
};

/**
Expand Down Expand Up @@ -598,6 +615,7 @@ export interface ITerminalProcessOptions {
shellIntegration: {
enabled: boolean;
suggestEnabled: boolean;
nonce: string;
};
windowsEnableConpty: boolean;
environmentVariableCollections: ISerializableEnvironmentVariableCollections | undefined;
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/terminal/common/terminalProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export interface IProcessDetails {
isFeatureTerminal?: boolean;
type?: TerminalType;
hasChildProcesses: boolean;
shellIntegrationNonce: string;
}

export type ITerminalTabLayoutInfoDto = IRawTerminalTabLayoutInfo<IProcessDetails>;
Expand Down
11 changes: 10 additions & 1 deletion src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ const enum VSCodeOscPt {
* "\n" -> "\x0a"
* ";" -> "\x3b"
* ```
*
* An optional nonce can be provided which is may be required by the terminal in order enable
* some features. This helps ensure no malicious command injection has occurred.
*
* Format: `OSC 633 ; E [; <CommandLine> [; <Nonce>]] ST`.
*/
CommandLine = 'E',

Expand Down Expand Up @@ -201,6 +206,7 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
readonly onDidChangeStatus = this._onDidChangeStatus.event;

constructor(
private _nonce: string,
private readonly _disableTelemetry: boolean | undefined,
private readonly _telemetryService: ITelemetryService | undefined,
@ILogService private readonly _logService: ILogService
Expand Down Expand Up @@ -327,8 +333,11 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
return true;
}
case VSCodeOscPt.CommandLine: {
// Only trust the command line if the optional nonce (according to the spec) is
// send in order to prevent spoofing. This is important as some interactions do not
// require verification before re-running a command.
let commandLine: string;
if (args.length === 1) {
if (args.length === 2 && args[1] === this._nonce) {
commandLine = deserializeMessage(args[0]);
} else {
commandLine = '';
Expand Down
7 changes: 5 additions & 2 deletions src/vs/platform/terminal/node/ptyService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,8 @@ export class PtyService extends Disposable implements IPtyService {
hideFromUser: persistentProcess.shellLaunchConfig.hideFromUser,
isFeatureTerminal: persistentProcess.shellLaunchConfig.isFeatureTerminal,
type: persistentProcess.shellLaunchConfig.type,
hasChildProcesses: persistentProcess.hasChildProcesses
hasChildProcesses: persistentProcess.hasChildProcesses,
shellIntegrationNonce: persistentProcess.processLaunchOptions.options.shellIntegration.nonce
};
}

Expand Down Expand Up @@ -609,6 +610,7 @@ class PersistentTerminalProcess extends Disposable {
reconnectConstants.scrollback,
unicodeVersion,
reviveBuffer,
processLaunchOptions.options.shellIntegration.nonce,
shouldPersistTerminal ? rawReviveBuffer : undefined,
this._logService
);
Expand Down Expand Up @@ -882,6 +884,7 @@ class XtermSerializer implements ITerminalSerializer {
scrollback: number,
unicodeVersion: '6' | '11',
reviveBufferWithRestoreMessage: string | undefined,
shellIntegrationNonce: string,
private _rawReviveBuffer: string | undefined,
logService: ILogService
) {
Expand All @@ -895,7 +898,7 @@ class XtermSerializer implements ITerminalSerializer {
this._xterm.writeln(reviveBufferWithRestoreMessage);
}
this.setUnicodeVersion(unicodeVersion);
this._shellIntegrationAddon = new ShellIntegrationAddon(true, undefined, logService);
this._shellIntegrationAddon = new ShellIntegrationAddon(shellIntegrationNonce, true, undefined, logService);
this._xterm.loadAddon(this._shellIntegrationAddon);
}

Expand Down
4 changes: 4 additions & 0 deletions src/vs/platform/terminal/node/terminalEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ export function getShellIntegrationInjection(
'VSCODE_INJECTION': '1'
};

if (options.shellIntegration.nonce) {
envMixin['VSCODE_NONCE'] = options.shellIntegration.nonce;
}

// Windows
if (isWindows) {
if (shell === 'pwsh.exe' || shell === 'powershell.exe') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { ITerminalProcessOptions } from 'vs/platform/terminal/common/terminal';
import { getShellIntegrationInjection, getWindowsBuildNumber, IShellIntegrationConfigInjection } from 'vs/platform/terminal/node/terminalEnvironment';

const enabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false }, windowsEnableConpty: true, environmentVariableCollections: undefined, workspaceFolder: undefined };
const disabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: false, suggestEnabled: false }, windowsEnableConpty: true, environmentVariableCollections: undefined, workspaceFolder: undefined };
const winptyProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false }, windowsEnableConpty: false, environmentVariableCollections: undefined, workspaceFolder: undefined };
const enabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false, nonce: '' }, windowsEnableConpty: true, environmentVariableCollections: undefined, workspaceFolder: undefined };
const disabledProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: false, suggestEnabled: false, nonce: '' }, windowsEnableConpty: true, environmentVariableCollections: undefined, workspaceFolder: undefined };
const winptyProcessOptions: ITerminalProcessOptions = { shellIntegration: { enabled: true, suggestEnabled: false, nonce: '' }, windowsEnableConpty: false, environmentVariableCollections: undefined, workspaceFolder: undefined };
const pwshExe = process.platform === 'win32' ? 'pwsh.exe' : 'pwsh';
const repoRoot = process.platform === 'win32' ? process.cwd()[0].toLowerCase() + process.cwd().substring(1) : process.cwd();
const logService = new NullLogService();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ __vsc_custom_PS2=""
__vsc_in_command_execution="1"
__vsc_current_command=""

# It's fine this is in the global scope as it getting at it requires access to the shell environment
__vsc_nonce="$VSCODE_NONCE"
unset VSCODE_NONCE

__vsc_prompt_start() {
builtin printf '\e]633;A\a'
}
Expand All @@ -154,7 +158,7 @@ __vsc_update_cwd() {

__vsc_command_output_start() {
builtin printf '\e]633;C\a'
builtin printf '\e]633;E;%s\a' "$(__vsc_escape_value "${__vsc_current_command}")"
builtin printf '\e]633;E;%s;%s\a' "$(__vsc_escape_value "${__vsc_current_command}")" $__vsc_nonce
}

__vsc_continuation_start() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ $Global:__VSCodeOriginalPrompt = $function:Prompt

$Global:__LastHistoryId = -1

# Store the nonce in script scope and unset the global
$Nonce = $env:VSCODE_NONCE
$env:VSCODE_NONCE = $null

if ($env:VSCODE_ENV_REPLACE) {
$Split = $env:VSCODE_ENV_REPLACE.Split(":")
foreach ($Item in $Split) {
Expand Down Expand Up @@ -67,7 +71,7 @@ function Global:Prompt() {
$Result += "$([char]0x1b)]633;D`a"
} else {
# Command finished command line
# OSC 633 ; A ; <CommandLine?> ST
# OSC 633 ; E ; <CommandLine?> ; <Nonce?> ST
$Result = "$([char]0x1b)]633;E;"
# Sanitize the command line to ensure it can get transferred to the terminal and can be parsed
# correctly. This isn't entirely safe but good for most cases, it's important for the Pt parameter
Expand All @@ -78,6 +82,7 @@ function Global:Prompt() {
$CommandLine = ""
}
$Result += $(__VSCode-Escape-Value $CommandLine)
$Result += ";$Nonce"
$Result += "`a"
# Command finished exit code
# OSC 633 ; D [; <ExitCode>] ST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack
async listProcesses(): Promise<IProcessDetails[]> {
const terms = this._remoteTerminalChannel ? await this._remoteTerminalChannel.listProcesses() : [];
return terms.map(termDto => {
// TODO: This is an unsafe cast, why not just return channel.listProcesses?
return <IProcessDetails>{
id: termDto.id,
pid: termDto.pid,
Expand All @@ -273,7 +274,8 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack
icon: termDto.icon,
color: termDto.color,
isOrphan: termDto.isOrphan,
fixedDimensions: termDto.fixedDimensions
fixedDimensions: termDto.fixedDimensions,
shellIntegrationNonce: termDto.shellIntegrationNonce
};
});
}
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ interface ITerminalEditorInputObject {
readonly isFeatureTerminal?: boolean;
readonly hideFromUser?: boolean;
readonly reconnectionProperties?: IReconnectionProperties;
readonly shellIntegrationNonce: string;
}

export interface ISerializedTerminalEditorInput extends ITerminalEditorInputObject {
Expand Down Expand Up @@ -651,6 +652,11 @@ export interface ITerminalInstance {
*/
userHome: string | undefined;

/**
* The nonce used to verify commands coming from shell integration.
*/
shellIntegrationNonce: string;

/**
* Registers and returns a marker
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export class TerminalInputSerializer implements IEditorSerializer {
hasChildProcesses: instance.hasChildProcesses,
isFeatureTerminal: instance.shellLaunchConfig.isFeatureTerminal,
hideFromUser: instance.shellLaunchConfig.hideFromUser,
reconnectionProperties: instance.shellLaunchConfig.reconnectionProperties
reconnectionProperties: instance.shellLaunchConfig.reconnectionProperties,
shellIntegrationNonce: instance.shellIntegrationNonce
};
}
}
13 changes: 11 additions & 2 deletions src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return undefined;
}
get userHome(): string | undefined { return this._userHome; }
get shellIntegrationNonce(): string { return this._processManager.shellIntegrationNonce; }
get injectedArgs(): string[] | undefined { return this._injectedArgs; }

// The onExit event is special in that it fires and is disposed after the terminal instance
Expand Down Expand Up @@ -751,6 +752,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
},
this.capabilities,
this._processManager.shellIntegrationNonce,
this._terminalSuggestWidgetVisibleContextKey,
this.disableShellIntegrationReporting
);
Expand Down Expand Up @@ -1379,7 +1381,14 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (this.shellLaunchConfig.attachPersistentProcess?.environmentVariableCollections) {
deserializedCollections = deserializeEnvironmentVariableCollections(this.shellLaunchConfig.attachPersistentProcess.environmentVariableCollections);
}
const processManager = this._scopedInstantiationService.createInstance(TerminalProcessManager, this._instanceId, this._configHelper, this.shellLaunchConfig?.cwd, deserializedCollections);
const processManager = this._scopedInstantiationService.createInstance(
TerminalProcessManager,
this._instanceId,
this._configHelper,
this.shellLaunchConfig?.cwd,
deserializedCollections,
this.shellLaunchConfig.attachPersistentProcess?.shellIntegrationNonce
);
this.capabilities.add(processManager.capabilities);
processManager.onProcessReady(async (e) => {
this._onProcessIdReady.fire(this);
Expand Down Expand Up @@ -1501,7 +1510,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
});
if (this.xterm?.shellIntegration) {
this.capabilities.add(this.xterm?.shellIntegration.capabilities);
this.capabilities.add(this.xterm.shellIntegration.capabilities);
}
if (originalIcon !== this.shellLaunchConfig.icon || this.shellLaunchConfig.color) {
this._icon = this._shellLaunchConfig.attachPersistentProcess?.icon || this._shellLaunchConfig.icon;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import Severity from 'vs/base/common/severity';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IEnvironmentVariableCollection, IMergedEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable';
import { getWorkspaceForTerminal } from 'vs/workbench/services/configurationResolver/common/terminalResolver';
import { generateUuid } from 'vs/base/common/uuid';

const enum ProcessConstants {
/**
Expand Down Expand Up @@ -74,6 +75,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
environmentVariableInfo: IEnvironmentVariableInfo | undefined;
backend: ITerminalBackend | undefined;
readonly capabilities = new TerminalCapabilityStore();
readonly shellIntegrationNonce: string;

private _isDisposed: boolean = false;
private _process: ITerminalChildProcess | null = null;
Expand Down Expand Up @@ -130,6 +132,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
private readonly _configHelper: ITerminalConfigHelper,
cwd: string | URI | undefined,
environmentVariableCollections: ReadonlyMap<string, IEnvironmentVariableCollection> | undefined,
shellIntegrationNonce: string | undefined,
@IHistoryService private readonly _historyService: IHistoryService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
Expand Down Expand Up @@ -177,6 +180,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
this.environmentVariableInfo = this._instantiationService.createInstance(EnvironmentVariableInfoChangesActive, this._extEnvironmentVariableCollection);
this._onEnvironmentVariableInfoChange.fire(this.environmentVariableInfo);
}

this.shellIntegrationNonce = shellIntegrationNonce ?? generateUuid();
}

async freePortKillProcess(port: string): Promise<void> {
Expand Down Expand Up @@ -279,6 +284,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
shellIntegration: {
enabled: this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled),
suggestEnabled: this._configurationService.getValue(TerminalSettingId.ShellIntegrationSuggestEnabled),
nonce: this.shellIntegrationNonce
},
windowsEnableConpty: this._configHelper.config.windowsEnableConpty,
environmentVariableCollections: this._extEnvironmentVariableCollection?.collections ? serializeEnvironmentVariableCollections(this._extEnvironmentVariableCollection.collections) : undefined,
Expand Down Expand Up @@ -470,6 +476,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
shellIntegration: {
enabled: this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled),
suggestEnabled: this._configurationService.getValue(TerminalSettingId.ShellIntegrationSuggestEnabled),
nonce: this.shellIntegrationNonce
},
windowsEnableConpty: this._configHelper.config.windowsEnableConpty,
environmentVariableCollections: this._extEnvironmentVariableCollection ? serializeEnvironmentVariableCollections(this._extEnvironmentVariableCollection.collections) : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
rows: number,
private readonly _backgroundColorProvider: IXtermColorProvider,
private readonly _capabilities: ITerminalCapabilityStore,
shellIntegrationNonce: string,
private readonly _terminalSuggestWidgetVisibleContextKey: IContextKey<boolean>,
disableShellIntegrationReporting: boolean,
@IConfigurationService private readonly _configurationService: IConfigurationService,
Expand Down Expand Up @@ -247,7 +248,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal, II
this._decorationAddon = this._instantiationService.createInstance(DecorationAddon, this._capabilities);
this._decorationAddon.onDidRequestRunCommand(e => this._onDidRequestRunCommand.fire(e));
this.raw.loadAddon(this._decorationAddon);
this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon, disableShellIntegrationReporting, this._telemetryService);
this._shellIntegrationAddon = this._instantiationService.createInstance(ShellIntegrationAddon, shellIntegrationNonce, disableShellIntegrationReporting, this._telemetryService);
this.raw.loadAddon(this._shellIntegrationAddon);

// Load the suggest addon, this should be loaded regardless of the setting as the sequences
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ export interface IRemoteTerminalAttachTarget {
icon: URI | { light: URI; dark: URI } | { id: string; color?: { id: string } } | undefined;
color: string | undefined;
fixedDimensions: IFixedTerminalDimensions | undefined;
shellIntegrationNonce: string;
}

export interface ITerminalCommand {
Expand Down Expand Up @@ -380,6 +381,7 @@ export interface ITerminalProcessManager extends IDisposable {
readonly hasChildProcesses: boolean;
readonly backend: ITerminalBackend | undefined;
readonly capabilities: ITerminalCapabilityStore;
readonly shellIntegrationNonce: string;
readonly extEnvironmentVariableCollection: IMergedEnvironmentVariableCollection | undefined;

readonly onPtyDisconnect: Event<void>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ suite('Workbench - TerminalProcessManager', () => {
instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService());

const configHelper = instantiationService.createInstance(TerminalConfigHelper);
manager = instantiationService.createInstance(TerminalProcessManager, 1, configHelper, undefined, undefined);
manager = instantiationService.createInstance(TerminalProcessManager, 1, configHelper, undefined, undefined, undefined);
});

teardown(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ suite('ShellIntegrationAddon', () => {
xterm = new Terminal({ allowProposedApi: true, cols: 80, rows: 30 });
const instantiationService = new TestInstantiationService();
instantiationService.stub(ILogService, NullLogService);
shellIntegrationAddon = instantiationService.createInstance(TestShellIntegrationAddon, true, undefined);
shellIntegrationAddon = instantiationService.createInstance(TestShellIntegrationAddon, '', true, undefined);
xterm.loadAddon(shellIntegrationAddon);
capabilities = shellIntegrationAddon.capabilities;
});
Expand Down
Loading

0 comments on commit e738d82

Please sign in to comment.