【问题标题】:How to get the return value of the block passed to eval method?如何获取传递给 eval 方法的块的返回值?
【发布时间】:2014-09-04 05:42:03
【问题描述】:

我想实现类似沙盒的东西

  • eval 给定字符串
  • 在与 eval 相同的上下文中执行给定的块
  • 返回block的结果

沙盒的目的是检查内容 - 函数、变量等。 - 易受攻击的代码。

这是我的规格

it 'returns return value of given block' do
  value = Sandbox.secure_eval('hoge = ["hoge", "fuga"]') do
    hoge[0]
  end
  expect(value).to eq('hoge')
end

而且,这是我的沙盒实现

require 'timeout'
module Sandbox
  def self.secure_eval(code, timeout: 5, safe_level: 2)
    raise ArgumentError, 'please set call back by block' unless block_given?

    proc = Proc.new do
      Timeout::timeout timeout do
        $SAFE = safe_level
        eval code do
          yield
        end
      end
    end

    proc.call
  end
end

但是#secure_eval返回的是eval的结果,在本例中是["hoge", "fuga"],无法捕获block的返回值。

我该怎么做?

【问题讨论】:

  • 我认为该块不会在任何地方被调用 - eval 不接受块...
  • 引用Ruby Security:“但是,$SAFE 不提供执行不受信任代码的安全环境。”您应该永远依赖 $SAFE 或 Ruby(或大多数其他语言)内置的任何其他通用“安全代码执行”机制来保护您在执行不受信任(并且可能是恶意)时免受伤害代码。你的沙盒根本不是沙盒!

标签: ruby eval return-value yield


【解决方案1】:

您可以使用yield 将 eval 的结果返回到块。你只需要yield这个值;因此我将您的yield 更改为yield eval code。在您提供给Sandbox.secure_eval 的块中,您必须将此结果绑定到块变量。 secure_eval 的结果将是块的结果,如您所愿。

proc = Proc.new do
  Timeout::timeout timeout do
    $SAFE = safe_level
    yield eval code # <= This line changed
  end
end

Sandbox.secure_eval('hoge = ["hoge", "fuga"]') { |hoge| hoge[0] }
# => "hoge"

Sandbox.secure_eval('2 ** 4') { |result| result - 5 }
# => 11

回应您的评论;事实证明,在Kernel#Binding 的帮助下,我们可以让它或多或少像你想要的那样工作。感觉很像一个黑客,所以谨慎使用它。

我使用绑定来评估代码,它可以访问所有定义的变量。此外,我为 Binding 类定义了一个method_missing,以便我们可以更轻松地访问变量。没有它,您将需要使用eval('varname') 而不仅仅是varname。根据@hakcho 的评论,他提到的猴子补丁解决方案并不理想,我现在使用仅在本地更改绑定行为的改进(即 method_missing 实现)。

我在您的方法中添加了一个显式的block 参数,我将其与instance_eval 一起使用,而不是yield。然后我们可以直接在块中访问变量。

require 'timeout'

module Sandbox
  refine Binding do
    def method_missing(meth, *args, &block)
      self.eval(meth.to_s)
    end
  end

  def self.secure_eval(code, timeout: 5, safe_level: 2, &block)
    raise ArgumentError, 'please set call back by block' unless block_given?

    proc = Proc.new do
      Timeout::timeout timeout do
        $SAFE = safe_level
        binding = binding()
        binding.eval(code)
        binding.instance_eval(&block)
      end
    end

    proc.call
  end
end

using Sandbox # Activate the refinement so we can use x, y, z directly
Sandbox.secure_eval('x = [1,2,3]; y = 0; z = { key: "Hello!" }') do
   x[1]    # => 2
   y       # => 0
   z[:key] # => "Hello!"
end

【讨论】:

  • 感谢您的回复。您的代码似乎有效,但我希望我的沙箱如下所示: Sandbox.secure_eval('hoge = ["hoge", "fuga"];piyo = "piyo"') { hoge[0] } # => "hoge "有什么办法吗?
  • @Mekajiki Ah 所以你希望能够访问你在eval.. 中声明的任何变量。我不知道这是否可能,eval 只是返回最后一个的值表达式,但不是中间表达式。也许有可能,但我不立即知道方法。
  • @Mekajiki 我更新了我的帖子,提供了一种访问其他已定义变量的方法。
  • 您必须在Binding 中将*argsblock 添加到eval,但对块执行此操作并非易事。
  • @hakcho 我添加了细化的使用,现在需要using Sandbox 来启用修补的绑定行为,而不是全局启用它。
猜你喜欢
  • 2010-11-03
  • 2021-12-10
  • 1970-01-01
  • 1970-01-01
  • 2014-03-07
  • 2014-07-13
  • 2019-11-19
  • 1970-01-01
  • 2016-02-13
相关资源
最近更新 更多