Reflection 如何系统地填充沙箱程序的白名单?

Reflection 如何系统地填充沙箱程序的白名单?,reflection,lua,hook,sandbox,introspection,Reflection,Lua,Hook,Sandbox,Introspection,在Lua编程(第四版)第260-263页,作者讨论了如何在Lua中实现“沙箱”(即运行不受信任的代码) 在限制不可信代码可以运行的功能时,他建议采用“白名单方法”: 我们永远不应该考虑删除哪些函数,而应该添加哪些函数 这个问题是关于将这个建议付诸实践的工具和技术。(我希望在这一点上会有混淆,我想先强调一下。) 作者给出了以下代码,作为基于允许函数白名单的沙盒程序的说明。(我添加或移动了一些注释,删除了一些空行,但我已从书中逐字复制了可执行内容) 一开始我就对这段代码感到困惑,因为钩子测试事件

在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
  • 更重要的是,什么是一种系统的方法来查找和确定要添加到白名单表中的内容
  • 第(2)部分是本文的核心。我正在寻找能够消除填充白名单问题中的猜测的工具/技术

    (我知道如果我替换
    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