【问题标题】:What methods make it possible to access the source code?哪些方法可以访问源代码?
【发布时间】:2013-04-14 18:11:50
【问题描述】:

有几种方法可以从需要/加载该库的 Ruby 代码中访问库的源代码。在这些方式中,有的直接读取库文件并解析。其他人通过一些提供有关源信息的内置方法(例如抽象语法树)访问源。在我无法直接读取文件内容的情况下(如前一种方式),访问源的唯一方法是访问提供信息的内置方法。通过重新定义这些方法来做其他事情,我将完全放弃对源代码的访问。什么是最小的方法集,如果我将它们重新定义为其他东西,我将完全失去对外部文件上库源代码的访问权限?


重新表述问题

假设:

  • 有一个用户可以在文件 A 中编写任何 Ruby 代码。
  • 有一个我写的静态Ruby文件B,它加载文件A并调用A中定义的主例程,还定义了一些用户可以在A中使用的类/方法。
  • 用户对 B 没有 +r(读取)或 +w(写入)权限。

我必须通过在文件 B 中写入来重新定义(无效)或删除哪些(标准 Ruby)方法,以使用户无法访问写入文件 B 中的源(通过用户可以编写的任何代码)在文件 A) 中当我运行文件 B 时?

有一些库,如 sorcerer、pry,可以提取它有权访问的方法的源代码。纯 Ruby 中必须有一些原始命令,这些库依赖这些命令才能访问源代码。是什么方法让这种事情成为可能?

如果您不知道完整的答案,但知道特定库如何提取某些方法的来源,那么这仍然会有所帮助。

【问题讨论】:

  • 我认为您可以定义一些抽象类或类似于 Java 中的接口的东西,它不实现任何东西。该接口将从外部组件公开访问。然后可以继承抽象类在内部实现。
  • 重新定义此类方法时,您可以使用Module#alias_method 来确保您拥有正在修改的原始方法的“副本”。 apidock.com/ruby/Module/alias_method
  • systembacktickfork/exec 等,将不得不被禁用。跨度>
  • 等等。你想加载一个“插件”(想不出更好的词),然后通过做一些疯狂的沙箱来阻止它读取你的应用程序的源代码?在另一个进程中对代码进行沙箱处理,使用内置/C 级别的操作系统沙箱功能(我知道 Linux 有这些功能)并执行一些 IPC 会更容易吗?
  • s/sourcerer/sorcerer?

标签: ruby metaprogramming


【解决方案1】:

TL;DR:仅限 Ruby 的解决方案只能使用 source_location,因此只需重新定义它以返回类似 ['/some/empty/file', 1] 的内容。对解释器的 C hack 不使用 source_location,但您可以通过阻止/白名单 require 和朋友来阻止对 C 扩展的任何使用。


首先,要能够执行 Ruby 脚本,您必须能够阅读它...

但回到问题。我知道 Sourcify 不使用任何神秘的方法,除了在 Proc 上的一个小方法和名为 source_location 的方法,它给出了文件名和行号是定义了方法/过程。我从经验中知道这种方法非常脆弱,需要编写某种解析器,并且有时只能在合法的情况下工作。因此,如果您在 B 中重新定义 source_location 以返回类似 /dev/null, line 0 的内容并让 Sourcify 抛出一个非 Ruby 源异常,那么 Sourcify 就已经失效了。

从 Pry 的 source 看来,Pry 使用了相同的 source_location 方法,所以一石二鸟。

现在,所有这些库都有另一种选择,即下拉到 C 并破解解释器以记录源代码。这几乎是完美无缺的。但是我们仍然可以通过一种非常简单的方式避免危险。有人可以在 A 中包含 Pry 方法源的所有代码。但是如果不需要 C 库,就不能包含内联 C/C 扩展。所以,解决方案很明显:重新定义 requirerequire_relativeload 要么不起作用,要么只允许某些库。这样,您就可以避开 C hack。

在 MRI 上,除了source_location 之外,没有其他方法(来自 Ruby 代码)可以做到这一点。就这样吧!

编辑:根据@banister 的说法,从 MRI 2.0+ 开始,有一个内置的 binding_of_caller 方法可以替换源位置。核爆这个。 ;)

警告:Ruby 不是一个很好的语言。如果您可以对它们进行元编程,那么除非您处于不同的进程中,否则它们可能可以对您进行元编程。

【讨论】:

  • 谢谢。我终于得到了一个中肯的答案。通过取消source_location,你认为我可以避免访问抽象语法树(AST)吗?
  • @sawa 你想做什么是非常奇怪的——但是搞乱 source_location 是不够的。 binding_of_caller 和 Binding#eval("FILE") 等的组合将返回与 source_location 大致相同的信息。任何有足够智慧的人都可以绕过你设置的任何障碍,真的,Ruby 不是这种语言,甚至不要尝试:)
  • @sawa:据我所知,您只能通过 C 扩展访问 AST,如果不控制 Ruby 环境或使用 require,则无法加载该扩展。
  • @banister:我以前从未听说过binding_of_caller。它是核心 MRI Ruby 的一部分吗?
  • @linuxios 它是 MRI 2.0+ 内置的,但在此之前它是 C 扩展:github.com/banister/binding_of_caller。即使没有 binding_of_caller,您仍然可以使用 set_trace_func 获取所需的信息并将绑定记录为代码跟踪
【解决方案2】:

如果您使用 John Mair(Pry 的制造商)的超棒“method_source”gem,这实际上非常简单: 该方法必须在 Ruby(不是 C)中实现,并且必须从文件(不是 irb)加载。

这是一个在 Rails 控制台中使用 method_source 显示方法源代码的示例:

  $ rails console
  > require 'method_source'

  # the following prints out the method code for #lookup in the Rails I18n Backend:

  > I18n::Backend::Simple.instance_method(:lookup).source.display
    def lookup(locale, key, scope = [], options = {})
      init_translations unless initialized?
      keys = I18n.normalize_keys(locale, key, scope, options[:separator])

      keys.inject(translations) do |result, _key|
        _key = _key.to_sym
        return nil unless result.is_a?(Hash) && result.has_key?(_key)
        result = result[_key]
        result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
        result
      end
    end
    => nil 

显示的 Ruby 代码需要来自已经加载的文件, (显然它必须是可读的),并且它需要是本机 Ruby 代码(这不适用于已编译/链接的库)。

另见:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-08-28
    • 2015-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-01
    相关资源
    最近更新 更多