【问题标题】:Recreating setfenv() in Lua 5.2在 Lua 5.2 中重新创建 setfenv()
【发布时间】:2012-12-26 18:21:36
【问题描述】:

如何在 Lua 5.2 中重新创建 setfenv 的功能?我无法准确理解您应该如何使用新的 _ENV 环境变量。

在 Lua 5.1 中,您可以使用 setfenv 轻松地对任何函数进行沙箱处理。

--# Lua 5.1

print('_G', _G)             -- address of _G

local foo = function()  
    print('env', _G)        -- address of sandbox _G
    bar = 1
end

-- create a simple sandbox
local env = { print = print }
env._G = env

-- set the environment and call the function
setfenv(foo, env)
foo()

-- we should have global in our environment table but not in _G
print(bar, env.bar)

运行此示例会显示输出:

_G    table: 0x62d6b0
env   table: 0x635d00
nil   1



我想在 Lua 5.2 中重新创建这个简单的例子。下面是我的尝试,但它不像上面的例子那样工作。

--# Lua 5.2

local function setfenv(f, env)
    local _ENV = env or {}       -- create the _ENV upvalue
    return function(...)
        print('upvalue', _ENV)   -- address of _ENV upvalue
        return f(...)
    end
end

local foo = function()
    print('_ENV', _ENV)          -- address of function _ENV
    bar = 1
end

-- create a simple sandbox
local env = { print = print }
env._G = env

-- set the environment and call the function
foo_env = setfenv(foo, env)
foo_env()

-- we should have global in our envoirnment table but not in _G
print(bar, env.bar)

运行此示例会显示输出:

upvalue    table: 0x637e90
_ENV       table: 0x6305f0
1          nil



我知道有关此主题的其他几个问题,但它们似乎主要处理加载动态代码(文件或字符串),这些代码使用 Lua 5.2 中提供的新 load 函数运行良好。在这里,我特别要求在沙箱中运行任意函数的解决方案。我想在不使用 debug 库的情况下执行此操作。根据 Lua documentation,我们不应该依赖它。

【问题讨论】:

    标签: lua


    【解决方案1】:

    如果不使用 Lua 5.2 中的调试库,您将无法更改函数的环境。一旦创建了一个函数,这就是它所拥有的环境。修改此环境的唯一方法是修改其第一个上值,这需要调试库。

    Lua 5.2 中环境的总体思路是,环境应该被认为是不可变的,在诡计之外(即:调试库)。你在一个环境中创建一个函数;一旦在那里创建,这就是它所拥有的环境。永远。

    这就是 Lua 5.1 中经常使用环境的方式,但是通过随意的函数调用来修改任何环境的环境是很容易和认可的。如果你的 Lua 解释器去掉了setfenv(防止用户破坏沙箱),那么用户代码就不能在内部为自己的函数设置环境。所以外部世界有一个沙箱,但内部世界不能有一个沙箱沙箱中。

    Lua 5.2 机制使得在函数创建后修改环境变得更加困难,但它确实允许您在创建过程 设置环境。这使您可以在沙箱中进行沙箱。

    所以你真正想要的只是像这样重新排列你的代码:

    local foo;
    
    do
      local _ENV = { print = print }
    
      function foo()
        print('env', _ENV)
        bar = 1
      end
    end
    

    foo 现在被沙盒化了。而现在,打破沙盒的难度要大得多。

    正如你所想,这在 Lua 开发者中引起了一些争论。

    【讨论】:

    • Lua 5.2 有 lexical 环境,而不是 Lua 5.1 中神奇的动态环境。正如 Nicol 所提到的,如果知道它在哪里,我们仍然可以通过改变一个上值来改变一个函数的环境。
    【解决方案2】:

    它有点贵,但如果它对你来说很重要......

    为什么不使用 string.dump,并将函数重新加载到正确的环境中?

    function setfenv(f, env)
        return load(string.dump(f), nil, nil, env)
    end
    function foo()
        herp(derp)
    end
    
    setfenv(foo, {herp = print, derp = "Hello, world!"})()
    

    【讨论】:

    • +1 直到现在,我在看说明书的时候脑子里总是跳过string.dump。
    【解决方案3】:

    要在 Lua 5.2 中重新创建 setfenv/getfenv,您可以执行以下操作:

    if not setfenv then -- Lua 5.2
      -- based on http://lua-users.org/lists/lua-l/2010-06/msg00314.html
      -- this assumes f is a function
      local function findenv(f)
        local level = 1
        repeat
          local name, value = debug.getupvalue(f, level)
          if name == '_ENV' then return level, value end
          level = level + 1
        until name == nil
        return nil end
      getfenv = function (f) return(select(2, findenv(f)) or _G) end
      setfenv = function (f, t)
        local level = findenv(f)
        if level then debug.setupvalue(f, level, t) end
        return f end
    end
    

    RPFeltz 的回答 (load(string.dump(f)...)) 很聪明,可能对您有用,但它不处理具有上值的函数(_ENV 除外)。

    还有 compat-env 模块在 Lua 5.2 中实现 Lua 5.1 功能,反之亦然。

    【讨论】:

    • 我一直在寻找一种不使用调试库的方法,但很高兴看到它应该如何完成。谢谢。
    【解决方案4】:

    在 Lua5.2 中,可沙盒化的函数需要自己指定。您可以使用的一种简单模式是让它接收 _ENV 作为参数

    function(_ENV)
        ...
    end
    

    或者将它包裹在定义环境的东西中

    local mk_func(_ENV)
        return function()
            ...
        end
    end
    
    local f = mk_func({print = print})
    

    但是,_ENV 的这种显式使用对于沙盒来说不太有用,因为您不能总是假设其他函数会通过使用 _ENV 变量来配合。在这种情况下,这取决于你做什么。如果您只想从其他文件加载代码,那么 loadloadfile 等函数通常会收到一个可选的环境参数,您可以将其用于沙盒。此外,如果您尝试加载的代码是字符串格式,您可以使用字符串操作来自己添加_ENV 变量(例如,通过在函数周围包装一个带有 env 参数的函数)

    local code = 'return function(_ENV) return ' .. their_code .. 'end'
    

    最后,如果你真的需要动态的函数环境操作,你可以使用调试库将函数的内部upvalue更改为_ENV。虽然通常不鼓励使用调试库,但我认为如果所有其他替代方案都不适用是可以接受的(我觉得在这种情况下,改变函数的环境已经是很深的巫术魔法,所以使用调试库并不会更糟)

    【讨论】:

    • 感谢您提供的信息。这是一个不错的方法,但似乎不可能在没有调试库的情况下对我没有编写的函数进行沙箱化。
    • @Adam:你从哪里得到这些函数?正如我所说,您仍然应该能够在模块级别而不是功能级别进行沙箱。
    猜你喜欢
    • 2018-04-16
    • 1970-01-01
    • 1970-01-01
    • 2014-03-11
    • 2016-01-11
    • 2014-10-08
    • 1970-01-01
    • 2016-12-08
    • 2013-04-06
    相关资源
    最近更新 更多