实际上,大多数语言在缓冲区溢出方面都是“安全的”。一种语言在这方面“安全”所需要的是:严格类型、系统数组绑定检查和自动内存管理(“垃圾收集器”)。有关详细信息,请参阅此答案。
从这个意义上说,一些旧语言并不“安全”,尤其是 C(和 C++),还有 Forth、Fortran……当然还有汇编。从技术上讲,可以编写一个“安全”的 C 实现,并且在形式上仍符合 C 标准,但代价高昂(例如,您必须进行free()
无操作,因此分配了已分配的内存“永远”)。没有人这样做。
“安全”语言(关于缓冲区溢出)包括 Java、C#、OCaml、Python、Perl、Go,甚至 PHP。其中一些语言的效率足以实现 SSL/TLS(即使在嵌入式系统上——我是根据经验说话的)。虽然可以编写安全的 C 代码,但需要(很多)专注和技巧,而且经验反复表明这很难,即使是最好的开发人员也不能假装他们总是应用所需的注意力和能力水平。这是一次令人谦卑的经历。“不要使用 C,它很危险”的断言不受欢迎,不是因为它是错误的,而是恰恰相反,因为它是正确的:它迫使开发人员面对他们可能不是半神人的想法他们认为的编程,深深地隐藏在他们的灵魂深处。
但是请注意,这些“安全”语言并不能防止错误:缓冲区溢出仍然是不受欢迎的行为。但是它们包含了损害:缓冲区之外的内存实际上并没有被读取或写入;相反,有问题的线程会触发异常,并且(通常)被终止。在 heartbleed 的情况下,这可以避免漏洞成为漏洞,并且可能有助于防止我们在过去几天观察到的全面恐慌(没有人真正知道是什么让随机漏洞像 Youtube 一样传播开来)以韩国隐形马为特色的视频;但是,“从逻辑上讲”,如果它根本不是一个漏洞,那么这应该避免所有这些悲喜剧)。
编辑:由于在评论中讨论得很充分,我想到了C的安全内存管理问题,并且有一种仍然可以free()
工作的解决方案,但是有一个作弊。
可以想象一个产生“胖指针”的 C 编译器。例如,在 32 位机器上,将指针设为 96 位值。每个分配的块都将被授予一个唯一的 64 位标识符(例如,一个计数器),并维护一个内部存储器结构(哈希表、平衡树...),它通过 ID 引用所有块。对于每个块,其长度也记录在结构中。然后,指针值是块 ID 和该块内的偏移量的串联。跟随指针时,通过ID定位块,将偏移量与块长度进行比较,然后才进行访问。此设置解决了双重释放和释放后使用问题。它还检测大多数缓冲区溢出(但不是全部:缓冲区可能是更大结构的一部分,并且malloc()
/free()
管理只看到外部块)。
“作弊”是“唯一的 64 位计数器”。仅当您没有用完 64 位整数时,这才是正确的;除此之外,您必须重用旧值。64 位在实践中应该可以避免这个问题(“环绕”需要数年时间),但较小的计数器(例如 32 位)可能会成为问题。
此外,当然,内存访问的开销可能是不可忽略的(每次访问都会进行相当多的物理读取,尽管某些情况可能会被优化掉),对于指针丰富的结构,双倍指针大小也意味着更高的内存使用率. 我不知道有任何现有的 C 编译器应用这种策略;现在纯粹是理论上的。