Skip to content

Commit 724b1c6

Browse files
authored
Merge pull request microsoft#76349 from skprabhanjan/fix-72650_3
Fix-72650 Shell paths should be validated before launched to prevent ambiguous errors - $Path check
2 parents 7152aa5 + f8bd0e3 commit 724b1c6

4 files changed

Lines changed: 86 additions & 16 deletions

File tree

src/vs/workbench/contrib/terminal/browser/terminalInstance.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB
2525
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
2626
import { PANEL_BACKGROUND } from 'vs/workbench/common/theme';
2727
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager';
28-
import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal';
28+
import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal';
2929
import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
3030
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
3131
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
@@ -1003,7 +1003,9 @@ export class TerminalInstance implements ITerminalInstance {
10031003
// Create exit code message
10041004
if (exitCode) {
10051005
if (exitCode === SHELL_PATH_INVALID_EXIT_CODE) {
1006-
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPath', 'The terminal shell path does not exist: {0}', this._shellLaunchConfig.executable);
1006+
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPath', 'The terminal shell path "{0}" does not exist', this._shellLaunchConfig.executable);
1007+
} else if (exitCode === SHELL_PATH_DIRECTORY_EXIT_CODE) {
1008+
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPathDirectory', 'The terminal shell path "{0}" is a directory', this._shellLaunchConfig.executable);
10071009
} else if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
10081010
let args = '';
10091011
if (typeof this._shellLaunchConfig.args === 'string') {

src/vs/workbench/contrib/terminal/common/terminal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export const DEFAULT_LETTER_SPACING = 0;
6363
export const MINIMUM_LETTER_SPACING = -5;
6464
export const DEFAULT_LINE_HEIGHT = 1;
6565
export const SHELL_PATH_INVALID_EXIT_CODE = -1;
66+
export const SHELL_PATH_DIRECTORY_EXIT_CODE = -2;
6667

6768
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
6869

src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
7-
import { readFile } from 'vs/base/node/pfs';
8-
import { basename } from 'vs/base/common/path';
7+
import { readFile, exists } from 'vs/base/node/pfs';
8+
import * as path from 'vs/base/common/path';
9+
import { isString } from 'vs/base/common/types';
910

1011
let mainProcessParentEnv: IProcessEnvironment | undefined;
1112

@@ -18,7 +19,7 @@ export async function getMainProcessParentEnv(): Promise<IProcessEnvironment> {
1819
// env using /proc/<pid>/environ.
1920
if (isLinux) {
2021
const mainProcessId = process.ppid;
21-
const codeProcessName = basename(process.argv[0]);
22+
const codeProcessName = path.basename(process.argv[0]);
2223
let pid: number = 0;
2324
let ppid: number = mainProcessId;
2425
let name: string = codeProcessName;
@@ -76,4 +77,56 @@ export async function getMainProcessParentEnv(): Promise<IProcessEnvironment> {
7677
}
7778

7879
return mainProcessParentEnv!;
80+
}
81+
82+
export async function findExecutable(command: string, cwd?: string, paths?: string[]): Promise<string | undefined> {
83+
// If we have an absolute path then we take it.
84+
if (path.isAbsolute(command)) {
85+
return await exists(command) ? command : undefined;
86+
}
87+
if (cwd === undefined) {
88+
cwd = process.cwd();
89+
}
90+
const dir = path.dirname(command);
91+
if (dir !== '.') {
92+
// We have a directory and the directory is relative (see above). Make the path absolute
93+
// to the current working directory.
94+
const fullPath = path.join(cwd, command);
95+
return await exists(fullPath) ? fullPath : undefined;
96+
}
97+
if (paths === undefined && isString(process.env.PATH)) {
98+
paths = process.env.PATH.split(path.delimiter);
99+
}
100+
// No PATH environment. Make path absolute to the cwd.
101+
if (paths === undefined || paths.length === 0) {
102+
const fullPath = path.join(cwd, command);
103+
return await exists(fullPath) ? fullPath : undefined;
104+
}
105+
// We have a simple file name. We get the path variable from the env
106+
// and try to find the executable on the path.
107+
for (let pathEntry of paths) {
108+
// The path entry is absolute.
109+
let fullPath: string;
110+
if (path.isAbsolute(pathEntry)) {
111+
fullPath = path.join(pathEntry, command);
112+
} else {
113+
fullPath = path.join(cwd, pathEntry, command);
114+
}
115+
116+
if (await exists(fullPath)) {
117+
return fullPath;
118+
}
119+
if (isWindows) {
120+
let withExtension = fullPath + '.com';
121+
if (await exists(withExtension)) {
122+
return withExtension;
123+
}
124+
withExtension = fullPath + '.exe';
125+
if (await exists(withExtension)) {
126+
return withExtension;
127+
}
128+
}
129+
}
130+
const fullPath = path.join(cwd, command);
131+
return await exists(fullPath) ? fullPath : undefined;
79132
}

src/vs/workbench/contrib/terminal/node/terminalProcess.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ import * as fs from 'fs';
1111
import { Event, Emitter } from 'vs/base/common/event';
1212
import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal';
1313
import { IDisposable } from 'vs/base/common/lifecycle';
14-
import { IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal';
14+
import { IShellLaunchConfig, ITerminalChildProcess, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal';
1515
import { exec } from 'child_process';
1616
import { ILogService } from 'vs/platform/log/common/log';
17+
import { stat } from 'vs/base/node/pfs';
18+
import { findExecutable } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
19+
import { URI } from 'vs/base/common/uri';
1720

1821
export class TerminalProcess implements ITerminalChildProcess, IDisposable {
1922
private _exitCode: number;
@@ -65,16 +68,27 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
6568
conptyInheritCursor: true
6669
};
6770

68-
// TODO: Need to verify whether executable is on $PATH, otherwise things like cmd.exe will break
69-
// fs.stat(shellLaunchConfig.executable!, (err) => {
70-
// if (err && err.code === 'ENOENT') {
71-
// this._exitCode = SHELL_PATH_INVALID_EXIT_CODE;
72-
// this._queueProcessExit();
73-
// this._processStartupComplete = Promise.resolve(undefined);
74-
// return;
75-
// }
76-
this.setupPtyProcess(shellLaunchConfig, options);
77-
// });
71+
stat(shellLaunchConfig.executable!).then(stat => {
72+
if (!stat.isFile() && !stat.isSymbolicLink()) {
73+
return this._launchFailed(stat.isDirectory() ? SHELL_PATH_DIRECTORY_EXIT_CODE : SHELL_PATH_INVALID_EXIT_CODE);
74+
}
75+
this.setupPtyProcess(shellLaunchConfig, options);
76+
}, async (err) => {
77+
if (err && err.code === 'ENOENT') {
78+
let cwd = shellLaunchConfig.cwd instanceof URI ? shellLaunchConfig.cwd.path : shellLaunchConfig.cwd!;
79+
const executable = await findExecutable(shellLaunchConfig.executable!, cwd);
80+
if (!executable) {
81+
return this._launchFailed(SHELL_PATH_INVALID_EXIT_CODE);
82+
}
83+
}
84+
this.setupPtyProcess(shellLaunchConfig, options);
85+
});
86+
}
87+
88+
private _launchFailed(exitCode: number): void {
89+
this._exitCode = exitCode;
90+
this._queueProcessExit();
91+
this._processStartupComplete = Promise.resolve(undefined);
7892
}
7993

8094
private setupPtyProcess(shellLaunchConfig: IShellLaunchConfig, options: pty.IPtyForkOptions): void {

0 commit comments

Comments
 (0)