一颗 CPU 的原子指令,一个打包死循环:LA664 丢失更新事件始末

一颗 CPU 的原子指令,一个打包死循环:LA664 丢失更新事件始末

💡 原文中文,约7500字,阅读约需18分钟。
📝

内容提要

2026年2月,王邈在龙架构服务器上为Debian打包normaliz时,测试因OpenMP原子累加计数丢失而陷入死循环。半年后借助AI定位到最小复现:LA664核心的原子加指令在特定条件下会丢失更新。触发需同时满足:线程位于不同物理核、对同一地址执行不带数据屏障的原子操作、且穿插向量化内存读取(如LASX的xvld)。该缺陷可影响引用计数,导致use-after-free。龙芯两周内给出固件修复,预计国庆前发布。

🔎

延伸解读

触发条件与影响范围

原子操作丢失更新需同时满足三个条件:线程运行在不同物理核、对同一地址执行不带数据屏障的原子操作、且至少一个线程在原子操作间穿插内存读取。内存读取可以是LASX的xvld,特定地址关系下普通标量读取也可能触发。受影响的原子指令包括amadd、amcas、ammax等,但ammax等因C标准无对应接口难以被编译器生成,amcas是LoongArch64 v1.1新增指令默认不生成,因此实际影响最大的是amadd,常用于引用计数,丢失可能导致use-after-free。

复现概率与关键因素

在3C6000/S上,双方都做LASX读时触发概率接近100%,双方都做LASX拷贝时amadd.d为67%,单侧LASX拷贝为53%。带数据屏障的amcas_db.d在所有测试中均为0%,说明数据屏障能避免问题。AOSC OS在2月能复现,8月却无法复现,原因是其Core 13版本glibc错误关闭了--enable-multi-arch,memcpy不再使用LASX加速;Debian正常启用,因此仍可复现。用GLIBC_TUNABLES=glibc.cpu.hwcaps=-LASX关闭LASX加速后,问题也不再

修复与临时规避

龙芯在收到反馈后两周内提供了测试固件,修复方式是将内部CSR MCSR24的bit 13置为1。实测单核性能无影响,多核性能略微下降。固件预计在国庆节前发布。若暂时不能更新固件,可在Linux内核中直接写入该bit,等效于完成固件修复。软件层面的规避需要编译器不再生成不带数据屏障的原子指令,或使用LL/SC循环,但已存在的二进制程序必须重新编译,代价高昂。

安全风险与利用难度

该缺陷很难被用于安全攻击。触发要求两个线程在同一进程内对同一对象的同一计数器并发执行relaxed原子操作,且至少一方执行向量化内存读取。这意味着攻击者与受害者必须处于同一进程内,无法跨越进程隔离,因此难以被单方面利用。不过,在Rust中,std::sync::Arc的引用计数和std::sync::mpsc的Sender克隆都使用不带数据屏障的amadd指令,构造的safe Rust程序使用Arc或mpsc可导致SIGABRT或堆破坏,表明use-after-free确实可能发生。

❓

Q&A

LA664 核心的原子指令丢失更新问题是什么?

LA664 核心(用于龙芯 3C6000/S 和 3A6000)的原子加等原子指令在特定条件下会丢失更新,即原子操作偶尔不原子,导致计数器值小于实际值。

触发 LA664 原子指令丢失更新需要满足哪些条件?

需要同时满足三个条件:线程运行在不同的物理核上;这些线程对同一地址进行不带数据屏障的原子操作;其中至少一个线程在原子操作之间穿插内存读取操作(如 LASX 的 xvld,或特定位置关系下的标量读取)。

这个问题是如何被发现的?

2026 年 2 月,王邈在龙架构服务器上为 Debian 打包 normaliz 时,测试因 OpenMP 原子累加计数丢失而陷入死循环。半年后借助 AI 定位到最小复现,发现是 CPU 原子加指令丢失更新。

LA664 原子指令丢失更新会导致什么安全风险?

该缺陷可影响引用计数,如果引用计数的增加操作丢失,计数会小于实际引用数,可能导致对象被提前释放,从而引发 use-after-free 或双重释放等内存安全问题。

龙芯是如何修复 LA664 原子指令丢失更新问题的?

龙芯通过将 MCSR24 的 bit 13 置为 1 来修复,设置后问题不再出现,性能损失很小。测试固件预计在国庆节(10 月 1 日)之前发布。

如果暂时不能更新固件,有什么临时规避方法?

可以在 Linux 内核里直接写入 MCSR24 的 bit 13,等效于完成固件修复,不需要等待主板固件更新。

🏷️

标签

➡️

继续阅读