如何用一行命令让 Systemd 崩溃

来源:开源中国社区 作者:zhangkai
  

以任何一个用户运行下面的命令行,都会让systemd崩溃:

1
NOTIFY_SOCKET=/run/systemd/notify systemd-notify ""

运行这行命令后,PID 1将在系统调用pause这里挂起,再也不能启动或停止后台程序。inetd-style服务不再接受任何连接。你也不能干净地重启系统,系统一般会感觉不稳定(例如,从systemd与登录系统集成后,ssh和su处于30秒的假死状态)。短的可以写在一条Tweet里的命令就会导致以上所有情况。 

这个bug非常普通。上述的systemd-notify 命令向位于/run/systemd/notify的全局UNIX域socket发送一个长度为零的消息。PID 1接收到这条消息,并且断言(消息长度大于0)判断为错误。尽管这个这个bug非常普通,但却非常严重,它允许任何本地用户很轻松的向关键的系统组件通过拒绝服务的方式发起攻击。

立即有人提出关于这个bug的疑问:是什么样的质量保证过程能使如此简单的bug生存了两年以上(这个bug在systemd 209就被引入了)。难道这个空串不是一个很明显的测试用例吗?有人希望最重要的用户空间进程PID 1能得到更好的质量保证。不幸的是,按照浏览systemd提交日志的情况来看,PID1的崩溃似乎并不罕见,比如:

Systemd出问题远不止是一个bug。Systemd的设计本来就有缺陷,要写出没有bug的软件及其困难。即使是优秀的程序员也不可避免在systemd这样的规模和复杂程度的项目中带入缺陷。然而,一旦优秀的程序员意识到了编写没有bug的软件的困难性,也就懂得了另外一种重要性,即在设计软件时降低bug发生的可能性或至少减少bug带来的影响。但是systemd的开发者没理解这一点,他们选择了用大量不必要的复杂内容填入到PID 1,并作为root用户运行,同时还采用了一种内存不安全的语言编写。

一定的复杂性是意料之中的,因为systemd提供了一些有用和有说服力的功能(尽管这些功能不是由systemd发明,但由它推广的)。systemd是否取得了功能性与复杂性之间的适当权衡,关于这一点还存在争议。但可以确认的是,systemd的复杂性不应该由PID 1来解决。正如 Rich Felker 所解释的, PID 1的唯一工作就是执行实际的init系统,并接管僵尸进程。除此之外,实际的init系统即使作为非PID 1进程运行时,也应以模块化的方式构造,使得某个高风险部件的故障不会影响更关键的部件。例如,守护管理相关代码出现的故障不应影响系统的正常重启。

特别的,对从不可信来源接收信息的任何代码都应在专用的进程中并作为非特权用户运行,比如systemd-notify 。无特权的进程在将消息向特权进程传递之前应分析并验证信息。这就是所谓的特权分离,这已在安全感知软件中良好地实践了十多年。相比之下,Systemd解析来自不受信任来源的消息,由C写成,并在PID 1以root身份运行。如果你觉得systemd不需要特权分离,因为它只解析来自本地用户的消息,但请记住,在互联网时代,本地攻击往往会获得远程载体。 考虑一下 Shellshock, 还有今年的systemd会议中名为 "从Web浏览器谈到systemd"的演示。

    Systemd对待安全问题上不犯错的态度在其他地方可以看出,比如PID 1的 main()函数中的这段代码:

    /* Disable the umask logic */
if (getpid() == 1)
        umask(0);

    设置umask为0意味着,默认情况下systemd创建的任何文件都是全局可读可写的。Systemd定义了一个宏RUN_WITH_UMASK,这个宏被用来在systemd需要创建一个不同权限的文件时,临时设置一个更加严格的umask。因此,当创建文件时忘记修改umask将导致文件无法工作,这被称为失效安全设计。反而systemd是故障时打开的,因此,忘记修改umask(这已经发生过两次了)创建了一个文件,却是一个潜在的安全隐患。

Linux的生态系统在编写安全,健壮的软件方面落后于其他操作系统.
Microsoft强化Windows,Apple发展IOS,与此同时开源软件却变得自满,发展缓慢.
但是,我认为改进即将到来.心脏滴血和破壳漏洞唤醒了人们对加强开源软件安全性的意识,并直接导致对开源软件审查更加严格.
在编写系统类型的软件上,Go 和 Rust相比传统的C语言更具安全性。
Go 和 Rust 安全的语言编写,系统类型的软件,传统上使用C书写
Systemd是非常危险的,不只因为引入了成千上万缺乏关于长期安全考虑(例如权限分离或故障安全设计)的C代码,还因为该设置本身是不可替代的。
Systemd远远超过一个init系统:
它成为一个次级操作系统核心,提供了一个日子服务,一个设备管理器,一个容器管理器,一个登陆管理器,一个DHCP客户端,一个DNS解析器,和一个NTP客户端。
这些服务是相互依赖的并且提供为其他应用提供非标准的接口。
这造成systemd的组件很难被替代,这也将阻碍未来采用更安全的替代方案。
 

考虑到systemd的DNS 解析器。DNS是一个辅助的,安全敏感的协议。在2014,8月份,Lennart Poettering声称"systemd-resolved,现在是一个完全缓存的DNS和LLMNR(链路本地多播名称解析)桩解析器"。实际上,systemd-resolved未实现任何有记录的最佳实践的方式来预防DNS缓存污染。它容易受到Dan Kaminsky的缓存中毒攻击,在2008年其他DNS服务经过了大规模协调一致的响应处理后已经修复(djbdns 在1999年已经修复这个问题)。尽管systemd 不强制你使用 systemd-resolved,但是它在DBUS暴露了一个非标准的接口,而他们鼓励应用程序使用DBUS来替代在53端口的标准的DNS协议代。如果应用程序遵循这个建议,这将造成不可能使用更安全地DNS解析器替换systemd-resolved,除非该DNS解析器选择效仿systemd提供有的DBUS API。
 

停止这种做法还不是太晚。虽然大多数 Linux 发行版现在使用 systemd 以初始化它们的系统,init 是针对systemd 的进程,因其可替换性很差。但对于其他systemd试图取代的服务,并不也是这样,例如,网络管理,DNS 和 NTP 等。在现有的实现中,Systemd 提供了一些引人注目的特性,但也存在大量的风险。如果你是一个系统管理员,会抵制更换成现有的服务,而会转而更换更安全的服务。如果你是一个应用开发者,就不会使用 systemd 专有的接口。未来肯定会有更好的替换选择,肯定会比我们现在替换安全得多的选项。如果在未来,使得创新成为可能的模块化和标准化没有被systemd摧,那么,到时候再去采用它也是可以的。

本文转自:开源中国社区 [http://www.oschina.net]
本文标题:如何用一行命令让 Systemd 崩溃
本文地址:
https://www.oschina.net/translate/how-to-crash-systemd-in-one-tweet
参与翻译:
shirleywong, Chihell, 混元归一, 无若

英文原文:How to Crash Systemd in One Tweet

 


时间:2016-10-06 08:11 来源:开源中国社区 作者:zhangkai 原文链接

好文,顶一下
(0)
0%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量