Camlp5 紧急问题

升级到 OCaml 4.06.1 时 opam-repository 中的 camlp5 7.03 包裹问题

在 2017 年 10 月 26 日到 2018 年 2 月 17 日之间,opam-repository 中 camlp5 7.03 的 OPAM 包在某些情况下能够在 macOS 和其他默认情况下不阻止递归根目录删除的系统上触发 rm -rf /。本文包含有关如何识别您的 OPAM 安装是否受到影响以及您可以采取哪些措施来修复它的建议。

TL;DR 如果 rm --preserve-root 显示类似于 unrecognised option 而不是 missing operand 的消息,并且您正在运行 OPAM 1.2.2,请确保在将系统编译器升级到 OCaml 4.06.1 之前运行 opam update。如果您已经将 *系统编译器* 升级到 OCaml 4.06.1(例如,使用 Homebrew),请继续阅读。

识别您是否受到影响

如果您同时满足以下三个条件,您的系统将面临严重风险,可能会导致所有文件被删除。

  • 您的系统 rm 命令不支持 --preserve-root 默认选项(您可以通过运行 rm --preserve-root 并注意错误消息是指“无法识别的选项”还是“缺少操作数”来识别这一点)
  • 您的系统 OCaml 编译器是 4.06.1,并且您正在使用 OPAM 1.2.2
  • 您已在 2017 年 10 月 26 日之后与 opam-repository 同步,但尚未在 2018 年 2 月 18 日之后同步

如果您的系统受到影响,大多数 OPAM 命令将无法运行。特别是,如果 OPAM 询问

dra@bionic:~$ opam update
Your system compiler has been changed. Do you want to upgrade your OPAM installation ? [Y/n] n

您必须对这个问题回答“否”.

我编写了一个脚本,可以安全地识别您的系统是否受到影响,可以在 GitHub 上查看,也可以通过执行以下命令直接运行

$ curl -L https://raw.githubusercontent.com/dra27/opam/camlp5-detection/shell/opam-detect.sh | sh -

此脚本扫描由 $HOME 标识的目录,查找任何看起来像 OPAM 根目录的内容。几乎所有用户在 ~/.opam 中都会有一个 OPAM 根目录,如果您不知道如何使用多个根目录运行 OPAM,那么您可能不需要担心其他任何根目录!

该脚本可能会显示各种消息。如果您的系统包含至少一个受影响的 OPAM 1.2 根目录,您将看到类似以下内容的输出

dra@bionic:~/opam$ shell/opam-detect.sh 
opam 1.2.2 found
Scanning /home/dra for opam roots...
opam 1.2 root found in /home/dra/.opam
camlp5 is faulty AND installed AND the system compiler is OCaml 4.06.1

THIS ROOT CANNOT BE UPDATED OR UPGRADED. DO NOT ALLOW OPAM TO UPGRADE THE SYSTEM
COMPILER. DOING SO WILL ATTEMPT TO ERASE YOUR MACHINE
Please see https://github.com/ocaml/opam/issues/3322 for more information

修复它

在所有情况下,一种解决方法是安装 opam 2 的最新发布候选版本,并将您的 OPAM 1.2 根目录升级到 opam 2 格式。升级将阻止 OPAM 1.2.2 读取根目录。如果您收到上述消息并选择升级到 opam 2(升级根目录最简单的方法是在安装 opam 2 后运行 opam list),然后再次运行 opam-detect.sh 脚本。和之前一样,**如果您被询问“您的系统编译器已更改。”,请不要回答“是”。**

如果您不想升级到 opam 2,而且有很多充分的理由不想这样做,还有另外两种选择。最简单的方法是将系统编译器降级回 4.06.0(或更早版本)。然后,您可以再次运行 opam-detect.sh 并检查错误消息。只要消息不再是上述消息,您就可以运行 opam update 来更新切换上的存储库元数据。然后,您可以再次将系统编译器升级回 OCaml 4.06.1。为了绝对确保,您可以再次运行 opam-detect.sh 脚本,并假设消息仍然不是上述消息,然后您可以允许 OPAM 1.2.2 升级您的系统切换。*这是推荐的操作步骤。*

最后一个选择是您可以手动编辑 opam 根目录,并欺骗 opam 相信 camlp5 包不再安装。这可以通过编辑根目录中的 system/installed 文件并删除**两个**camlp5 行和任何依赖 camlp5 的包(例如,coq)来完成。您无法在此时使用 opam 来确定依赖关系,因此您需要使用在线索引来检查依赖包。如果您未能删除所有依赖 camlp5 的包,OPAM 将显示类似以下内容的安装提示

