How can I create a secure Lua sandbox?

2019-01-02 15:01发布

So Lua seems ideal for implementing secure "user scripts" inside my application.

However, most examples of embedding lua seem to include loading all the standard libraries, including "io" and "package".

So I can exclude those libs from my interpreter, but even the base library includes the functions "dofile" and "loadfile" which access the filesystem.

How can I remove/block any unsafe functions like these, without just ending up with an interpreter that doesn't even have basic stuff like the "ipairs" function?

标签: lua sandbox
7条回答
余生请多指教
2楼-- · 2019-01-02 15:18

You can set the function environment that you run the untrusted code in via setfenv(). Here's an outline:

local env = {ipairs}
setfenv(user_script, env)
pcall(user_script)

The user_script function can only access what is in its environment. So you can then explicitly add in the functions that you want the untrusted code to have access to (whitelist). In this case the user script only has access to ipairs but nothing else (dofile, loadfile, etc).

See Lua Sandboxes for an example and more information on lua sandboxing.

查看更多
何处买醉
3楼-- · 2019-01-02 15:19

If you're using Lua 5.1 try this:

blockedThings = {'os', 'debug', 'loadstring', 'loadfile', 'setfenv', 'getfenv'}
scriptName = "user_script.lua"

function InList(list, val) 
    for i=1, #list do if list[i] == val then 
        return true 
    end 
end

local f, msg = loadfile(scriptName)

local env = {}
local envMT = {}
local blockedStorageOverride = {}
envMT.__index = function(tab, key)
    if InList(blockedThings, key) then return blockedStorageOverride[key] end
    return rawget(tab, key) or getfenv(0)[key]
end
envMT.__newindex = function(tab, key, val)
    if InList(blockedThings, key) then
        blockedStorageOverride[key] = val
    else
        rawset(tab, key, val)
    end
end

if not f then
    print("ERROR: " .. msg)
else
    setfenv(f, env)
    local a, b = pcall(f)
    if not a then print("ERROR: " .. b) end
end
查看更多
浅入江南
4楼-- · 2019-01-02 15:23

The Lua live demo contains a (specialized) sandbox. The source is freely available.

查看更多
十年一品温如言
5楼-- · 2019-01-02 15:28

You can use the lua_setglobal function provided by the Lua API to set those values in the global namespace to nil which will effectively prevent any user scripts from being able to access them.

lua_pushnil(state_pointer);
lua_setglobal(state_pointer, "io");

lua_pushnil(state_pointer);
lua_setglobal(state_pointer, "loadfile");

...etc...
查看更多
人气声优
6楼-- · 2019-01-02 15:31

Here's a solution for Lua 5.2 (including a sample environment that would also work in 5.1):

-- save a pointer to globals that would be unreachable in sandbox
local e=_ENV

-- sample sandbox environment
sandbox_env = {
  ipairs = ipairs,
  next = next,
  pairs = pairs,
  pcall = pcall,
  tonumber = tonumber,
  tostring = tostring,
  type = type,
  unpack = unpack,
  coroutine = { create = coroutine.create, resume = coroutine.resume, 
      running = coroutine.running, status = coroutine.status, 
      wrap = coroutine.wrap },
  string = { byte = string.byte, char = string.char, find = string.find, 
      format = string.format, gmatch = string.gmatch, gsub = string.gsub, 
      len = string.len, lower = string.lower, match = string.match, 
      rep = string.rep, reverse = string.reverse, sub = string.sub, 
      upper = string.upper },
  table = { insert = table.insert, maxn = table.maxn, remove = table.remove, 
      sort = table.sort },
  math = { abs = math.abs, acos = math.acos, asin = math.asin, 
      atan = math.atan, atan2 = math.atan2, ceil = math.ceil, cos = math.cos, 
      cosh = math.cosh, deg = math.deg, exp = math.exp, floor = math.floor, 
      fmod = math.fmod, frexp = math.frexp, huge = math.huge, 
      ldexp = math.ldexp, log = math.log, log10 = math.log10, max = math.max, 
      min = math.min, modf = math.modf, pi = math.pi, pow = math.pow, 
      rad = math.rad, random = math.random, sin = math.sin, sinh = math.sinh, 
      sqrt = math.sqrt, tan = math.tan, tanh = math.tanh },
  os = { clock = os.clock, difftime = os.difftime, time = os.time },
}

function run_sandbox(sb_env, sb_func, ...)
  local sb_orig_env=_ENV
  if (not sb_func) then return nil end
  _ENV=sb_env
  local sb_ret={e.pcall(sb_func, ...)}
  _ENV=sb_orig_env
  return e.table.unpack(sb_ret)
end

Then to use it, you would call your function (my_func) like the following:

pcall_rc, result_or_err_msg = run_sandbox(sandbox_env, my_func, arg1, arg2)
查看更多
只若初见
7楼-- · 2019-01-02 15:32

One of the easiest ways to clear out undesirables is to first load a Lua script of your own devising, that does things like:

load = nil
loadfile = nil
dofile = nil

Alternatively, you can use setfenv to create a restricted environment that you can insert specific safe functions into.

Totally safe sandboxing is a little harder. If you load code from anywhere, be aware that precompiled code can crash Lua. Even completely restricted code can go into an infinite loop and block indefinitely if you don't have system for shutting it down.

查看更多
登录 后发表回答