Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi-line exec #21

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
63 changes: 48 additions & 15 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 @@ -23,7 +26,28 @@ if (0) { // enable for debugging?
evel._jsGlobals['console'] = void 0;
}

evel._global = function () { return this || window; }.call(null); // NOTE: we fallback to `window` in case someone puts *us* into strict mode
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);
}
});

evel._globalNames = function (gObj) {
var globals = Object.create(null),
Expand All @@ -46,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 @@ -89,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