从 C++ 为字符串函数设置 _ENV

Set _ENV from C++ for string function

在我的项目中,我正在执行 XML 文件中包含的一些 lua 函数。我从 C++ 中读取 XML,解析代码字符串,执行它们并得到结果。 我发现的所有相关问题要么使用专用的 .lua 文件,要么直接在 Lua 中完成,但我找不到适合我的案例的解决方案。

我无法修改文件中的函数,它们都具有以下签名:

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

从 C++ 我像这样加载它们:

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;

一切正常,但是 运行 来自 XML 字符串的任意 Lua 代码似乎不太安全,所以我想设置代码可以使用的函数白名单。

根据 this lua-用户讨论,我创建了我的允许功能列表,例如:

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

问题是我不明白如何加载它以便在我调用的函数中可用。

我试过在 lua-users 网站上这样做:

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
}

但这会导致函数无法正确加载,并出现 Lua 错误

Trying to execute a string value

我也试过:

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

在执行加载的 XML 函数之前,这不会使程序崩溃,但也不会为调用的函数设置 _ENV

我发现我想要的唯一方法是用 C++ 搜索 () 解析函数字符串,在其后插入 whitelist,然后插入 luaL_loadstringlua_pcall 执行两次。

像这样:

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

这有效并为该函数设置了我的自定义 _ENV,但在我看来这是一种非常 hacky 的方法。

如何更好地为从 C 加载的字符串函数设置 _ENV 变量?

如果有办法为整个保存一次lua_State而不是每次我调用一个函数都可以加分。

在我们深入研究示例之前,让我们解释一下我们如何(以及在​​什么级别)我们可以 more-or-less 在 Lua:

中的沙箱代码
  1. 全局 - 删除或从不将不需要的 modules/functions 添加到全局环境中。

  2. Chunk(ly) - 修改块的 _ENV upvalue。

  3. 本地 - 通过 Lua 脚本中的本地或上值修改 _ENV。它可以通过 upvalue 传播到 children。

查看问题中提供的示例,您尝试执行 23.

我不确定您的需求是什么,但我会尝试为您提供上面列出的每种方法的示例。请注意,这些示例不是最终示例,也不是唯一可能的方法,请勿使用您的 return function () ... end 包装器。选择适合你的。

全球

这个非常straight-forward。如果您不打算使用所提供的所有库...

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

...然后就不要加载它们了:

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.

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

除了选择性库加载之外,您还可以通过将它们设置为 nil:

来删除选定的全局变量
lua_pushnil(L);
lua_setglobal(L, "print");

您为lua_State设置了一次。

块(ly)

让我们将自己限制在一个非常基本的操作上,即更改加载的主块的第一个上值。此类块的第一个上值预计为 _ENV(参考下文)。

请注意,我说的是 main 块。一般来说,你很少会加载期望 _ENV 以外的东西作为它们的第一个上值的块,但记住这种情况总是好的。

无论如何,请考虑以下示例:

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);

您可以通过多种方式实现 (A)。你可以使用 Lua C API:

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

或者您可以通过 dostring:

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

或者您可以尝试另一种方法,类似于问题中的方法:

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

关于环境的一般参考:2.2 Environments and the Global Environment

有关使用详情,请参阅 lua_setupvalue 文档。

参见 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 中的另一个示例。

本地

您可以修改上值 _ENV 或直接在 Lua 脚本中用本地覆盖它。让我们做后者:

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

这意味着您可以包装加载的脚本并 运行 像这样:

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);

这也适用于 return function () ... end 换行(而不是 (...)())。那是因为 local _ENV 将作为上值传递给返回的匿名函数。

请参阅 Environments Tutorial 以获取有关 Lua 内环境操作的更详细解释。