Skip to content

Commit

Permalink
Merged PR 560: Do no load kernels from insecure directories
Browse files Browse the repository at this point in the history
Do no load kernels from insecure directories
  • Loading branch information
DonJayamanne committed Oct 11, 2022
1 parent 2910d29 commit 91f4038
Show file tree
Hide file tree
Showing 16 changed files with 281 additions and 16 deletions.
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 2022.9.110 (11 October 2022)
### Fixes
1. Fixed vulnerability described in [CVE-2022-41083](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-41083)

### Thanks

Thanks to the following projects which we fully rely on to provide some of
our features:

- [Python Extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
- [debugpy](https://pypi.org/project/debugpy/)

Also thanks to the various projects we provide integrations with which help
make this extension useful:

- [Jupyter](https://jupyter.org/):
[Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest),
[JupyterHub](https://jupyterhub.readthedocs.io/en/stable/),
[ipywidgets](https://ipywidgets.readthedocs.io/en/latest/),
[nbconvert](https://nbconvert.readthedocs.io/en/latest/)


## 2022.9.100 (4 October 2022)

### Enhancements
Expand Down Expand Up @@ -2159,4 +2181,4 @@ make this extension useful:
- [Jupyter](https://jupyter.org/):
[Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest),
[JupyterHub](https://jupyterhub.readthedocs.io/en/stable/),
[ipywidgets](https://ipywidgets.readthedocs.io/en/latest/),
[ipywidgets](https://ipywidgets.readthedocs.io/en/latest/),
4 changes: 2 additions & 2 deletions build/azure-pipeline.stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ extends:
- script: python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r ./requirements.txt
displayName: Install Python libs

- script: npm run updateBuildNumber
displayName: Update build number
# - script: npm run updateBuildNumber
# displayName: Update build number

- script: npm run prePublishBundleStable
displayName: Build
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jupyter",
"displayName": "Jupyter",
"version": "2022.9.100",
"version": "2022.9.1100000000",
"description": "Jupyter notebook support, interactive programming and computing that supports Intellisense, debugging and more.",
"publisher": "ms-toolsai",
"author": {
Expand Down Expand Up @@ -1888,6 +1888,15 @@
]
]
},
"jupyter.kernels.trusted": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"markdownDescription": "%jupyter.configuration.jupyter.kernels.trusted.markdownDescription%",
"scope": "machine"
},
"jupyter.interactiveWindowMode": {
"type": "string",
"enum": [
Expand Down
5 changes: 4 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@
"message": "Behavior of the Interactive Window. 'perFile' will create a new interactive window for every file that runs a cell. 'single' allows a single window. 'multiple' allows the creation of multiple.",
"comment": ["{Locked='perFile'}", "{Locked=\"'single'\"}", "{Locked=\"'multiple'\"}"]
},
"jupyter.configuration.jupyter.interactiveWindowViewColumn.description":{
"jupyter.configuration.jupyter.interactiveWindowViewColumn.description": {
"message": "Where to open an Interactive Window that is not associated with a python file. 'beside' will open the interactive window to the right of the active editor. 'active' will open the interactive window in place of the active editor. 'secondGroup' will open the interactive window in the second editor group.",
"comment": ["{Locked='beside'}", "{Locked=\"'active'\"}", "{Locked=\"'secondGroup'\"}"]
},
Expand Down Expand Up @@ -844,6 +844,9 @@
"message": "Failed to interrupt the Kernel.",
"comment": ["{Locked='Kernel'}"]
},
"DataScience.updateSettingToTrustKernelSpecs": "Update setting to trust kernels",
"DataScience.untrustedKernelSpecsHidden": "Kernels found in an insecure location have not been loaded.",
"jupyter.configuration.jupyter.kernels.trusted.markdownDescription": "Enter fully qualified paths to Kernel specification files that are to be trusted. E.g. 'C:\\Program Data\\Jupyter\\kernels\\python3\\kernel.json'. \n**Note**: Kernels can execute code with user privileges. Click [here](https://aka.ms/JupyterTrustedKernelPaths) for further details.",
"DataScience.kernelDied": {
"message": "The kernel died. View Jupyter [log](command:jupyter.viewOutput) for further details. \nError: {0}...",
"comment": ["{Locked='command:jupyter.viewOutput'}"]
Expand Down
58 changes: 58 additions & 0 deletions src/kernels/hiddenKernelNotification.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

'use strict';
import { inject, injectable, named } from 'inversify';
import { commands, Memento } from 'vscode';
import { IExtensionSyncActivationService } from '../platform/activation/types';
import { IApplicationShell } from '../platform/common/application/types';
import { GLOBAL_MEMENTO, IBrowserService, IMemento } from '../platform/common/types';
import { Common, DataScience } from '../platform/common/utils/localize';
import { noop } from '../platform/common/utils/misc';
import { TrustedKernelPaths } from './raw/finder/trustedKernelSpecPaths.node';

const MEMENTO_KEY_NOTIFIED_ABOUT_HIDDEN_KERNEL = 'MEMENTO_KEY_NOTIFIED_ABOUT_HIDDEN_KERNEL_1';
@injectable()
export class HiddenKernelNotification implements IExtensionSyncActivationService {
private notifiedAboutHiddenKernel?: boolean;
constructor(
@inject(IMemento) @named(GLOBAL_MEMENTO) private readonly globalMemento: Memento,
@inject(IApplicationShell) private readonly appShell: IApplicationShell,
@inject(IBrowserService) private readonly browser: IBrowserService
) {}

public activate(): void {
TrustedKernelPaths.IsKernelSpecHidden.promise
.then((hidden) => {
if (
!hidden ||
this.notifiedAboutHiddenKernel ||
this.globalMemento.get<boolean>(MEMENTO_KEY_NOTIFIED_ABOUT_HIDDEN_KERNEL, false)
) {
return;
}
this.notifiedAboutHiddenKernel = true;
this.globalMemento.update(MEMENTO_KEY_NOTIFIED_ABOUT_HIDDEN_KERNEL, true).then(noop, noop);
this.appShell
.showWarningMessage(
DataScience.untrustedKernelSpecsHidden(),
Common.learnMore(),
DataScience.updateSettingToTrustKernelSpecs()
)
.then((selection) => {
switch (selection) {
case Common.learnMore():
this.browser.launch('https://aka.ms/JupyterTrustedKernelPaths');
break;
case DataScience.updateSettingToTrustKernelSpecs():
commands
.executeCommand('workbench.action.openSettings', 'jupyter.kernels.trusted')
.then(noop, noop);
break;
}
})
.then(noop, noop);
})
.catch(noop);
}
}
10 changes: 9 additions & 1 deletion src/kernels/raw/finder/localKernelFinder.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { debounceAsync } from '../../../platform/common/utils/decorators';
import { IPythonExtensionChecker } from '../../../platform/api/types';
import { IInterpreterService } from '../../../platform/interpreter/contracts';
import { EnvironmentType } from '../../../platform/pythonEnvironments/info';
import { ITrustedKernelPaths } from './types';

// This class searches for local kernels.
// First it searches on a global persistent state, then on the installed python interpreters,
Expand Down Expand Up @@ -63,7 +64,8 @@ export class LocalKernelFinder implements ILocalKernelFinder, IExtensionSingleAc
@inject(IInterpreterService) private readonly interpreters: IInterpreterService,
@inject(CondaService) private readonly condaService: CondaService,
@inject(IExtensions) private readonly extensions: IExtensions,
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
@inject(ITrustedKernelPaths) private readonly trustedKernelSpecPaths: ITrustedKernelPaths
) {
this._initializedPromise = new Promise<void>((resolve) => {
this._initializeResolve = resolve;
Expand Down Expand Up @@ -339,6 +341,12 @@ export class LocalKernelFinder implements ILocalKernelFinder, IExtensionSingleAc
return this.fs.exists(kernel.interpreter.uri);

case 'startUsingLocalKernelSpec':
if (
kernel.kernelSpec.specFile &&
!this.trustedKernelSpecPaths.isTrusted(Uri.file(kernel.kernelSpec.specFile))
) {
return false;
}
// Spec files have to still exist and interpreters have to exist
const promiseSpec = kernel.kernelSpec.specFile
? this.fs.exists(Uri.file(kernel.kernelSpec.specFile))
Expand Down
7 changes: 6 additions & 1 deletion src/kernels/raw/finder/localKernelSpecFinderBase.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from '../../../kernels/types';
import { JupyterKernelSpec } from '../../jupyter/jupyterKernelSpec';
import { getComparisonKey } from '../../../platform/vscode-path/resources';
import { ITrustedKernelPaths } from './types';
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
const flatten = require('lodash/flatten') as typeof import('lodash/flatten');

Expand Down Expand Up @@ -63,7 +64,8 @@ export abstract class LocalKernelSpecFinderBase {
protected readonly fs: IFileSystemNode,
protected readonly workspaceService: IWorkspaceService,
protected readonly extensionChecker: IPythonExtensionChecker,
protected readonly globalState: Memento
protected readonly globalState: Memento,
private readonly trustedKernelSpecPaths: ITrustedKernelPaths
) {}

@testOnlyMethod()
Expand Down Expand Up @@ -137,6 +139,9 @@ export abstract class LocalKernelSpecFinderBase {
globalSpecRootPath?: Uri,
cancelToken?: CancellationToken
): Promise<IJupyterKernelSpec | undefined> {
if (!this.trustedKernelSpecPaths.isTrusted(specPath)) {
return;
}
// This is a backup folder for old kernels created by us.
if (specPath.fsPath.includes(oldKernelsSpecFolderName)) {
return;
Expand Down
6 changes: 4 additions & 2 deletions src/kernels/raw/finder/localKnownPathKernelSpecFinder.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { IFileSystemNode } from '../../../platform/common/platform/types.node';
import { IMemento, GLOBAL_MEMENTO } from '../../../platform/common/types';
import { capturePerfTelemetry, Telemetry } from '../../../telemetry';
import { sendKernelSpecTelemetry } from './helper';
import { ITrustedKernelPaths } from './types';

/**
* This class searches for kernels on the file system in well known paths documented by Jupyter.
Expand All @@ -34,9 +35,10 @@ export class LocalKnownPathKernelSpecFinder extends LocalKernelSpecFinderBase {
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
@inject(JupyterPaths) private readonly jupyterPaths: JupyterPaths,
@inject(IPythonExtensionChecker) extensionChecker: IPythonExtensionChecker,
@inject(IMemento) @named(GLOBAL_MEMENTO) memento: Memento
@inject(IMemento) @named(GLOBAL_MEMENTO) memento: Memento,
@inject(ITrustedKernelPaths) trustedKernelSpecPaths: ITrustedKernelPaths
) {
super(fs, workspaceService, extensionChecker, memento);
super(fs, workspaceService, extensionChecker, memento, trustedKernelSpecPaths);
if (this.oldKernelSpecsFolder) {
traceInfo(
`Old kernelSpecs (created by Jupyter Extension) stored in directory ${this.oldKernelSpecsFolder}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { areInterpreterPathsSame } from '../../../platform/pythonEnvironments/in
import { capturePerfTelemetry, Telemetry } from '../../../telemetry';
import { PythonEnvironment } from '../../../platform/pythonEnvironments/info';
import { ResourceSet } from '../../../platform/vscode-path/map';
import { ITrustedKernelPaths } from './types';

/**
* Returns all Python kernels and any related kernels registered in the python environment.
Expand All @@ -52,9 +53,10 @@ export class LocalPythonAndRelatedNonPythonKernelSpecFinder extends LocalKernelS
@inject(IPythonExtensionChecker) extensionChecker: IPythonExtensionChecker,
@inject(LocalKnownPathKernelSpecFinder)
private readonly kernelSpecsFromKnownLocations: LocalKnownPathKernelSpecFinder,
@inject(IMemento) @named(GLOBAL_MEMENTO) globalState: Memento
@inject(IMemento) @named(GLOBAL_MEMENTO) globalState: Memento,
@inject(ITrustedKernelPaths) trustedKernelSpecPaths: ITrustedKernelPaths
) {
super(fs, workspaceService, extensionChecker, globalState);
super(fs, workspaceService, extensionChecker, globalState, trustedKernelSpecPaths);
}
public async listKernelSpecs(resource: Resource, ignoreCache?: boolean, cancelToken?: CancellationToken) {
// Get an id for the workspace folder, if we don't have one, use the fsPath of the resource
Expand Down
49 changes: 49 additions & 0 deletions src/kernels/raw/finder/trustedKernelSpecPaths.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { inject, injectable } from 'inversify';
import * as path from '../../../platform/vscode-path/path';
import { Uri } from 'vscode';
import { IPlatformService } from '../../../platform/common/platform/types';
import { ITrustedKernelPaths } from './types';
import { IWorkspaceService } from '../../../platform/common/application/types';
import { createDeferred } from '../../../platform/common/utils/async';

@injectable()
export class TrustedKernelPaths implements ITrustedKernelPaths {
public static IsKernelSpecHidden = createDeferred<boolean>();
private readonly programData = process.env['PROGRAMDATA']
? Uri.file(path.normalize(process.env['PROGRAMDATA']))
: undefined;
constructor(
@inject(IPlatformService) private readonly platform: IPlatformService,
@inject(IWorkspaceService) private readonly workspace: IWorkspaceService
) {}
private get trustedKernelSpecs(): string[] {
return this.workspace.getConfiguration('jupyter', undefined).get<string[]>('kernels.trusted', []);
}
public isTrusted(kernelPath: Uri): boolean {
const trusted = this.isTrustedImpl(kernelPath);
if (!trusted && !TrustedKernelPaths.IsKernelSpecHidden.completed) {
TrustedKernelPaths.IsKernelSpecHidden.resolve(true);
}
return trusted;
}
private isTrustedImpl(kernelPath: Uri): boolean {
if (kernelPath.scheme !== 'file') {
return true;
}
if (
this.trustedKernelSpecs
.map((p) => (this.platform.isWindows ? p.toLowerCase() : p))
.map((p) => Uri.file(p).path)
.includes(this.platform.isWindows ? kernelPath.path.toLowerCase() : kernelPath.path)
) {
return true;
}
if (this.platform.isWindows && this.programData) {
return !kernelPath.path.toLowerCase().startsWith(this.programData.path.toLowerCase());
}
return true;
}
}
9 changes: 9 additions & 0 deletions src/kernels/raw/finder/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { Uri } from 'vscode';

export const ITrustedKernelPaths = Symbol('ITrustedKernelPaths');
export interface ITrustedKernelPaths {
isTrusted(kernelPath: Uri): boolean;
}
8 changes: 8 additions & 0 deletions src/kernels/serviceRegistry.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ import { KernelAutoReconnectMonitor } from './kernelAutoReConnectMonitor';
import { PythonKernelInterruptDaemon } from './raw/finder/pythonKernelInterruptDaemon.node';
import { LocalKernelFinder } from './raw/finder/localKernelFinder.node';
import { DebugStartupCodeProvider } from './debuggerStartupCodeProvider';
import { TrustedKernelPaths } from './raw/finder/trustedKernelSpecPaths.node';
import { ITrustedKernelPaths } from './raw/finder/types';
import { HiddenKernelNotification } from './hiddenKernelNotification.node';

export function registerTypes(serviceManager: IServiceManager, isDevMode: boolean) {
serviceManager.addSingleton<IExtensionSingleActivationService>(IExtensionSingleActivationService, Activation);
Expand All @@ -53,6 +56,10 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea
IExtensionSyncActivationService,
PortAttributesProviders
);
serviceManager.addSingleton<IExtensionSyncActivationService>(
IExtensionSyncActivationService,
HiddenKernelNotification
);
serviceManager.addSingleton<IRawNotebookSupportedService>(
IRawNotebookSupportedService,
RawNotebookSupportedService
Expand All @@ -74,6 +81,7 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea
);

serviceManager.addSingleton<JupyterPaths>(JupyterPaths, JupyterPaths);
serviceManager.addSingleton<ITrustedKernelPaths>(ITrustedKernelPaths, TrustedKernelPaths);
serviceManager.addSingleton<LocalKnownPathKernelSpecFinder>(
LocalKnownPathKernelSpecFinder,
LocalKnownPathKernelSpecFinder
Expand Down
7 changes: 7 additions & 0 deletions src/platform/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,13 @@ export namespace DataScience {
},
'The kernel died. Error: {0}... View Jupyter [log](command:jupyter.viewOutput) for further details.'
);
export const untrustedKernelSpecsHidden = () =>
localize(
'DataScience.untrustedKernelSpecsHidden',
'Kernels found in an insecure location have not been loaded.'
);
export const updateSettingToTrustKernelSpecs = () =>
localize('DataScience.updateSettingToTrustKernelSpecs', 'Update setting to trust kernels');
export const kernelDiedWithoutError = () =>
localize(
{
Expand Down
Loading

0 comments on commit 91f4038

Please sign in to comment.