单车棚

  下载本文的PDF版本 PDF

最昂贵的一个字节的错误

肯、丹尼斯和布莱恩选择以NUL结尾的文本字符串是否是错误的?


保罗-亨宁·坎普


IT技术既驱动又实现了现代西方式经济。因此,我们经常看到关于与IT错误相关的惊人巨额资金的新闻标题。哪个IT或CS决策导致了最昂贵的错误?

不久前,相当多的评论员一直在大谈索尼PlayStation Network的麻烦所带来的财务影响,但像这样的事件在这里不算数。在我上学的时候,我曾与《吉尼斯世界纪录大全》的一位核查员交谈,他解释说,要成为“真正的纪录”,它不能仅仅是一个意外;必须有直接的因果关系,从人的意图开始(即,我们把26名高中生塞进了我们音乐老师的大众甲壳虫汽车并关上了车门)。

索尼(可能)并非有意要看看在最少关注安全的情况下能造成多大的混乱,因此这种以及其他此类虚假节约的例子不符合条件。另一个候选案例可能是IBM选择比尔·盖茨而不是加里·基尔代尔来为其个人电脑提供操作系统。这一决定的损害仍在以惊人的速度累积,StuxNet病毒和OOXML对ISO标准化过程的歪曲是损害蔓延范围的典型例子。但这实际上并不是一个IT或CS决策。这是一个商业决策,就历史所能揭示的而言,其中心是基尔代尔决定不接受IBM的保密要求。

一个更好的例子是MS-DOS决定发明自己的目录/文件名分隔符,使用反斜杠(\)而不是Unix使用的正斜杠(/)或DEC在其操作系统中使用的句点。然而,除了实际损害相对较小之外,这也不能算作一个好例子,因为它不是一个真正的选择真正偏好的决定。IBM已决定将斜杠用于命令标志,从而排除了Unix作为先例,并且句点用于文件名和文件扩展名之间,使其无法效仿DEC的例子。

太空探索历史提供了一系列广为人知且代价高昂的错误,但有趣的是,我没有在那里找到任何有效的候选案例。Fortran语法错误和航天飞机计算机同步错误由于缺乏意图而不符合条件。在一个项目的一部分中使用英制单位,而在另一部分中使用公制单位,是一种与CS或IT无关的“管理层的随机行为”。

我能想到的最佳候选案例是C/Unix/Posix使用NUL结尾的文本字符串。选择非常简单:C语言应该将字符串表示为地址 + 长度元组,还是仅仅作为带有标记结尾的魔术字符(NUL)的地址?这是肯·汤普森、丹尼斯·里奇和布莱恩·克尼汉这充满活力的三人组在1970年代早期某一天必须做出的决定,他们完全可以自由选择任何一种方式。我没有找到任何关于该决定的记录,我承认这是其候选资格的一个弱点:我没有证据证明这是一个有意识的决定。

然而,就我从研究中可以确定的,地址 + 长度格式在当时的大多数编程语言中是首选的,而地址 + 魔术标记格式主要用于汇编程序中。由于C语言是从汇编语言发展到可移植高级语言,我很难相信肯、丹尼斯和布莱恩对此根本没有考虑过。

使用地址 + 长度格式将比地址 + 魔术标记格式多花费一个字节的开销,而他们的PDP计算机的内核内存有限。换句话说,这可能是一个非常典型和合理的IT或CS决策,就像我们每天做出的许多类似决策一样;但这个决策却产生了相当非典型的经济后果。


硬件开发成本

最初,Unix对硬件和指令集设计影响甚微。提供字符串操作指令的CPU——例如,Z-80和DEC VAX——以更广泛的地址+长度模型来实现。然而,一旦Unix和C语言获得普及,以终止符结尾的字符串就作为优化目标出现在雷达上,CPU设计者开始添加指令来处理它们。一个例子是IBM在1992年添加到基于ES/9000 520处理器的逻辑字符串辅助指令1

向CPU添加指令并不便宜,而且只有当有切实可量化的货币理由这样做时才会发生。


