如何创建或引用一个用于 Eval 的空 Ruby 绑定

huangapple go评论54阅读模式
英文:

How to Make Or Reference a Null Ruby Binding For Eval

问题

Rubocop 不喜欢以下内容;它会发出警告向eval传递绑定、__FILE__和__LINE__

sort_lambda = eval "->(a) { a.date }"

是的,我知道eval存在安全问题。安全问题不在本问题的范围之内。

Ruby绑定的文档中提到:

Binding类的对象封装了代码中某个特定位置的执行上下文,并保留了这个上下文以供将来使用。在这个上下文中可以访问的变量、方法、self的值,以及可能在此上下文中可以访问的迭代器块都被保留下来。可以使用Kernel#binding创建绑定对象,并可以在Kernel#eval方法的第二个参数中传递这些绑定对象,以建立评估的环境。

创建的lambda不需要访问任何作用域中的变量。在从eval调用的作用域中创建一个快速且简陋的绑定会像这样:

sort_lambda = eval "->(a) { a.date }", self.binding, __FILE__, __LINE__

理想情况下,应该传递一个空绑定(没有任何内容定义在其中,不包括来自self的内容等)给这个eval。如何做到这一点呢?

英文:

Rubocop dislikes the following; it issues Pass a binding, __FILE__ and __LINE__ to eval.:

sort_lambda = eval "->(a) { a.date }"

Yes, I know that eval is a security problem. The issue of security is out of scope for this question.

The Ruby documentation on binding says:

> Objects of class Binding encapsulate the execution context at some particular place in the code and retain this context for future use. The variables, methods, value of self, and possibly an iterator block that can be accessed in this context are all retained. Binding objects can be created using Kernel#binding, and are made available to the callback of Kernel#set_trace_func and instances of TracePoint.

> These binding objects can be passed as the second argument of the Kernel#eval method, establishing an environment for the evaluation.

The lambda being created does not need to access any variables in any scopes.
A quick and dirty binding to the scope where the eval is invoked from would look like this:

sort_lambda = eval "->(a) { a.date }", self.binding, __FILE__, __LINE__

Ideally, a null binding (a binding without anything defined in it, nothing from self, etc.) should be passed to this eval instead.
How could this be done?

答案1

得分: 3

不完全是,但你可以近似实现它。

在我继续之前,我知道你已经说过这个,但我想强调给以后阅读这个问题的读者。我下面所描述的并不是一个沙盒。这不会保护你免受恶意用户的攻击。如果你将用户输入传递给 eval,它仍然可以使用我下面展示的绑定来造成很大的损害。在尝试在生产环境中使用之前,请咨询网络安全专家。

好的,现在说到正题。在Ruby中,你不能真正拥有一个空的绑定。Binding 类在某种程度上是编译时的魔法。虽然该类只公开了获取本地变量的方法,但它也捕获了在那个时间点处于作用域内的所有常量名(包括类名),以及当前的接收对象 self 以及所有可以从执行点调用的 self 上的方法。一个空绑定的问题在于,Ruby 有时候很像 Smalltalk。一切都存在于被称为 "对象" 的形而上理念的世界中,没有 Ruby 代码可以真正隔离运行。

事实上,试图这样做只是在设置障碍和笨拙的目标。认为你可以阻止我访问 BasicObject 吗?如果我在 Ruby 中有任何对象 a,那么 a.class.ancestors.last 就是 BasicObject。使用这种技术,我们可以通过简单拥有该类的一个实例或子类来获取任何全局类。一旦我们有了类,我们就有了模块,一旦我们有了模块,我们就有了 Kernel,此时我们就拥有了大部分 Ruby 内置功能。

同样,self 总是存在。你无法摆脱它。它是 Ruby 对象系统的一个基本部分,即使在你认为它不存在的情况下也存在(参见我的早些时候的问题)。Ruby 中的每个方法或代码块都有一个接收对象,所以你最多只能尝试将接收对象限制为尽可能小的对象。你可能认为希望 selfBasicObject,但有趣的是,实际上也没有办法做到这一点,因为只有在作用域中包含 Kernel 时,你才能 获取 绑定,而 BasicObject 并不包括 Kernel。因此,至少你将获得所有的 Kernel。你可能可以通过某种方式省略一些 BasicObject 子类,其中包括 Kernel,从而避免其他 Object 方法,但这可能会在以后引起混乱。

所有这些都是为了强调,一个假设的空绑定只会稍微增加获取所有全局名称的复杂性,而不是不可能。这就是为什么它不存在的原因。

话虽如此,如果你的目标是消除本地变量并尝试,你可以通过在模块内创建一个绑定来轻松实现这一点。

module F
  module_function def get_binding
    binding
  end
end

sort_lambda = eval "->(a) { a.date }", F.get_binding

这个绑定永远不会有本地变量,它可以访问的方法和常量仅限于 Kernel 中或全局作用域中可用的那些。这已经接近于"null",在我们所称之为 Ruby 的复杂类型和名称的联合体中是最接近的。

