Tweak sandboxing for Firefox 35

Now we have to wrap cross-origin objects with a wrapper on the sandbox
side. Also, Function.prototype.apply.apply...
This commit is contained in:
Simon Kornblith 2015-01-11 21:37:57 -05:00
parent fa75beeefd
commit da7ee2ba26
4 changed files with 97 additions and 44 deletions

View File

@ -242,6 +242,9 @@ Zotero.Translate.Sandbox = {
};
safeTranslator.setDocument = function(arg) {
if (Zotero.isFx && !Zotero.isBookmarklet) {
if (arg.wrappedJSObject && arg.wrappedJSObject.__wrappedObject) {
arg = arg.wrappedJSObject.__wrappedObject;
}
return translation.setDocument(new XPCNativeWrapper(arg));
} else {
return translation.setDocument(arg);
@ -388,7 +391,7 @@ Zotero.Translate.Sandbox = {
if (typeof(safeTranslator[i]) === "function") {
safeTranslator[i] = translate._sandboxManager._makeContentForwarder(function(func) {
return function() {
func.apply(safeTranslator, this.args.wrappedJSObject);
func.apply(safeTranslator, this.args.wrappedJSObject || this.args);
}
}(safeTranslator[i]));
}
@ -1169,7 +1172,7 @@ Zotero.Translate.Base.prototype = {
// translate
try {
this._sandboxManager.sandbox["do"+this._entryFunctionSuffix].apply(null, this._getParameters());
Function.prototype.apply.apply(this._sandboxManager.sandbox["do"+this._entryFunctionSuffix], [null, this._getParameters()]);
} catch(e) {
this.complete(false, e);
return false;
@ -1442,7 +1445,7 @@ Zotero.Translate.Base.prototype = {
this.incrementAsyncProcesses("Zotero.Translate#getTranslators");
try {
var returnValue = this._sandboxManager.sandbox["detect"+this._entryFunctionSuffix].apply(null, this._getParameters());
var returnValue = Function.prototype.apply.apply(this._sandboxManager.sandbox["detect"+this._entryFunctionSuffix], [null, this._getParameters()]);
} catch(e) {
this.complete(false, e);
return;
@ -1987,13 +1990,13 @@ Zotero.Translate.Import.prototype._loadTranslatorPrepareIO = function(translator
if(!this._io) {
if(Zotero.Translate.IO.Read && this.location && this.location instanceof Components.interfaces.nsIFile) {
try {
this._io = new Zotero.Translate.IO.Read(this.location);
this._io = new Zotero.Translate.IO.Read(this.location, this._sandboxManager);
} catch(e) {
err = e;
}
} else {
try {
this._io = new Zotero.Translate.IO.String(this._string, this.path ? this.path : "");
this._io = new Zotero.Translate.IO.String(this._string, this.path ? this.path : "", this._sandboxManager);
} catch(e) {
err = e;
}
@ -2156,7 +2159,7 @@ Zotero.Translate.Export.prototype._prepareTranslation = function() {
// this is currently hackish since we pass null callbacks to the init function (they have
// callbacks to be consistent with import, but they are synchronous, so we ignore them)
if(!this.location) {
this._io = new Zotero.Translate.IO.String(null, this.path ? this.path : "");
this._io = new Zotero.Translate.IO.String(null, this.path ? this.path : "", this._sandboxManager);
this._io.init(configOptions["dataMode"], function() {});
} else if(!Zotero.Translate.IO.Write) {
throw new Error("Writing to files is not supported in this build of Zotero.");
@ -2344,7 +2347,7 @@ Zotero.Translate.IO = {
/**
* @class Translate backend for translating from a string
*/
Zotero.Translate.IO.String = function(string, uri) {
Zotero.Translate.IO.String = function(string, uri, sandboxManager) {
if(string && typeof string === "string") {
this.string = string;
} else {
@ -2353,6 +2356,7 @@ Zotero.Translate.IO.String = function(string, uri) {
this.contentLength = this.string.length;
this.bytesRead = 0;
this._uri = uri;
this._sandboxManager = sandboxManager;
}
Zotero.Translate.IO.String.prototype = {
@ -2442,7 +2446,7 @@ Zotero.Translate.IO.String.prototype = {
this._xmlInvalid = true;
throw e;
}
return (Zotero.isFx ? Zotero.Translate.DOMWrapper.wrap(xml) : xml);
return (Zotero.isFx && !Zotero.isBookmarklet ? this._sandboxManager.wrap(xml) : xml);
},
"init":function(newMode, callback) {

View File

@ -49,7 +49,7 @@ Zotero.Translate.DOMWrapper = new function() {
};
function isWrapper(x) {
return isWrappable(x) && (typeof x.SpecialPowers_wrappedObject !== "undefined");
return isWrappable(x) && (typeof x.__wrappedObject !== "undefined");
};
function unwrapIfWrapped(x) {
@ -130,7 +130,7 @@ Zotero.Translate.DOMWrapper = new function() {
throw "Trying to unwrap a non-wrapped object!";
// Unwrap.
return x.SpecialPowers_wrappedObject;
return x.__wrappedObject;
};
function crawlProtoChain(obj, fn) {
@ -172,7 +172,7 @@ Zotero.Translate.DOMWrapper = new function() {
SpecialPowersHandler.prototype.doGetPropertyDescriptor = function(name, own) {
// Handle our special API.
if (name == "SpecialPowers_wrappedObject")
if (name == "__wrappedObject")
return { value: this.wrappedObject, writeable: false, configurable: false, enumerable: false };
// Handle __exposedProps__.
if (name == "__exposedProps__")
@ -281,7 +281,7 @@ Zotero.Translate.DOMWrapper = new function() {
// Insert our special API. It's not enumerable, but getPropertyNames()
// includes non-enumerable properties.
var specialAPI = 'SpecialPowers_wrappedObject';
var specialAPI = '__wrappedObject';
if (props.indexOf(specialAPI) == -1)
props.push(specialAPI);
@ -366,6 +366,14 @@ Zotero.Translate.DOMWrapper = new function() {
return obj;
}
}
/**
* Wraps an object in the same sandbox as another object
*/
this.wrapIn = function(obj, insamebox) {
if(insamebox.__wrappingManager) return insamebox.__wrappingManager.wrap(obj);
return this.wrap(obj);
}
/**
* Checks whether an object is wrapped by a DOM wrapper
@ -381,7 +389,8 @@ Zotero.Translate.DOMWrapper = new function() {
* @param {String|window} sandboxLocation
*/
Zotero.Translate.SandboxManager = function(sandboxLocation) {
this.sandbox = new Components.utils.Sandbox(sandboxLocation);
// sandboxLocation = Components.classes["@mozilla.org/systemprincipal;1"].createInstance(Components.interfaces.nsIPrincipal);
var sandbox = this.sandbox = new Components.utils.Sandbox(sandboxLocation, {wantComponents:false, wantGlobalProperties:["XMLHttpRequest"]});
this.sandbox.Zotero = {};
// import functions missing from global scope into Fx sandbox
@ -389,28 +398,14 @@ Zotero.Translate.SandboxManager = function(sandboxLocation) {
if(typeof sandboxLocation === "object" && "DOMParser" in sandboxLocation) {
this.sandbox.DOMParser = sandboxLocation.DOMParser;
} else {
var sandbox = this.sandbox;
this.sandbox.DOMParser = function() {
var uri, principal;
// get URI
if(typeof sandboxLocation === "string") { // if sandbox specified by URI
var secMan = Services.scriptSecurityManager;
uri = Services.io.newURI(sandboxLocation, "UTF-8", null);
principal = (secMan.getCodebasePrincipal || secMan.getSimpleCodebasePrincipal)(uri);
} else { // if sandbox specified by DOM document
principal = sandboxLocation.document.nodePrincipal;
uri = sandboxLocation.document.documentURIObject;
}
// initialize DOM parser
var _DOMParser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser);
_DOMParser.init(principal, uri, uri);
// expose parseFromString
this.__exposedProps__ = {"parseFromString":"r"};
this.parseFromString = function(str, contentType) {
return Zotero.Translate.DOMWrapper.wrap(_DOMParser.parseFromString(str, contentType));
var xhr = sandbox.XMLHttpRequest();
xhr.open("GET", "data:"+contentType+";base64,"+btoa(str), false);
xhr.send();
if (!xhr.responseXML) throw new Error("error parsing XML");
return xhr.responseXML;
}
};
}
@ -427,7 +422,55 @@ Zotero.Translate.SandboxManager = function(sandboxLocation) {
this.sandbox.XMLSerializer.prototype = {"__exposedProps__":{"serializeToString":"r"}};
var expr = "(function(x) { return function() { this.args = arguments; return x.apply(this); }.bind({}); })";
this._makeContentForwarder = Components.utils.evalInSandbox(expr, this.sandbox);
this._makeContentForwarder = Components.utils.evalInSandbox(expr, sandbox);
if (Zotero.platformMajorVersion >= 35) {
var _proxy = Components.utils.evalInSandbox('(function (target, x) {'+
' return new Proxy(x, ProxyHandler(target));'+
'})', sandbox);
var wrap = this.wrap = function(target, x) {
if (target === null || (typeof target !== "object" && typeof target !== "function")) return target;
if (!x) x = new sandbox.Object();
return _proxy(target, x);
};
var me = this;
sandbox.ProxyHandler = this._makeContentForwarder(function() {
var target = (this.args.wrappedJSObject || this.args)[0];
if(target instanceof Components.interfaces.nsISupports) {
target = new XPCNativeWrapper(target);
}
var ret = new sandbox.Object();
ret.wrappedJSObject.has = function(x, prop) {
return prop in target;
};
ret.wrappedJSObject.get = function(x, prop, receiver) {
if (prop === "__wrappedObject") return target;
if (prop === "__wrappingManager") return me;
var y = target[prop];
if (y === null || (typeof y !== "object" && typeof y !== "function")) return y;
return wrap(y, typeof y === "function" ? function() {
var args = Array.prototype.slice.apply(arguments);
for (var i = 0; i < args.length; i++) {
if (typeof args[i] === "object" && args[i] !== null &&
args[i].wrappedJSObject && args[i].wrappedJSObject.__wrappedObject)
args[i] = new XPCNativeWrapper(args[i].wrappedJSObject.__wrappedObject);
}
return wrap(y.apply(target, args));
} : new sandbox.Object());
};
ret.wrappedJSObject.ownKeys = function(x) {
return Components.utils.cloneInto(target.getOwnPropertyNames(), sandbox);
};
ret.wrappedJSObject.enumerate = function(x) {
var y = new sandbox.Array();
for (var i in target) y.wrappedJSObject.push(i);
return y;
};
return ret;
});
} else {
this.wrap = Zotero.Translate.DOMWrapper.wrap;
}
}
Zotero.Translate.SandboxManager.prototype = {
@ -463,7 +506,7 @@ Zotero.Translate.SandboxManager.prototype = {
if(isFunction) {
if (Zotero.platformMajorVersion >= 33) {
attachTo[localKey] = this._makeContentForwarder(function() {
var args = Array.prototype.slice.apply(this.args.wrappedJSObject);
var args = Array.prototype.slice.apply(this.args.wrappedJSObject || this.args);
for(var i = 0; i<args.length; i++) {
// Make sure we keep XPCNativeWrappers
if(args[i] instanceof Components.interfaces.nsISupports) {
@ -503,7 +546,7 @@ Zotero.Translate.SandboxManager.prototype = {
"_canCopy":function(obj) {
if(typeof obj !== "object" || obj === null) return false;
if((obj.constructor.name !== "Object" && obj.constructor.name !== "Array") ||
"__exposedProps__" in obj) {
"__exposedProps__" in obj || (obj.wrappedJSObject && obj.wrappedJSObject.__wrappingManager)) {
return false;
}
return true;
@ -582,6 +625,9 @@ Zotero.Translate.ChildSandboxManager.prototype = {
},
"_makeContentForwarder":function(f) {
return this._parent._makeContentForwarder(f);
},
"wrap":function(x) {
return this._parent.wrap(x);
}
}
@ -594,10 +640,11 @@ Zotero.Translate.IO.maintainedInstances = [];
/******* (Native) Read support *******/
Zotero.Translate.IO.Read = function(file, mode) {
Zotero.Translate.IO.Read = function(file, sandboxManager) {
Zotero.Translate.IO.maintainedInstances.push(this);
this.file = file;
this._sandboxManager = sandboxManager;
// open file
this._openRawStream();
@ -862,7 +909,7 @@ Zotero.Translate.IO.Read.prototype = {
this._xmlInvalid = true;
throw e;
}
return (Zotero.isFx ? Zotero.Translate.DOMWrapper.wrap(xml) : xml);
return (Zotero.isFx ? this._sandboxManager.wrap(xml) : xml);
},
"init":function(newMode, callback) {

View File

@ -229,7 +229,7 @@ Zotero.Utilities = {
*/
"trimInternal":function(/**String*/ s) {
if (typeof(s) != "string") {
throw "trimInternal: argument must be a string";
throw new Error("trimInternal: argument must be a string");
}
s = s.replace(/[\xA0\r\n\s]+/g, " ");
@ -1046,11 +1046,11 @@ Zotero.Utilities = {
// For some reason, if elements is wrapped by an object
// Xray, we won't be able to unwrap the DOMWrapper around
// the element. So waive the object Xray.
var element = elements.wrappedJSObject ? elements.wrappedJSObject[i] : elements[i];
var maybeWrappedEl = elements.wrappedJSObject ? elements.wrappedJSObject[i] : elements[i];
// Firefox 5 hack, so we will preserve Fx5DOMWrappers
var isWrapped = Zotero.Translate.DOMWrapper && Zotero.Translate.DOMWrapper.isWrapped(element);
if(isWrapped) element = Zotero.Translate.DOMWrapper.unwrap(element);
var isWrapped = Zotero.Translate.DOMWrapper && Zotero.Translate.DOMWrapper.isWrapped(maybeWrappedEl);
var element = isWrapped ? Zotero.Translate.DOMWrapper.unwrap(maybeWrappedEl) : maybeWrappedEl;
// We waived the object Xray above, which will waive the
// DOM Xray, so make sure we have a DOM Xray wrapper.
@ -1083,7 +1083,7 @@ Zotero.Utilities = {
var newEl;
while(newEl = xpathObject.iterateNext()) {
// Firefox 5 hack
results.push(isWrapped ? Zotero.Translate.DOMWrapper.wrap(newEl) : newEl);
results.push(isWrapped ? Zotero.Translate.DOMWrapper.wrapIn(newEl, maybeWrappedEl) : newEl);
}
} else if("selectNodes" in element) {
// We use JavaScript-XPath in IE for HTML documents, but with an XML
@ -1125,6 +1125,8 @@ Zotero.Utilities = {
var strings = new Array(elements.length);
for(var i=0, n=elements.length; i<n; i++) {
var el = elements[i];
if(el.wrappedJSObject) el = el.wrappedJSObject;
if(Zotero.Translate.DOMWrapper) el = Zotero.Translate.DOMWrapper.unwrap(el);
strings[i] =
(el.nodeType === 2 /*ATTRIBUTE_NODE*/ && "value" in el) ? el.value
: "textContent" in el ? el.textContent

View File

@ -268,9 +268,9 @@ Zotero.Utilities.Translate.prototype.processDocuments = function(urls, processor
if(!processor) return;
var newLoc = doc.location;
if(Zotero.isFx && (protocol != newLoc.protocol || host != newLoc.host)) {
if(Zotero.isFx && !Zotero.isBookmarklet && (protocol != newLoc.protocol || host != newLoc.host)) {
// Cross-site; need to wrap
processor(Zotero.Translate.DOMWrapper.wrap(doc), newLoc.toString());
processor(translate._sandboxManager.wrap(doc), newLoc.toString());
} else {
// Not cross-site; no need to wrap
processor(doc, newLoc.toString());