lua-users wiki: Sand Boxes (original) (raw)
![]() |
---|
This page uses setfenv in its examples, setfenv is no longer available as of Lua 5.2, so it needs an update. If you use Lua 5.2 do not use this code, I would have updated this myself, but I have no experience in sandboxing.
This page discusses issues relating to sandboxing: running untrusted Lua code in a restricted Lua environment.
WARNING!!!
Sandboxing is tricky and generally speaking difficult to get right [3]. You should start by trusting nothing and only permit things that you are absolutely sure are safe (i.e. whitelist rather than blacklist approach). It's difficult to be sure some code is safe without thorough knowledge of the language and implementation (e.g. hash table performance may have poor performance in rare cases [4]). It's also possible there could be a bug in the Lua implementation (see [bugs]), so you should monitor bug reports on the mailing list and possibly take additional steps for isolation in the event of such a breach, such as operating system level isolation mechanisms (like restricted user accounts or a [chroot jail]). You should also keep your operating system patched and secured by means like firewalls. Operating system level resource limits may also be necessary [5]. Restricting to a subset of the Lua language can mitigate some of these concerns.
See some libs in "Sandboxing" chapter of LibrariesAndBindings.
The following notes are probably incomplete and intended only as a starting point.
A Simple Sandbox
The following is one of the simplest sandboxes. It is also one of the most restrictive, except it doesn't handle resource exhaustion issues.
local env = {}
local function run(untrusted_code) if untrusted_code:byte(1) == 27 then return nil, "binary bytecode prohibited" end local untrusted_function, message = loadstring(untrusted_code) if not untrusted_function then return nil, message end setfenv(untrusted_function, env) return pcall(untrusted_function) end
local function run(untrusted_code) local untrusted_function, message = load(untrusted_code, nil, 't', env) if not untrusted_function then return nil, message end return pcall(untrusted_function) end
assert(not run [[print(debug.getinfo(1))]]) assert(run [[x=1]]) assert(run [[while 1 do end]])
Code in this sandbox can create variables in the sandbox environment, create values of primitive types (thereby allocating memory), and perform computations. There is no limit to memory usage and computation, so the untrusted code could still severely impact system performance unless further restrictions are made. The sandbox does not have access to I/O nor functions and variables outside its environment. The only way for the sandbox to communicate with the external world is by affecting its environment (e.g. getting and setting variables and calling functions in that environment), assuming there is code outside the sandbox that also has access to those variables and functions.
You could write a parser that accepts a subset of the Lua language (e.g. prevents loops), but this will likely still be insufficient to fully prevent CPU exhaustion.
Table of Variables
The following is a list of Lua 5.1 variables with descriptions of how safe they for use in sandbox environments. Note that whether a variable is safe or not may **depend on the security requirements of your particular application** and your Lua state. No warranty is given that the following listing is complete or correct, but it is only a guideline. To make a sandbox you should start with an empty environment and pull in only functions you know for certain to be safe [1] (i.e. a whitelist not a blacklist). You should not rely on the manual providing a complete list of functions (e.g. HiddenFeatures).
NOTE: The following list has not been updated for Lua 5.2.
assert
- SAFEcollectgarbage
- UNSAFE - can globally and adversely affect garbage collectiondofile
- UNSAFE - The may read arbitrary files on the file system. It can also read standard input. The code is run under the global environment, not the sandbox, so this can be used to break out of the sandbox. Related discussion: DofileNamespaceProposal. Also unsafe if file is compiled bytecodes (seeload
).error
- SAFE_G
- UNSAFE - by default this contains the global environment. You may, however, want to set this variable to contain the environment of the sandbox.getfenv
- UNSAFE - this can locate an environment outside of the sandbox, thereby breaking out of the sandbox. LuaList:2007-11/msg00202.htmlgetmetatable
- UNSAFE - Note thatgetmetatable""
returns the metatable of strings. Modification of the contents of that metatable can break code outside the sandbox that relies on this string behavior. Similar cases may exist unless objects are protected appropriately via__metatable
. Ideally__metatable
should be immutable.ipairs
-- SAFEload
- UNSAFE. The function returned has the global environment, thereby breaking out of the sandbox. More seriously, is dangerous if loading bytecode rather than Lua source: LuaList:2010-08/msg00487.html .loadfile
- UNSAFE. Seeload
anddofile
loadstring
-- UNSAFE. Seeload
. Even this:
local oldloadstring = loadstring local function safeloadstring(s, chunkname) local f, message = oldloadstring(s, chunkname) if not f then return f, message end setfenv(f, getfenv(2)) return f end
isn't safe. For example, pcall(safeloadstring, some_script)
will load some_script
in global environment. --SergeyRozhenko
next
- SAFEpairs
- SAFEpcall
- SAFEprint
- SAFE (assuming output tostdout
is ok)rawequal
- UNSAFE (potentially?) - bypasses metatablesrawget
- UNSAFE - bypasses metatablesrawset
- UNSAFE - bypasses metatablesselect
- SAFEsetfenv
- UNSAFE - can modify environments of functions that are up the call-chain and outside the sandboxsetmetatable
- UNSAFE - seegetmetatable
tonumber
- SAFEtostring
- SAFEtype
- SAFEunpack
- SAFE_VERSION
- SAFExpcall
- SAFEcoroutine
- UNSAFE - modifying this table could affect code outside the sandboxcoroutine.create
- SAFEcoroutine.resume
- SAFEcoroutine.running
- SAFEcoroutine.status
- SAFEcoroutine.wrap
- SAFEcoroutine.yield
- SAFE (probably) - assuming caller handles thismodule
- UNSAFE - for example, modifies globals (e..gpackage.loaded
) and provides access to environments outside the sandbox.require
- UNSAFE - modifies globals (e.g.package.loaded
), provides access to environments outside the sandbox, and accesses the file system.package
- UNSAFE - modifying this table could affect code outside the sandboxpackage.*
- UNSAFE - affects module loading outside the sandboxpackage.loaded
- UNSAFE - provides access to modules loaded outside the sandboxpackage.loaders
- UNSAFE - provides access to loading modules outside the sandboxpackage.loadlib
- UNSAFE - loads arbitrary executable code that runs outside of Luapackage.path/package.cpath
- UNSAFE (effectively) since this is probably useful only for UNSAFE functions. In itself, it is probably SAFE, for reading that is.package.preload
- UNSAFE (probably) - may allow loading modules outside the sandboxpackage.seeall
- UNSAFE - provides access to the global environmentstring
- UNSAFE - modifying this table could affect code outside the sandboxstring.byte
- SAFEstring.char
- SAFEstring.dump
- UNSAFE (potentially) - allows seeing implementation of functions.string.find
- SAFE -- warning: a number of functions like this can still lock up the CPU [6]string.format
- SAFEstring.gmatch
- SAFEstring.gsub
- SAFEstring.len
- SAFEstring.lower
- SAFEstring.match
- SAFEstring.rep
- SAFEstring.reverse
- SAFEstring.sub
- SAFEstring.upper
- SAFEtable
- UNSAFE - modifying this table could affect code outside the sandboxtable.insert
- SAFEtable.maxn
- SAFEtable.remove
- SAFEtable.sort
- SAFEmath
- UNSAFE - modifying this table could affect code outside the sandboxmath.abs
- SAFEmath.acos
- SAFEmath.asin
- SAFEmath.atan
- SAFEmath.atan2
- SAFEmath.ceil
- SAFEmath.cos
- SAFEmath.cosh
- SAFEmath.deg
- SAFEmath.exp
- SAFEmath.floor
- SAFEmath.fmod
- SAFEmath.frexp
- SAFEmath.huge
- SAFEmath.ldexp
- SAFEmath.log
- SAFEmath.log10
- SAFEmath.max
- SAFEmath.min
- SAFEmath.modf
- SAFEmath.pi
- SAFEmath.pow
- SAFEmath.rad
- SAFEmath.random
- SAFE (mostly) - but note that returned numbers are pseudorandom, and calls to this function affect subsequent calls. This may have statistical implications.math.randomseed
- UNSAFE (maybe) - seemath.random
math.sin
- SAFEmath.sinh
- SAFEmath.sqrt
- SAFEmath.tan
- SAFEmath.tanh
- SAFEio
- UNSAFE - modifying this table could affect code outside the sandboxio.*
- These can be UNSAFE as they provide access to the file system. Note also thatio.close
on standard file handles (e.g.io.stdin
) may be unsafe and could cause a crash.io.read
- SAFE (probably)io.write
- SAFE (probably) - note: potentially could consume all disk space, thereby bringing down a systemio.flush
- SAFE (probably)io.type
- SAFEos
- UNSAFE - modifying this table could affect code outside the sandboxos.clock
- SAFEos.date
- UNSAFE - This can crash on some platforms (undocumented). For example,os.date'%v'
. It is reported that this will be fixed in 5.2 or 5.1.3.os.difftime
- SAFEos.execute
- UNSAFE - calls external programsos.exit
- UNSAFE - terminates programos.getenv
- UNSAFE (potentially) - depending on what environment variables containos.remove
- UNSAFE - modifies file systemos.rename
- UNSAFE - modifies file systemos.setlocale
- UNSAFE - modifies global locale, affecting code outside the sandboxos.time
- SAFEos.tmpname
- UNSAFE (maybe) - only in that it provides some information on the structure of the file systemdebug
- UNSAFE - modifying this table could affect code outside the sandboxdebug.*
- UNSAFE - functions here can break out of the sandbox and access variables outside the sandbox. Note warnings in the Lua Reference Manual concerningdebug
.newproxy
- UNSAFE (potentially) - This is an undocumented function (HiddenFeatures), so it does not have a specified interface that you can rely on.newproxy(nil)
andnewproxy(true)
are probably safe, though fairly useless ifgetmetatable
is disabled, at least for the proxy.newproxy(o)
whereo
is another proxy object will assign the metatable ofo
to the new proxy, so there are potential side-effects ono
or the metatable ofo
for any exposed proxyo
.
Older Comments
Anonymous: Attacks to consider:
- io functions, you will have to remove/hide/wrap them, of course
- when hiding the env, make sure the untrusted code can't getenv(some_lib_function) to get it back
- dofile('/home/username/.some_other_app/passwords') can read a totally unrelated Lua config file
- loading unrelated C libraries from other projects
- Lua 5.0: maybe the user can add a metatable a string object, which affects all string concatenations, tricking a library function into manipulating the wrong files (Lua 5.1 requires C the API to do this)
- ??? maybe the stacktrace can be expected to get back the env ???
- Running an infinite loop. (Bad, but not really harmful.)
- Using up all available memory.
- Check critical function arguments (stuff with metamethods attached where string/int expected)
- The usual C stuff (buffer overflow, integer overflow) just requires careful coding
- __gc on userdata metatables
- Currently the quota enforcer does not protect agains malicious scripts using pcall (and xpcall) UNSAFE? [details]
See Also
- The lua-l archive [2] contains discussions on sandboxing.
RecentChanges · preferences
edit · history
Last edited December 12, 2023 12:04 pm GMT (diff)