Skip to content

Commit 91f4038

Browse files
committed
Merged PR 560: Do no load kernels from insecure directories
Do no load kernels from insecure directories
1 parent 2910d29 commit 91f4038

16 files changed

+281
-16
lines changed

CHANGELOG.md

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Changelog
22

3+
## 2022.9.110 (11 October 2022)
4+
### Fixes
5+
1. Fixed vulnerability described in [CVE-2022-41083](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-41083)
6+
7+
### Thanks
8+
9+
Thanks to the following projects which we fully rely on to provide some of
10+
our features:
11+
12+
- [Python Extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
13+
- [debugpy](https://pypi.org/project/debugpy/)
14+
15+
Also thanks to the various projects we provide integrations with which help
16+
make this extension useful:
17+
18+
- [Jupyter](https://jupyter.org/):
19+
[Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest),
20+
[JupyterHub](https://jupyterhub.readthedocs.io/en/stable/),
21+
[ipywidgets](https://ipywidgets.readthedocs.io/en/latest/),
22+
[nbconvert](https://nbconvert.readthedocs.io/en/latest/)
23+
24+
325
## 2022.9.100 (4 October 2022)
426

527
### Enhancements
@@ -2159,4 +2181,4 @@ make this extension useful:
21592181
- [Jupyter](https://jupyter.org/):
21602182
[Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/?badge=latest),
21612183
[JupyterHub](https://jupyterhub.readthedocs.io/en/stable/),
2162-
[ipywidgets](https://ipywidgets.readthedocs.io/en/latest/),
2184+
[ipywidgets](https://ipywidgets.readthedocs.io/en/latest/),

build/azure-pipeline.stable.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ extends:
3939
- script: python -m pip --disable-pip-version-check install -t ./pythonFiles/lib/python --no-cache-dir --implementation py --no-deps --upgrade -r ./requirements.txt
4040
displayName: Install Python libs
4141

42-
- script: npm run updateBuildNumber
43-
displayName: Update build number
42+
# - script: npm run updateBuildNumber
43+
# displayName: Update build number
4444

4545
- script: npm run prePublishBundleStable
4646
displayName: Build

package-lock.json

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

package.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "jupyter",
33
"displayName": "Jupyter",
4-
"version": "2022.9.100",
4+
"version": "2022.9.1100000000",
55
"description": "Jupyter notebook support, interactive programming and computing that supports Intellisense, debugging and more.",
66
"publisher": "ms-toolsai",
77
"author": {
@@ -1888,6 +1888,15 @@
18881888
]
18891889
]
18901890
},
1891+
"jupyter.kernels.trusted": {
1892+
"type": "array",
1893+
"items": {
1894+
"type": "string"
1895+
},
1896+
"default": [],
1897+
"markdownDescription": "%jupyter.configuration.jupyter.kernels.trusted.markdownDescription%",
1898+
"scope": "machine"
1899+
},
18911900
"jupyter.interactiveWindowMode": {
18921901
"type": "string",
18931902
"enum": [

package.nls.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@
342342
"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.",
343343
"comment": ["{Locked='perFile'}", "{Locked=\"'single'\"}", "{Locked=\"'multiple'\"}"]
344344
},
345-
"jupyter.configuration.jupyter.interactiveWindowViewColumn.description":{
345+
"jupyter.configuration.jupyter.interactiveWindowViewColumn.description": {
346346
"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.",
347347
"comment": ["{Locked='beside'}", "{Locked=\"'active'\"}", "{Locked=\"'secondGroup'\"}"]
348348
},
@@ -844,6 +844,9 @@
844844
"message": "Failed to interrupt the Kernel.",
845845
"comment": ["{Locked='Kernel'}"]
846846
},
847+
"DataScience.updateSettingToTrustKernelSpecs": "Update setting to trust kernels",
848+
"DataScience.untrustedKernelSpecsHidden": "Kernels found in an insecure location have not been loaded.",
849+
"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.",
847850
"DataScience.kernelDied": {
848851
"message": "The kernel died. View Jupyter [log](command:jupyter.viewOutput) for further details. \nError: {0}...",
849852
"comment": ["{Locked='command:jupyter.viewOutput'}"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
import { inject, injectable, named } from 'inversify';
6+
import { commands, Memento } from 'vscode';
7+
import { IExtensionSyncActivationService } from '../platform/activation/types';
8+
import { IApplicationShell } from '../platform/common/application/types';
9+
import { GLOBAL_MEMENTO, IBrowserService, IMemento } from '../platform/common/types';
10+
import { Common, DataScience } from '../platform/common/utils/localize';
11+
import { noop } from '../platform/common/utils/misc';
12+
import { TrustedKernelPaths } from './raw/finder/trustedKernelSpecPaths.node';
13+
14+
const MEMENTO_KEY_NOTIFIED_ABOUT_HIDDEN_KERNEL = 'MEMENTO_KEY_NOTIFIED_ABOUT_HIDDEN_KERNEL_1';
15+
@injectable()
16+
export class HiddenKernelNotification implements IExtensionSyncActivationService {
17+
private notifiedAboutHiddenKernel?: boolean;
18+
constructor(
19+
@inject(IMemento) @named(GLOBAL_MEMENTO) private readonly globalMemento: Memento,
20+
@inject(IApplicationShell) private readonly appShell: IApplicationShell,
21+
@inject(IBrowserService) private readonly browser: IBrowserService
22+
) {}
23+
24+
public activate(): void {
25+
TrustedKernelPaths.IsKernelSpecHidden.promise
26+
.then((hidden) => {
27+
if (
28+
!hidden ||
29+
this.notifiedAboutHiddenKernel ||
30+
this.globalMemento.get<boolean>(MEMENTO_KEY_NOTIFIED_ABOUT_HIDDEN_KERNEL, false)
31+
) {
32+
return;
33+
}
34+
this.notifiedAboutHiddenKernel = true;
35+
this.globalMemento.update(MEMENTO_KEY_NOTIFIED_ABOUT_HIDDEN_KERNEL, true).then(noop, noop);
36+
this.appShell
37+
.showWarningMessage(
38+
DataScience.untrustedKernelSpecsHidden(),
39+
Common.learnMore(),
40+
DataScience.updateSettingToTrustKernelSpecs()
41+
)
42+
.then((selection) => {
43+
switch (selection) {
44+
case Common.learnMore():
45+
this.browser.launch('https://aka.ms/JupyterTrustedKernelPaths');
46+
break;
47+
case DataScience.updateSettingToTrustKernelSpecs():
48+
commands
49+
.executeCommand('workbench.action.openSettings', 'jupyter.kernels.trusted')
50+
.then(noop, noop);
51+
break;
52+
}
53+
})
54+
.then(noop, noop);
55+
})
56+
.catch(noop);
57+
}
58+
}

src/kernels/raw/finder/localKernelFinder.node.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { debounceAsync } from '../../../platform/common/utils/decorators';
2727
import { IPythonExtensionChecker } from '../../../platform/api/types';
2828
import { IInterpreterService } from '../../../platform/interpreter/contracts';
2929
import { EnvironmentType } from '../../../platform/pythonEnvironments/info';
30+
import { ITrustedKernelPaths } from './types';
3031

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

341343
case 'startUsingLocalKernelSpec':
344+
if (
345+
kernel.kernelSpec.specFile &&
346+
!this.trustedKernelSpecPaths.isTrusted(Uri.file(kernel.kernelSpec.specFile))
347+
) {
348+
return false;
349+
}
342350
// Spec files have to still exist and interpreters have to exist
343351
const promiseSpec = kernel.kernelSpec.specFile
344352
? this.fs.exists(Uri.file(kernel.kernelSpec.specFile))

src/kernels/raw/finder/localKernelSpecFinderBase.node.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from '../../../kernels/types';
2525
import { JupyterKernelSpec } from '../../jupyter/jupyterKernelSpec';
2626
import { getComparisonKey } from '../../../platform/vscode-path/resources';
27+
import { ITrustedKernelPaths } from './types';
2728
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
2829
const flatten = require('lodash/flatten') as typeof import('lodash/flatten');
2930

@@ -63,7 +64,8 @@ export abstract class LocalKernelSpecFinderBase {
6364
protected readonly fs: IFileSystemNode,
6465
protected readonly workspaceService: IWorkspaceService,
6566
protected readonly extensionChecker: IPythonExtensionChecker,
66-
protected readonly globalState: Memento
67+
protected readonly globalState: Memento,
68+
private readonly trustedKernelSpecPaths: ITrustedKernelPaths
6769
) {}
6870

6971
@testOnlyMethod()
@@ -137,6 +139,9 @@ export abstract class LocalKernelSpecFinderBase {
137139
globalSpecRootPath?: Uri,
138140
cancelToken?: CancellationToken
139141
): Promise<IJupyterKernelSpec | undefined> {
142+
if (!this.trustedKernelSpecPaths.isTrusted(specPath)) {
143+
return;
144+
}
140145
// This is a backup folder for old kernels created by us.
141146
if (specPath.fsPath.includes(oldKernelsSpecFolderName)) {
142147
return;

src/kernels/raw/finder/localKnownPathKernelSpecFinder.node.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { IFileSystemNode } from '../../../platform/common/platform/types.node';
2121
import { IMemento, GLOBAL_MEMENTO } from '../../../platform/common/types';
2222
import { capturePerfTelemetry, Telemetry } from '../../../telemetry';
2323
import { sendKernelSpecTelemetry } from './helper';
24+
import { ITrustedKernelPaths } from './types';
2425

2526
/**
2627
* This class searches for kernels on the file system in well known paths documented by Jupyter.
@@ -34,9 +35,10 @@ export class LocalKnownPathKernelSpecFinder extends LocalKernelSpecFinderBase {
3435
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
3536
@inject(JupyterPaths) private readonly jupyterPaths: JupyterPaths,
3637
@inject(IPythonExtensionChecker) extensionChecker: IPythonExtensionChecker,
37-
@inject(IMemento) @named(GLOBAL_MEMENTO) memento: Memento
38+
@inject(IMemento) @named(GLOBAL_MEMENTO) memento: Memento,
39+
@inject(ITrustedKernelPaths) trustedKernelSpecPaths: ITrustedKernelPaths
3840
) {
39-
super(fs, workspaceService, extensionChecker, memento);
41+
super(fs, workspaceService, extensionChecker, memento, trustedKernelSpecPaths);
4042
if (this.oldKernelSpecsFolder) {
4143
traceInfo(
4244
`Old kernelSpecs (created by Jupyter Extension) stored in directory ${this.oldKernelSpecsFolder}`

src/kernels/raw/finder/localPythonAndRelatedNonPythonKernelSpecFinder.node.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { areInterpreterPathsSame } from '../../../platform/pythonEnvironments/in
3333
import { capturePerfTelemetry, Telemetry } from '../../../telemetry';
3434
import { PythonEnvironment } from '../../../platform/pythonEnvironments/info';
3535
import { ResourceSet } from '../../../platform/vscode-path/map';
36+
import { ITrustedKernelPaths } from './types';
3637

3738
/**
3839
* Returns all Python kernels and any related kernels registered in the python environment.
@@ -52,9 +53,10 @@ export class LocalPythonAndRelatedNonPythonKernelSpecFinder extends LocalKernelS
5253
@inject(IPythonExtensionChecker) extensionChecker: IPythonExtensionChecker,
5354
@inject(LocalKnownPathKernelSpecFinder)
5455
private readonly kernelSpecsFromKnownLocations: LocalKnownPathKernelSpecFinder,
55-
@inject(IMemento) @named(GLOBAL_MEMENTO) globalState: Memento
56+
@inject(IMemento) @named(GLOBAL_MEMENTO) globalState: Memento,
57+
@inject(ITrustedKernelPaths) trustedKernelSpecPaths: ITrustedKernelPaths
5658
) {
57-
super(fs, workspaceService, extensionChecker, globalState);
59+
super(fs, workspaceService, extensionChecker, globalState, trustedKernelSpecPaths);
5860
}
5961
public async listKernelSpecs(resource: Resource, ignoreCache?: boolean, cancelToken?: CancellationToken) {
6062
// Get an id for the workspace folder, if we don't have one, use the fsPath of the resource
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { inject, injectable } from 'inversify';
5+
import * as path from '../../../platform/vscode-path/path';
6+
import { Uri } from 'vscode';
7+
import { IPlatformService } from '../../../platform/common/platform/types';
8+
import { ITrustedKernelPaths } from './types';
9+
import { IWorkspaceService } from '../../../platform/common/application/types';
10+
import { createDeferred } from '../../../platform/common/utils/async';
11+
12+
@injectable()
13+
export class TrustedKernelPaths implements ITrustedKernelPaths {
14+
public static IsKernelSpecHidden = createDeferred<boolean>();
15+
private readonly programData = process.env['PROGRAMDATA']
16+
? Uri.file(path.normalize(process.env['PROGRAMDATA']))
17+
: undefined;
18+
constructor(
19+
@inject(IPlatformService) private readonly platform: IPlatformService,
20+
@inject(IWorkspaceService) private readonly workspace: IWorkspaceService
21+
) {}
22+
private get trustedKernelSpecs(): string[] {
23+
return this.workspace.getConfiguration('jupyter', undefined).get<string[]>('kernels.trusted', []);
24+
}
25+
public isTrusted(kernelPath: Uri): boolean {
26+
const trusted = this.isTrustedImpl(kernelPath);
27+
if (!trusted && !TrustedKernelPaths.IsKernelSpecHidden.completed) {
28+
TrustedKernelPaths.IsKernelSpecHidden.resolve(true);
29+
}
30+
return trusted;
31+
}
32+
private isTrustedImpl(kernelPath: Uri): boolean {
33+
if (kernelPath.scheme !== 'file') {
34+
return true;
35+
}
36+
if (
37+
this.trustedKernelSpecs
38+
.map((p) => (this.platform.isWindows ? p.toLowerCase() : p))
39+
.map((p) => Uri.file(p).path)
40+
.includes(this.platform.isWindows ? kernelPath.path.toLowerCase() : kernelPath.path)
41+
) {
42+
return true;
43+
}
44+
if (this.platform.isWindows && this.programData) {
45+
return !kernelPath.path.toLowerCase().startsWith(this.programData.path.toLowerCase());
46+
}
47+
return true;
48+
}
49+
}

src/kernels/raw/finder/types.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { Uri } from 'vscode';
5+
6+
export const ITrustedKernelPaths = Symbol('ITrustedKernelPaths');
7+
export interface ITrustedKernelPaths {
8+
isTrusted(kernelPath: Uri): boolean;
9+
}

src/kernels/serviceRegistry.node.ts

+8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ import { KernelAutoReconnectMonitor } from './kernelAutoReConnectMonitor';
4545
import { PythonKernelInterruptDaemon } from './raw/finder/pythonKernelInterruptDaemon.node';
4646
import { LocalKernelFinder } from './raw/finder/localKernelFinder.node';
4747
import { DebugStartupCodeProvider } from './debuggerStartupCodeProvider';
48+
import { TrustedKernelPaths } from './raw/finder/trustedKernelSpecPaths.node';
49+
import { ITrustedKernelPaths } from './raw/finder/types';
50+
import { HiddenKernelNotification } from './hiddenKernelNotification.node';
4851

4952
export function registerTypes(serviceManager: IServiceManager, isDevMode: boolean) {
5053
serviceManager.addSingleton<IExtensionSingleActivationService>(IExtensionSingleActivationService, Activation);
@@ -53,6 +56,10 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea
5356
IExtensionSyncActivationService,
5457
PortAttributesProviders
5558
);
59+
serviceManager.addSingleton<IExtensionSyncActivationService>(
60+
IExtensionSyncActivationService,
61+
HiddenKernelNotification
62+
);
5663
serviceManager.addSingleton<IRawNotebookSupportedService>(
5764
IRawNotebookSupportedService,
5865
RawNotebookSupportedService
@@ -74,6 +81,7 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea
7481
);
7582

7683
serviceManager.addSingleton<JupyterPaths>(JupyterPaths, JupyterPaths);
84+
serviceManager.addSingleton<ITrustedKernelPaths>(ITrustedKernelPaths, TrustedKernelPaths);
7785
serviceManager.addSingleton<LocalKnownPathKernelSpecFinder>(
7886
LocalKnownPathKernelSpecFinder,
7987
LocalKnownPathKernelSpecFinder

src/platform/common/utils/localize.ts

+7
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,13 @@ export namespace DataScience {
537537
},
538538
'The kernel died. Error: {0}... View Jupyter [log](command:jupyter.viewOutput) for further details.'
539539
);
540+
export const untrustedKernelSpecsHidden = () =>
541+
localize(
542+
'DataScience.untrustedKernelSpecsHidden',
543+
'Kernels found in an insecure location have not been loaded.'
544+
);
545+
export const updateSettingToTrustKernelSpecs = () =>
546+
localize('DataScience.updateSettingToTrustKernelSpecs', 'Update setting to trust kernels');
540547
export const kernelDiedWithoutError = () =>
541548
localize(
542549
{

0 commit comments

Comments
 (0)