每个C程序员都知道除非标准输入连接到受信任的源,否则无法安全地使用gets
。但是为什么C的开发人员在成为C标准的正式部分之前没有注意到这样一个明显的错误?为什么要在C11中将其从标准中删除并用一个执行边界检查的函数替换它?我知道fgets
通常用在它的位置,但这有一个让\n
保持最后的烦人习惯。
答案 0 :(得分:4)
答案很简单,C语言是一种非常古老的语言,可以追溯到20世纪70年代早期。我们今天认为理所当然的那种安全威胁在语言最初开发时并未出现。
很长一段时间,C是AT& T的内部语言。直到20世纪70年代末才很难找到C的商业编译器。但是当用C语言重写UNIX操作系统时,编译器变得更容易获得,并且语言起步,特别是在Kernighan和Ritchie的1978标准参考文献The C Programming Language
之后。
尽管它广泛且越来越受欢迎,但语言本身直到1989年才被标准化。到那时,C已经有近20年的历史了,并且有很多已安装的C代码。标准委员会相对保守;它的工作假设标准会编纂现有的做法,而不是需要新的做事方式。与声明大部分安装的代码库非标准的成本相比,gets()
的缓冲区溢出漏洞似乎微不足道。
1988年的Morris互联网蠕虫确实表明需要更安全的编码实践,但即便如此,早在20世纪80年代后期,互联网仍处于初期阶段。 (如果我没记错的话,20世纪90年代早期由David Pogue撰写的Macintosh书籍回答了如何将Mac连接到互联网的问题,其中包括“不要打扰,互联网不值得付出努力”。)标准委员会错误判断互联网的指数级增长并出现安全威胁,这几乎是不容错过的。
当标准于1999年修订时,问题当然已经改变。但是,委员会再次选择对使现有代码无效而谨慎,因此要弃用而不是完全删除gets()
。这是否是正确的决定是有争议的,但这显然不是错误的。
在C11标准中保留gets()
显然是错误的决定,并且当前的标准非常正确地消除了它。但是你的问题依赖于这种假设,即“总是已经”正确的事情,并且从历史的角度来看,这种假设似乎值得怀疑。
答案 1 :(得分:2)
最初的ANSI标准的任务是编纂现有的做法,不发明一种新的语言。
理由文件中明确指出:
最初的X3J11章程明确要求编纂共同的现行做法,而C89委员会在任何明确和明确的地方都坚持先例。 C89定义的绝大多数语言与Brian Kernighan和Dennis Ritchie在C语言编程语言第一版的附录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
前端来为你做这件事,比如this one here,显然是由一个更聪明,看起来很漂亮的SO成员写的: - )
答案 2 :(得分:1)
gets()
的程序,然后抱怨你通过给它一个太大的输入而使它崩溃,那么响应就是“好吧,不要”那就做那个!“ “不可信输入”的整个概念几乎是无意义的 - 输入是由运营商明确提供的。
C89标准没有删除它,因为标准委员会的任务主要是编纂现有的做法,gets()
肯定是现有做法的一部分。
它在C99中被弃用,作为其删除的第一步,然后在C11中发生这种情况。
答案 3 :(得分:1)
首先将gets
置于标准中是否存在争议,但委员会认为gets
在程序员对输入有足够控制权时很有用。
以下是委员会的官方解释。
Rationale for International Standard - Programming Languages C§7.19.7.7
gets
函数:因为
gets
没有检查缓冲区溢出,所以当它的输入不在程序员的控制之下时,使用它通常是不安全的。这导致一些人质疑它是否应该出现在标准中。委员会认为,gets
在程序员对输入有足够控制的特殊情况下是有用和方便的,而且作为长期存在的做法,它需要一个标准规范。但是,一般来说,首选函数是fgets
(见第7.19.7节)。
答案 4 :(得分:0)
早期计算技术的空间和时间限制不允许采用当今常见的更实用的安全措施。由于代码兼容性原因,维护了现有的有缺陷的例程。