Sandboxing Embedded Lua in 5.2 / Set Envirenment for Functions from lua.file
Asked Answered
B

2

7

Lets say i have at least two lua script files.

test1.lua test2.lua

both define an init function and other functions with similar names.

How can i load each script file using c++/c into a separate environment using Lua 5.2 so that the same function names will not clash - i found a sample code for 5.1 which does not work for me (because setenv is gone and lua_setuservalue does not seem to work)

Sample here Calling lua functions from .lua's using handles?

Basically if i replace setenv with setuservalue - i get an access violation.

Bony answered 13/6, 2012 at 11:17 Comment(10)
This is one of the reasons why I've stuck with lua 5.1 in my own projects for now. I believe that the v5.2 load and loadfile functions let you specify an environment; they are probably the best place to start searching for a solution.Ark
thanks but i read about load and loadfile however i could not find a solutionBony
setuservalue is definitely not the correct function to use when trying to manipulate the environment. The lua docs seem somewhat unclear on what you should be doing, however. lua_load says that a single upvalue associated with a loaded chunk is set as its environment, but does not mention how you go about associating an upvalue with the chunk from C. If you were loading the chunk from within lua, the lua version of the load function looks like it should set the environment correctly.Ark
thanks - i looked into the upvalues - and found this post on the list lua-list.2524044.n2.nabble.com/… basically it is said you should call if (luaL_loadfile(L, filename)) return lua_error(L); lua_getglobal(L, "environ); lua_setupvalue(L, -2, 1); lua_call(L, 0, 0); however - when i do this i get the error "PANIC: unprotected error in call to Lua API (test1.lua:1: attempt to index upvaue '_ENV' (a nil value))"Bony
I'd be suspicious of people suggesting you call lua_setupvalue. It is in the docs as part of the debug API, and as such you should not need to touch it for normal use of lua.Ark
@Rook: If you're going to be suspicious of the Lua documentation (which specifically tells you to do this), then what exactly do you intend to trust? The debug API would be more appropriately called the "introspection" API; there's nothing inherently wrong with using it.Silvie
I was suspicious because it did not specifically tell you to do this. It makes a passing reference to upvalues, and the string "lua_setupvalue" is referenced nowhere outside of its own documentation in the debug library section of the documentation. The 5.1 manual says "You should exert care when using this library. The functions provided here should be used exclusively for debugging and similar tasks, such as profiling. Please resist the temptation to use them as a usual programming tool". My hesitation was not unreasonable, though in this case it turned out to be unfounded.Ark
As an addendum, I note that neither load or loadfile as called from lua require you to mess with the debug interface, and lua_pushcclosure (part of the C API) has a way of specifying the number of upvalues you wish to push by way of a function argument, also avoiding the need to use the debug API. So lua_load is a bit unusual. Guess I'll just have to see what the lua mailing list folk have to say about that particular little wart.Ark
if you have an example with lua_pushcclosure instead - could you also post this as answer? thanksBony
FYI, here's some code to sandbox in 5.2 within Lua (as opposed to C): https://mcmap.net/q/259761/-how-can-i-create-a-secure-lua-sandboxMagalymagan
O
9

The unofficial Lua FAQ has an entry about sandboxing in Lua. My guess is that you can transpose that logic easily enough to your C/C++ code.

See also LuaFiveTo on the lua-users wiki.

Correction

It's indeed not as trivial as it seemed. But in the end the point is simple: load your chunk, push the _ENV table, use lua_setupvalue(L,-2,1). The important is that the table should be at the top of the stack.

As a small example, using 2 environments defaulting to _G for reading stuff via metatables:

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int main(void){
        lua_State *L = luaL_newstate();
        char *file1 = "file1.lua";
        char *file2 = "file2.lua";

        luaL_openlibs(L);

        luaL_loadfile(L,file2); // S: 1
        luaL_loadfile(L,file1); // S: 2
        lua_newtable(L); // ENV for file 1: S: 321
        lua_newtable(L); // ENV for file 2: S: 4321

        //lets have each function have its metatable, where missed lookups are
        //instead looked up in the global table _G

        lua_newtable(L); // metatable S: 54321
        lua_getglobal(L,"_G"); // pushes _G, which will be the __index metatable entry S: 654321

        lua_setfield(L,-2,"__index"); // metatable on top S: 54321
        lua_pushvalue(L,-1); // copy the metatable S: 554321
        lua_setmetatable(L,-3); // set the last copy for env2 S: 54321
        lua_setmetatable(L,-3); // set the original for env1  S: 4321
        // here we end up having 2 tables on the stack for 2 environments
        lua_setupvalue(L,1,1); // first upvalue == _ENV so set it. S: 321
        lua_setupvalue(L,2,1); // set _ENV for file S: 21
        // Remaining on the stack: 2 chunks with env set.
        lua_pcall(L,0,LUA_MULTRET,0);
        lua_pcall(L,0,LUA_MULTRET,0);
        lua_close(L);
        return 0;
}

And for the 2 Lua files:

-- file1.lua
function init()
        A="foo"
        print("Hello from file1")
        print(A)
end
init()

-- file2.lua
-- this shows that stuff defined in file1 will not polute the environment for file2
print("init function is",tostring(init))
function init()
        A="bar"
        print("Hello from file2")
        print(A)
end
init()
Outclass answered 13/6, 2012 at 12:31 Comment(3)
thanks but i know these resources - however im unable to reproduce this in C/C++Bony
Indeed, the problem is that the C equivalents of load and loadfile do not have quite such a straightfoward interface or documentation. The lua-users wiki page also appears to be a little out of data, and contains features that never made it into the 5.2 spec.Ark
jpjacobs, we met in lua-irc today and as i said i got the solution but thanks again for laying out an alternative example!Bony
S
0

both define an init function and other functions with similar names.

First of all, why are those functions global? They should be local to the script. If you're going to require them in other files, they should create and return a table containing the functions that they wish to expose.

The modern idiom when requiring these files is to do something like this:

local Library = require 'library'

Library.Func1(...)

Thus, you do not pollute the global Lua namespace. You use local variables.

However, if you insist on using globals like this, you can do exactly what the documentation said: change the first upvalue of the compiled chunk.

Basically if i replace setenv with setuservalue - i get an access violation.

Of course you do. That's not what lua_setuservalue does. It's for setting values associated with userdata. What you want is appropriately called lua_setupvalue.

Using the sample code you cite, the correct answer would be:

lua_setupvalue(L, -2, 1);
Silvie answered 13/6, 2012 at 14:31 Comment(7)
i accidentally figured it out myself - lua_setupvalue(L, -2, 0); crashes! - i got away with using lua_setupvalue(L, -2, 1); - my knowledge is limited and i do exactly know what 1 is in this context. but it seems to work - now is it also possible to expose my object only in this "namespace" and not in the global table? im using luabind to expose my classes and currently im using luabind::globals(myLuaState)["myObj"] = myObjBony
as for "why" are the functions global - i like to create an event system with scripts for multiple entities which use the same function signature e.g. init, since the scripts are generally not created by developers i want to make it as painless as possible and not bother anyone with libaries or modules - therefor i want to make sure each script is executed in its own environment - its not sourced anyway!Bony
@Steve: The documentation isn't clear if the lua_setupvalue function takes zero-based or one-based indices. It looks like they're one-based.Silvie
so basically - im not even sure what that parameter means - is it always 1 ? - and furthermore to you happen to know how to expose my c++ obj only for this environment?Bony
@Steve: Functions of any type can have 0 or more upvalues. Lua functions (compiled Lua code) will have at least one upvalue. The first upvalue, index 1, is the environment for that function (which is why every Lua function has at least one). As to the latter, that's up to you. Putting it into the environment table is one way to do it.Silvie
alright - but how do i put it in the envirenment table ? as i said i have a list of scripts for entites - and each script should refer to it just as entity::someMethod() - since i now successfully sandboxed 2 scripts - i want that obj1 is named "entity" in the scope of script 1 and obj2 is named "entity in the scope of script 2Bony
@Steve: This is not a forum; it's a Q&A site. If you have another question, then ask another question. Using the big "Ask Question" button.Silvie

© 2022 - 2024 — McMap. All rights reserved.