Bug 1760608 - Restrict MV2 pageAction/browserAction setPopup to same extension urls on GeckoView. r=mixedpuppy,geckoview-reviewers,owlish

This patch extends restricts setPopup to extension url to MV2 extensions running on GeckoView.

Differential Revision: https://phabricator.services.mozilla.com/D154549
This commit is contained in:
Luca Greco 2022-08-23 10:35:58 +00:00
parent 28400cd40e
commit 9efa813720
5 changed files with 135 additions and 2 deletions

View File

@ -157,6 +157,10 @@ pref("xpinstall.signatures.required", true);
// Use blocklist v2 until blocklist v3 is enabled on Android - bug 1639050
pref("extensions.blocklist.useMLBF", false);
// Whether MV3 restrictions for actions popup urls should be extended to MV2 extensions
// (only allowing same extension urls to be used as action popup urls).
pref("extensions.manifestV2.actionsPopupURLRestricted", true);
// Disable add-ons that are not installed by the user in all scopes by default (See the SCOPE
// constants in AddonManager.jsm for values to use here, and Bug 1405528 for a rationale).
pref("extensions.autoDisableScopes", 15);

View File

@ -36,6 +36,31 @@ function handlePageActionMessage(message, tabId) {
});
break;
case "setPopupCheckRestrictions":
browser.pageAction
.setPopup({
tabId,
popup: message.popup,
})
.then(
() => {
port.postMessage({
resultFor: "setPopup",
type: "pageAction",
success: true,
});
},
err => {
port.postMessage({
resultFor: "setPopup",
type: "pageAction",
success: false,
error: String(err),
});
}
);
break;
case "setTitle":
browser.pageAction.setTitle({
tabId,
@ -94,6 +119,31 @@ function handleBrowserActionMessage(message, tabId) {
});
break;
case "setPopupCheckRestrictions":
browser.browserAction
.setPopup({
tabId,
popup: message.popup,
})
.then(
() => {
port.postMessage({
resultFor: "setPopup",
type: "browserAction",
success: true,
});
},
err => {
port.postMessage({
resultFor: "setPopup",
type: "browserAction",
success: false,
error: String(err),
});
}
);
break;
case "setTitle":
browser.browserAction.setTitle({
tabId,

View File

@ -26,6 +26,7 @@ import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
@RunWith(Parameterized::class)
class ExtensionActionTest : BaseSessionTest() {
private var extension: WebExtension? = null
private var otherExtension: WebExtension? = null
private var default: WebExtension.Action? = null
private var backgroundPort: WebExtension.Port? = null
private var windowPort: WebExtension.Port? = null
@ -57,6 +58,10 @@ class ExtensionActionTest : BaseSessionTest() {
extension = sessionRule.waitForResult(
controller.installBuiltIn("resource://android/assets/web_extensions/actions/"));
// Another dummy extension, only used to check restrictions related to setting
// another extension url as a popup url, and so there is no delegate needed for it.
otherExtension = sessionRule.waitForResult(
controller.installBuiltIn("resource://android/assets/web_extensions/dummy/"));
mainSession.webExtensionController.setMessageDelegate(
extension!!,
@ -123,6 +128,10 @@ class ExtensionActionTest : BaseSessionTest() {
extension!!.setActionDelegate(null)
sessionRule.waitForResult(controller.uninstall(extension!!))
}
if (otherExtension != null) {
sessionRule.waitForResult(controller.uninstall(otherExtension!!))
}
}
private fun testBackgroundActionApi(message: String, tester: (WebExtension.Action) -> Unit) {
@ -163,6 +172,43 @@ class ExtensionActionTest : BaseSessionTest() {
sessionRule.waitForResult(result)
}
private fun testSetPopup(popupUrl: String, isUrlAllowed: Boolean) {
val setPopupResult = GeckoResult<Void>()
backgroundPort!!.setDelegate(object : WebExtension.PortDelegate {
override fun onPortMessage(message: Any, port: WebExtension.Port) {
val json = message as JSONObject
if (json.getString("resultFor") == "setPopup" &&
json.getString("type") == type) {
if (isUrlAllowed != json.getBoolean("success")) {
val expectedResString = when(isUrlAllowed) {
true -> "allowed"
else -> "disallowed"
};
setPopupResult.completeExceptionally(IllegalArgumentException(
"Expected \"${popupUrl}\" to be ${ expectedResString }"))
} else {
setPopupResult.complete(null)
}
} else {
// We should NOT receive the expected message result.
setPopupResult.completeExceptionally(IllegalArgumentException(
"Received unexpected result for: ${json.getString("type")} ${json.getString("resultFor")}"))
}
}
})
var json = JSONObject("""{
"action": "setPopupCheckRestrictions",
"popup": "$popupUrl"
}""");
json.put("type", type)
windowPort!!.postMessage(json)
sessionRule.waitForResult(setPopupResult)
}
private fun testActionApi(message: String, tester: (WebExtension.Action) -> Unit) {
val result = GeckoResult<Void>()
@ -477,6 +523,15 @@ class ExtensionActionTest : BaseSessionTest() {
sessionRule.waitForResult(error)
}
@Test
fun testSetPopupRestrictions() {
testSetPopup("https://example.com", false)
testSetPopup("${otherExtension!!.metaData.baseUrl}other-extension.html", false)
testSetPopup("${extension!!.metaData.baseUrl}same-extension.html", true)
testSetPopup("relative-url-01.html", true);
testSetPopup("/relative-url-02.html", true);
}
@Test
@GeckoSessionTestRule.WithDisplay(width=100, height=100)
fun testOpenPopup() {

View File

@ -2079,6 +2079,9 @@ pref("extensions.blocklist.addonItemURL", "https://addons.mozilla.org/%LOCALE%/%
pref("extensions.blocklist.level", 2);
// Whether event pages should be enabled for "manifest_version: 2" extensions.
pref("extensions.eventPages.enabled", false);
// Whether MV3 restrictions for actions popup urls should be extended to MV2 extensions
// (only allowing same extension urls to be used as action popup urls).
pref("extensions.manifestV2.actionsPopupURLRestricted", false);
// Whether "manifest_version: 3" extensions should be allowed to install successfully.
pref("extensions.manifestV3.enabled", false);
// Whether to enable the unified extensions feature.

View File

@ -16,6 +16,19 @@ const { ExtensionParent } = ChromeUtils.import(
);
const { IconDetails, StartupCache } = ExtensionParent;
const { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"MV2_ACTION_POPURL_RESTRICTED",
"extensions.manifestV2.actionsPopupURLRestricted",
false
);
function parseColor(color, kind) {
if (typeof color == "string") {
let rgba = InspectorUtils.colorToRGBA(color);
@ -265,10 +278,18 @@ class PanelActionBase {
// On manifest_version 3 is mandatory for the resolved URI to belong to the
// current extension (see Bug 1760608).
//
// The same restriction is extended extend to MV2 extensions if the
// "extensions.manifestV2.actionsPopupURLRestricted" preference is set to true.
//
// (Currently set to true by default on GeckoView builds, where the set of
// extensions supported is limited to a small set and so less risks of
// unexpected regressions for the existing extensions).
if (
context.extension.manifestVersion >= 3 &&
url &&
!url.startsWith(extension.baseURI.spec)
!url.startsWith(extension.baseURI.spec) &&
(context.extension.manifestVersion >= 3 ||
lazy.MV2_ACTION_POPURL_RESTRICTED)
) {
return Promise.reject({ message: `Access denied for URL ${url}` });
}