Skip to content

Commit 4e6f06d

Browse files
authored
disable, restart extension host, uninstall and remove problematic extension (microsoft#242249)
* disable, restart extension host, uninstall and remove problematic extension * fix tests * fix tests
1 parent 715bdfa commit 4e6f06d

File tree

12 files changed

+144
-95
lines changed

12 files changed

+144
-95
lines changed

src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -723,14 +723,22 @@ export abstract class AbstractExtensionManagementService extends CommontExtensio
723723
this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString());
724724
}
725725
reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', { extensionData: getLocalExtensionTelemetryData(extension), error });
726-
this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: error?.code, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped });
726+
this._onDidUninstallExtension.fire({ identifier: extension.identifier, context: uninstallOptions.context, error: error?.code, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped });
727727
};
728728

729729
const allTasks: IUninstallExtensionTask[] = [];
730730
const processedTasks: IUninstallExtensionTask[] = [];
731731
const alreadyRequestedUninstalls: Promise<any>[] = [];
732+
const extensionsToRemove: ILocalExtension[] = [];
732733

733734
const installedExtensionsMap = new ResourceMap<ILocalExtension[]>();
735+
const getInstalledExtensions = async (profileLocation: URI) => {
736+
let installed = installedExtensionsMap.get(profileLocation);
737+
if (!installed) {
738+
installedExtensionsMap.set(profileLocation, installed = await this.getInstalled(ExtensionType.User, profileLocation));
739+
}
740+
return installed;
741+
};
734742

735743
for (const { extension, options } of extensions) {
736744
const uninstallOptions: UninstallExtensionTaskOptions = {
@@ -744,14 +752,32 @@ export abstract class AbstractExtensionManagementService extends CommontExtensio
744752
} else {
745753
allTasks.push(createUninstallExtensionTask(extension, uninstallOptions));
746754
}
755+
756+
if (uninstallOptions.remove) {
757+
extensionsToRemove.push(extension);
758+
for (const profile of this.userDataProfilesService.profiles) {
759+
if (this.uriIdentityService.extUri.isEqual(profile.extensionsResource, uninstallOptions.profileLocation)) {
760+
continue;
761+
}
762+
const installed = await getInstalledExtensions(profile.extensionsResource);
763+
const profileExtension = installed.find(e => areSameExtensions(e.identifier, extension.identifier));
764+
if (profileExtension) {
765+
const uninstallOptionsWithProfile = { ...uninstallOptions, profileLocation: profile.extensionsResource };
766+
const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(profileExtension, uninstallOptionsWithProfile));
767+
if (uninstallExtensionTask) {
768+
this.logService.info('Extensions is already requested to uninstall', profileExtension.identifier.id);
769+
alreadyRequestedUninstalls.push(uninstallExtensionTask.waitUntilTaskIsFinished());
770+
} else {
771+
allTasks.push(createUninstallExtensionTask(profileExtension, uninstallOptionsWithProfile));
772+
}
773+
}
774+
}
775+
}
747776
}
748777