dra@bionic:~$ opam update
Your system compiler has been changed. Do you want to upgrade your OPAM installation ? [Y/n] y

=-=- Upgrading system -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
The following actions will be performed:
  ∗  install camlp5        7.03               [required by coq]
  ∗  install conf-m4       1    
  ∗  install base-threads  base 
  ∗  install base-unix     base 
  ∗  install base-bigarray base 
  ∗  install ocamlfind     1.7.3
  ∗  install num           1.1  
  ∗  install coq           8.7.0
===== ∗  8 =====
Do you want to continue ? [Y/n] 

如果发生这种情况,请回答“否”,但此时您的系统切换将被清空所有包(当然,您现在可以安全地运行 opam update)。当然,您可以在尝试此操作之前备份 OPAM 根目录。升级后,您可以运行 opam update 并重新安装丢失的包。**不建议采取此措施,因为 opam-detect.sh 脚本将不再提供帮助。强烈建议您在尝试此解决方案之前备份您的文件。**

问题

2017 年 10 月 26 日,PR#10523 被合并,该 PR 包含了 camlp5 7.03。这是发布到 opam 的第一个支持 OCaml 4.06.0 的 camlp5 版本。

不幸的是,这也是第一个包含执行 make uninstallremove 部分的 opam 包版本。该包还包含一个不正确的 available 约束 - 它应该只允许 4.06 分支中的 OCaml 4.06.0,但给定的约束允许所有版本。

camlp5 的 configure 脚本负责用所有通常的配置设置(包括 PREFIX 等)编写 config/Makefile。该脚本包含一个针对 OCaml 的版本检查,如果版本不受支持,则会失败。不幸的是,即使它失败,它也会编写一个部分 config/Makefile 来启用一些开发目标。遗憾的是,这使得 rm -rf "$(DESTDIR)$(LIBDIR)/$(CAMLP5N)" 命令出现在 uninstall 目标中,所有三个变量都没有定义,导致了肯定不希望的 rm -rf /

自 2003 年 11 月(在 5.1.0 版本中)以来,GNU coreutils 的用户默认情况下都设置了 --preserve-root 选项,这使得 rm -rf / 会引发错误。不幸的是,macOS 默认情况下不使用 GNU coreutils。

在 OPAM 1.2 之前,opam 文件中的 buildinstall 部分是合并在一起的。因此,如果 build 失败,OPAM 将静默执行 remove 命令,以清理可能已经发生的任何部分安装。尽管 OPAM 1.2 建议将 buildinstall 命令分开,但这并非强制性,因此它保留了“静默删除”行为。opam 2 要求进行分离(并且,如果沙箱可用,现在强制执行它)。opam 2 还期望 remove 命令在干净的源代码树中运行,对于此 camlp5 案例,这意味着**opam 2 用户不受此问题的影响**。

OCaml 4.06.1 于 2018 年 2 月 16 日在 PR#11433 中被添加到 opam-repository。在接下来的 48 小时内,有人注意到 camlp5 包正在尝试运行 rm -rf /(参见 问题 #11440),该包在 2018 年 2 月 18 日的 PR#11443 中被修复。不幸的是,当时没有意识到 GNU coreutils 保护的重要性,并且还假设问题只会出现在以下情况下:如果有人不幸地在 OCaml 4.06.1 发布到 opam-repository 和 camlp5 7.03 在 opam-repository 中被修复之间(即 2018 年 2 月 16 日至 18 日)更新了 OPAM,并且正是基于此,OPAM PR#3231 被认为非常不幸。

然而,真正的问题是系统编译器升级到 4.06.1,这在该问题中没有被注意到,但在 问题 #3316 中被正确地识别出来。不幸的是,这为问题提供了更广泛的时间窗口 - 如果您在 2017 年 10 月 26 日到 2018 年 2 月 18 日之间运行了 opam update,并且此后一直没有运行它,那么如果您在没有先运行 opam update 的情况下将系统编译器更新到 OCaml 4.06.1,您的系统将有风险。

如果系统编译器发生更改,OPAM 1.2.2 在几乎所有命令(包括 opam update)中都会首先询问是否要升级 system 切换。此步骤是强制性的,可以阻止进一步安全使用 OPAM 1.2.2。

未来缓解

由于对 opam 2 处理包安装方式的更改,opam 2 并未受到这种情况的影响,但 opam 2 的首席开发人员 @AltGr 承认这更多是运气而不是判断。但是,opam 2 的第二个发布候选版本包括对 Linux 和 macOS 上沙箱的强制支持。沙箱包构建和安装将保护 opam 2 免受此类问题的困扰,因为故障的构建系统将无法操作其构建目录之外的文件,或者在安装期间切换根目录。