From cdcca1ad4ef86413b2db3cc5ce546b1a50f91e9e Mon Sep 17 00:00:00 2001 From: Philip Pfaffe Date: Fri, 28 Mar 2025 10:17:38 +0000 Subject: [PATCH] Check file access on the host side of the extension api Fixed: 406034851 Change-Id: I125bfa572ba9e987569e8524da99de84db83d389 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6401296 Reviewed-by: Danil Somsikov Commit-Queue: Philip Pfaffe --- front_end/models/extensions/ExtensionAPI.ts | 31 +------ .../models/extensions/ExtensionServer.test.ts | 9 +- .../models/extensions/ExtensionServer.ts | 93 +++++++++++-------- 3 files changed, 59 insertions(+), 74 deletions(-) diff --git a/front_end/models/extensions/ExtensionAPI.ts b/front_end/models/extensions/ExtensionAPI.ts index 9529380cf3..86cda66425 100644 --- a/front_end/models/extensions/ExtensionAPI.ts +++ b/front_end/models/extensions/ExtensionAPI.ts @@ -699,9 +699,7 @@ self.injectedExtensionAPI = function( userAction = true; try { const {resource, lineNumber} = message as {resource: APIImpl.ResourceData, lineNumber: number}; - if (canAccessResource(resource)) { - callback.call(null, new (Constructor(Resource))(resource), lineNumber); - } + callback.call(null, new (Constructor(Resource))(resource), lineNumber); } finally { userAction = false; } @@ -1228,29 +1226,10 @@ self.injectedExtensionAPI = function( }, }; - const protocolGet = Object.getOwnPropertyDescriptor(URL.prototype, 'protocol')?.get; - function getProtocol(url: string): string { - if (!protocolGet) { - throw new Error('URL.protocol is not available'); - } - return protocolGet.call(new URL(url)); - } - - function canAccessResource(resource: APIImpl.ResourceData): boolean { - try { - return extensionInfo.allowFileAccess || getProtocol(resource.url) !== 'file:'; - } catch { - return false; - } - } - function InspectedWindow(this: PublicAPI.Chrome.DevTools.InspectedWindow): void { function dispatchResourceEvent( this: APIImpl.EventSink<(resource: APIImpl.Resource) => unknown>, message: {arguments: unknown[]}): void { const resourceData = message.arguments[0] as APIImpl.ResourceData; - if (!canAccessResource(resourceData)) { - return; - } this._fire(new (Constructor(Resource))(resourceData)); } @@ -1258,9 +1237,6 @@ self.injectedExtensionAPI = function( this: APIImpl.EventSink<(resource: APIImpl.Resource, content: string) => unknown>, message: {arguments: unknown[]}): void { const resourceData = message.arguments[0] as APIImpl.ResourceData; - if (!canAccessResource(resourceData)) { - return; - } this._fire(new (Constructor(Resource))(resourceData), message.arguments[1] as string); } @@ -1323,16 +1299,13 @@ self.injectedExtensionAPI = function( return new (Constructor(Resource))(resourceData); } function callbackWrapper(resources: unknown): void { - callback?.((resources as APIImpl.ResourceData[]).filter(canAccessResource).map(wrapResource)); + callback?.((resources as APIImpl.ResourceData[]).map(wrapResource)); } extensionServer.sendRequest({command: PrivateAPI.Commands.GetPageResources}, callback && callbackWrapper); }, }; function ResourceImpl(this: APIImpl.Resource, resourceData: APIImpl.ResourceData): void { - if (!canAccessResource(resourceData)) { - throw new Error('Resource access not allowed'); - } this._url = resourceData.url; this._type = resourceData.type; } diff --git a/front_end/models/extensions/ExtensionServer.test.ts b/front_end/models/extensions/ExtensionServer.test.ts index 32701ca3f6..0d4fc93c4d 100644 --- a/front_end/models/extensions/ExtensionServer.test.ts +++ b/front_end/models/extensions/ExtensionServer.test.ts @@ -618,14 +618,13 @@ describeWithDevtoolsExtension('Runtime hosts policy', {hostsPolicy}, context => assert.exists(context.chrome.devtools); const resources = await new Promise(r => context.chrome.devtools?.inspectedWindow.getResources(r)); - assert.deepEqual(resources.map(r => r.url), [blockedUrl, allowedUrl]); + assert.deepEqual(resources.map(r => r.url), [allowedUrl]); const resourceContents = await Promise.all(resources.map( resource => new Promise<{url: string, content?: string, encoding?: string}>( r => resource.getContent((content, encoding) => r({url: resource.url, content, encoding}))))); assert.deepEqual(resourceContents, [ - {url: blockedUrl, content: undefined, encoding: undefined}, {url: allowedUrl, content: 'content', encoding: ''}, ]); }); @@ -683,7 +682,7 @@ describeWithDevtoolsExtension('Runtime hosts policy', {hostsPolicy}, context => assert.exists(context.chrome.devtools); const resources = await new Promise(r => context.chrome.devtools?.inspectedWindow.getResources(r)); - assert.deepEqual(resources.map(r => r.url), [blockedUrl, allowedUrl]); + assert.deepEqual(resources.map(r => r.url), [allowedUrl]); assert.deepEqual(project.uiSourceCodeForURL(allowedUrl)?.content(), 'content'); assert.deepEqual(project.uiSourceCodeForURL(blockedUrl)?.content(), 'content'); @@ -691,8 +690,8 @@ describeWithDevtoolsExtension('Runtime hosts policy', {hostsPolicy}, context => resource => new Promise(r => resource.setContent('modified', true, r)))) as Array; - assert.deepEqual(responses.map(response => response?.code), ['E_FAILED', 'OK']); - assert.deepEqual(responses.map(response => response?.details), [['Permission denied'], []]); + assert.deepEqual(responses.map(response => response?.code), ['OK']); + assert.deepEqual(responses.map(response => response?.details), [[]]); assert.deepEqual(project.uiSourceCodeForURL(allowedUrl)?.content(), 'modified'); assert.deepEqual(project.uiSourceCodeForURL(blockedUrl)?.content(), 'content'); diff --git a/front_end/models/extensions/ExtensionServer.ts b/front_end/models/extensions/ExtensionServer.ts index 7c3fe908eb..885965835b 100644 --- a/front_end/models/extensions/ExtensionServer.ts +++ b/front_end/models/extensions/ExtensionServer.ts @@ -281,27 +281,27 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper this.addExtension(e)); } @@ -536,20 +539,7 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper boolean): void { if (!this.extensionsEnabled) { return; } @@ -557,11 +547,19 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper(); function pushResourceData( this: ExtensionServer, contentProvider: TextUtils.ContentProvider.ContentProvider): boolean { - if (!resources.has(contentProvider.contentURL())) { + if (!resources.has(contentProvider.contentURL()) && + this.extensionAllowedOnURL(contentProvider.contentURL(), port)) { resources.set(contentProvider.contentURL(), this.makeResource(contentProvider)); } return false; @@ -999,7 +1000,8 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper): void { const uiSourceCode = event.data; - this.postNotification(PrivateAPI.Events.ResourceAdded, this.makeResource(uiSourceCode)); + this.postNotification( + PrivateAPI.Events.ResourceAdded, [this.makeResource(uiSourceCode)], + extension => extension.isAllowedOnTarget(uiSourceCode.url())); } private notifyUISourceCodeContentCommitted( event: Common.EventTarget.EventTargetEvent): void { const {uiSourceCode, content} = event.data; - this.postNotification(PrivateAPI.Events.ResourceContentCommitted, this.makeResource(uiSourceCode), content); + this.postNotification( + PrivateAPI.Events.ResourceContentCommitted, [this.makeResource(uiSourceCode), content], + extension => extension.isAllowedOnTarget(uiSourceCode.url())); } private async notifyRequestFinished(event: Common.EventTarget.EventTargetEvent): Promise { const request = event.data; const entry = await HAR.Log.Entry.build(request, {sanitize: false}); - this.postNotification(PrivateAPI.Events.NetworkRequestFinished, this.requestId(request), entry); + this.postNotification( + PrivateAPI.Events.NetworkRequestFinished, [this.requestId(request), entry], + extension => extension.isAllowedOnTarget(entry.request.url)); } private notifyElementsSelectionChanged(): void { - this.postNotification(PrivateAPI.Events.PanelObjectSelected + 'elements'); + this.postNotification(PrivateAPI.Events.PanelObjectSelected + 'elements', []); } sourceSelectionChanged(url: Platform.DevToolsPath.UrlString, range: TextUtils.TextRange.TextRange): void { - this.postNotification(PrivateAPI.Events.PanelObjectSelected + 'sources', { - startLine: range.startLine, - startColumn: range.startColumn, - endLine: range.endLine, - endColumn: range.endColumn, - url, - }); + this.postNotification( + PrivateAPI.Events.PanelObjectSelected + 'sources', [{ + startLine: range.startLine, + startColumn: range.startColumn, + endLine: range.endLine, + endColumn: range.endColumn, + url, + }], + extension => extension.isAllowedOnTarget(url)); } private setInspectedTabId(event: Common.EventTarget.EventTargetEvent): void {