Bug 1072980 - Don't allow CPOWs to be passed to C++ code (r=mrbkap,ally,mconley)

This commit is contained in:
Bill McCloskey 2015-01-29 11:28:01 -08:00
Родитель 082d988ab6
Коммит 94aebaaa39
10 изменённых файлов: 179 добавлений и 51 удалений

Просмотреть файл

@ -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, &param_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();
});
});
});
}