If you want to protect against DoS (infinite or very long loops, memory exhaustion), it's a hell. I'd advise you to look into a different language or framework.
If protecting against DoS is not a requirement, it's still a hell but not so much. Besides blocking bytecode, you need to include only safe things (better to use a whitelist than a blacklist). This might be safe, not sure:
print, pcall, xpcall, next, pairs, ipairs, tostring, tonumber, table, math, string, coroutine, assert, error, unpack, select, type, getmetatable, setmetatable
Every module (table, math, string, coroutine) should be a copy of the main one, never the original. Note that string functions are an easy way to DoS.
One subtlety is that attackers can obtain the metatable of a string, and modify the main string table through it, making your code use any strings that the attacker wants in place of the strings you expect. An example where this is especially dangerous is if your (non-sandboxed) code uses os.execute.
To protect against that, you can pass a modified getmetatable that returns the sandbox's string table instead of the real one. For example:
Code: Select all
local function copy(x)
local ret = {}
for k, v in next, x do
ret[k] = v
end
return ret
end
local function new_sandboxed_env()
local safe_string = copy(string)
local safe_table = copy(table)
local safe_math = copy(math)
local safe_coroutine = copy(coroutine)
local env = { print=print, pcall=pcall, xpcall=xpcall, next=next, pairs=pairs, ipairs=ipairs,
tostring=tostring, tonumber=tonumber, assert=assert, error=error, unpack=unpack,
select=select, type=type, setmetatable=setmetatable,
table=safe_table, math=safe_math, string=safe_string, coroutine=safe_coroutine,
}
env._G = env
local _G = _G
env.getmetatable = function(x)
local ret = _G.getmetatable(x)
if ret == _G.string then
return safe_string
end
return ret
end
return env
end
Edit: Sorry, that won't work. The client can still do ("").fn = rogue_fn. You need to swap the metatable of strings when calling the sandboxed function, and swap it back on end. This is done with debug.setmetatable (normal setmetatable does not allow changing the metatable of strings).