英文:

Not exactly, but you can approximate it.

Before I go further, I know you've already said this, but I want to emphasize it for future readers of this question as well. What I'm describing below is NOT a sandbox. This will NOT protect you from malicious users. If you pass user input to eval, it can still do a lot of damage with the binding I show you below. Consult a cybersecurity expert before trying this in production.

Great, with that out of the way, let's move on. You can't really have an empty binding in Ruby. The Binding class is sort of compile-time magic. Although the class proper only exposes a way to get local variables, it also captures any constant names (including class names) that are in scope at the time, as well as the current receiver object self and all methods on self that can be invoked from the point of execution. The problem with an empty binding is that Ruby is a lot like Smalltalk sometimes. Everything exists in one big world of Platonic ideals called "objects", and no Ruby code can truly run in isolation.

In fact, trying to do so is really just putting up obstacles and awkward goalposts. Think you can block me from accessing BasicObject? If I have literally any object a in Ruby, then a.class.ancestors.last is BasicObject. Using this technique, we can get any global class by simply having an instance of that class or a subclass. Once we have classes, we have modules, and once we have modules we have Kernel, and at that point we have most of the Ruby built-in functionality.

Likewise, self always exists. You can't get rid of it. It's a fundamental part of the Ruby object system, and it exists even in situations where you don't think it does (see this question of mine from awhile back, for instance). Every method or block of code in Ruby has a receiver, so the most you can do is try to limit the receiver to be as small an object as possible. One might think you want self to be BasicObject, but amusingly there's not really a way to do that either, since you can only get a binding if Kernel is in scope, and BasicObject doesn't include Kernel. So at minimum, you're getting all of Kernel. You might be able to skimp by somehow and use some subclass of BasicObject that includes Kernel, thereby avoiding other Object methods, but that's likely to cause confusion down the road too.

All of this is to emphasize that a hypothetical null binding would really only make it slightly more complicated to get all of the global names, not impossible. And that's why it doesn't exist.

That being said, if your goal is to eliminate local variables and to try, you can get that easily by creating a binding inside of a module.

module F
  module_function def get_binding
    binding
  end
end

sort_lambda = eval "->(a) { a.date }", F.get_binding

This binding will never have local variables, and the methods and constants it has access to are limited to those available in Kernel or at the global scope. That's about as close to "null" as you're going to get in the complex nexus of interconnected types and names we call Ruby.

答案2

得分: 3

虽然我最初将这条评论留在了[@Silvio Mayolo](https://stackoverflow.com/users/2288659/silvio-mayolo)的答案上,他的答案写得非常好,但似乎把它作为一个答案发布更为恰当。

虽然该答案中包含的大部分内容都是正确的,但我们可以通过`BasicObject`继承略微接近“空绑定”:
```ruby
class NullBinding < BasicObject 
  def get_binding 
    ::Kernel
      .instance_method(:binding)
      .bind(self)
      .call
  end
end

这个绑定上下文在 Ruby 中尽可能地限制了上下文。

在这个上下文中,你将无法仅通过名称引用常量:

eval 'Class', NullBinding.new.get_binding 
#=> NameError 

也就是说,你仍然可以引用顶层作用域:

eval '::Class', NullBinding.new.get_binding 
#=> Class

在这个绑定上下文中,直接可用的方法仅限于BasicObject可用的实例方法。例如:

eval "puts 'name'", NullBinding.new.get_binding 
#=> NoMethodError 

同样,你可以访问顶层作用域,所以:

eval "::Kernel.puts 'name'", NullBinding.new.get_binding 
# name
#=> nil

<details>
<summary>英文:</summary>

While I originally left this as a comment on [@Silvio Mayolo](https://stackoverflow.com/users/2288659/silvio-mayolo)&#39;s answer, which is very well written, it seems germane to post it as an answer instead. 

While most of what is contained within that answer is correct we can get slightly closer to a *&quot;Null Binding&quot;* through `BasicObject` inheritance: 

class NullBinding < BasicObject
def get_binding
::Kernel
.instance_method(:binding)
.bind(self)
.call
end
end

This binding context has as limited a context as possible in ruby.

Using this context you will be unable to reference constants solely by name: 

eval 'Class', NullBinding.new.get_binding
#=> NameError

That being said you can still reference the TOP_LEVEL scope so 

eval '::Class', NullBinding.new.get_binding
#=> Class

The methods *directly* available in this binding context are limited only to the instance methods available to [BasicObject](https://ruby-doc.org/core-3.1.1/BasicObject.html). By way of Example: 

eval "puts 'name'", NullBinding.new.get_binding
#=> NoMethodError

Again with the caveat that you can access TOP_LEVEL scope so:

eval "::Kernel.puts 'name'", NullBinding.new.get_binding

name

#=> nil


</details>



huangapple
  • 本文由 发表于 2023年2月8日 23:04:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/75387695.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定