/ bjvxlx_util / sandbox.lua
sandbox.lua
  1  --[[ sandbox.lua
  2  Provides a setfenv preset intended to provide the maximum amount
  3  of standard library functionality that does not allow for privesc. ]]
  4  
  5  local util = bjvxlx.util
  6  
  7  --[[ bjvxlx.util.make_sandbox_env(append)
  8  Pulls in a nearly full-featured Lua 5.1 environment for bjvxlx.util.sandbox,
  9  lacking only those features which would allow the environment to be breached.
 10  If a table `append` is given, it is iterated, and every key-value pair within
 11  is grafted into the environment ultimately constructed, possibly creating
 12  privesc routes if `append` is not chosen carefully. Otherwise, only the names
 13  shown below, in the function body, are provided. ]]
 14  function util.make_sandbox_env(append)
 15      local env = {
 16          _VERSION = _VERSION,
 17          assert = assert,
 18          error = error,
 19          getmetatable = getmetatable,
 20          ipairs = ipairs,
 21          load = load,
 22          loadstring = loadstring,
 23          next = next,
 24          pairs = pairs,
 25          pcall = pcall,
 26          rawequal = rawequal,
 27          rawget = rawget,
 28          rawset = rawset,
 29          select = select,
 30          setmetatable = setmetatable,
 31          tonumber = tonumber,
 32          tostring = tostring,
 33          type = type,
 34          unpack = unpack,
 35          xpcall = xpcall,
 36          coroutine = {
 37              create = coroutine.create,
 38              resume = coroutine.resume,
 39              running = coroutine.running,
 40              status = coroutine.status,
 41              wrap = coroutine.wrap,
 42              yield = coroutine.yield
 43          },
 44          string = {
 45              byte = string.byte,
 46              char = string.char,
 47              dump = string.dump,
 48              find = string.find,
 49              format = string.format,
 50              gmatch = string.gmatch,
 51              gsub = string.gsub,
 52              len = string.len,
 53              lower = string.lower,
 54              match = string.match,
 55              rep = string.rep,
 56              reverse = string.reverse,
 57              sub = string.sub,
 58              upper = string.upper
 59          },
 60          table = {
 61              concat = table.concat,
 62              insert = table.insert,
 63              maxn = table.maxn,
 64              remove = table.remove,
 65              sort = table.sort
 66          },
 67          math = {
 68              abs = math.abs,
 69              acos = math.acos,
 70              asin = math.asin,
 71              atan = math.atan,
 72              atan2 = math.atan2,
 73              ceil = math.ceil,
 74              cos = math.cos,
 75              cosh = math.cosh,
 76              deg = math.deg,
 77              exp = math.exp,
 78              floor = math.floor,
 79              fmod = math.fmod,
 80              frexp = math.frexp,
 81              huge = math.huge,
 82              ldexp = math.ldexp,
 83              log = math.log,
 84              log10 = math.log10,
 85              max = math.max,
 86              min = math.min,
 87              modf = math.modf,
 88              pi = math.pi,
 89              pow = math.pow,
 90              rad = math.rad,
 91              random = math.random,
 92              randomseed = math.randomseed,
 93              sin = math.sin,
 94              sinh = math.sinh,
 95              sqrt = math.sqrt,
 96              tan = math.tan,
 97              tanh = math.tanh
 98          }
 99      }
100      if append then
101          for k, v in pairs(append) do
102              env[k] = v
103          end
104      end
105      env._G = env
106      return env
107  end
108  
109  --[[ bjvxlx.util.sandbox(func, env)
110  Essentially composes `coroutine.create` over `setfenv`, so the guest function
111  can't breach its environment with `coroutine.yield` or `error`.
112  A bit more plumbing is added so that the value ultimately returned
113  is a function, not a thread, in a manner similar to `coroutine.wrap`,
114  except the wrapper directly returns the results of `coroutine.resume`
115  instead of unwrapping the status boolean and rethrowing errors,
116  thus providing (or rather, simply not undoing) a `pcall`-like safety net,
117  and the function may be called even if the coroutine is already dead,
118  in which case the coroutine will be remade (so that in the expected case
119  that the untrusted function is supposed to just be a normal function
120  and never calls `coroutine.yield`, it can be called more than once
121  if needed). ]]
122  function util.sandbox(func, env)
123      local coro = coroutine.create(setfenv(func, env))
124      return function (...)
125          if coroutine.status(coro) == 'dead' then
126              coro = coroutine.create(setfenv(func, env))
127          end
128          return coroutine.resume(coro, ...)
129      end
130  end