Making WebWorkers a safe environment
javascript, web-worker
Solution
The current code (listed below) has been now in use in the Stackoverflow javascript chat room for a while and so far the toughest problem was `Array(5000000000).join("adasdadadasd")` instantly crashing some browser tabs for me when I was running the code executor bot. Monkeypatching `Array.prototype.join` seems to have fixed this and the maximum execution time of 50ms has worked for any other attempt to hog memory or crash the browser.
var global = this;
/* Could possibly create some helper functions here so they are always available when executing code in chat?*/
/* Most extra functions could be possibly unsafe */
var wl = {
"self": 1,
"onmessage": 1,
"postMessage": 1,
"global": 1,
"wl": 1,
"eval": 1,
"Array": 1,
"Boolean": 1,
"Date": 1,
"Function": 1,
"Number" : 1,
"Object": 1,
"RegExp": 1,
"String": 1,
"Error": 1,
"EvalError": 1,
"RangeError": 1,
"ReferenceError": 1,
"SyntaxError": 1,
"TypeError": 1,
"URIError": 1,
"decodeURI": 1,
"decodeURIComponent": 1,
"encodeURI": 1,
"encodeURIComponent": 1,
"isFinite": 1,
"isNaN": 1,
"parseFloat": 1,
"parseInt": 1,
"Infinity": 1,
"JSON": 1,
"Math": 1,
"NaN": 1,
"undefined": 1
};
Object.getOwnPropertyNames( global ).forEach( function( prop ) {
if( !wl.hasOwnProperty( prop ) ) {
Object.defineProperty( global, prop, {
get : function() {
throw "Security Exception: cannot access "+prop;
return 1;
},
configurable : false
});
}
});
Object.getOwnPropertyNames( global.__proto__ ).forEach( function( prop ) {
if( !wl.hasOwnProperty( prop ) ) {
Object.defineProperty( global.__proto__, prop, {
get : function() {
throw "Security Exception: cannot access "+prop;
return 1;
},
configurable : false
});
}
});
Object.defineProperty( Array.prototype, "join", {
writable: false,
configurable: false,
enumerable: false,
value: function(old){
return function(arg){
if( this.length > 500 || (arg && arg.length > 500 ) ) {
throw "Exception: too many items";
}
return old.apply( this, arguments );
};
}(Array.prototype.join)
});
(function(){
var cvalues = [];
var console = {
log: function(){
cvalues = cvalues.concat( [].slice.call( arguments ) );
}
};
function objToResult( obj ) {
var result = obj;
switch( typeof result ) {
case "string":
return '"' + result + '"';
break;
case "number":
case "boolean":
case "undefined":
case "null":
case "function":
return result + "";
break;
case "object":
if( !result ) {
return "null";
}
else if( result.constructor === Object || result.constructor === Array ) {
var type = ({}).toString.call( result );
var stringified;
try {
stringified = JSON.stringify(result);
}
catch(e) {
return ""+e;
}
return type + " " + stringified;
}
else {
return ({}).toString.call( result );
}
break;
}
}
onmessage = function( event ) {
"use strict";
var code = event.data.code;
var result;
try {
result = eval( '"use strict";\n'+code );
}
catch(e) {
postMessage( e.toString() );
return;
}
result = objToResult( result );
if( cvalues && cvalues.length ) {
result = result + cvalues.map( function( value, index ) {
return "Console log "+(index+1)+":" + objToResult(value);
}).join(" ");
}
postMessage( (""+result).substr(0,400) );
};
})();
Problem
In a quest to have an interface capable of running arbitrary javascript code inside the browser, without having a security hole the size of a typical yo-mama joke, Esailija proposed using Web Workers. They run in a semi-sandboxed environment (no DOM access and already inside the browser) and can be killed so the user can't put them in an infinite loop. Here's the example he brought up: http://tuohiniemi.fi/~runeli/petka/workertest.html (open your console) jsfiddle (Google chrome only) Now, this seems like a good solution; however, is it a complete (or approaching complete) one? Is there anything obvious missing? The entire thing (as it's hooked up to a bot) can be found on github: worker, evaluator main: ``` workercode = "worker.js"; function makeWorkerExecuteSomeCode( code, callback ) { var timeout; code = code + ""; var worker = new Worker( workercode ); worker.addEventListener( "message", function(event) { clearTimeout(timeout); callback( event.data ); }); worker.postMessage({ code: code }); timeout = window.setTimeout( function() { callback( "Maximum execution time exceeded" ); worker.terminate(); }, 1000 ); } makeWorkerExecuteSomeCode( '5 + 5', function(answer){ console.log( answer ); }); makeWorkerExecuteSomeCode( 'while(true);', function(answer){ console.log( answer ); }); var kertoma = 'function kertoma(n){return n === 1 ? 1 : n * kertoma(n-1)}; kertoma(15);'; makeWorkerExecuteSomeCode( kertoma, function(answer){ console.log( answer ); }); ``` worker: ``` var global = this; /* Could possibly create some helper functions here so they are always available when executing code in chat?*/ /* Most extra functions could be possibly unsafe */ var wl = { "self": 1, "onmessage": 1, "postMessage": 1, "global": 1, "wl": 1, "eval": 1, "Array": 1, "Boolean": 1, "Date": 1, "Function": 1, "Number" : 1, "Object": 1, "RegExp": 1, "String": 1, "Error": 1, "EvalError": 1, "RangeError": 1, "ReferenceError": 1, "SyntaxError": 1, "TypeError": 1, "URIError": 1, "decodeURI": 1, "decodeURIComponent": 1, "encodeURI": 1, "encodeURIComponent": 1, "isFinite": 1, "isNaN": 1, "parseFloat": 1, "parseInt": 1, "Infinity": 1, "JSON": 1, "Math": 1, "NaN": 1, "undefined": 1 }; Object.getOwnPropertyNames( global ).forEach( function( prop ) { if( !wl.hasOwnProperty( prop ) ) { Object.defineProperty( global, prop, { get : function() { throw new Error( "Security Exception: cannot access "+prop); return 1; }, configurable : false }); } }); Object.getOwnPropertyNames( global.__proto__ ).forEach( function( prop ) { if( !wl.hasOwnProperty( prop ) ) { Object.defineProperty( global.__proto__, prop, { get : function() { throw new Error( "Security Exception: cannot access "+prop); return 1; }, configurable : false }); } }); onmessage = function( event ) { "use strict"; var code = event.data.code; var result; try { result = eval( '"use strict";\n'+code ); } catch(e){ result = e.toString(); } postMessage( "(" + typeof result + ")" + " " + result ); }; ```