【发布时间】:2019-12-11 00:07:01
【问题描述】:
在Lua 编程(第 4 版)的第 260-263 页,作者讨论了如何在 Lua 中实现“沙盒”(即运行不受信任的代码)。
在限制不受信任的代码可以运行的功能时,他建议采用“白名单方法”:
我们永远不应该考虑要删除哪些功能,而是要添加哪些功能。
这个问题是关于将这个建议付诸实践的工具和技术。(我想在这一点上会有一些混淆,我想先强调一下。)
作者给出以下代码作为基于允许功能白名单的沙盒程序的说明。 (我添加或移动了一些 cmets,并删除了一些空白行,但我已经从书中逐字复制了可执行内容。
-- From p. 263 of *Programming in Lua* (4th ed.)
-- Listing 25.6. Using hooks to bar calls to unauthorized functions
local debug = require "debug"
local steplimit = 1000 -- maximum "steps" that can be performed
local count = 0 -- counter for steps
local validfunc = { -- set of authorized functions
[string.upper] = true,
[string.lower] = true,
... -- other authorized functions
}
local function hook (event)
if event == "call" then
local info = debug.getinfo(2, "fn")
if not validfunc[info.func] then
error("calling bad function: " .. (info.name or "?"))
end
end
count = count + 1
if count > steplimit then
error("script uses too much CPU")
end
end
local f = assert(loadfile(arg[1], "t", {})) -- load chunk
debug.sethook(hook, "", 100) -- set hook
f() -- run chunk
马上我就对这段代码感到困惑,因为钩子测试事件类型(if event == "call" then...),然而,当设置钩子时,只请求计数事件(debug.sethook(hook, "", 100))。所以,与validfunc的整个歌舞都是徒劳的。
也许这是一个错字。所以我尝试使用这段代码进行试验,但我发现将白名单技术付诸实践非常困难。下面的示例是我遇到的问题类型的非常简化的说明。
首先,这里是作者的代码稍作修改的版本。
#!/usr/bin/env lua5.3
-- Filename: sandbox
-- ----------------------------------------------------------------------------
local debug = require "debug"
local steplimit = 1000 -- maximum "steps" that can be performed
local count = 0 -- counter for steps
local validfunc = { -- set of authorized functions
[string.upper] = true,
[string.lower] = true,
[io.stdout.write] = true,
-- ... -- other authorized functions
}
local function hook (event)
if event == "call" then
local info = debug.getinfo(2, "fnS")
if not validfunc[info.func] then
error(string.format("calling bad function (%s:%d): %s",
info.short_src, info.linedefined, (info.name or "?")))
end
end
count = count + 1
if count > steplimit then
error("script uses too much CPU")
end
end
local f = assert(loadfile(arg[1], "t", {})) -- load chunk
validfunc[f] = true
debug.sethook(hook, "c", 100) -- set hook
f() -- run chunk
第二个sn-p相对于第一个sn-p最显着的区别是:
- 对
debug.sethook的调用将"c"作为掩码; - 已加载块的
f函数被添加到validfunc白名单中; -
io.stdout.write被添加到validfunc白名单中;
当我使用这个sandbox 程序运行如下所示的一行脚本时:
# Filename: helloworld.lua
io.stdout:write("Hello, World!\n")
...我收到以下错误:
% ./sandbox helloworld.lua
lua5.3: ./sandbox:20: calling bad function ([C]:-1): __index
stack traceback:
[C]: in function 'error'
./sandbox:20: in function <./sandbox:16>
[C]: in metamethod '__index'
helloworld.lua:3: in local 'f'
./sandbox:34: in main chunk
[C]: in ?
我尝试通过将以下内容添加到 validfunc 来解决此问题:
[getmetatable(io.stdout).__index] = true,
...但我仍然遇到几乎相同的错误。我可以继续猜测并尝试添加更多内容,但这是我想避免的。
我有两个相关的问题:
- 我可以添加什么到
validfunc以便sandbox将运行helloworld(按原样)完成? - 更重要的是,什么是系统化的方法来确定要添加到白名单表中的内容?
第 (2) 部分是这篇文章的核心。我正在寻找可以消除填充白名单表问题的猜测的工具/技术。
(我知道如果我将io.stdout:write 替换为print,我可以让helloworld 工作,在sandbox 的validfunc 中注册print,并将{print = print} 作为最后一个参数传递给loadfile,但这样做并不能回答一般问题,即如何系统地确定需要添加到白名单中以允许某些特定代码在沙箱中工作.)
编辑: 询问@DarkWiiPlayer 指出,calling bad function 错误是由调用未注册函数 (__index?) 触发的,这是对之前的响应的一部分attempt to index a nil value 错误。所以,这篇文章的问题都是关于系统地确定要添加到 validfunc 的内容以允许 Lua 正常发出 attempt to index a nil value 错误。
我应该补充一点,哪个函数的调用触发了导致calling bad function 错误消息的钩子执行的问题目前完全不清楚。此错误消息将错误归咎于 __index,但我怀疑这可能是一个红鲱鱼,可能是由于 Lua 中的错误。
为什么要怀疑 Lua 中的错误?如果我将sandbox 中的error 调用稍微更改为
error(string.format("calling bad function (%s:%d): %s (%s)",
info.short_src, info.linedefined, (info.name or "?"),
info.func))
...那么错误信息如下所示:
lua5.3: ./sandbox:20: calling bad function ([C]:-1): __index (function: 0x55b391b79ef0)
stack traceback:
[C]: in function 'error'
./sandbox:20: in function <./sandbox:16>
[C]: in metamethod '__index'
helloworld.lua:3: in local 'f'
./sandbox:34: in main chunk
[C]: in ?
这并不奇怪,但如果现在我将helloworld.lua 更改为
# Filename: helloworld.lua
nonexistent()
io.stdout:write("Hello, World!\n")
...在sandbox下运行,错误信息变为
lua5.3: ./sandbox:20: calling bad function ([C]:-1): nonexistent (function: 0x556a161cdef0)
stack traceback:
[C]: in function 'error'
./sandbox:20: in function <./sandbox:16>
[C]: in global 'nonexistent'
helloworld.lua:3: in local 'f'
./sandbox:34: in main chunk
[C]: in ?
从这个错误信息中,我们可以断定nonexistent 是一个真正的函数;毕竟,它就在0x556a161cdef0!但我们知道nonexistent 名副其实:它不存在!
臭虫的味道肯定在空气中。触发钩子的函数可能真的应该从那些触发"c"-masked 钩子的函数中排除?尽管如此,在这种特殊情况下,对 debug.info 的调用似乎返回了不一致的信息(因为函数的名称 [例如 nonexistent] 显然与实际的函数对象 [例如function: 0x556a161cdef0] 应该触发了钩子)。
【问题讨论】:
-
我刚刚回来更新我的答案,现在已经过了一段时间,并注意到我无法在这台 PC 上重现该问题。不知道为什么,但它实际上打印了一条对我有意义的错误消息,尽管我之前确实设法得到了你的奇怪错误。无论哪种方式,这只是 Lua 很奇怪,真正的问题出在其他地方;有关详细信息,请参阅我的答案。
标签: reflection lua hook sandbox introspection