зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1072980 - Don't allow CPOWs to be passed to C++ code (r=mrbkap,ally,mconley)
This commit is contained in:
Родитель
082d988ab6
Коммит
94aebaaa39
|
@ -3760,13 +3760,14 @@ function FillHistoryMenu(aParent) {
|
|||
let item = document.createElement("menuitem");
|
||||
let entry = sessionHistory.getEntryAtIndex(j, false);
|
||||
let uri = entry.URI.spec;
|
||||
let uriCopy = BrowserUtils.makeURI(uri);
|
||||
|
||||
item.setAttribute("uri", uri);
|
||||
item.setAttribute("label", entry.title || uri);
|
||||
item.setAttribute("index", j);
|
||||
|
||||
if (j != index) {
|
||||
PlacesUtils.favicons.getFaviconURLForPage(entry.URI, function (aURI) {
|
||||
PlacesUtils.favicons.getFaviconURLForPage(uriCopy, function (aURI) {
|
||||
if (aURI) {
|
||||
let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec;
|
||||
iconURL = PlacesUtils.getImageURLForResolution(window, iconURL);
|
||||
|
|
|
@ -162,7 +162,10 @@ let handleContentContextMenu = function (event) {
|
|||
}
|
||||
|
||||
let customMenuItems = PageMenuChild.build(event.target);
|
||||
sendSyncMessage("contextmenu", { editFlags, spellInfo, customMenuItems, addonInfo }, { event, popupNode: event.target });
|
||||
let principal = event.target.ownerDocument.nodePrincipal;
|
||||
sendSyncMessage("contextmenu",
|
||||
{ editFlags, spellInfo, customMenuItems, addonInfo, principal },
|
||||
{ event, popupNode: event.target });
|
||||
}
|
||||
else {
|
||||
// Break out to the parent window and pass the add-on info along
|
||||
|
|
|
@ -596,6 +596,7 @@ nsContextMenu.prototype = {
|
|||
// gContextMenuContentData instead.
|
||||
if (this.isRemote) {
|
||||
this.browser = gContextMenuContentData.browser;
|
||||
this.principal = gContextMenuContentData.principal;
|
||||
} else {
|
||||
editFlags = SpellCheckHelper.isEditable(this.target, window);
|
||||
this.browser = this.target.ownerDocument.defaultView
|
||||
|
@ -603,6 +604,7 @@ nsContextMenu.prototype = {
|
|||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler;
|
||||
this.principal = this.target.ownerDocument.nodePrincipal;
|
||||
}
|
||||
this.onSocial = !!this.browser.getAttribute("origin");
|
||||
|
||||
|
@ -834,18 +836,6 @@ nsContextMenu.prototype = {
|
|||
this.linkProtocol == "snews" );
|
||||
},
|
||||
|
||||
_unremotePrincipal: function(aRemotePrincipal) {
|
||||
if (this.isRemote) {
|
||||
return Cc["@mozilla.org/scriptsecuritymanager;1"]
|
||||
.getService(Ci.nsIScriptSecurityManager)
|
||||
.getAppCodebasePrincipal(aRemotePrincipal.URI,
|
||||
aRemotePrincipal.appId,
|
||||
aRemotePrincipal.isInBrowserElement);
|
||||
}
|
||||
|
||||
return aRemotePrincipal;
|
||||
},
|
||||
|
||||
_isSpellCheckEnabled: function(aNode) {
|
||||
// We can always force-enable spellchecking on textboxes
|
||||
if (this.isTargetATextBox(aNode)) {
|
||||
|
@ -875,14 +865,14 @@ nsContextMenu.prototype = {
|
|||
// Open linked-to URL in a new window.
|
||||
openLink : function () {
|
||||
var doc = this.target.ownerDocument;
|
||||
urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
|
||||
urlSecurityCheck(this.linkURL, this.principal);
|
||||
openLinkIn(this.linkURL, "window", this._openLinkInParameters(doc));
|
||||
},
|
||||
|
||||
// Open linked-to URL in a new private window.
|
||||
openLinkInPrivateWindow : function () {
|
||||
var doc = this.target.ownerDocument;
|
||||
urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
|
||||
urlSecurityCheck(this.linkURL, this.principal);
|
||||
openLinkIn(this.linkURL, "window",
|
||||
this._openLinkInParameters(doc, { private: true }));
|
||||
},
|
||||
|
@ -890,7 +880,7 @@ nsContextMenu.prototype = {
|
|||
// Open linked-to URL in a new tab.
|
||||
openLinkInTab: function() {
|
||||
var doc = this.target.ownerDocument;
|
||||
urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
|
||||
urlSecurityCheck(this.linkURL, this.principal);
|
||||
var referrerURI = doc.documentURIObject;
|
||||
|
||||
// if the mixedContentChannel is present and the referring URI passes
|
||||
|
@ -917,7 +907,7 @@ nsContextMenu.prototype = {
|
|||
// open URL in current tab
|
||||
openLinkInCurrent: function() {
|
||||
var doc = this.target.ownerDocument;
|
||||
urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
|
||||
urlSecurityCheck(this.linkURL, this.principal);
|
||||
openLinkIn(this.linkURL, "current", this._openLinkInParameters(doc));
|
||||
},
|
||||
|
||||
|
@ -1125,8 +1115,7 @@ nsContextMenu.prototype = {
|
|||
return;
|
||||
|
||||
var doc = this.target.ownerDocument;
|
||||
urlSecurityCheck(this.target.currentURI.spec,
|
||||
this._unremotePrincipal(doc.nodePrincipal));
|
||||
urlSecurityCheck(this.target.currentURI.spec, this.principal);
|
||||
|
||||
// Confirm since it's annoying if you hit this accidentally.
|
||||
const kDesktopBackgroundURL =
|
||||
|
@ -1303,7 +1292,7 @@ nsContextMenu.prototype = {
|
|||
linkText = this.focusedWindow.getSelection().toString().trim();
|
||||
else
|
||||
linkText = this.linkText();
|
||||
urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal));
|
||||
urlSecurityCheck(this.linkURL, this.principal);
|
||||
|
||||
this.saveHelper(this.linkURL, linkText, null, true, doc);
|
||||
},
|
||||
|
@ -1323,14 +1312,12 @@ nsContextMenu.prototype = {
|
|||
true, false, doc.documentURIObject, doc);
|
||||
}
|
||||
else if (this.onImage) {
|
||||
urlSecurityCheck(this.mediaURL,
|
||||
this._unremotePrincipal(doc.nodePrincipal));
|
||||
urlSecurityCheck(this.mediaURL, this.principal);
|
||||
saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
|
||||
false, doc.documentURIObject, doc);
|
||||
}
|
||||
else if (this.onVideo || this.onAudio) {
|
||||
urlSecurityCheck(this.mediaURL,
|
||||
this._unremotePrincipal(doc.nodePrincipal));
|
||||
urlSecurityCheck(this.mediaURL, this.principal);
|
||||
var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
|
||||
this.saveHelper(this.mediaURL, null, dialogTitle, false, doc);
|
||||
}
|
||||
|
|
|
@ -3171,6 +3171,7 @@
|
|||
browser: browser,
|
||||
editFlags: aMessage.data.editFlags,
|
||||
spellInfo: spellInfo,
|
||||
principal: aMessage.data.principal,
|
||||
customMenuItems: aMessage.data.customMenuItems,
|
||||
addonInfo: aMessage.data.addonInfo };
|
||||
let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
|
||||
|
|
|
@ -42,9 +42,9 @@ function checkState(tab) {
|
|||
// deserialized in the content scope. And in this case, since RegExps are
|
||||
// not currently Xrayable (see bug 1014991), trying to pull |obj3| (a RegExp)
|
||||
// off of an Xrayed Object won't work. So we need to waive.
|
||||
runInContent(tab.linkedBrowser, function(win, event) {
|
||||
return Cu.waiveXrays(event.state).obj3.toString();
|
||||
}, aEvent).then(function(stateStr) {
|
||||
runInContent(tab.linkedBrowser, function(win, state) {
|
||||
return Cu.waiveXrays(state).obj3.toString();
|
||||
}, aEvent.state).then(function(stateStr) {
|
||||
is(stateStr, '/^a$/', "second popstate object.");
|
||||
|
||||
// Make sure that the new-elem node is present in the document. If it's
|
||||
|
|
|
@ -197,6 +197,24 @@
|
|||
let savedElement = null;
|
||||
function recvDomTest(message) {
|
||||
savedElement = message.objects.element;
|
||||
|
||||
// Test to ensure that we don't pass CPOWs to C++-implemented interfaces.
|
||||
// See bug 1072980.
|
||||
if (test_state == "remote") {
|
||||
let walker = Components.classes["@mozilla.org/inspector/deep-tree-walker;1"]
|
||||
.createInstance(Components.interfaces.inIDeepTreeWalker);
|
||||
const SHOW_ELEMENT = Components.interfaces.nsIDOMNodeFilter.SHOW_ELEMENT;
|
||||
walker.showAnonymousContent = true;
|
||||
walker.showSubDocuments = false;
|
||||
|
||||
try {
|
||||
walker.init(savedElement, SHOW_ELEMENT);
|
||||
ok(false, "expected exception passing CPOW to C++");
|
||||
} catch (e) {
|
||||
is(e.result, Components.results.NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE,
|
||||
"got exception when passing CPOW to C++");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function recvDomTestAfterGC(message) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
/* Wrapper object for reflecting native xpcom objects into JavaScript. */
|
||||
|
||||
#include "xpcprivate.h"
|
||||
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
|
||||
#include "nsWrapperCacheInlines.h"
|
||||
#include "XPCLog.h"
|
||||
#include "jsprf.h"
|
||||
|
@ -2171,6 +2172,21 @@ CallMethodHelper::ConvertIndependentParam(uint8_t i)
|
|||
return false;
|
||||
}
|
||||
|
||||
// Don't allow CPOWs to be passed to native code (in case they try to cast
|
||||
// to a concrete type).
|
||||
if (src.isObject() &&
|
||||
jsipc::IsWrappedCPOW(&src.toObject()) &&
|
||||
type_tag == nsXPTType::T_INTERFACE &&
|
||||
!param_iid.Equals(NS_GET_IID(nsISupports)))
|
||||
{
|
||||
// Allow passing CPOWs to XPCWrappedJS.
|
||||
nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS(do_QueryInterface(mCallee));
|
||||
if (!wrappedJS) {
|
||||
ThrowBadParam(NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE, i, mCallContext);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
nsresult err;
|
||||
if (!XPCConvert::JSData2Native(&dp->val, src, type, ¶m_iid, &err)) {
|
||||
ThrowBadParam(err, i, mCallContext);
|
||||
|
|
|
@ -1601,10 +1601,10 @@ SpecialPowersAPI.prototype = {
|
|||
|
||||
var xferable = Components.classes["@mozilla.org/widget/transferable;1"].
|
||||
createInstance(Components.interfaces.nsITransferable);
|
||||
// in e10s b-c tests |content.window| is null whereas |window| works fine.
|
||||
// in e10s b-c tests |content.window| is a CPOW whereas |window| works fine.
|
||||
// for some non-e10s mochi tests, |window| is null whereas |content.window|
|
||||
// works fine. So we take whatever is non-null!
|
||||
xferable.init(this._getDocShell(content.window || window)
|
||||
xferable.init(this._getDocShell(typeof(window) == "undefined" ? content.window : window)
|
||||
.QueryInterface(Components.interfaces.nsILoadContext));
|
||||
xferable.addDataFlavor(flavor);
|
||||
this._cb.getData(xferable, whichClipboard);
|
||||
|
|
|
@ -239,12 +239,22 @@ let AboutProtocolParent = {
|
|||
// We immediately read all the data out of the channel here and
|
||||
// return it to the child.
|
||||
openChannel: function(msg) {
|
||||
function wrapGetInterface(cpow) {
|
||||
return {
|
||||
getInterface: function(intf) { return cpow.getInterface(intf); }
|
||||
};
|
||||
}
|
||||
|
||||
let uri = BrowserUtils.makeURI(msg.data.uri);
|
||||
let contractID = msg.data.contractID;
|
||||
let module = Cc[contractID].getService(Ci.nsIAboutModule);
|
||||
try {
|
||||
let channel = module.newChannel(uri, null);
|
||||
channel.notificationCallbacks = msg.objects.notificationCallbacks;
|
||||
// We're not allowed to set channel.notificationCallbacks to a
|
||||
// CPOW, since the setter for notificationCallbacks is in C++,
|
||||
// which can't tolerate CPOWs. Instead we just use a JS object
|
||||
// that wraps the CPOW.
|
||||
channel.notificationCallbacks = wrapGetInterface(msg.objects.notificationCallbacks);
|
||||
if (msg.objects.loadGroupNotificationCallbacks) {
|
||||
channel.loadGroup = {notificationCallbacks: msg.objects.loadGroupNotificationCallbacks};
|
||||
} else {
|
||||
|
@ -434,13 +444,17 @@ let EventTargetParent = {
|
|||
// If there's already an identical listener, don't do anything.
|
||||
for (let i = 0; i < forType.length; i++) {
|
||||
if (forType[i].listener === listener &&
|
||||
forType[i].target === target &&
|
||||
forType[i].useCapture === useCapture &&
|
||||
forType[i].wantsUntrusted === wantsUntrusted) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
forType.push({listener: listener, wantsUntrusted: wantsUntrusted, useCapture: useCapture});
|
||||
forType.push({listener: listener,
|
||||
target: target,
|
||||
wantsUntrusted: wantsUntrusted,
|
||||
useCapture: useCapture});
|
||||
},
|
||||
|
||||
removeEventListener: function(addon, target, type, listener, useCapture) {
|
||||
|
@ -458,7 +472,9 @@ let EventTargetParent = {
|
|||
let forType = setDefault(listeners, type, []);
|
||||
|
||||
for (let i = 0; i < forType.length; i++) {
|
||||
if (forType[i].listener === listener && forType[i].useCapture === useCapture) {
|
||||
if (forType[i].listener === listener &&
|
||||
forType[i].target === target &&
|
||||
forType[i].useCapture === useCapture) {
|
||||
forType.splice(i, 1);
|
||||
NotificationTracker.remove(["event", type, useCapture, addon]);
|
||||
break;
|
||||
|
@ -488,19 +504,30 @@ let EventTargetParent = {
|
|||
|
||||
// Make a copy in case they call removeEventListener in the listener.
|
||||
let handlers = [];
|
||||
for (let {listener, wantsUntrusted, useCapture} of forType) {
|
||||
for (let {listener, target, wantsUntrusted, useCapture} of forType) {
|
||||
if ((wantsUntrusted || isTrusted) && useCapture == capturing) {
|
||||
handlers.push(listener);
|
||||
handlers.push([listener, target]);
|
||||
}
|
||||
}
|
||||
|
||||
for (let handler of handlers) {
|
||||
for (let [handler, target] of handlers) {
|
||||
let EventProxy = {
|
||||
get: function(actualEvent, name) {
|
||||
if (name == "currentTarget") {
|
||||
return target;
|
||||
} else {
|
||||
return actualEvent[name];
|
||||
}
|
||||
}
|
||||
};
|
||||
let proxyEvent = new Proxy(event, EventProxy);
|
||||
|
||||
try {
|
||||
Prefetcher.withPrefetching(prefetched, cpows, () => {
|
||||
if ("handleEvent" in handler) {
|
||||
handler.handleEvent(event);
|
||||
handler.handleEvent(proxyEvent);
|
||||
} else {
|
||||
handler.call(event.target, event);
|
||||
handler.call(event.target, proxyEvent);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cu = Components.utils;
|
||||
var Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/BrowserUtils.jsm");
|
||||
|
@ -269,6 +270,73 @@ function testAboutModuleRegistration()
|
|||
|
||||
let modulesToUnregister = new Map();
|
||||
|
||||
function TestChannel(uri, aboutName) {
|
||||
this.aboutName = aboutName;
|
||||
this.URI = this.originalURI = uri;
|
||||
}
|
||||
|
||||
TestChannel.prototype = {
|
||||
asyncOpen: function(listener, context) {
|
||||
let stream = this.open();
|
||||
let runnable = {
|
||||
run: () => {
|
||||
try {
|
||||
listener.onStartRequest(this, context);
|
||||
} catch(e) {}
|
||||
try {
|
||||
listener.onDataAvailable(this, context, stream, 0, stream.available());
|
||||
} catch(e) {}
|
||||
try {
|
||||
listener.onStopRequest(this, context, Cr.NS_OK);
|
||||
} catch(e) {}
|
||||
}
|
||||
};
|
||||
Services.tm.currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
|
||||
},
|
||||
|
||||
open: function() {
|
||||
function getWindow(channel) {
|
||||
try
|
||||
{
|
||||
if (channel.notificationCallbacks)
|
||||
return channel.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
|
||||
} catch(e) {}
|
||||
|
||||
try
|
||||
{
|
||||
if (channel.loadGroup && channel.loadGroup.notificationCallbacks)
|
||||
return channel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow;
|
||||
} catch(e) {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
let data = `<html><h1>${this.aboutName}</h1></html>`;
|
||||
let wnd = getWindow(this);
|
||||
if (!wnd)
|
||||
throw Cr.NS_ERROR_UNEXPECTED;
|
||||
|
||||
let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
|
||||
stream.setData(data, data.length);
|
||||
return stream;
|
||||
},
|
||||
|
||||
isPending: function() {
|
||||
return false;
|
||||
},
|
||||
cancel: function() {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
suspend: function() {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
resume: function() {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
|
||||
};
|
||||
|
||||
/**
|
||||
* This function creates a new nsIAboutModule and registers it. Callers
|
||||
* should also call unregisterModules after using this function to clean
|
||||
|
@ -294,10 +362,7 @@ function testAboutModuleRegistration()
|
|||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
|
||||
|
||||
newChannel: (aURI) => {
|
||||
let uri = Services.io.newURI(`data:,<html><h1>${aboutName}</h1></html>`, null, null);
|
||||
let chan = Services.io.newChannelFromURI(uri);
|
||||
chan.originalURI = aURI;
|
||||
return chan;
|
||||
return new TestChannel(aURI, aboutName);
|
||||
},
|
||||
|
||||
getURIFlags: (aURI) => {
|
||||
|
@ -357,23 +422,27 @@ function testAboutModuleRegistration()
|
|||
*/
|
||||
let testAboutModulesWork = (browser) => {
|
||||
let testConnection = () => {
|
||||
const XMLHttpRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1",
|
||||
"nsIXMLHttpRequest");
|
||||
let request = new XMLHttpRequest();
|
||||
let request = new content.XMLHttpRequest();
|
||||
try {
|
||||
request.open("GET", "about:test1", false);
|
||||
request.send(null);
|
||||
if (request.status != 200) {
|
||||
throw(`about:test1 response had status ${request.status} - expected 200`);
|
||||
}
|
||||
if (request.responseText.indexOf("test1") == -1) {
|
||||
throw(`about:test1 response had result ${request.responseText}`);
|
||||
}
|
||||
|
||||
request = new XMLHttpRequest();
|
||||
request = new content.XMLHttpRequest();
|
||||
request.open("GET", "about:test2", false);
|
||||
request.send(null);
|
||||
|
||||
if (request.status != 200) {
|
||||
throw(`about:test2 response had status ${request.status} - expected 200`);
|
||||
}
|
||||
if (request.responseText.indexOf("test2") == -1) {
|
||||
throw(`about:test2 response had result ${request.responseText}`);
|
||||
}
|
||||
|
||||
sendAsyncMessage("test:result", {
|
||||
pass: true,
|
||||
|
@ -406,14 +475,20 @@ function testAboutModuleRegistration()
|
|||
createAndRegisterAboutModule("test1", "5f3a921b-250f-4ac5-a61c-8f79372e6063");
|
||||
createAndRegisterAboutModule("test2", "d7ec0389-1d49-40fa-b55c-a1fc3a6dbf6f");
|
||||
|
||||
let newTab = gBrowser.addTab();
|
||||
// This needs to be a chrome-privileged page that loads in the
|
||||
// content process. It needs chrome privs because otherwise the
|
||||
// XHRs for about:test[12] will fail with a privilege error
|
||||
// despite the presence of URI_SAFE_FOR_UNTRUSTED_CONTENT.
|
||||
let newTab = gBrowser.addTab("chrome://addonshim1/content/page.html");
|
||||
gBrowser.selectedTab = newTab;
|
||||
let browser = newTab.linkedBrowser;
|
||||
|
||||
testAboutModulesWork(browser).then(() => {
|
||||
gBrowser.removeTab(newTab);
|
||||
unregisterModules();
|
||||
resolve();
|
||||
addLoadListener(browser, function() {
|
||||
testAboutModulesWork(browser).then(() => {
|
||||
gBrowser.removeTab(newTab);
|
||||
unregisterModules();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче