lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]

It was thus said that the Great Kynn Jones once stated:
> Hi Sean,
> Regarding your question, it seems to me that the whitelist should
> include (a) functions that the loaded code invokes, directly or
> indirectly; (b) functions that get called by the interpreter in the
> process of running the loaded code (e.g. functions that intercept
> errors in the loaded code).

  The purpose of a sandbox is to run code in a restricted environment,
whether that's because the code is untrusted, or it's supposed to do one
thing and only one thing.  Generally, such code only needs access to
functions to do its job.

  In Lua, global variables are stored in a table that's normally visible to
all code.  When you do:

	chunk = load('print("hellow world!")',"=(load)","t")
	hello world!

  This will compile the code and return it as a callable function, that when
run, will pring "hellow world!" to the screen.  It can do this because the
chunk is compiled with the current global table, which includes the print()

  When you give an empty table to load() as the last parameter, what you are
doing is giving the chunk of code with no global variables defined.

	chunk = load('print("hellow world!")',"=(load)","t",{})
	(load):1: attempt to call a nil value (global 'print')
	stack traceback:
	        (load):1: in function 'chunk'
	        (...tail calls...)
	        [C]: in ?

  To fix this, you preload the table to load() with the "global" functions
and data you want the chunk to see:

	env = { print = print }	-- make print available to the chunk of code
	chunk = load('print("hellow world!")',"=(load)","t",env)
	hello world!

  As an example, if the code you want to sandbox is only supposed to
calculate math formulas, you will probably want to make the math library,
the table library, and the print() function available to the code---it
shouldn't do anything else, so stuff like the io library, or the os library,
or the package library, shouldn't be included in the sandbox:

	env = { math = math , table = table , print = print }
	chunk = load(chunk_of_code,"=(load)","t",env)

  If the code in chunk_of_code tries to call some function not in math,
table, or print(), the code will compile, but when you try to run it, it
will fail with an eeor, like:

	(load):15: attempt to call a nil value (global 'select')

  Then you can either make the given function available to the code (in this
case, it's probably okay to add select() to its "global environment") or
not.  If you do:

	env = 
	  math   = math,
	  table  = table,
	  print  = print,
	  select = select,

  The above is what is meant as "whitelisting".

> In other words, the loaded code should not be blocked just because the
> interpeter called its own functions in the process of interpreting the
> loaded code. 
> ---
> In any case, I tried your idea, after first modifying the snippet so
> that it looks more like a sandbox.
>     #!/usr/bin/env lua5.3
>     local loaded_chunk = assert(load('nonexistent()', "=(load)", "t", {}))
>     local whitelist = {
>                        [loaded_chunk] = true,
>                        [pcall] = true,
>                        [assert] = true,
>                        -- ... other allowed functions
>                       }
>     local function callhook ()
>       local info = debug.getinfo(2, "fnS")
>       if not whitelist[info.func] then
>         error(string.format("calling disallowed function (%s:%d): %s (%s)",
>                             info.short_src,
>                             info.linedefined,
>                             ( or "?"),
>                             info.func))
>       end
>     end
>     debug.sethook(callhook, "c")
>     assert(pcall(loaded_chunk))
> Now, the snippet fails with
>     lua5.3: ./snippet.lua:12: calling disallowed function ([C]:-1): ?
> (function: 0x56179333bef0)
>     stack traceback:
>         [C]: in function 'error'
>         ./snippet.lua:12: in function <./snippet.lua:9>
>         [C]: in ?
>         [C]: in function 'assert'
>         ./snippet.lua:20: in main chunk
>         [C]: in ?
> Again, the nature of the underlying error (the attempt to call
> nonexistent) gets obscured.  Instead, we get a `calling disallowed
> function` error, which is not entirely accurate.

  Your code is trying to duplicate what the Lua interpreter will do for you. 
Of course, the error won't be "calling disallowed function"---is THAT what
you are trying to get?