gets
每个 C 程序员都知道,除非标准输入连接到受信任的源,否则无法安全使用。但是为什么 C 语言的开发者在它成为 C 标准的正式部分之前没有注意到如此明显的错误呢?为什么直到 C11 才从标准中删除它并用执行边界检查的函数替换它?我知道fgets
通常使用它来代替它,但这有一个令人讨厌的习惯,即\n
在末尾保留。
5 回答
答案很简单,C 是一种非常古老的语言,可以追溯到 1970 年代初期。当该语言最初开发时,我们今天认为理所当然的那种安全威胁并没有出现。
很长一段时间,C 是 AT&T 的内部语言。直到 1970 年代后期,才很难找到 C 的商业编译器。但是当 UNIX 操作系统用 C 重写时,编译器变得更容易获得,语言开始流行,特别是在 Kernighan 和 Ritchie 1978 年的标准参考之后,The C Programming Language
.
尽管其广泛且日益流行,但该语言本身直到 1989 年才标准化。到那时,C 已经将近 20 年了,并且已经安装了很多 C 代码。标准委员会相对保守;它的工作假设是该标准将编纂现有做法,而不是要求新的做事方式。gets()
与声明大部分已安装代码库非标准的成本相比,缓冲区溢出漏洞似乎微不足道。
1988 年的莫里斯互联网蠕虫确实表明需要更安全的编码实践,但即便如此,早在 1980 年代后期,互联网仍然非常新生。(如果我没记错的话,David Pogue 在 1990 年代早期的 Macintosh 书中回答了如何将 Mac 连接到 Internet 的问题,大意是“不要打扰,互联网不值得努力”。)很难指责标准委员会误判了互联网的指数级增长和参与的安全威胁。
当 1999 年标准修订时,情况当然发生了变化。然而,委员会再次选择对使现有代码失效保持谨慎,因此弃用而不是gets()
完全删除。这是否是正确的决定值得商榷,但显然不是错误的决定。
保留gets()
在 C11 标准中显然是错误的决定,而当前的标准非常恰当地消除了它。但是您的问题基于这样的假设,即这“总是已经”是正确的做法,并且从历史的角度来看,该假设似乎是有问题的。
最初的 ANSI 标准的任务是编纂现有实践,而不是发明一种新语言。
理由文件中明确说明了这一点:
最初的 X3J11 章程明确要求编纂现有的通用实践,并且 C89 委员会坚持先例,只要这是明确和明确的。C89 定义的绝大多数语言与 Brian Kernighan 和 Dennis Ritchie 在 The C Programming Language 第一版的附录 A 中定义的完全相同,并且在当时几乎所有的 C 翻译器中都实现了这一点。(本文档以下简称 K&R。)
因此,因为gets
它是语言的一部分,所以它成为标准的一部分。还有其他不安全的东西仍然存在,希望从业者知道如何明智地使用他们的工具。
而且,如果您担心多余的换行符,修复起来很容易:
{
size_t len = strlen (buffer);
if ((len > 0) && (buffer[len-1] == '\n'))
buffer[len-1] = '\0';
}
或更简单的:
buffer[strcspn (buffer, "\n")] = '\n';
你甚至可以编写自己的fgets
前端来为你做这件事,比如这里的这个,显然是由 SO 中更聪明、更漂亮的成员之一编写的 :-)
C 最初来自计算机互联网络普及之前的时间。在当时的情况下,如果你用 C 语言编写了一个使用 的程序gets()
,然后抱怨你给它的输入太大而导致它崩溃,那么响应只会是“好吧,那就不要那样做! ”。“不受信任的输入”的整个概念几乎是无稽之谈——输入是由操作员明确提供的。
C89 标准并没有删除它,因为标准委员会的主要任务是编纂现有实践,并且gets()
在那时绝对是现有实践的一部分。
作为移除它的第一步,它在 C99 中已被弃用,正如您所注意到的,它随后发生在 C11 中。
首先是否gets
加入标准是有争议的,但委员会认为,gets
当程序员确实对输入有足够的控制权时,这很有用。
这是委员会的官方解释。
国际标准的基本原理 - 编程语言C
gets
§7.19.7.7功能:因为
gets
不检查缓冲区溢出,所以当其输入不受程序员控制时使用通常是不安全的。这导致一些人质疑它是否应该出现在标准中。委员会决定gets
在程序员确实对输入有足够控制权的特殊情况下,这是有用和方便的,并且作为长期存在的实践,它需要一个标准规范。然而,一般来说,首选函数是fgets
(参见第 7.19.7.2 节)。
早期计算技术的空间和时间限制不允许当今普遍的更实际的安全实践。出于代码兼容性的原因,维护了现有的有缺陷的例程。