Reputation: 2522
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
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.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