简体   繁体   中英

Set _ENV from C++ for string function

In my project I'm executing some lua functions contained in an XML file. I read the XML from C++, parse the code strings, execute them and get the result. All of the related questions I have found either used a dedicated .lua file or did it in Lua directly and I couldn't find a solution that worked for my case.

I can't modify the functions in the file and they all have the following signature:

function() --or function ()
    --do stuff
    return foo
end

from C++ i load them like so:

lua_State *L = luaL_newstate();
luaL_openlibs(L);
std::string code = get_code_from_XML();
std::string wrapped_code = "return " + code;
luaL_loadstring(L, wrapped_code.c_str());
if (lua_pcall(L, 0, 1, 0)){
    return 1;
}

argNum = load_arg_number();

if (lua_pcall(L, argNum, 1, 0)){
    return 1;
}
return 0;

Everything works but running arbitrary Lua code from an XML string doesn't seem very safe so i wanted to set up a whitelist of functions that the code can use.

Following this lua-users discussion i created my list of allowed functions like:

// Of course my list is bigger
std::string whitelist = "sandbox_env = {ipairs = ipairs} _ENV = sandbox_env"

The problem is that I don't understand how to load it to be available in the functions that I'm calling.

I tried doing it like on the lua-users website:

 std::string Lua_sandboxed_script_to_run( Lua_sandboxing_script + Lua_script_to_run ) if (luaL_dostring(sandboxed_L, Lua_sandboxed_script_to_run)) { // error checking }

But this results in the function not being loaded correctly and the Lua error

Trying to execute a string value

I also tried to do:

luaL_loadstring(L, whitelist.c_str());
lua_getglobal(L, "_ENV");
lua_setupvalue(L, -2, 1);

Before executing the loaded XML function, this doesn't crash the program but doesn't set the _ENV for the called function either.

The only way I have found to have what I want is to parse the function string with C++ searching for () , insert whitelist after it and then luaL_loadstring and lua_pcall it twice to execute it.

Like this:

.
.
size_t n = code.find("()") + 2;
code.insert(n, whitelist);
std::string wrapped_code = "return " + code;
.
.

This works and sets my custom _ENV for the function but seems to me like a really hacky way to go about it.

How can I set the _ENV variable for a string function loaded from C in a nicer way?

Bonus points if there is a way to save it once for the whole lua_State instead of every time I call a function.

Before we dive into examples let's explain how (and at what level) we can more-or-less sandbox code in Lua:

  1. Globally - remove or never add unwanted modules/functions into the global environment.

  2. Chunk(ly) - modify the _ENV upvalue of a chunk.

  3. Locally - modify _ENV through a local or upvalue in Lua script. It can be propagated then to children via upvalue.

Looking at the examples provided in the question you tried to do either 2 or 3 .

I'm not sure what are your needs but I will try to provide you examples for each of the approaches listed above. Please note that those examples are not the ultimate ones, are not the only possible approaches and do not take yours return function ()... end wrapper. Pick any that suits you.

Globally

This one is very straight-forward. If you don't plan to use all of the libraries provided...

lua_State * L = luaL_newstate();
luaL_openlibs(L); // <-- Remove this

... then just don't load them:

lua_State * L = luaL_newstate();
// Instead of luaL_openlibs:
luaL_requiref(L, "_G", luaopen_base, 1);
luaL_requiref(L, LUA_MATHLIBNAME, luaopen_math, 1);
luaL_requiref(L, LUA_TABLIBNAME, luaopen_table, 1);
// luaL_requiref pushes to stack - clean it up:
lua_pop(L, 3);
// Load and execute desired scripts here.

Refer to 6 Standard Libraries and linit.c for list of available libraries. See also: luaL_requiref .

In addition to selective library loading you can also remove selected globals by setting them to nil :

lua_pushnil(L);
lua_setglobal(L, "print");

You set this once for the lua_State.

Chunk(ly)

Let's limit ourselves to a very basic operation of changing the first upvalue of loaded main chunk. The first upvalue of such chunk is expected to be _ENV (reference below).

Note that I said main chunk. In general you will rarely load chunks that expect something else than _ENV as their first upvalue but it is always nice to remember about such case.

Anyway, consider following example:

lua_State * L = luaL_newstate();
luaL_openlibs(L);
luaL_loadstring(L, "print2 \"Hello there\"");
// (A) Create a table with desired environment and place it at the top of the stack.
//     Replace this comment with any of the options described below
lua_setupvalue(L, -2, 1);
lua_call(L, 0, 0);

You can achieve ( A ) in various ways. You could use Lua C API:

lua_newtable(L);
lua_pushliteral(L, "print2");
lua_getglobal(L, "print");
lua_settable(L, -3);

Or you could do it via dostring :

lua_dostring(L, "return { print2 = print }");

Or you could try yet another approach, similar to one from the question:

lua_dostring(L, "sandbox_env = { print2 = print }"); // Without _ENV = sandbox_env
lua_getglobal(L, "sandbox_env");

General reference on environments: 2.2 Environments and the Global Environment .

Seelua_setupvalue documentation for usage details.

See another example in load_aux . Note that this is part of the source code for load function available in Lua. This function allows to set environment of loaded chunk through one of its arguments (see load ).

Locally

You could either modify upvalue _ENV or override it with a local directly in the Lua script. Let's do the latter:

local _ENV = { print2 = print }
(function ()
   print2("Hello there")
end)()

This means you could wrap loaded script and run it like this:

std::string loaded_script /* = ... */;
std::string wrapped_script = "local _ENV = { print2 = print }; (" + loaded_script + ")()";
luaL_loadstring(L, wrapped_script.c_str());
lua_call(L, 0, 0);

This will work with return function ()... end wrap, too (instead of (...)() ). That's because local _ENV will be passed as upvalue to returned anonymous function.

Refer to Environments Tutorial for more verbose explanation on environments manipulation inside Lua.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM