LWN: kernel中GCC plugin的未来命运!
共 3829字,需浏览 8分钟
·
2021-04-22 02:22
关注了就能看到更多这么棒的文章哦~
The future of GCC plugins in the kernel
April 1, 2021
This article was contributed by Marta Rybczyńska
DeepL assisted translation
https://lwn.net/Articles/851090/
内核加固(kernel hardening)的流程可以在多方面受益于编译器支持。近年来,内核自我保护项目(Kernel Self Protection Project)将 grsecurity/PaX 中的一些 patch set 以 GCC plugin 的形式来给 kernel 提供了更多支持。LWN 早在 2017 年就报道过这个流程。最近的一次讨论中重点讲述了这样一个事实:使用 GCC plugin 方式也带来了一些缺点,一些开发者希望能看到后续用其他方式取代这些插件。
讨论来源于 Josh Poimboeuf 的报告,他在启用 GCC 插件编译 kernel tree 之外的 module 时遇到一个问题。在他的场景中,只要用来编译 module 的 GCC 版本与用来构建 kernel 的版本稍有不同时,编译就会失败。他附上了一个 patch,用来将 error 改为 warning,并禁用了受影响的 GCC 插件。稍后,Justin Forbes 解释了一下这个导致问题的环境是如何产生的:这发生于 Fedora 的持续集成系统中,该系统会先从建立一个当前的 toolchain snapshot 来开始。然后用新的 toolchain 来编译 kernel tree 之外的 module,而不会重新编译 kernel 本身。自从 GCC plugin 被启用后,所有带有 kernel tre 之外 module 的编译 job 都会失败。
将 error 改为 warning 的想法,在内核构建系统维护者 Masahiro Yamada 看来不是很赞同,他说:"我们有一个假设,就是 kernel tree 之内和之外的代码都应该是用同一个编译器编译的"。Poimboeuf 回应说,现实世界中的的情况并不是这样的。不过,其他内核开发者也同意 Yamada 的观点。Greg Kroah-Hartman 写道:
你难道没有注意到 include/linux/compiler.h 以及我们为各种不同版本的 gcc/clang/intel 等编译器所做的那些各不相同改动以及 workaround 吗?我们从来没有保证过用不同的编译器所编译出来的 kernel module 能够工作,我认为我们现在也不应该开始改变这个观点。
此外,Yamada 指出,在以前的讨论中,对内核及其 module 的编译会使用相同的编译器版本,这已被接受为一种潜在规定。由于内核开发者明确表示了不同意,所以讨论似乎已经结束。
The dislike for GCC plugins
然而,Poimboeuf 几天后带着另一个可以解决他的问题的方案回来时,讨论又重新开始了,这个方案是:每当 GCC 版本改变时,就重新编译所有的 plugin。这被 Yamada 拒绝了,他指出 Ubuntu 并没有 GCC 版本不匹配的问题,所以这个问题似乎是 Fedora 特有的。Linus Torvalds 也不同意这个提议,但是因为另一个原因。对他来说,当 GCC 版本改变时,并没有什么出于技术上的限制会需要人们必须重新编译更多的东西,但他对 GCC 插件的一些通用使用和设计方面表示了关注。在后续的邮件中,他用了比较强烈的语言解释了他的理由:
内核的 GCC 插件最终 会 消失。它们是一场无妄之灾。一直都是这样。我很抱歉我曾经合并了这些功能进 kernel。它不仅对于维护工作是个噩梦,而且它首先本身就是一个可怕的东西,带着很不好用的 interface。这个技术简直太糟糕了。
对于 Torvalds 来说,如果要正确实现这种插件,就应该是在中间表示法(IR,intermediate representation)这一层来进行修改,但是 GCC 的插件由于一些非技术性的原因而被设计成用其他方式实现了(主要是担心 non-free 插件,也就是非自由软件的插件,LWN 在 2008 年就报道过这一点)。他说,对插件感兴趣的人应该使用 Clang,因为它有一个干净的 IR,并且很容易允许在 IR 级别添加类似的检查。
GCC plugins and their Clang equivalents
然而,删除内核里 GCC 插件这件事,似乎在近期并不可能实现。Kees Cook 介绍了 GCC 插件的现状、它们在 Clang 中的等价功能、以及为什么其中一些插件会有个用户社区。GCC 插件提供的一些功能还不能用 Clang 来实现,其实无论如何,很多发行版提供商都没有使用 Clang 来构建内核。
目前内核支持以下插件(位于 scripts/gcc-plugins/):
cyc_complexity,计算函数的循环复杂性。它是两个最初的示例插件之一,可能并没有用户。
latent_entropy 增加了 CPU 执行时的熵。Cook 认为没有什么地方用到它,特别是在增加了抖动熵机制之后。后续没有计划在 Clang 中支持这个。
arm32 的各个任务堆栈保护器(per-task stack protector for arm32)为 32 位 ARM 平台提供了堆栈保护。Cook 说,目前 Clang 连 64 位系统都还没有对应的功能。
randstruct 随机地改变内核数据结构中各个字段的顺序,这只对那些只包含函数指针的结构生效,或者那些明确用__randomize_layout 标记过的结构生效。这个插件有两个版本:一个是完整版,一个是限制版。限制版只会改变同一 cacheline 中的多个成员之间的顺序,这降低了性能开销,但也降低了保护水平。Clang 中有一个版本已经被提出来了,但目前被搁置。Cook 指出,有安全意识的那些最终用户都倾向于启用这个插件,但发行版提供商通常不启用。
sancov(Cook 没有提到)会通过在每个 basic block 的开始插入一个对__sanitizer_cov_trace_pc()的调用,来帮助那些 fuzzing coverage 测试,也就是用来确定哪些代码块真正得到了利用。
stackleak 会追踪内核里的堆栈深度,以便在返回用户空间时可以用特定模式的内容来覆盖掉已使用过的堆栈。目前还没有计划支持 Clang。
structleak 初始化了那些可能会被传递给用户空间的结构。Clang 的 -ftrivial-auto-var-init=zero 选项就是类似实现。GCC 后续可能也会支持该选项。
最终的结果看来,这些插件还是继续存在一段时间的。
同时,讨论中也有一些积极的成果。在这个过程中我们意识到,这些插件对它们所对应的 GCC 版本非常敏感,每当 GCC 版本发生变化时,它们并没有被重新编译过。显然,自从插件第一次被添加以来就是这个情况了;尽管 Yamada 在讨论中拒绝了这个想法,但他还是解决了这个问题。作为 Poimboeuf 最初问题的解决方案,开发者们最终同意在内核和模块之间出现 GCC 版本不匹配时显示一个警告。这将由用户来决定这个差异可可以忽略的,还是需要重新编译内核。
GCC 版本不匹配的问题并不是 Poimboeuf 注意到的唯一一个问题;他还指出了插件构建系统对一个 gcc-plugin-devel 包(可选的)的依赖关系。即使用户的 GCC 版本与编译内核时使用的版本相同,但如果他们没有这个包,插件也会被悄悄地禁用,而内核编译会成功完成且没有任何警告。这个问题还没有得到进一步解决。
Conclusions
这次讨论涵盖了 GCC 插件的一些问题。这很可能意味着开发者在启用它们时应该小心谨慎。Poimboeuf 最初的问题利用这个 warning 就得到了某种程度的解决,这个 warning 可能开始在一些系统中报告出来。如果两个 GCC 版本很接近的话,用户可能会选择忽略这个 warning。在启用插件时,开发者应该注意先安装 gcc-plugin-devel,否则他们的 module 可能并没有按照你的要求来完成编译。
内核中的 GCC 插件的未来尚未决定。Clang 似乎是 hardening 工作的首选,这个方向也得到了 Torvalds 的鼓励,但是现有的各个 GCC 插件(除了一个以外)并没有 Clang 的对应物。看来,它们至少还会存在一段时间。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~