mirror of
https://github.com/kikito/lua-sandbox.git
synced 2024-12-18 03:04:20 +00:00
moved specs from busted to telescope in order to fix issue with debug.sethook-based quota errors
This commit is contained in:
parent
e9ef4bb57c
commit
83a5a2b1e2
@ -71,12 +71,9 @@ This library is released under the MIT license. See MIT-LICENSE.txt for details
|
|||||||
Specs
|
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
|
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.
|
||||||
|
60
sandbox.lua
60
sandbox.lua
@ -1,17 +1,17 @@
|
|||||||
local BASE_ENV = {}
|
local BASE_ENV = {}
|
||||||
-- Non-safe :
|
-- Non-safe :
|
||||||
-- string.rep: can be used to allocate millions of bytes in 1 operation
|
-- * 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)
|
-- * {set|get}metatable: can be used to modify the metatable of global objects (strings, integers)
|
||||||
-- collectgarbage: can affect performance of other systems
|
-- * collectgarbage: can affect performance of other systems
|
||||||
-- dofile: can access the server filesystem
|
-- * dofile: can access the server filesystem
|
||||||
-- _G: Unsafe. It can be mocked though
|
-- * _G: Unsafe. It can be mocked though
|
||||||
-- load{file|string}: All unsafe because they can grant acces to global env
|
-- * load{file|string}: All unsafe because they can grant acces to global env
|
||||||
-- raw{get|set|equal}: Potentially unsafe
|
-- * raw{get|set|equal}: Potentially unsafe
|
||||||
-- module|require|module: Can modify the host settings
|
-- * module|require|module: Can modify the host settings
|
||||||
-- string.dump: Can display confidential server info (implementation of functions)
|
-- * string.dump: Can display confidential server info (implementation of functions)
|
||||||
-- string.rep: Can allocate millions of bytes in one go
|
-- * string.rep: Can allocate millions of bytes in one go
|
||||||
-- math.randomseed: Can affect the host sytem
|
-- * math.randomseed: Can affect the host sytem
|
||||||
-- io.*, os.*: Most stuff there is non-save
|
-- * io.*, os.*: Most stuff there is non-save
|
||||||
|
|
||||||
([[
|
([[
|
||||||
|
|
||||||
@ -74,30 +74,22 @@ local function cleanup()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function protect(f, options)
|
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(...)
|
return function(...)
|
||||||
if type(f) == 'string' then f = assert(loadstring(f)) end
|
local timeout = function()
|
||||||
|
cleanup()
|
||||||
options = options or {}
|
error('Quota exceeded: ' .. tostring(quota))
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
debug.sethook(timeout, "", step)
|
|
||||||
|
debug.sethook(timeout, "", quota)
|
||||||
string.rep = nil
|
string.rep = nil
|
||||||
|
|
||||||
local ok, result = pcall(f, ...)
|
local ok, result = pcall(f, ...)
|
||||||
|
@ -5,45 +5,45 @@ describe('sandbox.run', function()
|
|||||||
describe('when handling base cases', function()
|
describe('when handling base cases', function()
|
||||||
it('can run harmless functions', function()
|
it('can run harmless functions', function()
|
||||||
local r = sandbox.run(function() return 'hello' end)
|
local r = sandbox.run(function() return 'hello' end)
|
||||||
assert.equal(r, 'hello')
|
assert_equal(r, 'hello')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('can run harmless strings', function()
|
it('can run harmless strings', function()
|
||||||
local r = sandbox.run("return 'hello'")
|
local r = sandbox.run("return 'hello'")
|
||||||
assert.equal(r, 'hello')
|
assert_equal(r, 'hello')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('has access to safe methods', function()
|
it('has access to safe methods', function()
|
||||||
assert.equal(10, sandbox.run("return tonumber('10')"))
|
assert_equal(10, sandbox.run("return tonumber('10')"))
|
||||||
assert.equal('HELLO', sandbox.run("return string.upper('hello')"))
|
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(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 math.max(1,10)"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('does not allow access to not-safe stuff', function()
|
it('does not allow access to not-safe stuff', function()
|
||||||
assert.has_error(function() sandbox.run('return setmetatable({}, {})') end)
|
assert_error(function() sandbox.run('return setmetatable({}, {})') end)
|
||||||
assert.has_error(function() sandbox.run('return string.rep("hello", 5)') end)
|
assert_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 _G.string.upper("hello")') end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('when handling string.rep', function()
|
describe('when handling string.rep', function()
|
||||||
it('does not allow pesky 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)
|
end)
|
||||||
|
|
||||||
it('restores the value of string.rep', function()
|
it('restores the value of string.rep', function()
|
||||||
sandbox.run("")
|
sandbox.run("")
|
||||||
assert.equal('hellohello', string.rep('hello', 2))
|
assert_equal('hellohello', string.rep('hello', 2))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('restores string.rep even if there is an error', function()
|
it('restores string.rep even if there is an error', function()
|
||||||
assert.has_error(function() sandbox.run("error('foo')") end)
|
assert_error(function() sandbox.run("error('foo')") end)
|
||||||
assert.equal('hellohello', string.rep('hello', 2))
|
assert_equal('hellohello', string.rep('hello', 2))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('passes parameters to the function', function()
|
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)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -51,19 +51,19 @@ describe('sandbox.run', function()
|
|||||||
describe('when the sandboxed function tries to modify the base environment', function()
|
describe('when the sandboxed function tries to modify the base environment', function()
|
||||||
|
|
||||||
it('does not allow modifying the modules', function()
|
it('does not allow modifying the modules', function()
|
||||||
assert.has_error(function() sandbox.run("string.foo = 1") end)
|
assert_error(function() sandbox.run("string.foo = 1") end)
|
||||||
assert.has_error(function() sandbox.run("string.char = 1") end)
|
assert_error(function() sandbox.run("string.char = 1") end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('does not persist modifications of base functions', function()
|
it('does not persist modifications of base functions', function()
|
||||||
sandbox.run('error = function() end')
|
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)
|
end)
|
||||||
|
|
||||||
it('DOES persist modification to base functions when they are provided by the base env', function()
|
it('DOES persist modification to base functions when they are provided by the base env', function()
|
||||||
local env = {['next'] = 'hello'}
|
local env = {['next'] = 'hello'}
|
||||||
sandbox.run('next = "bye"', {env=env})
|
sandbox.run('next = "bye"', {env=env})
|
||||||
assert.equal(env['next'], 'bye')
|
assert_equal(env['next'], 'bye')
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -71,17 +71,17 @@ describe('sandbox.run', function()
|
|||||||
describe('when given infinite loops', function()
|
describe('when given infinite loops', function()
|
||||||
|
|
||||||
it('throws an error with 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)
|
end)
|
||||||
|
|
||||||
it('restores string.rep even after a while true', function()
|
it('restores string.rep even after a while true', function()
|
||||||
assert.has_error(function() sandbox.run("while true do end") end)
|
assert_error(function() sandbox.run("while true do end") end)
|
||||||
assert.equal('hellohello', string.rep('hello', 2))
|
assert_equal('hellohello', string.rep('hello', 2))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('accepts a quota param', function()
|
it('accepts a quota param', function()
|
||||||
assert.no_has_error(function() sandbox.run("for i=1,100 do end") end)
|
assert_not_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_error(function() sandbox.run("for i=1,100 do end", {quota = 20}) end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
end)
|
end)
|
||||||
@ -89,17 +89,17 @@ describe('sandbox.run', function()
|
|||||||
|
|
||||||
describe('when given an env option', function()
|
describe('when given an env option', function()
|
||||||
it('is available on the sandboxed env', 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)
|
end)
|
||||||
|
|
||||||
it('does not hide base env', function()
|
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)
|
end)
|
||||||
|
|
||||||
it('can modify the env', function()
|
it('can modify the env', function()
|
||||||
local env = {foo = 1}
|
local env = {foo = 1}
|
||||||
sandbox.run("foo = 2", {env = env})
|
sandbox.run("foo = 2", {env = env})
|
||||||
assert.equal(env.foo, 2)
|
assert_equal(env.foo, 2)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user