749778
try {
750779
for (const task of allTasks.slice(0)) {
751-
let installed = installedExtensionsMap.get(task.options.profileLocation);
752-
if (!installed) {
753-
installedExtensionsMap.set(task.options.profileLocation, installed = await this.getInstalled(ExtensionType.User, task.options.profileLocation));
754-
}
780+
const installed = await getInstalledExtensions(task.options.profileLocation);
755781

756782
if (task.options.donotIncludePack) {
757783
this.logService.info('Uninstalling the extension without including packed extension', `${task.extension.identifier.id}@${task.extension.manifest.version}`);
@@ -799,6 +825,10 @@ export abstract class AbstractExtensionManagementService extends CommontExtensio
799825
for (const task of allTasks) {
800826
postUninstallExtension(task.extension, task.options);
801827
}
828+
829+
if (extensionsToRemove.length) {
830+
await this.joinAllSettled(extensionsToRemove.map(extension => this.removeExtension(extension)));
831+
}
802832
} catch (e) {
803833
const error = toExtensionManagementError(e);
804834
for (const task of allTasks) {
@@ -895,6 +925,7 @@ export abstract class AbstractExtensionManagementService extends CommontExtensio
895925
protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask;
896926
protected abstract createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask;
897927
protected abstract copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata?: Partial<Metadata>): Promise<ILocalExtension>;
928+
protected abstract removeExtension(extension: ILocalExtension): Promise<void>;
898929
}
899930

900931
export function toExtensionManagementError(error: Error, code?: ExtensionManagementErrorCode): ExtensionManagementError {

src/vs/platform/extensionManagement/common/extensionManagement.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const EXTENSION_INSTALL_SKIP_PUBLISHER_TRUST_CONTEXT = 'skipPublisherTrus
2323
export const EXTENSION_INSTALL_SOURCE_CONTEXT = 'extensionInstallSource';
2424
export const EXTENSION_INSTALL_DEP_PACK_CONTEXT = 'dependecyOrPackExtensionInstall';
2525
export const EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT = 'clientTargetPlatform';
26+
export const EXTENSION_UNINSTALL_MALICIOUS_CONTEXT = 'uninstallMaliciousExtension';
2627

2728
export const enum ExtensionInstallSource {
2829
COMMAND = 'command',
@@ -415,6 +416,7 @@ export interface UninstallExtensionEvent {
415416

416417
export interface DidUninstallExtensionEvent {
417418
readonly identifier: IExtensionIdentifier;
419+
readonly context?: IStringDictionary<any>;
418420
readonly error?: string;
419421
readonly profileLocation: URI;
420422
readonly applicationScoped?: boolean;
@@ -549,6 +551,10 @@ export type UninstallOptions = {
549551
readonly donotCheckDependents?: boolean;
550552
readonly versionOnly?: boolean;
551553
readonly remove?: boolean;
554+
/**
555+
* Context passed through to DidUninstallExtensionEvent
556+
*/
557+
context?: IStringDictionary<any>;
552558
};
553559

554560
export interface IExtensionManagementParticipant {

src/vs/platform/extensionManagement/node/extensionManagementService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
214214
return local;
215215
}
216216

217+
protected removeExtension(extension: ILocalExtension): Promise<void> {
218+
return this.extensionsScanner.deleteExtension(extension, 'remove');
219+
}
220+
217221
protected copyExtension(extension: ILocalExtension, fromProfileLocation: URI, toProfileLocation: URI, metadata: Partial<Metadata>): Promise<ILocalExtension> {
218222
return this.extensionsScanner.copyExtension(extension, fromProfileLocation, toProfileLocation, metadata);
219223
}

src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer,
1717
import { InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction, ToggleAutoUpdateForExtensionAction, ToggleAutoUpdatesForPublisherAction, TogglePreReleaseExtensionAction, InstallAnotherVersionAction, InstallAction } from './extensionsActions.js';
1818
import { ExtensionsInput } from '../common/extensionsInput.js';
1919
import { ExtensionEditor } from './extensionEditor.js';
20-
import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer, BuiltInExtensionsContext, SearchMarketplaceExtensionsContext, RecommendedExtensionsContext, DefaultViewsContext, ExtensionsSortByContext, SearchHasTextContext, ExtensionsSearchValueContext } from './extensionsViewlet.js';
20+
import { StatusUpdater, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer, BuiltInExtensionsContext, SearchMarketplaceExtensionsContext, RecommendedExtensionsContext, DefaultViewsContext, ExtensionsSortByContext, SearchHasTextContext, ExtensionsSearchValueContext } from './extensionsViewlet.js';
2121
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from '../../../../platform/configuration/common/configurationRegistry.js';
2222
import * as jsonContributionRegistry from '../../../../platform/jsonschemas/common/jsonContributionRegistry.js';
2323
import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from '../common/extensionsFileTemplate.js';
@@ -1670,7 +1670,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
16701670
title: localize('download VSIX', "Download VSIX"),
16711671
menu: {
16721672
id: MenuId.ExtensionContext,
1673-
when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension')),
1673+
when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.not('extensionDisallowInstall'), ContextKeyExpr.has('isGalleryExtension')),
16741674
order: this.productService.quality === 'stable' ? 0 : 1
16751675
},
16761676
run: async (accessor: ServicesAccessor, extensionId: string) => {
@@ -1683,7 +1683,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
16831683
title: localize('download pre-release', "Download Pre-Release VSIX"),
16841684
menu: {
16851685
id: MenuId.ExtensionContext,
1686-
when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('extensionHasPreReleaseVersion')),
1686+
when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.not('extensionDisallowInstall'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('extensionHasPreReleaseVersion')),
16871687
order: this.productService.quality === 'stable' ? 1 : 0
16881688
},
16891689
run: async (accessor: ServicesAccessor, extensionId: string) => {
@@ -1997,7 +1997,6 @@ class TrustedPublishersInitializer implements IWorkbenchContribution {
19971997
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
19981998
workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Restored);
19991999
workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Eventually);
2000-
workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually);
20012000
workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Restored);
20022001
workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Restored);
20032002
workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually);

