OpenAI公布了它如何借助“流行病学”的思路,查明ChatGPT背后数据基础设施中一连串神秘崩溃的原因[1]。调查发现了两个毫不相干、却恰好同时暴露的漏洞,其中一个已经在一个被广泛使用的开源库中潜藏了18年[1]。
看似“不可能”的崩溃
OpenAI的模型和智能体在推理时需要检索相关数据,因此依赖可扩展的数据基础设施[1]。其中一部分服务用C++编写,以最大化性能和内存效率。但由于C++缺乏内存安全性,写入错误的内存地址就可能引发崩溃[1]。
问题出在名为Rockset的服务上,它是ChatGPT数据基础设施的核心之一[1]。Rockset是一套面向搜索和实时分析的云原生数据系统,OpenAI已于2024年将其收购[2]。当ChatGPT检索对话内容或工作区知识库时,就会用到它[1]。
数月前,团队在Rockset内部观察到一些奇怪的崩溃[1]。一个正常的C++函数看似执行完毕,却试图返回到一个无效地址,于是内核(操作系统的核心)终止了程序[1]。有时函数保存的返回地址是NULL(空),有时栈指针(%rsp,指向栈当前位置的CPU寄存器)偏移了8个字节,这些都是普通应用程序不可能出现的损坏方式[1]。团队能想到的每一个假设都有强有力的反证,因此这个漏洞看上去“根本不可能存在”[1]。
从“医生”到“流行病学家”的思路转变
起初,团队把这些崩溃当作常规的调试问题:仔细检查少量核心转储(core dump,即崩溃瞬间内存状态的快照文件),提出假设并逐一排除[1]。但返回目标的搜索范围过于庞大,即便想从日志中对崩溃分类也不可靠,因为记录下来的调用栈本身就是损坏的[1]。
陷入僵局后,团队大幅调整了思路:从像医生那样深入诊断单个病例,转向像流行病学家那样审视整个群体、寻找规律[1]。漏洞是从某个特定版本开始出现的吗?它与某种硬件、某个地区或某个内核版本相关吗?会不会是多个不同的群体被误看成了同一种症状?带着这些问题,他们决定先收集高质量的数据[1]。
转机出现在他们让ChatGPT编写脚本、自动分析过去一年全部生产环境核心转储的时候[1]。脚本会下载每个核心文件的开头部分,取出寄存器,过滤掉已知的误报,再把崩溃自动标记为“返回NULL”“栈错位”或“其他”[1]。
真相是两个各自独立的漏洞
有了干净的数据集之后,相关性立刻显现出来[1]。原本以为是一个奇怪漏洞的现象,其实是两个各自独立的崩溃群体[1]。
“栈错位”型集中在一个地区,有明确的开始时间,而且从不发生在长时间运行的节点上[1]。追查结果表明,根源是一台硬件故障的物理机器,将该主机停用后崩溃便消失了[1]。换言之,这是一种“静默的硬件损坏”,即CPU根本没有正确完成运算[1]。
而“返回NULL”型则分散在多个集群和地区[1]。在把硬件因素剥离出来之后,团队发现剩下的崩溃全部发生在C++异常展开(exception unwinding,即抛出异常时把控制权转交给相应清理处理和捕获块的机制)过程中[1]。
在libunwind中沉睡18年的竞态条件
OpenAI的二进制文件链接了两个实现异常展开的库——GNU libunwind和libgcc,而实际被使用的是GNU libunwind[1]。
根源就在GNU libunwind的内部处理中[1]。它会在栈上构造一个保存目标寄存器状态的结构体,交给内部的 _Ux86_64_setcontext 处理;但这段处理在改写栈指针%rsp之后,才从该结构体中读取指令指针(指向下一条要执行指令的位置的值)[1]。一旦%rsp被改写,那个结构体就已经脱离了“正在使用的栈”[1]。
此时若有信号(打断正在运行程序的通知)到来,内核会在%rsp稍下方开辟处理区域,从而可能覆盖尚未读取的那个结构体[1]。结果就是目标指令指针被损坏成了NULL[1]。这个竞态条件(race condition,即结果取决于时序的缺陷)的危险窗口只有一条指令那么宽——在现代CPU上大约只有100皮秒(1皮秒为1万亿分之一秒)[1]。
这个漏洞自x86_64首个支持C++异常展开的版本起就已存在,18年多来一直无人察觉[1]。
为何直到现在才暴露
潜藏18年的漏洞如今才浮出水面,是因为Rockset同时满足了让该问题容易出现的三个条件[1]:作为过载控制手段而高频抛出异常;为精细测量CPU时间而频繁发送名为SIGUSR2的信号;以及今年以来该信号处理所占用的栈空间有所增加[1]。这三者的乘积终于越过了问题显现的临界值[1]。
作为应对,OpenAI把异常展开从GNU libunwind切换到了libgcc的实现[1],这在大规模环境下的锁竞争方面也是更优的选择[1]。同时,他们把可复现的代码和修复方案上报(upstream)给了GNU libunwind本身[1]。此外,他们还改进了信号处理,使得无需核心转储、仅凭日志即可检测复发,并在运维层面做了让故障节点更易被发现的调整[1]。
总结
OpenAI从这次调查中得到的最大教训是:与精巧的汇编分析或深厚的底层知识相比,“构建高质量的数据集”才是最重要的一步[1]。当两种现象被混进同一个故事里推理时始终无法解决的问题,一旦掌握了覆盖整个群体的准确数据,其结构便清晰可见[1]。可靠性不仅在于修复漏洞,更在于建立能把无解问题转化为可诊断问题的数据与流程——这对任何大规模系统的运维者而言,都是一个朴素却富有启发的案例。
来源:https://openai.com/index/core-dump-epidemiology-data-infrastructure-bug
