Search code examples
functionluasandboxlua-5.1

How do you "sandbox" functions?


Currently, I am writing a sandbox in Lua. It's working so far, but I could use getfenv for that function to get the scope outside the sandbox. For my sandbox, I filled an empty table with trusted functions and libraries. However, with functions such as print, you could use getfenv to get the global variable(s) in that scope. For example:

asd = "asd"
assert(pcall(assert(load([[
print(getfenv(print).asd) -- "asd"
]], nil, "t", {print = print, getfenv = getfenv}))))

This can obviously allow the "adversary" to bypass the sandbox.


Solution

  • You need to write a wrapper for getfenv that prevents leaking the unsandboxed environment, like MediaWiki's Scribunto extension does:

        local function my_getfenv( func )
            local env
            if type( func ) == 'number' then
                if func <= 0 then
                    error( "'getfenv' cannot get the global environment" )
                end
                env = old_getfenv( func + 1 )
            elseif type( func ) == 'function' then
                env = old_getfenv( func )
            else
                error( "'getfenv' cannot get the global environment" )
            end
    
            if protectedEnvironments[env] then
                return nil
            else
                return env
            end
        end
    

    The gist of it is that you check the returned environment, and if it's a protected environment (e.g., _G), then refuse to return it. The only trick is handling the argument, since it means different things depending on its type, and it's sensitive to the extra function in the call stack.