From ca013c1e2f75483f72a8f3893beb9df1624b4aa4 Mon Sep 17 00:00:00 2001 From: jkonieczny Date: Wed, 8 Apr 2020 00:52:55 +0200 Subject: [PATCH 1/4] add timeout and worker --- index.js | 52 ++++++++++++++++++++++++++++++++++------- lib/infinite-checker.js | 6 ++++- test/sandbox.js | 8 +++++++ 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 1058af3..d31de06 100644 --- a/index.js +++ b/index.js @@ -4,18 +4,47 @@ var hoist = require('hoister') var InfiniteChecker = require('./lib/infinite-checker') var Primitives = require('./lib/primitives') +var self = this + +var getNow = self.performance ? self.performance.now : process.uptime + module.exports = safeEval module.exports.eval = safeEval +module.exports.compile = compile module.exports.FunctionFactory = FunctionFactory module.exports.Function = FunctionFactory() -var maxIterations = 1000000 +var MAX_ITERATIONS_DEFAULT = 1000000 // 'eval' with a controlled environment -function safeEval(src, parentContext){ +function safeEval(src, parentContext, _options={}){ + const options = Object.assign({ + maxIterations: _options.maxIteration ? _options.maxIteration : MAX_ITERATIONS_DEFAULT, + timeout: _options.timeout ? _options.timeout : 0 + }, _options) + options.timeout = options.timeout / (self.performance ? 1 : 1000) var tree = prepareAst(src) var context = Object.create(parentContext || {}) - return finalValue(evaluateAst(tree, context)) + return finalValue(evaluateAst(tree, context, options)) + +} + +if (self.document === undefined) { + self.onmessage = function ({src, parentContext, timeout, cmd="exe"}) { + switch(cmd) { + case 'exe': + self.postMessage(safeEval(src, parentContext, { maxIterations: 0 })) + break + case 'compile': + self.postMessage(prepareAst(src)) + break + } + } +} + +function compile(src) { + var tree = prepareAst(src) + return tree } // create a 'Function' constructor for a controlled environment @@ -42,7 +71,9 @@ function prepareAst(src){ } // evaluate an AST in the given context -function evaluateAst(tree, context){ +function evaluateAst(tree, context, options){ + var startTime = getNow() + var isTimeout = options.timeout ? options.timeout > 0 : false var safeFunction = FunctionFactory(context) var primitives = Primitives(context) @@ -68,6 +99,11 @@ function evaluateAst(tree, context){ // recursively evalutate the node of an AST function walk(node, traceNode){ + if (isTimeout){ + if (getNow() - startTime > options.timeout){ + throw new Error('Execution time') + } + } try { if (!node) return switch (node.type) { @@ -171,7 +207,7 @@ function evaluateAst(tree, context){ } case 'ForStatement': - var infinite = InfiniteChecker(maxIterations) + var infinite = InfiniteChecker(options.maxIterations) var result = undefined enterBlock() // allow lets on delarations @@ -192,7 +228,7 @@ function evaluateAst(tree, context){ return result case 'ForInStatement': - var infinite = InfiniteChecker(maxIterations) + var infinite = InfiniteChecker(options.maxIterations) var result = undefined var value = walk(node.right) @@ -228,7 +264,7 @@ function evaluateAst(tree, context){ return result case 'WhileStatement': - var infinite = InfiniteChecker(maxIterations) + var infinite = InfiniteChecker(options.maxIterations) while (walk(node.test)){ walk(node.body) infinite.check() @@ -509,7 +545,7 @@ function getFunction(body, params, parentContext, traceNode){ context[param] = arg } }) - var result = evaluateAst(body, context) + var result = evaluateAst(body, context, {timeout: 0, maxIterations: MAX_ITERATIONS_DEFAULT}) if (result instanceof ReturnValue){ return result.value diff --git a/lib/infinite-checker.js b/lib/infinite-checker.js index d3b43a6..c6193e1 100644 --- a/lib/infinite-checker.js +++ b/lib/infinite-checker.js @@ -2,7 +2,11 @@ module.exports = InfiniteChecker function InfiniteChecker(maxIterations){ if (this instanceof InfiniteChecker){ - this.maxIterations = maxIterations + if (maxIterations == 0) { + this.maxIterations = Number.POSITIVE_INFINITY + } else { + this.maxIterations = maxIterations + } this.count = 0 } else { return new InfiniteChecker(maxIterations) diff --git a/test/sandbox.js b/test/sandbox.js index 295520c..1c09bf8 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -53,6 +53,14 @@ test('infinite while loop', function(t){ t.end() }) + +test('infinite while loop timeout', function(t){ + t.throws(function(){ + safeEval('while (true){}', {}, {timeout: 100, maxIterations: 0}) + }) + t.end() +}) + test('set wrapped string prototype', function(t){ var code = 'String.prototype.makeLouder = function() { return this + "!" }; "test".makeLouder()' t.equal(safeEval(code), 'test!') From 271de70c4f01d42925ba7afa86ff271055b8a5c3 Mon Sep 17 00:00:00 2001 From: jkonieczny Date: Wed, 8 Apr 2020 00:56:52 +0200 Subject: [PATCH 2/4] fix --- test/sandbox.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/sandbox.js b/test/sandbox.js index 1c09bf8..c639f55 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -53,7 +53,6 @@ test('infinite while loop', function(t){ t.end() }) - test('infinite while loop timeout', function(t){ t.throws(function(){ safeEval('while (true){}', {}, {timeout: 100, maxIterations: 0}) From 08ab4996a90da47a36e368d00e47a2a356b0d5f0 Mon Sep 17 00:00:00 2001 From: jkonieczny Date: Wed, 8 Apr 2020 01:10:00 +0200 Subject: [PATCH 3/4] use `new Date().getTime()` --- index.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index d31de06..9a4b9eb 100644 --- a/index.js +++ b/index.js @@ -6,8 +6,6 @@ var Primitives = require('./lib/primitives') var self = this -var getNow = self.performance ? self.performance.now : process.uptime - module.exports = safeEval module.exports.eval = safeEval module.exports.compile = compile @@ -22,7 +20,6 @@ function safeEval(src, parentContext, _options={}){ maxIterations: _options.maxIteration ? _options.maxIteration : MAX_ITERATIONS_DEFAULT, timeout: _options.timeout ? _options.timeout : 0 }, _options) - options.timeout = options.timeout / (self.performance ? 1 : 1000) var tree = prepareAst(src) var context = Object.create(parentContext || {}) return finalValue(evaluateAst(tree, context, options)) @@ -72,7 +69,7 @@ function prepareAst(src){ // evaluate an AST in the given context function evaluateAst(tree, context, options){ - var startTime = getNow() + var startTime = new Date().getTime() var isTimeout = options.timeout ? options.timeout > 0 : false var safeFunction = FunctionFactory(context) @@ -100,7 +97,7 @@ function evaluateAst(tree, context, options){ // recursively evalutate the node of an AST function walk(node, traceNode){ if (isTimeout){ - if (getNow() - startTime > options.timeout){ + if (new Date().getTime() - startTime > options.timeout){ throw new Error('Execution time') } } From c0cfb60124c2083733a0c5cbad8919324214f1ac Mon Sep 17 00:00:00 2001 From: jkonieczny Date: Wed, 8 Apr 2020 01:10:53 +0200 Subject: [PATCH 4/4] remove worker support --- index.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/index.js b/index.js index 9a4b9eb..11694e3 100644 --- a/index.js +++ b/index.js @@ -23,20 +23,6 @@ function safeEval(src, parentContext, _options={}){ var tree = prepareAst(src) var context = Object.create(parentContext || {}) return finalValue(evaluateAst(tree, context, options)) - -} - -if (self.document === undefined) { - self.onmessage = function ({src, parentContext, timeout, cmd="exe"}) { - switch(cmd) { - case 'exe': - self.postMessage(safeEval(src, parentContext, { maxIterations: 0 })) - break - case 'compile': - self.postMessage(prepareAst(src)) - break - } - } } function compile(src) {