diff --git a/README.md b/README.md index d039240..c227a49 100644 --- a/README.md +++ b/README.md @@ -71,12 +71,9 @@ This library is released under the MIT license. See MIT-LICENSE.txt for details Specs ===== -This project uses [busted](http://olivinelabs.com/busted/) for its specs. In order to run them, install `busted` and then: +This project uses [telescope](https://github.com/norman/telescope) for its specs. In order to run them, install it and then: cd /path/to/where/the/spec/folder/is - busted - - - - + tsc spec/* +I would love to use [busted](http://olivinelabs.com/busted/), but it has some incompatibility with `debug.sethook(f, "", quota)` and the tests just hanged up. diff --git a/sandbox.lua b/sandbox.lua index 060cee7..617ab34 100644 --- a/sandbox.lua +++ b/sandbox.lua @@ -1,17 +1,17 @@ local BASE_ENV = {} -- Non-safe : --- string.rep: can be used to allocate millions of bytes in 1 operation --- {set|get}metatable: can be used to modify the metatable of global objects (strings, integers) --- collectgarbage: can affect performance of other systems --- dofile: can access the server filesystem --- _G: Unsafe. It can be mocked though --- load{file|string}: All unsafe because they can grant acces to global env --- raw{get|set|equal}: Potentially unsafe --- module|require|module: Can modify the host settings --- string.dump: Can display confidential server info (implementation of functions) --- string.rep: Can allocate millions of bytes in one go --- math.randomseed: Can affect the host sytem --- io.*, os.*: Most stuff there is non-save +-- * string.rep: can be used to allocate millions of bytes in 1 operation +-- * {set|get}metatable: can be used to modify the metatable of global objects (strings, integers) +-- * collectgarbage: can affect performance of other systems +-- * dofile: can access the server filesystem +-- * _G: Unsafe. It can be mocked though +-- * load{file|string}: All unsafe because they can grant acces to global env +-- * raw{get|set|equal}: Potentially unsafe +-- * module|require|module: Can modify the host settings +-- * string.dump: Can display confidential server info (implementation of functions) +-- * string.rep: Can allocate millions of bytes in one go +-- * math.randomseed: Can affect the host sytem +-- * io.*, os.*: Most stuff there is non-save ([[ @@ -74,30 +74,22 @@ local function cleanup() end local function protect(f, options) + if type(f) == 'string' then f = assert(loadstring(f)) end + + options = options or {} + + local quota = options.quota or 500000 + local env = merge(options.env or {}, BASE_ENV) + + setfenv(f, env) + return function(...) - if type(f) == 'string' then f = assert(loadstring(f)) end - - options = options or {} - - local quota = options.quota or 500000 - local env = merge(options.env or {}, BASE_ENV) - - setfenv(f, env) - - -- I would love to be able to make step greater than 1 - -- (say, 500000) but any value > 1 seems to choke with a simple while true do end - -- After ~100 iterations, they stop calling timeout. So I need to use step = 1 and - -- instructions_count the steps separatedly - local step = 1 - local instructions_count = 0 - local timeout = function(str) - instructions_count = instructions_count + 1 - if instructions_count >= quota then - cleanup() - error('Quota exceeded: ' .. tostring(instructions_count) .. '/' .. tostring(quota) .. ' instructions') - end + local timeout = function() + cleanup() + error('Quota exceeded: ' .. tostring(quota)) end - debug.sethook(timeout, "", step) + + debug.sethook(timeout, "", quota) string.rep = nil local ok, result = pcall(f, ...) diff --git a/spec/sandbox_spec.lua b/spec/sandbox_spec.lua index 53bbebe..b7b3317 100644 --- a/spec/sandbox_spec.lua +++ b/spec/sandbox_spec.lua @@ -5,45 +5,45 @@ describe('sandbox.run', function() describe('when handling base cases', function() it('can run harmless functions', function() local r = sandbox.run(function() return 'hello' end) - assert.equal(r, 'hello') + assert_equal(r, 'hello') end) it('can run harmless strings', function() local r = sandbox.run("return 'hello'") - assert.equal(r, 'hello') + assert_equal(r, 'hello') end) it('has access to safe methods', function() - assert.equal(10, sandbox.run("return tonumber('10')")) - assert.equal('HELLO', sandbox.run("return string.upper('hello')")) - assert.equal(1, sandbox.run("local a = {3,2,1}; table.sort(a); return a[1]")) - assert.equal(10, sandbox.run("return math.max(1,10)")) + assert_equal(10, sandbox.run("return tonumber('10')")) + assert_equal('HELLO', sandbox.run("return string.upper('hello')")) + assert_equal(1, sandbox.run("local a = {3,2,1}; table.sort(a); return a[1]")) + assert_equal(10, sandbox.run("return math.max(1,10)")) end) it('does not allow access to not-safe stuff', function() - assert.has_error(function() sandbox.run('return setmetatable({}, {})') end) - assert.has_error(function() sandbox.run('return string.rep("hello", 5)') end) - assert.has_error(function() sandbox.run('return _G.string.upper("hello")') end) + assert_error(function() sandbox.run('return setmetatable({}, {})') end) + assert_error(function() sandbox.run('return string.rep("hello", 5)') end) + assert_error(function() sandbox.run('return _G.string.upper("hello")') end) end) end) describe('when handling string.rep', function() it('does not allow pesky string:rep', function() - assert.has_error(function() sandbox.run('return ("hello"):rep(5)') end) + assert_error(function() sandbox.run('return ("hello"):rep(5)') end) end) it('restores the value of string.rep', function() sandbox.run("") - assert.equal('hellohello', string.rep('hello', 2)) + assert_equal('hellohello', string.rep('hello', 2)) end) it('restores string.rep even if there is an error', function() - assert.has_error(function() sandbox.run("error('foo')") end) - assert.equal('hellohello', string.rep('hello', 2)) + assert_error(function() sandbox.run("error('foo')") end) + assert_equal('hellohello', string.rep('hello', 2)) end) it('passes parameters to the function', function() - assert.equal(sandbox.run(function(a,b) return a + b end, {}, 1,2), 3) + assert_equal(sandbox.run(function(a,b) return a + b end, {}, 1,2), 3) end) end) @@ -51,19 +51,19 @@ describe('sandbox.run', function() describe('when the sandboxed function tries to modify the base environment', function() it('does not allow modifying the modules', function() - assert.has_error(function() sandbox.run("string.foo = 1") end) - assert.has_error(function() sandbox.run("string.char = 1") end) + assert_error(function() sandbox.run("string.foo = 1") end) + assert_error(function() sandbox.run("string.char = 1") end) end) it('does not persist modifications of base functions', function() sandbox.run('error = function() end') - assert.has_error(function() sandbox.run("error('this should be raised')") end) + assert_error(function() sandbox.run("error('this should be raised')") end) end) it('DOES persist modification to base functions when they are provided by the base env', function() local env = {['next'] = 'hello'} sandbox.run('next = "bye"', {env=env}) - assert.equal(env['next'], 'bye') + assert_equal(env['next'], 'bye') end) end) @@ -71,17 +71,17 @@ describe('sandbox.run', function() describe('when given infinite loops', function() it('throws an error with infinite loops', function() - assert.has_error(function() sandbox.run("while true do end") end) + assert_error(function() sandbox.run("while true do end") end) end) it('restores string.rep even after a while true', function() - assert.has_error(function() sandbox.run("while true do end") end) - assert.equal('hellohello', string.rep('hello', 2)) + assert_error(function() sandbox.run("while true do end") end) + assert_equal('hellohello', string.rep('hello', 2)) end) it('accepts a quota param', function() - assert.no_has_error(function() sandbox.run("for i=1,100 do end") end) - assert.has_error(function() sandbox.run("for i=1,100 do end", {quota = 20}) end) + assert_not_error(function() sandbox.run("for i=1,100 do end") end) + assert_error(function() sandbox.run("for i=1,100 do end", {quota = 20}) end) end) end) @@ -89,17 +89,17 @@ describe('sandbox.run', function() describe('when given an env option', function() it('is available on the sandboxed env', function() - assert.equal(1, sandbox.run("return foo", {env = {foo = 1}})) + assert_equal(1, sandbox.run("return foo", {env = {foo = 1}})) end) it('does not hide base env', function() - assert.equal('HELLO', sandbox.run("return string.upper(foo)", {env = {foo = 'hello'}})) + assert_equal('HELLO', sandbox.run("return string.upper(foo)", {env = {foo = 'hello'}})) end) it('can modify the env', function() local env = {foo = 1} sandbox.run("foo = 2", {env = env}) - assert.equal(env.foo, 2) + assert_equal(env.foo, 2) end) end)