Addy
Addy

Reputation: 2522

Lua 5.2 Sandboxing in different objects with C API

Consider the following C++ code using the Lua C API:

#include <string>
#include <cassert>

#include <lua/lua.hpp>

class AwesomeThing
{
    lua_State* _lua;
    std::string _name;

public:
    AwesomeThing(lua_State* L, const std::string& name, const std::string& luafile)
        : _lua{ L },
          _name{ name }
    {
        assert(luaL_loadfile(_lua, luafile.c_str()) == 0); // 1:chunk

        lua_newtable(_lua); // 1:chunk, 2:tbl
        lua_newtable(_lua); // 1:chunk, 2:tbl, 3:tbl(mt)
        lua_getglobal(_lua, "_G"); // 1:chunk, 2: tbl, 3:tbl(mt), 4:_G
        lua_setfield(_lua, 3, "__index"); // 1:chunk, 2: tbl, 3:tbl(mt)
        lua_setmetatable(_lua, 2); // 1:chunk, 2: tbl

        lua_setupvalue(_lua, -2, 1); // 1:chunk
        if (lua_pcall(_lua, 0, 0, 0) != 0) // compiled chunk
        {
            auto error = lua_tostring(_lua, -1);
            throw std::runtime_error(error);
        }

        lua_setglobal(_lua, _name.c_str()); // empty stack
    }

    void init()
    {
        lua_getglobal(_lua, _name.c_str()); // 1:env
        assert(lua_isnil(_lua, 1) == 0);

        lua_getfield(_lua, 1, "onInit"); // 1:env, 2:func
        assert(lua_isnil(_lua, 2) == 0);
        assert(lua_isfunction(_lua, 2) == 1);

        assert(lua_pcall(_lua, 0, LUA_MULTRET, 0) == 0); // 1:env, 2:retval

        lua_pop(_lua, 1); // -1:env
        lua_pop(_lua, 1); // empty stack
        assert(lua_gettop(_lua) == 0);
    }
};

int main()
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);

    AwesomeThing at1(L, "thing1", "file1.lua");
    AwesomeThing at2(L, "thing2", "file2.lua");

    at1.init();
    at2.init();

    return 0;
}

With two very basic Lua files:

file1.lua

function onInit()
    print("init file1")
end

file2.lua

function onInit()
    print("init file2")
end

As is, I get an error in at2's constructor call at lua_pcall: attempt to call table value

When I comment out all references/calls to at2, I instead get an error in at1's init() at lua_getfield(_lua, 1, "onInit"): PANIC: unprotected error in call to Lua API (attempt to index a nil value)

I feel like there's something fundamental I'm missing in the way I'm handling the sandboxing. I've tried my best to follow a few other Lua 5.2 sandboxing examples I've found online, but so far nothing has helped.

Upvotes: 4

Views: 430

Answers (1)

cdhowie
cdhowie

Reputation: 168988

After messing around with the code myself, I was able to fix it and the errors seem to come from just a few errors.

  • lua_pcall pops the called function from the stack, but in both cases in your code you assume the function is still on the stack after lua_pcall. This results in bad stack manipulation.
  • In the constructor, you apparently try to store a reference to the chunk (function) instead of the environment table. This doesn't even work though, because the function was already popped. If it did work, the lua_getfield call in init() wouldn't work as intended since the chunk doesn't have a field named onInit -- the environment table does.

Fixing the constructor involves creating the environment table and loading the chunk in the opposite order, so that the environment table is left on the stack after the function call:

        lua_newtable(_lua); // 1:tbl

        assert(luaL_loadfile(_lua, luafile.c_str()) == 0); // 1:tbl, 2:chunk

        lua_newtable(_lua); // 1:tbl, 2:chunk, 3:tbl(mt)
        lua_getglobal(_lua, "_G"); // 1:tbl, 2:chunk, 3:tbl(mt), 4:_G
        lua_setfield(_lua, 3, "__index"); // 1:tbl, 2:chunk, 3:tbl(mt)
        lua_setmetatable(_lua, 1); // 1:tbl, 2:chunk
        lua_pushvalue(_lua, 1); // 1:tbl, 2:chunk, 3:tbl

        lua_setupvalue(_lua, -2, 1); // 1:tbl, 2:chunk
        if (lua_pcall(_lua, 0, 0, 0) != 0) // compiled chunk
        {
            auto error = lua_tostring(_lua, -1);
            throw std::runtime_error(error);
        }

        // 1:tbl

        lua_setglobal(_lua, _name.c_str()); // empty stack

Then in init(), since you use LUA_MULTRET, just clear the stack by replacing both pop calls with lua_settop(_lua, 0).

Upvotes: 4

Related Questions