feat(sandbox) make envs read-only, change the way they are built

This changes envs in three ways:
* They are strict read-only. This minimizes the surface attack if someone with malicious intent overrides global stuff on an environment which happens to be reused.
* Envs can override the base env
* Envs with metatables now use them
This commit is contained in:
Enrique García Cota 2021-01-06 00:44:04 +01:00 committed by Enrique García Cota
parent 7de90f6ccf
commit e1e0faf150
2 changed files with 26 additions and 16 deletions

View File

@ -106,13 +106,6 @@ end)
local string_rep = string.rep local string_rep = string.rep
local function merge(dest, source)
for k,v in pairs(source) do
dest[k] = dest[k] or v
end
return dest
end
local function sethook(f, key, quota) local function sethook(f, key, quota)
if type(debug) ~= 'table' or type(debug.sethook) ~= 'function' then return end if type(debug) ~= 'table' or type(debug.sethook) ~= 'function' then return end
debug.sethook(f, key, quota) debug.sethook(f, key, quota)
@ -135,11 +128,16 @@ function sandbox.protect(code, options)
quota = options.quota or 500000 quota = options.quota or 500000
end end
local env = merge(options.env or {}, BASE_ENV)
env._G = env._G or env
assert(type(code) == 'string', "expected a string") assert(type(code) == 'string', "expected a string")
local passed_env = options.env or {}
local env = {}
for k, v in pairs(BASE_ENV) do
local pv = passed_env[k]
env[k] = pv ~= nil and pv or v
end
setmetatable(env, { __index = options.env })
env._G = env
local f local f
if bytecode_blocked then if bytecode_blocked then

View File

@ -72,10 +72,10 @@ describe('sandbox.run', function()
assert.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 not persist modification to base functions even 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'], 'hello')
end) end)
end) end)
@ -111,17 +111,29 @@ describe('sandbox.run', function()
it('is available on the sandboxed env as the _G variable', function() it('is available on the sandboxed env as the _G variable', function()
local env = {foo = 1} local env = {foo = 1}
assert.equal(1, sandbox.run("return foo", {env = env})) assert.equal(1, sandbox.run("return foo", {env = env}))
assert.equal(env, sandbox.run("return _G", {env = env})) assert.equal(1, sandbox.run("return _G.foo", {env = env}))
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('cannot 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, 1)
end)
it('uses the env metatable, if it exists', function()
local env1 = { foo = 1 }
local env2 = { bar = 2 }
setmetatable(env2, { __index = env1 })
assert.equal(3, sandbox.run("return foo + bar", { env = env2 }))
end)
it('can override the base env', function()
local env = { tostring = function(x) return "hello " .. x end }
assert.equal("hello peter", sandbox.run("return tostring('peter')", { env = env }))
end) end)
end) end)