作者丨Jim Salter
译者丨核子可乐
策划丨张之栋、蔡芳芳
AMD 公司发布的 Ryzen 3000 处理器中,存在一项与随机数生成器相关的严重微代码 bug。也正是因为这个早已曝光数月却没有正真获得有效解决的“错误”,本篇文章的主人公 Jim Salter 度过了一个非常糟糕的周末。你能想象,一整天都在跟踪错误的问题,寻找 bug 原因的场景吗?
Ryzen 3000 的 RDRAND 函数(本应成为一款高质量的伪随机数生成器)每次都会返回 0xFFFFFFFF,修复过程也是相当煎熬。
上周末,Jim 怀着激动的心情坐在家中的工作间里,在他打算部署自己的第一台 Ryzen 3000 工作站时,却遗憾的发现,AMD 的一项微代码 bug(最初公布于今年 7 月,但直到现在仍广泛存在于各类系统当中)打破了这美好的幻想。
虽然 Jim 经过努力,让这套 Ryzen 3700X 系统恢复了正常,而且速度也相当惊人,但他不得不承认的是:AMD 的微代码 bug 仍然存在,而且现在缺少简单易行的修复方法。
事实上,在 Ryzen 3700X 发布后不久,AMD Ryzen 3000 的使用者就已经发现了这款崭新的 CPU 存在问题:Windows 用户无法顺利启动 Dstiny 2(原因是存在电源管理 bug,不过困扰 Jim 的倒不是这样的一个问题),Linux 用户甚至发现了自己经常无法正常启动系统。
福布斯杂志作者 Jason Evangelho 在今年 7 月的文章中报告了初步现象与总结意见,AMD 公司的代表则通过电子邮件发出了回复声明:
AMD 公司已经确认了引发问题的根本原因,并对 BIOS 程序进行了修复,旨在解决 Ryzen 3000 处理器无法运行某些 Linux 发行版以及 Destiny 2 的功能问题。我们已经将更新后的 BIOS 发放给各主板合作伙伴,希望消费者能够在未来几天之内使用到新的 BIOS。
而且 AMD 方面虽然在今年 7 月就做出了回复,但截至到目前为止,AMD 方面也仅仅只是发送了通知邮件而已。它并没有在其他渠道做出任何的相关提醒,不仅没有新闻稿,而且这封邮件看起来诚意略显不足,仿佛在说这只是一个能够在一两周之内轻松解决的小问题。
现在,已经过去三个月的今天,踩了坑的 Jim 表示:AMD 的微代码 bug 问题非常严重。
从 RDRAND 开始
存在 bug 的代码会对 RDRAND 指令做出错误的响应,而这个 bug 之所以非常严重,则需要从 RDRAND 说起。
事实上,从英特尔的 Broadwell 以及 AMD Zen 架构开始,现代 x86_64 CPU 必须内置高质量的板载随机数生成器(RNG),负责利用热“噪声”快速向具有内核级访问权限的用户更好的提供高熵伪随机数。而 RDRAND,正是触发随机数生成的指令。
AMD 的整个设计体系本应该非常安全,然而真实的情况却存在一些偏差。
首先是一条 CPUID 函数调用,用于检查 RDRAND 的可用性;此外,RDRAND 调用的返回值中还包含一条“进位”,该位会在 CPU RNG 无法生成足够的随机数时通知作为调用方的应用程序。遗憾的是,在安装修复补丁之前,Ryzen 3000 会对 CPUID 01H 调用回应“yes”,并将进位设置为“1”,表明其已经成功创建了有机高质量随机数……同时每一次的“随机”数都被赋值为了 0xFFFFFFFF。
只有在足够广泛的数据集当中,连续 20 个 0xFFFFFFFF 才可能被视为有效的“随机”分组。但在大多数情况下,我们使用的数据集都没那么广泛。
大家不要尝试使用 /dev/hwrng 来检查自己的设备是否受到 AMD 微代码 bug 的影响,是因为 /dev/hwrng 可能会从其他来源获取数据。在 Jim 的案例中,它倒确实是从 RDRAND 处获取数据,这让 Jim 很快就发现了问题。
RDRAND bug 的主要影响
今年 6 月,Ryzen 3000 中的 RDRAND bug 首次被发现时,Linux 用户开始大量发布报告,表示自己基于 Ryzen 3000 的操作系统无法正常启动。而引导失败正是由 systemd 使用 RDRAND 引发的;不过遗憾的是,这已经不是 systemd 第一次与 AMD CPU 上存在问题的随机数生成器发生冲突了。
早期一批 CPU 中存在的一项 bug,会导致某些 AMD 系统在从挂起状态恢复至正常工作状态后停止生成正确的“随机”数。而此次发生的新 bug,则会导致 Ryzen 3000 用户全程得不到任何合适的随机数。这两个问题,都导致了 systemd 在 Linux 操作系统中被长期锁定。因此今年 5 月,systemd 提交了一项修复程序,如果 systemd 从 RNG 得到的返回值为 0xFFFFFFFF,则该修复程序会转而使用备用 RNG 源。(但这种修复方式本身也有问题,因为从技术上讲 0xFFFFFFFF 是个完全有效的随机数——因此,只要经过足够长的时间,systemd 终究会在收到这个哪怕本应正确的随机数时,将其误判为错误状况,而后再直接切换至 RNG。)
Systemd 的补丁虽然做的很一般,但确实能够让操作系统顺利启动。只是它并不能真正从根本上处理问题,毕竟这种治标不治本的方式压根没有触及到随机数生成器那个层面。
Jim 在自己的系统上,用了一整个周末的时间,跟踪一个个错误问题。这样的一个过程让 Jim 非常抓狂。
Jim 首先怀疑问题来自系统上安装的全新 RX 590 显卡,然后就一次次更新操作系统发行版以及内核版本。但这些更新根本没用。
崭新的系统不断报出一个个讨厌的 BUG:软锁定——22 项错误彻底卡住了 PU#n,并迅速令系统整体陷入瘫痪。/var/log/syslog 中的调用信息也没什么指导意义,不过至少让 Jim 找到了第一条线索。
最终,在经历了一系列尝试、气急败坏的咒骂、无数杯咖啡和几口小酒之后,Jim 终于把 CPU 的频繁锁定与调用跟踪记录联系了起来。因为 Jim 发现,在每一次跟踪中,都会出现“WireGuard”的身影。
Jim 称:事实证明,WireGuard 依靠 RDRAND(如果可用)生成新的会话 ID。会话 ID 必须唯一,WireGuard 还要求其不是简单的连续整数,因此它会从 RDRAND 中获取伪随机值,将其与现有会话 ID 清单进行比较以确保不存在冲突,而后将该 ID 分配给新会话。
请注意这里的最后一句“确保不存在冲突”,它的意义在于,如果现有会话同新会话拥有相同的 ID,则 WireGuard 会向 RDRAND 请求另一个“随机”数,并再次检查其唯一性,依此类推。由于系统上的 RDRAND(以及任何未经更新的 Ryzen 3000 系统)始终返回 0xFFFFFFFF,因此这样的一个过程就会无限循环。内核代码发生无限循环当然不是什么好事,紧随其后的必然是系统崩溃与硬件重启。
但,问题并不是出在 WireGuard 身上!WireGuard 正确地检查了 RDRAND 是否可用、是否给出一个值,以及该值是否正确设置了进位。它的责任,就是确保不仅获得了一个值,而且确定该值确实是适当的随机值。然而即使这样,爱岗敬业的 WireGuard 还是让用户的系统陷入了瘫痪。
现代系统需要高质量的伪随机数来完成众多任务,其中“随机”的含义当然不可能是“始终返回 0xFFFFFFFF”。还有另一种直观的候选方法,也就是地址空间布局随机化(ASLR)。Windows 与 Linux 都只采用 RDRAND 作为随机机制中的一部分,旨在确保永远不会以相同的顺序加载同一段代码,从而减轻相关软件栈遭到破坏的风险。
修复问题,至少是发现问题
正如 AMD 公司代表在今年 7 月接受媒体采访时所言,真正的修复手段是对主板进行 BIOS 更新,同时确保 BIOS 当中包含针对 CPU 本体的微代码补丁。然而当 Jim 这样做时,却发现并没那么简单。
Jim 使用 dmidecode 程序检查了自己的 BIOS,发现日期是 2019 年 8 月 12 日,所以当看到华硕主板下载页面上更新到 9 月的 BIOS 时,Jim 非常惊喜。为了尽快结束这糟糕的一天,Jim 选择了立刻下载 BIOS 更新,并将其保存到 U 盘当中,重新启动系统,然后进入设置程序,一气呵成。
不过遗憾的是,在成功更新并再次重新启动之后,Jim 突然意识到自己犯了个错误——没错,华硕虽然列出了 BIOS 的更新日期,但其提供的实际版本与 Jim 之前使用的一样,都是 3.2.0。所以 Jim 的 CPU 仍然坚持认为 0xFFFFFFFF 是随机度最高、质量最好的生成数。
遭遇这一系列事情的 Jim ,火气一下就上来了:systemd 虽然悄悄解决了这个 bug,但大多数应用程序只是直接忽略,我怎么知道问题到底有没有正真获得修复?如果两年之后,事实证明 ASLR 根本无法提供真正的随机数,那我因此遇到的破坏性攻击又由谁来负责?
不过,好在几番冥思苦想后,Jim 突然发现了自己可完全使用 Linux 上的 hexdump 工具对内核设备 /dev/hwrng 进行全方位检查,以证明确实存在这一问题。
但 WireGuard 项目的 Jason Donenfeld 却警告称,/dev/hwrng 在某些系统上可能从其他来源处获取随机数,换句话说,当看到一大堆 FF 时肯定能够证明它有问题,但当看到一大堆有效的伪随机数时也不能说明它没问题。为此,Jason Donenfeld 还慷慨地分享了多种测试程序,可以帮助用户安全地直接访问 RDRAND。
如果大家使用的是 Linux,可以下载 rdrand-test.zip ,正常解压,然后在文件夹中直接运行。通过./amd-rdrandbug 命令,用户都能够查看自己的系统是不是真的存在这一特定 bug。./test-rdrand 能够输出 20 条 RDRAND 取值,如果大家在测试中发现了自己总是得到相同的值集,那么无论其看起来是否随机,都表明你慢慢的变成了这项 bug 的受害者!
如果大家使用的是 Windows,那还得额外做点工作。首先下载 Ubuntu 桌面安装程序,然后创建一个 Ubuntu 启动 U 盘。接下来,你可以启动 Ubuntu 启动盘的实时环境(点击「Try Ubuntu」),然后下载并运行以下测试:
只有在足够广泛的数据集当中,连续 20 个 0xFFFFFFFF 才可能被视为有效的“随机”分组。但在大多数情况下,用户使用的数据集都没那么广泛。
Jim 的总结
经历了这样一个糟糕的周末,Jim 最后总结了自己的收获:
随机数生成器 bug 是个相当严重的问题,但更加令人不安的是,在过去三个月的时间里,AMD 公司并没有就此问题进行积极的努力与强调。虽然总体而言,Ryzen 3000 确实是一款出色的 CPU 平台,新系统也给我留下了深刻的印象……但是,整整一个周末令人头痛的故障排查经历,让我对这套系统的整体安全性产生了严重的怀疑,我甚至不知道这个 bug 何时才能得到修复。
日前,我与 AMD 公司的代表取得了联系。对方回答了我关于硬件的问题,但没有给出具体解决方案。等有了最新消息,我会及时向大家汇报更新消息。
事件后续
事后,在 Jim 联系 AMD 就此事进行询问时,AMD 的一位代表询问了 Jim 的主板型号(Asrock Rack X470D4U),而后又联系了 Asrock。Asrock 团队提供了已经完成微代码修复的定制 BIOS 版本,但 Jim 认为这种只适用于个人的 BIOS 版本没有一点意义。
与此同时,有读者在 Jim 的文章评论区给出了一些别的解决办法及缓解措施,尽管好像并没什么用。
一种是在引导时将 nordrand 以参数形式传递给 GRUB。这种方法并不能处理问题,因为该方法实际上就是告知内核不要使用 RDRAND 指令,这往往无法影响该指令自身的实际可用性。指令仍然存在于系统当中,而一切通过 CPUID 检查 RDRAND 可用性的代码也仍然会受一定的影响。类似的 random.trust_cpu Linux 引导选项也是如此。这些缓解性措施都没能真正禁用 RDRAND,因此不能算是问题的理想解决方案。
另一种是安装 amd64-microcode 软件包。Jim 尝试后表示,结果还是不行。因为在默认情况下,amd64-microcode 与 intel-microcode 软件包被直接安装在 Ubuntu 19.10,以及所使用的系统当中,但这并不能解决 RDRAND 的故障。为此,Jim 再次联系了 AMD,要求对方代表检查该软件包的状态并确定是否有必要对其做出更新。
以下是 Jim 的验证:
目前最新 Ubuntu 10.10 系统上的 amd-64 微代码补丁,其仍然无法解决 RDRAND 的问题。
在设置 nordrand 或者 random-trust_cpu=0 之后,rdrand 在 /proc/cpuinfo 下仍显示为可用,因此这两种引导选项均无法真正解决这样的一个问题。
One more thing
截止到本文发布,Jim 的文章并没有进一步更新,我们也无从得知他是否已经等到了 AMD 所承诺的那个“具有真正意义的通用 BIOS 版本”。
其实,对于某些“极客” 来说,处理器的微代码早已不是什么神秘的东西,它可以被看成是一种处理器固件,从主板的 BIOS 中进行加载。而主板制造商则可以通过在新的 BIOS 版本中集成新的处理器微代码,支持新的处理器或是对一些所谓的 bug 进行修复。
但是真实情况我们也看到了,BIOS 的更新并不容易,bug 的修复难度也不低。不信,你看看隔壁的英特尔,它也同样正在忙着修补自家的漏洞呢。
https://arstechnica.com/gadgets/2019/10/how-a-months-old-amd-microcode-bug-destroyed-my-weekend/#lg=1&slide=1
点个在看少个 bug