性能成本

IBM添加了操作NUL结尾字符串的指令,因为其客户花费了昂贵的CPU周期来处理此类字符串。然而,这条信息并没有告诉我们,如果使用ptr+len格式,是否会需要更少的CPU周期。

稍微思考一下虚拟内存系统就可以为我们解决这个问题。优化已知长度的字节字符串的移动可以充分利用内存总线和缓存线的宽度,而无需访问不属于源字符串或目标字符串的内存位置。

一个例子是FreeBSD的libc,其中bcopy(3)/memcpy(3)实现将尽可能多地以“unsigned long”的块(通常为32或64位)移动数据,然后用字节宽的操作“清理任何尾随字节”,正如注释所述2

但是,如果源字符串是以NUL结尾的,则尝试以大于字节的单位访问它可能会冒着读取NUL之后字符的风险。如果NUL字符是VM(虚拟内存)页的最后一个字节,并且未定义下一个VM页,则这将导致进程因不必要的“页面不存在”错误而终止。

当然,可以编写代码来检测该极端情况,然后再启用优化的代码路径,但这会为所有字符串移动增加相对较高的固定成本,仅仅是为了捕获这个不太可能的极端情况——无论如何都不是有利可图的权衡。

如果我们有字符串的带外知识,情况就不同了。


编译器开发成本

编译器通常了解字符串的一件事是其长度,特别是如果它是常量字符串。这允许编译器发出对更快的memcpy(3)的调用,即使程序员在源代码中使用了strcpy(3)

编译器进行的更深层次的代码检查允许更高级的优化,其中一些非常巧妙,但前提是有人编写了代码供编译器执行。编译器优化的开发在历史上既不容易也不便宜,但显然苹果公司希望这种情况会随着LLVM(底层虚拟机)的出现而改变,在LLVM中,优化器似乎大量涌现。

重型编译器优化的缺点——特别是那些从整体角度看待源代码并在大规模操作中重新排列源代码的优化——是程序员必须非常小心,确保源代码精确地指定了他的或她的完整意图。一位曾在Convex C3800系列超级计算机上使用编译器的程序员讲述了他的经验,即“必须像对待我前妻的律师一样对待编译器进行编程。”


安全成本

即使您的编译器没有敌意,源代码也应该编写为能够抵御攻击,而NUL结尾的字符串在这方面有着糟糕的记录。诸如gets(3)之类的彻底的安全灾难,“假设缓冲区足够大”,是一个“我们相对可控”的问题3

然而,将其控制住需要对编译器进行添加,如果调用gets(3)函数,编译器会抱怨。尽管已经关注了15年,但字符串缓冲区溢出和欠载仍然是犯罪分子的首选攻击媒介,而且往往非常有效。

在各个层面上都增加了对这些风险的缓解措施。CPU的内存管理硬件中添加了长期缺失的不可执行位;操作系统和编译器增加了地址空间随机化,通常以高昂的性能为代价;程序静态和动态分析耗费了无数小时,试图找出拜占庭式的诊断是真正的错误还是聪明的编程。

然而,如果索尼的麻烦被揭露是从缓冲区溢出或错误的NUL终止假设开始的,绝对没有人会感到惊讶。


Slashdot轰动效应预防部分

我们从错误中学习,因此在有人为本文提出一个朗朗上口但完全误导性的互联网标题之前,让我在此声明,肯、丹尼斯和布莱恩绝对不可能预见到他们大约30年前的选择的全部后果,而且他们当时已经声明不承担任何担保责任。据我所知,至少过了15年才有人意识到为什么这个微妙的决定是个坏主意,而且我自己的IT决策中,很少有(如果有的话)能持续这么长时间。

换句话说,肯、丹尼斯和布莱恩做对了。


但这并没有解决问题

对于很多人来说,C语言是一种死语言,而${lang}是未来的语言,${lang}的值会不断变化。实际情况是,今天所有其他语言都直接或间接地位于Posix API和C语言的NUL结尾字符串之上。

