'Lo' 崩溃:互联网的第一份 bug 报告
为什么 Charley Kline 敲了三个字母之后 SRI 的计算机就崩了?到底是什么坏了?一个自动补全功能的缓冲区溢出如何成了互联网的起源故事?
TL;DR
1969 年 10 月 29 日,UCLA 的研究生 Charley Kline 在 ARPANET 第一条能用的链路上敲了三个字母——L、O、G——对端的机器就崩了。原因不是网络,是 SRI 那边一个聪明的自动补全功能,试图送回比它输出缓冲区能装下更多的字节。修复花了大约一小时。当晚晚些时候,完整的 LOGIN 过去了。但没人记得那次成功,所有人都记得”Lo”。
背景
那是 1969 年 10 月 29 日的晚上。两台 Interface Message Processor 已经装好——一台在 9 月初装到 UCLA,一台在 10 月初装到 Menlo Park 的 SRI。它们之间的跨国链路已经通了大约三周。还没有人真正用过它。
在 UCLA,Charley Kline 21 岁,是 Leonard Kleinrock 的 Network Measurement Center 的程序员。他坐在一台接到 SIGMA 7 大型机的 teletype 前。往北 350 英里,在 SRI 的 Augmentation Research Center,Bill Duvall 坐在一台运行 Genie 分时系统的 SDS 940 前。
目标很朴素:远程登录。Kline 在 UCLA 的终端敲命令,字符经过两台 IMP 送到 SRI,SRI 处理之后把响应送回来。一次普通的交互会话——只是键盘和计算机在不同的州。
两个程序员通过电话协调。
三次按键
SRI 的登录提示让你敲 LOGIN。Bill Duvall 写了主机端软件,还加了个体验小改进:命令补全。如果你敲了一个命令的唯一前缀,系统会把剩下的替你填完。敲 LO 就足以无歧义地标识 LOGIN,于是 SRI 会回送剩下的字符。
Kline 敲了 L。SRI 回送 L。
Kline 敲了 O。SRI 回送 O。
Kline 敲了 G。SRI 崩了。
电话里,Duvall 说系统挂了。Kline 看着自己的终端。它最后收到的字符是 SRI 刚开始发送的自动补全的一部分——然后什么都没了。
有史以来第一条在 ARPANET 上送达的消息,是两个字母的片段 Lo。
到底是什么坏了
命令补全听起来无害。失败在于 SRI 如何缓冲它向外发送的响应。
当 Kline 敲 G 的时候,SRI 识别 LOG 是 LOGIN 的前缀,把剩下的字符——I、N,加上提示符通常的结尾——塞进一个发送缓冲区要回送出去。这个缓冲区被设计成只够装一个字符的响应,因为正常情况就是:一次一个字符地 echo。突然被要求装一整个多字符的补全串,就溢出了。1969 年在一台跑研究用 OS 的 SDS 940 上,系统缓冲区里一次未检查的溢出,就足以把机器放倒。
没有现代意义上的内存保护。echo 例程的缓冲区和操作系统其余工作区之间没有隔离。系统之所以崩溃,不是因为网络不可靠——字节完全照原样到达。它崩溃是因为一个本地功能有个本地 bug,第一次被从机器外面送进来的流量暴露出来。
UCLA SIGMA 7 SRI SDS 940
│ │
│────── L ────────────────────>│
│<───── L ──────────────────────│ echo
│ │
│────── O ────────────────────>│
│<───── O ──────────────────────│ echo
│ │
│────── G ────────────────────>│
│ │ → 触发自动补全
│ │ → 缓冲区排入 "IN<CR><LF>..."
│ │ → 溢出 → 系统停机
│ 💥 │
Kline 发出了两个字符的负载,就触发了用今天的说法可以叫做”echo 路径上缓冲区溢出导致的远程拒绝服务攻击”。完全是无意的。
一小时后
Duvall 把系统重搭起来了。补丁的细节在随意的历史叙述里已经丢了——他很可能把缓冲区调大了,或者对远程终端禁用了补全,或者两者都做了。大约晚上 10:30,他们又试了一次。
Kline 敲 L、O、G、I、N。五个字符全部回送。SDS 940 提示输入用户名。Kline 输入一个。系统让他登录了。
历史上第一次完全成功的远程登录发生在那个晚上。它没有产生截图,没有新闻报道,也没有传奇。被写下来的——在 Kleinrock 的实验室日志里,就一行干巴巴的字——是一小时前坏掉的那件事。
22:30 Talked to SRI Host to Host
没有提 Lo。没有提崩溃。过了很多年,这个故事才根据记忆被重构出来,写成今天这种著名的叙述。
为什么这成了起源故事
工程师喜欢以失败为起源的故事。崩溃可以传播,成功的登录传不开。“Lo” 的故事以缩影的形式包含了接下来五十年互联网工程要艰辛总结出来的一切:
- 负载下 bug 变成了功能。 自动补全对每一个本地用户都好使。它第一次对远程呼叫者运行时就失败了,因为关于时序和负载大小的假设在网络上并不成立。
- 网络是最不可靠的部分——直到它不是。 IMP 把自己的工作做得完美。崩溃在主机里,在比网络还早的代码里。
- 每个协议都先在边缘情况下失败。 一个多出来的字节越过了缓冲区边界。这个模式——Morris Worm 的
fingerd、Heartbleed、标题带 “improper bounds checking” 的每一个 CVE——都是同一种失败模式,相隔五十年、部署规模相差九个零。 - 你不真用一个网络是没法测试它的。 ARPANET 已经”上线”好几周了。直到他们真的跑了真实流量,才知道它是坏的。
ARPANET 那篇文章把 Lo 当成一个有趣的轶事。它也是一个 bug tracker 里的第一条记录——那个 tracker 现在有几十亿张未关闭的工单。一小时后登录成功了,它揭示的那个模式,今天仍在起作用。