lua-users home
lua-l archive

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


Here is an implementation of the example I described in my previous
message, hopefully making clear the distinction I am making between
directly and indirectly invoked functions on the one hand, and
"unnamed code X" functions on the other.

    #!/usr/bin/env lua5.3
    -- Filename: example.lua

    function A (x) return B(x) end

    function B (x) return math.random(x) end

    -- this environment is probably more generous than it needs to be for this
    -- example, but just in case...
    local environment = {
                          A = A,
                          B = B,
                          math = math,
                          print = print,
                        }

    local snippet = string.format('print(A(%s))', arg[1])
    local loaded_chunk = assert(load(snippet, "=(load)", "t", environment))

    local whitelist = {
      [loaded_chunk] = true,
      [A] = true,
      [B] = true,
      [math.random] = true,
      [print] = true,
      [tostring] = true,
    }

    local function callhook (event)
      local info = debug.getinfo(2, "fnS")
      print(string.format("%-15s\t%-15s\t%s",
                          info.source, info.name or "?", info.func))
      if not whitelist[info.func] then
        error(string.format("calling disallowed function (%s:%d): %s (%s)",
                            info.short_src, info.linedefined, (info.name or "?"),
                            info.func))
      end
    end

    debug.sethook(callhook, "c")
    loaded_chunk()

If I run this script with argument, say, "1000", I get the following
output:

    =(load)            loaded_chunk       function: 0x5588e62a2b80
    @./example.lua     A                  function: 0x5588e62a2260
    @./example.lua     ?                  function: 0x5588e62a2780
    =[C]               random             function: 0x5588e59bba80
    =[C]               print              function: 0x5588e59b7cc0
    =[C]               ?                  function: 0x5588e59b7830
    841

...where 841 is, basically, `math.random(1000)`.  This confirms that
the loaded chunk does not invoke, *directly or indirectly*, any
function that is not in `whitelist`.

But if I run the script with argument "-1", I get the following
output

    =(load)            loaded_chunk       function: 0x5565a23f3b80
    @./example.lua     A                  function: 0x5565a23f3260
    @./example.lua     ?                  function: 0x5565a23f3780
    =[C]               random             function: 0x5565a10c9a80
    =[C]               ?                  function: 0x5565a10adef0
    lua5.3: ./example.lua:34: calling disallowed function ([C]:-1): ? (function: 0x5565a10adef0)
    stack traceback:
        [C]: in function 'error'
        ./example.lua:34: in function <./example.lua:29>
        [C]: in ?
        [C]: in function 'math.random'
        ./example.lua:6: in function 'B'
        (...tail calls...)
        (load):1: in local 'loaded_chunk'
        ./example.lua:41: in main chunk
        [C]: in ?

Whatever is triggering the error whose traceback is shown above is a
call to some "unnamed code X" function, which occurred when Lua tried
to evaluate `math.random(-1)`.

My one and only point is that in order to properly implement a
whitelist strategy, one needs to include functions like this "unnamed
code X" function in `whitelist`.  I am trying to find out whether this
is at all possible, and if so, how to do it.

kj


On Wed, Aug 7, 2019 at 4:08 AM Kynn Jones <kynnjo@gmail.com> wrote:
On Wed, Aug 7, 2019 at 12:22 AM Sean Conner <sean@conman.org> wrote:
It was thus said that the Great Kynn Jones once stated:
> 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).

Let me elaborate on this a bit.  What I meant to say is that the
functions included in the whitelist can be classified into two groups.

The first group consists of functions that we have included in the
whitelist because we consider them integral to what we have decided to
allow the loaded code to do (for example perform mathematical
operations); these are functions that the loaded code invokes directly
or indirectly.

The second group of functions in the whitelist are other functions,
which are *not* invoked directly or indirectly by the loaded code, but
that get called nonetheless by the Lua interpreter in the process of
running the loaded code.  These include functions that get invoked
when the loaded code has a runtime error.

For example, if functions A and B are defined as

    function A (x) return B(x) end

    function B (x) return math.random(x) end

...and the loaded code is the string 'print(A(1/0))', then `print`,
`A`, and integer division are being *directly* invoked by the loaded
code, while `B` and `math.random` are being *indirectly* invoked by
the loaded code.

On the other hand, the "unnamed code X" responsible for catching the
ensuing error

    bad argument #1 to 'random' (number has no integer representation)

...and printing the useful error message for it is not being invoked,
either directly or indirectly (at least according to the way I'm
defining these terms here), by the loaded code.  Nevertheless, this
"unnamed code X" is carrying out Lua's desired behavior, and should
not be disallowed by the sandbox.  In other words, the functions in
"unnamed code X" should be in included in the whitelist.

My only point is that, in order to put the whitelisting idea into
practice, one needs to be able to include in the whitelist functions
belonging to either of the two categories described above.

Currently I know how to include functions of the first category in the
whitelist, not those of the second category.  Maybe the
whitelist-based sandbox idea can only be implemented properly in C?

kj