src/vs/workbench/contrib/extensions/browser/extensionsActions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1250,7 +1250,7 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n
12501250
cksOverlay.push(['galleryExtensionHasPreReleaseVersion', extension.gallery?.hasPreReleaseVersion]);
12511251
cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]);
12521252
cksOverlay.push(['extensionHasReleaseVersion', extension.hasReleaseVersion]);
1253-
cksOverlay.push(['extensionDisallowInstall', !!extension.deprecationInfo?.disallowInstall]);
1253+
cksOverlay.push(['extensionDisallowInstall', extension.isMalicious || extension.deprecationInfo?.disallowInstall]);
12541254
cksOverlay.push(['isExtensionAllowed', allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }) === true]);
12551255
cksOverlay.push(['isPreReleaseExtensionAllowed', allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName, prerelease: true }) === true]);
12561256
cksOverlay.push(['extensionIsUnsigned', extension.gallery && !extension.gallery.isSigned]);

src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts

Lines changed: 3 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import './media/extensionsViewlet.css';
77
import { localize, localize2 } from '../../../../nls.js';
8-
import { timeout, Delayer, Promises } from '../../../../base/common/async.js';
8+
import { Delayer } from '../../../../base/common/async.js';
99
import { isCancellationError } from '../../../../base/common/errors.js';
1010
import { createErrorWithActions } from '../../../../base/common/errorMessage.js';
1111
import { IWorkbenchContribution } from '../../../common/contributions.js';
@@ -18,7 +18,6 @@ import { IInstantiationService, ServicesAccessor } from '../../../../platform/in
1818
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
1919
import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, AutoCheckUpdatesConfigurationKey, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, extensionsSearchActionsMenu, AutoRestartConfigurationKey } from '../common/extensions.js';
2020
import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAction } from './extensionsActions.js';
21-
import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js';
2221
import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from '../../../services/extensionManagement/common/extensionManagement.js';
2322
import { ExtensionsInput } from '../common/extensionsInput.js';
2423
import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView, VirtualWorkspacePartiallySupportedExtensionsView, DefaultPopularExtensionsView, DeprecatedExtensionsView, SearchMarketplaceExtensionsView, RecentlyUpdatedExtensionsView, OutdatedExtensionsView, StaticQueryExtensionsView, NONE_CATEGORY } from './extensionsViews.js';
@@ -34,22 +33,20 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/
3433
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from '../../../../platform/contextkey/common/contextkey.js';
3534
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
3635
import { ILogService } from '../../../../platform/log/common/log.js';
37-
import { INotificationService, NotificationPriority } from '../../../../platform/notification/common/notification.js';
38-
import { IHostService } from '../../../services/host/browser/host.js';
36+
import { INotificationService } from '../../../../platform/notification/common/notification.js';
3937
import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';
4038
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
4139
import { ViewPane } from '../../../browser/parts/views/viewPane.js';
4240
import { Query } from '../common/extensionQuery.js';
4341
import { SuggestEnabledInput } from '../../codeEditor/browser/suggestEnabledInput/suggestEnabledInput.js';
4442
import { alert } from '../../../../base/browser/ui/aria/aria.js';
45-
import { EXTENSION_CATEGORIES, ExtensionType } from '../../../../platform/extensions/common/extensions.js';
43+
import { EXTENSION_CATEGORIES } from '../../../../platform/extensions/common/extensions.js';
4644
import { Registry } from '../../../../platform/registry/common/platform.js';
4745
import { ILabelService } from '../../../../platform/label/common/label.js';
4846
import { MementoObject } from '../../../common/memento.js';
4947
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
5048
import { IPreferencesService } from '../../../services/preferences/common/preferences.js';
5149
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js';
52-
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
5350
import { VirtualWorkspaceContext, WorkbenchStateContext } from '../../../common/contextkeys.js';
5451
import { ICommandService } from '../../../../platform/commands/common/commands.js';
5552
import { installLocalInRemoteIcon } from './extensionsIcons.js';
@@ -59,7 +56,6 @@ import { IPaneCompositePartService } from '../../../services/panecomposite/brows
5956
import { coalesce } from '../../../../base/common/arrays.js';
6057
import { extractEditorsAndFilesDropData } from '../../../../platform/dnd/browser/dnd.js';
6158
import { extname } from '../../../../base/common/resources.js';
62-
import { isMalicious } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
6359
import { ILocalizedString } from '../../../../platform/action/common/action.js';
6460
import { registerNavigableContainer } from '../../../browser/actions/widgetNavigationCommands.js';
6561
import { MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';
@@ -967,52 +963,3 @@ export class StatusUpdater extends Disposable implements IWorkbenchContribution
967963
}
968964
}
969965
}
970-
971-
export class MaliciousExtensionChecker implements IWorkbenchContribution {
972-
973-
constructor(
974-
@IExtensionManagementService private readonly extensionsManagementService: IExtensionManagementService,
975-
@IHostService private readonly hostService: IHostService,
976-
@ILogService private readonly logService: ILogService,
977-
@INotificationService private readonly notificationService: INotificationService,
978-
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
979-
) {
980-
if (!this.environmentService.disableExtensions) {
981-
this.loopCheckForMaliciousExtensions();
982-
}
983-
}
984-
985-
private loopCheckForMaliciousExtensions(): void {
986-
this.checkForMaliciousExtensions()
987-
.then(() => timeout(1000 * 60 * 5)) // every five minutes
988-
.then(() => this.loopCheckForMaliciousExtensions());
989-
}
990-
991-
private checkForMaliciousExtensions(): Promise<void> {
992-
return this.extensionsManagementService.getExtensionsControlManifest().then(extensionsControlManifest => {
993-
994-
return this.extensionsManagementService.getInstalled(ExtensionType.User).then(installed => {
995-
const maliciousExtensions = installed.filter(e => isMalicious(e.identifier, extensionsControlManifest));
996-
997-
if (maliciousExtensions.length) {
998-
return Promises.settled(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e).then(() => {
999-
this.notificationService.prompt(
1000-
Severity.Warning,
1001-
localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", e.identifier.id),
1002-
[{
1003-
label: localize('reloadNow', "Reload Now"),
1004-
run: () => this.hostService.reload()
1005-
}],
1006-
{
1007-
sticky: true,
1008-
priority: NotificationPriority.URGENT
1009-
}
1010-
);
1011-
})));
1012-
} else {
1013-
return Promise.resolve(undefined);
1014-
}
1015-
}).then(() => undefined);
1016-
}, err => this.logService.error(err));
1017-
}
1018-
}

0 commit comments

Comments
 (0)