从C ++设置_ENV以获得字符串功能

时间:2019-10-29 09:17:46

标签: c++ lua lua-5.3

在我的项目中,我正在执行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用户网站上那样做:

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错误

  

尝试执行字符串值

我也尝试这样做:

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

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

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

赞:

lua_pcall

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

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

奖励指出是否有办法为整个. . size_t n = code.find("()") + 2; code.insert(n, whitelist); std::string wrapped_code = "return " + code; . . 而不是每次调用函数都保存一次。

1 个答案:

答案 0 :(得分:1)

在深入研究示例之前,让我们解释一下如何(以及在​​什么级别上)在Lua中使用或多或少的沙箱代码:

  1. 全局-删除或永远不要将不需要的模块/功能添加到全局环境中。

  2. 大块(ly)-修改块的_ENV高值。

  3. 本地-通过Lua脚本中的本地或高值修改_ENV。然后可以通过升值将其传播给子代。

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

我不确定您的需求是什么,但我将尝试为您提供上述每种方法的示例。请注意,这些示例不是最终的示例,不是唯一可行的方法,请不要使用您的return function () ... end包装器。选择任何适合您的东西。

全球

这很简单。如果您不打算使用提供的所有库...

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 Librarieslinit.c。另请参阅:luaL_requiref

除了选择性加载库外,您还可以通过将选定的全局变量设置为nil来删除它们:

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

您为lua_State设置了一次。

Chunk(ly)

让我们局限于更改加载的主块的第一个升值的非常基本的操作。此类块的第一个升值预期为_ENV(请参见下文)。

请注意,我说的是主要块。通常,您很少加载期望_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中的另一个示例。请注意,这是Lua中load函数源代码的一部分。此功能允许通过其参数之一来设置已加载块的环境(请参见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内部环境操作的详细说明。