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的回应乍听起来似乎不错,AMD自己的态度也很乐观,但现实情况却并非如此。因为一旦CPU的微代码中存在bug,就只能由主板供应商发布新的系统BIOS才能实现修复。换句话说,用户并不能简单通过AMD提供的下载链接自行安装修复。
而且AMD方面虽然在今年7月就做出了回复,但截至到目前为止,AMD方面也仅仅只是发送了通知邮件而已。它并没有在其他渠道做出任何的相关提醒,不仅没有新闻稿,而且这封邮件看起来诚意略显不足,仿佛在说这只是一个能够在一两周之内轻松解决的小问题。
现在,已经过去三个月的今天,踩了坑的 Jim 表示:AMD的微代码bug问题非常严重。
存在 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很快就发现了问题。
今年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」),然后下载并运行以下测试:
you@ubuntu-live:~$ wget https://cdn.arstechnica.net/wp-content/uploads/2019/10/rdrand-test.zip
you@ubuntu-live:~$ unzip rdrand-test.zip
you@ubuntu-live:~$ cd rdrand-test
you@ubuntu-live:~$ ./amd-rdrand.bug
只有在足够广泛的数据集当中,连续20个0xFFFFFFFF才可能被视为有效的“随机”分组。但在大多数情况下,用户使用的数据集都没那么广泛。
经历了这样一个糟糕的周末,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下仍显示为可用,因此这两种引导选项均无法真正解决这个问题。
截止到本文发布,Jim的文章并没有进一步更新,我们也无从得知他是否已经等到了AMD所承诺的那个“具有真正意义的通用BIOS版本”。
其实,对于某些“极客” 来说,处理器的微代码早已不是什么神秘的东西,它可以被看成是一种处理器固件,从主板的BIOS中进行加载。而主板制造商则可以通过在新的BIOS版本中集成新的处理器微代码,支持新的处理器或是对一些所谓的bug进行修复。
但是真实情况我们也看到了,BIOS的更新并不容易,bug的修复难度也不低。不信,你看看隔壁的英特尔,它也同样正在忙着修补自家的漏洞呢。
领取专属 10元无门槛券
私享最新 技术干货