当您的Java、Python、Ruby或Haskell程序打开文件时,其运行时环境会将文件名作为NUL结尾的字符串传递给open(3),当它将queue.acm.org解析为IP地址时,它会将主机名作为NUL结尾的字符串传递给getaddrinfo(3)。只要您一直这样做,当您在PDP/11上运行程序时,您将保留所有优势,如果您在任何其他设备上运行它们,您将保留所有劣势。

我可以在这里写一份稻草人API提案,建议表示形式、操作和错误处理策略,我非常确定这将是一个完美地浪费美好下午的方式。经验表明,此类提案毫无进展,因为与PDP/11的向后兼容性和已编写的有限数量的程序比以高效且安全的方式编写未来可能无限数量的程序的能力更重要。

因此,肯、丹尼斯和布莱恩的决定的成本将不断累积,就像几个世纪以来几乎掩埋了古罗马纪念碑的灰尘一样。


参考文献

1. Computer Business Review. 1992. Partitioning and Escon enhancements for top-end ES/9000s; http://www.cbronline.com/news/ibm_announcements_71.

2. ViewVC. 2007. Contents of /head/lib/libc/string/bcopy.c; http://svnweb.freebsd.org/base/head/lib/libc/string/bcopy.c?view=markup.

3. Wikipedia. 2011. Lifeboat sketch; http://en.wikipedia.org/wiki/Lifeboat_sketch.


喜欢它,讨厌它?请告诉我们

[email protected]


保罗-亨宁·坎普 ([email protected]) 已经编写计算机程序26年,并且是bikeshed.org背后的灵感来源。他的软件已被广泛采用为开源和商业产品中的“幕后”构建块。他最近的项目是Varnish HTTP加速器,它被用于加速大型网站,如Facebook。

© 2011 1542-7730/11/0700 $10.00

acmqueue

最初发表于Queue vol. 9, no. 7
数字图书馆中评论本文





更多相关文章

凯瑟琳·海耶斯,大卫·马龙 - 质疑非加密哈希函数的评估标准
虽然加密和非加密哈希函数无处不在,但在它们的设计方式上似乎存在差距。针对各种安全需求,存在许多用于加密哈希的标准,但在非加密方面,存在一定程度的民间传统,尽管哈希函数历史悠久,但尚未得到充分探索。虽然针对真实世界数据集的均匀分布很有意义,但在面对具有特定模式的数据集时,这可能是一个挑战。


妮可·福斯格伦,埃里尼·卡利亚姆瓦库,艾比·诺达,米凯拉·格雷勒,布莱恩·豪克,玛格丽特-安妮·斯托里 - DevEx 在行动
DevEx(开发者体验)在许多软件组织中越来越受到关注,因为领导者寻求在财政紧缩和人工智能等变革性技术的背景下优化软件交付。直观地看,技术领导者普遍认为良好的开发者体验可以提高软件交付的效率和开发者的幸福感。然而,在许多组织中,旨在改进DevEx的拟议计划和投资难以获得支持,因为业务利益相关者质疑改进的价值主张。


若昂·瓦拉霍,安东尼奥·特里戈,米格尔·阿尔梅达 - 低代码开发生产力
本文旨在通过展示使用基于代码、低代码和极端低代码技术进行的实验室实验结果,以研究生产力差异,从而为该主题提供新的见解。低代码技术已清楚地显示出更高的生产力水平,为低代码在短期/中期内主导软件开发主流提供了强有力的论据。本文报告了程序和协议、结果、局限性和未来研究的机会。


伊瓦尔·雅各布森,阿里斯泰尔·科克本 - 用例至关重要
虽然软件行业是一个快节奏且令人兴奋的世界,其中不断开发新的工具、技术和技巧来服务于商业和社会,但它也很健忘。在其快速前进的匆忙中,它容易受到时尚的 whims 的影响,并且可能会忘记或忽略已证明的解决方案来解决它面临的一些永恒问题。用例,最初于1986年引入,后来普及,是这些已证明的解决方案之一。





© 保留所有权利。

© . All rights reserved.