Replace the XPCOM sandbox with an unsafe eval sandbox

This commit is contained in:
Adomas Venčkauskas 2018-08-30 10:19:01 +03:00 committed by Dan Stillman
parent 1c6840f9a5
commit 5216f11885
3 changed files with 63 additions and 359 deletions

View file

@ -180,11 +180,6 @@ Zotero.Translate.Sandbox = {
// just return the item array
if(translate._libraryID === false || translate._parentTranslator) {
translate.newItems.push(item);
if(translate._parentTranslator && Zotero.isFx && !Zotero.isBookmarklet) {
// Copy object so it is accessible to parent translator
item = translate._sandboxManager.copyObject(item);
item.complete = oldItem.complete;
}
return translate._runHandler("itemDone", item, item);
}
@ -239,31 +234,7 @@ Zotero.Translate.Sandbox = {
}
};
if (!translate._sandboxManager.sandbox.Promise) {
Zotero.debug("Translate: Promise not available in sandbox in _itemDone()");
run();
return;
}
return new translate._sandboxManager.sandbox.Promise(function (resolve, reject) {
try {
run(true).then(
resolve,
function (e) {
// Fix wrapping error from sandbox when error is thrown from _saveItems()
if (Zotero.isFx) {
reject(translate._sandboxManager.copyObject(e));
}
else {
reject(e);
}
}
);
}
catch (e) {
reject(e);
}
});
return run(asyncTranslator);
},
/**
@ -374,13 +345,6 @@ Zotero.Translate.Sandbox = {
item = item.wrappedJSObject ? item.wrappedJSObject : item;
if(arg1 == "itemDone") {
item.complete = translate._sandboxZotero.Item.prototype.complete;
} else if(arg1 == "translators" && Zotero.isFx && !Zotero.isBookmarklet) {
var translators = new translate._sandboxManager.sandbox.Array();
translators = translators.wrappedJSObject || translators;
for (var i=0; i<item.length; i++) {
translators.push(item[i]);
}
item = translators;
}
arg2(obj, item);
} catch(e) {
@ -446,27 +410,6 @@ Zotero.Translate.Sandbox = {
return translation._loadTranslator(translator)
})
.then(function() {
if(Zotero.isFx && !Zotero.isBookmarklet) {
// do same origin check
var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
.getService(Components.interfaces.nsIScriptSecurityManager);
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var outerSandboxURI = ioService.newURI(typeof translate._sandboxLocation === "object" ?
translate._sandboxLocation.location : translate._sandboxLocation, null, null);
var innerSandboxURI = ioService.newURI(typeof translation._sandboxLocation === "object" ?
translation._sandboxLocation.location : translation._sandboxLocation, null, null);
try {
secMan.checkSameOriginURI(outerSandboxURI, innerSandboxURI, false);
} catch(e) {
throw new Error("getTranslatorObject() may not be called from web or search "+
"translators to web or search translators from different origins.");
return;
}
}
return translation._prepareTranslation();
})
.then(function () {
@ -489,18 +432,6 @@ Zotero.Translate.Sandbox = {
return;
});
};
if (Zotero.isFx) {
for(var i in safeTranslator) {
if (typeof(safeTranslator[i]) === "function") {
safeTranslator[i] = translate._sandboxManager._makeContentForwarder(function(func) {
return function() {
func.apply(safeTranslator, this.args.wrappedJSObject || this.args);
}
}(safeTranslator[i]));
}
}
}
return safeTranslator;
},
@ -547,7 +478,7 @@ Zotero.Translate.Sandbox = {
*/
"selectItems":function(translate, items, callback) {
function transferObject(obj) {
return Zotero.isFx && !Zotero.isBookmarklet ? translate._sandboxManager.copyObject(obj) : obj;
return obj;
}
if(Zotero.Utilities.isEmpty(items)) {
@ -1887,16 +1818,9 @@ Zotero.Translate.Base.prototype = {
*/
"_generateSandbox":function() {
Zotero.debug("Translate: Binding sandbox to "+(typeof this._sandboxLocation == "object" ? this._sandboxLocation.document.location : this._sandboxLocation), 4);
if (this._parentTranslator && this._parentTranslator._sandboxManager.newChild) {
this._sandboxManager = this._parentTranslator._sandboxManager.newChild();
} else {
this._sandboxManager = new Zotero.Translate.SandboxManager(this._sandboxLocation);
}
this._sandboxManager = new Zotero.Translate.SandboxManager(this._sandboxLocation);
const createArrays = "['creators', 'notes', 'tags', 'seeAlso', 'attachments']";
var src = "";
if (Zotero.isFx && !Zotero.isBookmarklet) {
src = "var Zotero = {};";
}
src += "Zotero.Item = function (itemType) {"+
"var createArrays = "+createArrays+";"+
"this.itemType = itemType;"+
@ -1918,9 +1842,6 @@ Zotero.Translate.Base.prototype = {
this._sandboxZotero = this._sandboxManager.sandbox.Zotero;
if(Zotero.isFx) {
if(this._sandboxZotero.wrappedJSObject) this._sandboxZotero = this._sandboxZotero.wrappedJSObject;
}
this._sandboxZotero.Utilities.HTTP = this._sandboxZotero.Utilities;
this._sandboxZotero.isBookmarklet = Zotero.isBookmarklet || false;
@ -2132,18 +2053,7 @@ Zotero.Translate.Web.prototype._getSandboxLocation = function() {
* Pass document and location to detect* and do* functions
*/
Zotero.Translate.Web.prototype._getParameters = function() {
if (Zotero.Translate.DOMWrapper && Zotero.Translate.DOMWrapper.isWrapped(this.document)) {
return [
this._sandboxManager.wrap(
Zotero.Translate.DOMWrapper.unwrap(this.document),
null,
this.document.SpecialPowers_wrapperOverrides
),
this.location
];
} else {
return [this.document, this.location];
}
return [this.document, this.location];
};
/**
@ -2759,9 +2669,6 @@ Zotero.Translate.Search.prototype.complete = function(returnValue, error) {
* Pass search item to detect* and do* functions
*/
Zotero.Translate.Search.prototype._getParameters = function() {
if(Zotero.isFx) {
return [this._sandboxManager.copyObject(this.search)];
}
return [this.search];
};
@ -2926,7 +2833,7 @@ Zotero.Translate.IO.String.prototype = {
this._xmlInvalid = true;
throw e;
}
return (Zotero.isFx && !Zotero.isBookmarklet ? this._sandboxManager.wrap(xml) : xml);
return xml;
},
init: function (newMode) {

View file

@ -363,271 +363,88 @@ Zotero.Translate.DOMWrapper = new function() {
/**
* @class Manages the translator sandbox
* @param {Zotero.Translate} translate
* @param {Translate} translate
* @param {String|window} sandboxLocation
*/
Zotero.Translate.SandboxManager = function(sandboxLocation) {
// sandboxLocation = Components.classes["@mozilla.org/systemprincipal;1"].createInstance(Components.interfaces.nsIPrincipal);
var sandbox = this.sandbox = new Components.utils.Sandbox(
sandboxLocation,
{
wantComponents: false,
wantGlobalProperties: [
'atob',
'XMLHttpRequest'
]
this.sandbox = {
Zotero: {},
XPathResult: Components.interfaces.nsIDOMXPathResult,
DOMParser: function() {
return Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser);
},
XMLSerializer: function() {
return Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
.createInstance(Components.interfaces.nsIDOMSerializer);
}
);
this.sandbox.Zotero = {};
// import functions missing from global scope into Fx sandbox
this.sandbox.XPathResult = Components.interfaces.nsIDOMXPathResult;
if(typeof sandboxLocation === "object" && "DOMParser" in sandboxLocation) {
this.sandbox.DOMParser = sandboxLocation.DOMParser;
} else {
this.sandbox.DOMParser = function() {
var obj = new sandbox.Object();
var wrappedObj = obj.wrappedJSObject || obj;
wrappedObj.__exposedProps__ = {"parseFromString":"r"};
wrappedObj.parseFromString = function(str, contentType) {
var xhr = new sandbox.XMLHttpRequest();
xhr.open("GET", "data:"+contentType+";charset=utf-8,"+encodeURIComponent(str), false);
xhr.send();
if (!xhr.responseXML) throw new Error("error parsing XML");
return xhr.responseXML;
}
return obj;
};
}
this.sandbox.DOMParser.__exposedProps__ = {"prototype":"r"};
this.sandbox.DOMParser.prototype = {};
this.sandbox.XMLSerializer = function() {
var s = Components.classes["@mozilla.org/xmlextras/xmlserializer;1"]
.createInstance(Components.interfaces.nsIDOMSerializer);
var obj = new sandbox.Object();
var wrappedObj = obj.wrappedJSObject || obj;
wrappedObj.serializeToString = function(doc) {
return s.serializeToString(Zotero.Translate.DOMWrapper.unwrap(doc));
};
return obj;
};
this.sandbox.XMLSerializer.__exposedProps__ = {"prototype":"r"};
this.sandbox.XMLSerializer.prototype = {"__exposedProps__":{"serializeToString":"r"}};
var expr = "(function(x) { return function() { this.args = arguments; return Function.prototype.apply.call(x, this); }.bind({}); })";
this._makeContentForwarder = Components.utils.evalInSandbox(expr, sandbox);
var _proxy = Components.utils.evalInSandbox('(function (target, x, overrides) {'+
' return new Proxy(x, ProxyHandler(target, overrides));'+
'})', sandbox);
var wrap = this.wrap = function(target, x, overrides) {
if (target === null || (typeof target !== "object" && typeof target !== "function")) return target;
if (!x) x = new sandbox.Object();
return _proxy(target, x, overrides);
};
var me = this;
sandbox.ProxyHandler = this._makeContentForwarder(function() {
var target = (this.args.wrappedJSObject || this.args)[0];
var overrides = (this.args.wrappedJSObject || this.args)[1] || {};
if(target instanceof Components.interfaces.nsISupports) {
target = new XPCNativeWrapper(target);
}
var ret = new sandbox.Object();
var wrappedRet = ret.wrappedJSObject || ret;
wrappedRet.has = function(x, prop) {
return overrides.hasOwnProperty(prop) || prop in target;
};
wrappedRet.get = function(x, prop, receiver) {
if (prop === "SpecialPowers_wrappedObject") return target;
if (prop === "SpecialPowers_wrapperOverrides") return overrides;
if (prop === "__wrappingManager") return me;
var y = overrides.hasOwnProperty(prop) ? overrides[prop] : 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.SpecialPowers_wrappedObject)
args[i] = new XPCNativeWrapper(args[i].wrappedJSObject.SpecialPowers_wrappedObject);
}
return wrap(y.apply(target, args));
} : new sandbox.Object());
};
wrappedRet.ownKeys = function(x) {
return Components.utils.cloneInto(
Object.getOwnPropertyNames(target)
.concat(Object.getOwnPropertySymbols(target)),
sandbox
);
};
wrappedRet.enumerate = function(x) {
var y = new sandbox.Array();
for (var i in target) y.wrappedJSObject.push(i);
return y;
};
return ret;
});
}
};
Zotero.Translate.SandboxManager.prototype = {
/**
* Evaluates code in the sandbox
* @param {String} code Code to evaluate
* @param {String[]} functions Functions to import into the sandbox (rather than leaving
* as inner functions)
*/
"eval":function(code, exported, path) {
Components.utils.evalInSandbox(code, this.sandbox, "1.8", path, 1);
eval: function(code, functions) {
// delete functions to import
for (var i in functions) {
delete this.sandbox[functions[i]];
}
// Prepend sandbox properties within eval environment (what a mess (1))
for (var prop in this.sandbox) {
code = 'var ' + prop + ' = this.sandbox.' + prop + ';' + code;
}
// Import inner functions back into the sandbox
for (var i in functions) {
try {
code += 'try{this.sandbox.' + functions[i] + ' = ' + functions[i] + ';}catch(e){}';
} catch (e) {
}
}
// Eval in a closure
(function() {
eval(code);
}).call(this);
},
/**
* Imports an object into the sandbox
*
* @param {Object} object Object to be imported (under Zotero)
* @param {*} [passTranslateAsFirstArgument] An argument to pass
* as the first argument to the function.
* @param {Object} [attachTo] The object to attach `object` to.
* Defaults to this.sandbox.Zotero
* @param {Boolean} passTranslateAsFirstArgument Whether the translate instance should be passed
* as the first argument to the function.
*/
"importObject":function(object, passAsFirstArgument, attachTo) {
importObject: function(object, passAsFirstArgument, attachTo) {
if(!attachTo) attachTo = this.sandbox.Zotero;
if(attachTo.wrappedJSObject) attachTo = attachTo.wrappedJSObject;
var newExposedProps = false, sandbox = this.sandbox, me = this;
if(!object.__exposedProps__) newExposedProps = {};
for(var key in (newExposedProps ? object : object.__exposedProps__)) {
let localKey = key;
if(newExposedProps) newExposedProps[localKey] = "r";
var type = typeof object[localKey];
var isFunction = type === "function";
var isObject = typeof object[localKey] === "object";
if(isFunction || isObject) {
if(isFunction) {
attachTo[localKey] = this._makeContentForwarder(function() {
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) {
args[i] = new XPCNativeWrapper(args[i]);
}
for(var key in (object.__exposedProps__ ? object.__exposedProps__ : object)) {
if(Function.prototype[key]) continue;
if(typeof object[key] === "function" || typeof object[key] === "object") {
// magic closures
attachTo[key] = new function() {
var fn = object[key];
return function() {
var args = (passAsFirstArgument ? [passAsFirstArgument] : []);
for(var i=0; i<arguments.length; i++) {
args.push(arguments[i]);
}
if(passAsFirstArgument) args.unshift(passAsFirstArgument);
return me.copyObject(object[localKey].apply(object, args));
});
} else {
attachTo[localKey] = new sandbox.Object();
return fn.apply(object, args);
};
}
// attach members
if(!(object instanceof Components.interfaces.nsISupports)) {
this.importObject(object[localKey], passAsFirstArgument, attachTo[localKey]);
}
this.importObject(object[key], passAsFirstArgument ? passAsFirstArgument : null, attachTo[key]);
} else {
attachTo[localKey] = object[localKey];
attachTo[key] = object[key];
}
}
if(newExposedProps) {
attachTo.__exposedProps__ = newExposedProps;
} else {
attachTo.__exposedProps__ = object.__exposedProps__;
}
},
"_canCopy":function(obj) {
if(typeof obj !== "object" || obj === null) return false;
if ((obj.wrappedJSObject && obj.wrappedJSObject.__wrappingManager)
|| Zotero.Translate.DOMWrapper.isWrapped(obj)
|| "__exposedProps__" in obj
|| !["Object", "Array", "Error"].includes(obj.constructor.name)) {
return false;
}
return true;
},
/**
* Copies a JavaScript object to this sandbox
* @param {Object} obj
* @return {Object}
*/
"copyObject":function(obj, wm) {
if(!this._canCopy(obj)) return obj;
if(!wm) wm = new WeakMap();
switch (obj.constructor.name) {
case 'Array':
case 'Error':
var obj2 = this.sandbox[obj.constructor.name]();
break;
default:
var obj2 = this.sandbox.Object();
break;
}
var wobj2 = obj2.wrappedJSObject ? obj2.wrappedJSObject : obj2;
for(var i in obj) {
if(!obj.hasOwnProperty(i)) continue;
var prop1 = obj[i];
if(this._canCopy(prop1)) {
var prop2 = wm.get(prop1);
if(prop2 === undefined) {
prop2 = this.copyObject(prop1, wm);
wm.set(prop1, prop2);
}
wobj2[i] = prop2;
} else {
wobj2[i] = prop1;
}
}
return obj2;
},
"newChild":function() {
return new Zotero.Translate.ChildSandboxManager(this);
}
}
Zotero.Translate.ChildSandboxManager = function(parent) {
this._wrappedSandbox = new parent.sandbox.Object();
this._wrappedSandbox.Zotero = new parent.sandbox.Object();
this.sandbox = this._wrappedSandbox.wrappedJSObject || this._wrappedSandbox;
this._parent = parent;
}
Zotero.Translate.ChildSandboxManager.prototype = {
"eval":function(code, functions, path) {
// eval in sandbox scope
if(functions) {
for(var i = 0; i < functions.length; i++) {
delete this.sandbox[functions[i]];
}
}
this._parent.sandbox._withSandbox = this._wrappedSandbox;
Components.utils.evalInSandbox("with(_withSandbox){"+code+"};", this._parent.sandbox, "1.8", path, 1);
if(functions) {
for(var i = 0; i < functions.length; i++) {
try {
this._wrappedSandbox[functions[i]] = Components.utils.evalInSandbox(functions[i], this._parent.sandbox);
} catch(e) {}
}
}
this._parent.sandbox._withSandbox = undefined;
},
"importObject":function(object, passAsFirstArgument, attachTo) {
if(!attachTo) attachTo = this.sandbox.Zotero;
// Zotero.debug(object);
// Zotero.debug(attachTo);
this._parent.importObject(object, passAsFirstArgument, attachTo);
// Zotero.debug(attachTo);
},
"copyObject":function(obj) {
return this._parent.copyObject(obj);
},
"newChild":function() {
return this._parent.newChild();
},
"_makeContentForwarder":function(f) {
return this._parent._makeContentForwarder(f);
},
"wrap": function (target, x, overrides) {
return this._parent.wrap(target, x, overrides);
}
}
@ -909,7 +726,7 @@ Zotero.Translate.IO.Read.prototype = {
this._xmlInvalid = true;
throw e;
}
return (Zotero.isFx ? this._sandboxManager.wrap(xml) : xml);
return xml;
},
init: function (newMode) {

View file

@ -216,26 +216,6 @@ Zotero.Utilities.Translate.prototype.processDocuments = async function (urls, pr
}
var processDoc = function (doc) {
if (Zotero.isFx) {
let newLoc = doc.location;
let url = Services.io.newURI(newLoc.href, null, null);
return processor(
// Rewrap document for the sandbox
translate._sandboxManager.wrap(
Zotero.Translate.DOMWrapper.unwrap(doc),
null,
// Duplicate overrides from Zotero.HTTP.wrapDocument()
{
documentURI: newLoc.spec,
URL: newLoc.spec,
location: new Zotero.HTTP.Location(url),
defaultView: new Zotero.HTTP.Window(url)
}
),
newLoc.href
);
}
return processor(doc, doc.location.href);
};