From 8a82a24c7d6833cdd41f9d245ecbd3a57f62d267 Mon Sep 17 00:00:00 2001 From: Kushagra Gour Date: Mon, 14 Nov 2016 12:41:59 +0530 Subject: [PATCH] Protect webmaker users from INFINITE LOOPS! fixes #18 --- src/index.html | 1 + src/script.js | 22 ++++++++++++++++------ src/utils.js | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/index.html b/src/index.html index adeebb5..d3b1ee4 100644 --- a/src/index.html +++ b/src/index.html @@ -676,6 +676,7 @@ + diff --git a/src/script.js b/src/script.js index 96975dc..fac93ca 100644 --- a/src/script.js +++ b/src/script.js @@ -334,16 +334,18 @@ var code = editur.cm.js.getValue(); cleanupErrors('js'); + var ast; if (jsMode === JsModes.JS) { try { - esprima.parse(code, { + ast = esprima.parse(code, { tolerant: true }); } catch(e) { showErrors('js', [ { lineNumber: e.lineNumber-1, message: e.description } ]); } finally { - d.resolve(code); + utils.addInfiniteLoopProtection(ast); + d.resolve(escodegen.generate(ast)); } } else if (jsMode === JsModes.COFFEESCRIPT) { var coffeeCode; @@ -352,23 +354,31 @@ } catch (e) { showErrors('js', [ { lineNumber: e.location.first_line, message: e.message } ]); } finally { - d.resolve(coffeeCode); + ast = esprima.parse(coffeeCode, { + tolerant: true + }); + utils.addInfiniteLoopProtection(ast); + d.resolve(escodegen.generate(ast)); } } else if (jsMode === JsModes.ES6) { try { - esprima.parse(code, { + ast = esprima.parse(code, { tolerant: true }); } catch(e) { showErrors('js', [ { lineNumber: e.lineNumber-1, message: e.description } ]); } finally { - d.resolve(Babel.transform(code, { presets: ['es2015'] }).code); + utils.addInfiniteLoopProtection(ast); + d.resolve(Babel.transform(escodegen.generate(ast), { presets: ['es2015'] }).code); } } return d.promise; } + window.previewException = function (error) { + console.error('Possible infinite loop detected.', error.stack) + } window.onunload = function () { saveCode('code'); }; @@ -391,7 +401,7 @@ var contents = '\n\n' + '\n' + '\n' + - '\n' + html + '\n\n'; + '\n' + html + '\n\n'; var fileWritten = false; diff --git a/src/utils.js b/src/utils.js index fe16fc3..cdf4534 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,7 @@ (function () { window.$ = document.querySelector.bind(document); window.$all = document.querySelectorAll.bind(document); + var alphaNum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; // https://github.com/substack/semver-compare/blob/master/index.js function semverCompare(a, b) { @@ -21,7 +22,7 @@ len = len || 10; var id = ''; for (var i = len; i--;) { - id += String.fromCharCode(~~(Math.random() * 52) + 65); + id += alphaNum[~~(Math.random() * alphaNum.length)]; } return id; } @@ -39,10 +40,57 @@ } } + // Generate 2 ASTs for the code to be inserted in loops for infinite run protection. + // The `myVar` variable names would be changed later for every insertion. + function getLoopProtectorBlocks() { + var ast1 = esprima.parse('var myvar = Date.now();'); + var ast2 = esprima.parse('while(a){if (Date.now() - a787897 > 1000) { window.top.previewException(new Error("Infinite loop")); break;}}'); + return { + before: ast1.body[0], + inside: ast2.body[0].body.body[0] + } + } + /** + * Add timed limit on the loops found in the passed AST body + * @param {ASTBody} Body of an AST generated by esprima or any ES compliant AST + */ + function addInfiniteLoopProtection(astBody) { + if (!Array.isArray(astBody)) { + addInfiniteLoopProtection(astBody.body); + return; + } + var el, randomVariableName, insertionBLocks; + + for (var i = astBody.length; i--;) { + el = astBody[i]; + if (el && el.type === 'ForStatement' || el.type === 'WhileStatement' || el.type === 'DoWhileStatement') { + randomVariableName = '_' + generateRandomId(3); + insertionBLocks = getLoopProtectorBlocks(); + insertionBLocks.before.declarations[0].id.name = insertionBLocks.inside.test.left.right.name = randomVariableName; + // Insert time variable assignment + astBody.splice(i, 0, insertionBLocks.before); + // If the loop's body is a single statement, then convert it into a block statement + // so that we can insert our conditional break inside it. + if (!Array.isArray(el.body)) { + el.body = { + body: [ el.body ], + type: 'BlockStatement' + }; + } + // Insert IfStatement + el.body.body.unshift(insertionBLocks.inside); + } + if (el.body) { + addInfiniteLoopProtection(el.body); + } + } + } + window.utils = { semverCompare: semverCompare, generateRandomId: generateRandomId, onButtonClick: onButtonClick, + addInfiniteLoopProtection: addInfiniteLoopProtection, log: log }; })();