我想实现类似沙盒的东西
沙箱的目的是检查内容 - 函数,变量,e.t.c。 - 易受攻击的代码。
这是我的规范
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"],并且无法捕获块的返回值。
我该怎么做?
答案 0 :(得分:4)
您可以使用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来评估代码,该代码可以访问所有已定义的变量。另外,我为Binding类定义了一个method_missing
,这样我们就可以更容易地访问变量了。没有它,您需要eval('varname')
而不仅仅是varname
。根据提到的猴子补丁解决方案的@hakcho的评论并不理想,我现在使用仅在本地改变Binding行为的改进(即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