Skip to content

Commit

Permalink
fixes natevw#12
Browse files Browse the repository at this point in the history
  • Loading branch information
duralog committed Jun 15, 2014
1 parent 5fddad3 commit 13edfaa
Showing 1 changed file with 44 additions and 32 deletions.
76 changes: 44 additions & 32 deletions evel.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
if (typeof exports === 'object') module.exports = evel; // node.js module support

function evel(code) {
if (typeof code !== 'string') return code;
else if (code) return evel.Function("return ("+code+");")();
function evel(name, code) {
if(typeof code === 'undefined') {
code = name;
name = 'evel';
}
return typeof code === 'string' ? evel.Function(name, code)() : code;
};

evel._supportsStrict = function () {
Expand All @@ -25,24 +28,24 @@ if (0) { // enable for debugging?

Object.defineProperty(evel, "_global", {
get: function () {
if (this._evel) {
var o = {};
Object.getOwnPropertyNames(this).forEach(function (k) { o[k] = this[k]; });
if (o._eval) delete o.eval._global;
return o;
}
delete this._evel;
return function() {
if(this) {
if(!this.evel._evel) Object.defineProperty(this.evel, "_evel", { value: true });
var o = {};
Object.getOwnPropertyNames(this).forEach(function (k) { o[k] = this[k]; });
return o;
} else {
// NOTE: we fallback to `window` in case someone puts *us* into strict mode
return this || window;
}
}.call(null);
if (this._evel) {
var o = {};
Object.getOwnPropertyNames(this).forEach(function (k) { o[k] = this[k]; });
if (o._eval) delete o.eval._global;
return o;
}
delete this._evel;
return function() {
if(this) {
if(!this.evel._evel) Object.defineProperty(this.evel, "_evel", { value: true });
var o = {};
Object.getOwnPropertyNames(this).forEach(function (k) { o[k] = this[k]; });
return o;
} else {
// NOTE: we fallback to `window` in case someone puts *us* into strict mode
return this || window;
}
}.call(null);
}
});

Expand All @@ -67,36 +70,45 @@ evel._globalNames = function (gObj) {
evel._globalNames.memoizedFilterResults = Object.create(null);
evel._globalNames(); // warm the cache


evel.Function = function () {
if (!evel._supportsStrict()) throw Error("This browser does not support sandboxed code execution.");
/* This works by:
1. Sanitizing the provided source (immediately, which also serves to flag syntax errors at expected time)
2. Wrapping source in a "use strict";` environment to eliminate global access via `this` tricks
3. Shadowing all non-ES5 globals† (each time called!) to eliminate direct access via name
4. Replacing direct/indirect access to ES5's function stuff, as they allow global access
5. …doing the last three steps from a new iframe's JS environment
Basically instead of returning the provided code directly, we wrap it like this:
function ({{g1}}, {{g2}}, …, {{gN}}) { // imagine {g1:'document', g2:'XMLHttpRequest', g3:'d3', … }
"use strict";
var fn = {{sanitizedSource}};
return fn.apply(non_window_ctx, original_args);
}
Note that all bets are off if browser doesn't support strict mode, so we block that.
† IMPORTANT: the untrusted code WILL have access to anything named the same as the normal built-in JavaScript globals!
This is both for practical purposes (since said code prolly expects them!) and because blocking them doesn't do much good;
can't prevent access to shared prototypes of e.g. objects/arrays/regexes while still keeping the function calls synchronous.
Since we run in a separate frame, this is less likely a problem, but browser add-ons could potentially expose "useful" stuff. */

var src = "\"use strict\"; var fn = "+Function.apply(null, arguments).toString()+"; return fn.apply(this.ctx, this.args);";

var regex_space = new RegExp(' ', 'g');
var name = arguments[0] + '';
var args1 = [].slice.call(arguments, 1);
var fn = Function.apply(null, args1).toString();
var i = fn.indexOf('(');
var ii = fn.indexOf(')');
var j = fn.indexOf('{');
var jj = fn.lastIndexOf('}');
var args = fn.substring(++i, ii).replace(regex_space, '');
var body = '"use strict"\n ' + fn.substring(++j, jj).trim();
var src = "\"use strict\";\nvar fn = function("+args+") {\n\t"+fn.substring(++j, jj).trim()+"\n};\nreturn fn.apply(this.ctx, this.args);";
return function () {
"use strict"; // avoids boxing of this callee's own `this`

// c.f. http://dean.edwards.name/weblog/2006/11/sandbox/ via https://github.com/substack/vm-browserify/
var sbx = document.createElement('iframe');
sbx.setAttribute('sandbox', "allow-same-origin"); // need same-origin so *we* can access it!
Expand All @@ -110,10 +122,10 @@ evel.Function = function () {
_gObj.Function = evel.Function; // avoid direct accesses (must be done while sbx in DOM!)
document.documentElement.removeChild(sbx);
_Function.prototype.constructor = evel.Function; // avoid prototypical Function accesss

return _Function.apply(null, wrapper).call({
ctx: (this !== evel._global) ? this : null,
args: arguments
args: args1
});
};
};
Expand Down

0 comments on commit 13edfaa

Please sign in to comment.