Reflection 如何系统地填充沙箱程序的白名单?
在Lua编程(第四版)第260-263页,作者讨论了如何在Lua中实现“沙箱”(即运行不受信任的代码) 在限制不可信代码可以运行的功能时,他建议采用“白名单方法”: 我们永远不应该考虑删除哪些函数,而应该添加哪些函数Reflection 如何系统地填充沙箱程序的白名单?,reflection,lua,hook,sandbox,introspection,Reflection,Lua,Hook,Sandbox,Introspection,在Lua编程(第四版)第260-263页,作者讨论了如何在Lua中实现“沙箱”(即运行不受信任的代码) 在限制不可信代码可以运行的功能时,他建议采用“白名单方法”: 我们永远不应该考虑删除哪些函数,而应该添加哪些函数 这个问题是关于将这个建议付诸实践的工具和技术。(我希望在这一点上会有混淆,我想先强调一下。) 作者给出了以下代码,作为基于允许函数白名单的沙盒程序的说明。(我添加或移动了一些注释,删除了一些空行,但我已从书中逐字复制了可执行内容) 一开始我就对这段代码感到困惑,因为钩子测试事件
这个问题是关于将这个建议付诸实践的工具和技术。(我希望在这一点上会有混淆,我想先强调一下。)
作者给出了以下代码,作为基于允许函数白名单的沙盒程序的说明。(我添加或移动了一些注释,删除了一些空行,但我已从书中逐字复制了可执行内容) 一开始我就对这段代码感到困惑,因为钩子测试事件类型(
if event==“call”,那么
…),然而,当钩子被设置时,只请求计数事件(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
与第一个片段相比,第二个片段的最大区别是:
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
添加哪些内容,以便sandbox
可以按原样运行helloworld
io,我可以让helloworld
工作。stdout:write
为print
,在sandbox
的validfunc
中注册print
,并通过{print=print}
作为loadfile
的最后一个参数,但这样做并不能回答如何系统地确定哪些内容需要添加到白名单中以允许某些特定代码在沙箱中工作的一般问题。)
EDIT:Ask@DarkWiiPlayer指出,
调用坏函数
错误是由调用未注册函数(\uu index
?)触发的,这是对早期尝试索引nil值错误的响应的一部分。因此,这篇文章的问题都是关于系统地确定向validfunc
添加什么,以允许Lua正常地发出尝试索引nil值的错误
我应该补充一点,目前还不清楚是哪个函数的调用触发了钩子的执行,导致了调用坏函数错误消息。此错误消息将错误归咎于\uuu index
,但我怀疑这可能是一种误导,可能是由于Lua中的错误
为什么怀疑Lua中有bug?如果我将错误
调用沙盒
稍微更改为
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 ?
…并在沙盒下运行,错误消息变为
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 ?
lua5.3:./sandbox:20:调用错误的函数([C]:-1):不存在(函数:0x556a161cdef0)
堆栈回溯:
[C] :在函数“error”中
/沙盒:20:处于功能中
[C] :在全局“不存在”中
helloworld.lua:3:在本地“f”中
/沙盒:34:在主块中
[C] :在哪?
从这个错误消息中,可以得出结论,不存在
是一个实函数;毕竟,它就在那里0x556a161cdef0
!但我们知道,不存在
名副其实:它不存在
空气中肯定有臭虫的气味。有可能触发钩子的函数真的应该被排除在触发此类“c”
-屏蔽钩子的函数之外?尽管如此,在这种特殊情况下,debug.info
的调用似乎返回了不一致的信息(因为函数名[例如不存在的
]显然与实际函数对象根本不对应[例如函数:0x556a161cdef0
]这可能会触发钩子)。(最后的答案在底部,请随意跳过,直到
行)
我将逐步解释我的调试
这真是一个奇怪的现象。经过一些测试,我设法缩小了范围:
- 由于将
{}
传递给加载,函数在空环境中运行,因此io
实际上是nil(并且io.stdout
无论如何都会出错)
- 尝试索引
io
(为零值)时直接发生错误
- 函数
\u索引是一个C函数(请参阅错误消息)
我的第一个直觉是,\u index
在内部的某个地方被调用。因此,为了了解它的功能,我决定看看它在h
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 ?
# Filename: helloworld.lua
nonexistent()
io.stdout:write("Hello, World!\n")
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 ?
(*temporary) stdin:43: attempt to index a nil value (global 'io')
(*temporary) table: 0x563cef2fd170
lua: stdin:29: calling bad function ([C]:-1): __index
stack traceback:
[C]: in function 'error'
stdin:29: in function <stdin:21>
[C]: in metamethod '__index'
stdin:43: in function 'f'
stdin:49: in main chunk
[C]: in ?
shell returned 1