Answers:
要以最简洁的方式回答您的问题-是的,此错误可能已被正式的验证工具捕获。实际上,在大多数规范语言(例如LTL)中,属性“从不发送大于发送的听音的大小的块”相当简单。
问题(这是对形式方法的普遍批评)是您使用的规范是人类编写的。确实,形式化方法只会将寻找错误的挑战从发现错误转变为定义错误是什么。这是一项艰巨的任务。
此外,由于状态爆炸问题,正式验证软件也非常困难。在这种情况下,这尤其重要,因为为了避免状态爆炸,我们多次抽象出界限。例如,当我们想说“每个请求都在100000步之内被授予之后”时,我们需要一个很长的公式,因此我们将其抽象为“每个请求最终都被授予之后”的公式。
因此,在令人沮丧的情况下,即使试图正式化需求,也可以将所讨论的界限抽象化,从而导致相同的行为。
综上所述,可以通过使用形式化方法来避免此错误,但是必须事先指定此属性。
诸如Klocwork或Coverity之类的商业程序检查器可能已经找到Heartbleed,因为它是一个相对简单的“忘记做边界检查错误”,这是他们设计要检查的主要问题之一。但是有一种更简单的方法:使用经过良好测试的不透明抽象数据类型,以防止缓冲区溢出。
有许多可用于C编程的“安全字符串”抽象数据类型。我最熟悉的是Vstr。作者James Antill 讨论了为什么您需要一个具有自身构造函数/工厂方法的字符串抽象数据类型以及C的其他字符串抽象数据类型的列表。
如果您将运行时边界检查和模糊测试结合起来视为一种“ 程序验证技术 ”,那么肯定会捕获到该特定错误。
适当的模糊处理将导致现在臭名昭著memcpy(bp, pl, payload);
的读取超出存储块pl
所属的限制。运行时绑定检查原则上可以捕获任何此类访问,并且在实际情况下,在这种特殊情况下,即使是调试版本的malloc
该版本也关心绑定参数的调试memcpy
都可以完成此工作(无需在此处与MMU混淆) 。问题是,对每种网络数据包执行模糊测试会很费力。
memcpy
,达到原先由系统请求的(大)区域的真实边界的可能性大大降低malloc
。
memcpy(bp, pl, payload)
使用OpenSSL而言,必须检查OpenSSL malloc
替代产品使用的范围,而不是系统malloc
。这就排除了二进制级别的自动边界检查(至少在不具备malloc
替代知识的情况下)。必须使用源代码级向导进行重新编译,例如使用C宏替换令牌malloc
或使用任何替代的OpenSSL;似乎memcpy
除了MMU技巧非常巧妙之外,我们也需要同样的技巧。
使用更严格的语言不仅可以将目标发布从正确实施实现到正确制定规范。很难做出非常错误却在逻辑上是一致的东西。这就是为什么编译器会捕获许多错误的原因。
正常情况下制定的指针算术是不正确的,因为类型系统实际上并不意味着其应具有的含义。您可以通过使用垃圾回收语言(正常的方法使您也为抽象付出代价)来完全避免此问题。或者,您可以更详细地说明您使用的是哪种类型的指针,以便编译器可以拒绝不一致的内容或无法证明其正确编写的任何内容。这是Rust等某些语言的方法。
构造类型等同于证明,因此,如果您编写了一个忘记了这种类型的类型系统,那么所有事情都会出错。假设有一阵子,当我们声明一个类型时,实际上意味着我们在断言关于变量中内容的真相。
在这个世界上,指针不能为null。NullPointer取消引用不存在,并且不必在任何地方检查指针是否为空。相反,“ nullable int *”是另一种类型,可以将其值提取为null或指针。这意味着在非空假设开始的那一刻,您要么记录您的异常,要么就走一个空分支。
在这个世界上,也不存在数组越界错误。如果编译器无法证明它在范围之内,请尝试重写以使编译器可以证明它。如果不能,那么您将不得不在该位置手动放置一个假设。以后编译器可能会发现与此矛盾。
同样,如果您没有未初始化的指针,那么您将没有指向未初始化内存的指针。如果您有一个指向释放内存的指针,则编译器应拒绝该指针。在Rust中,可以使用不同的指针类型来使这些证明合理。有专门拥有的指针(即没有别名),指向深度不变的结构的指针。默认存储类型是不可变的,等等。
还有一个问题,就是在协议(包括接口成员)上强制实施定义明确的语法,以将输入表面积限制在预期的范围之内。关于“正确性”的事情是:1)摆脱所有未定义的状态2)确保逻辑一致性。从正确性的角度来看,到达那里的困难与使用非常差的工具有很大关系。
这就是为什么两个最差的做法是全局变量和陷阱。这些东西可以防止在任何事物周围放置前置/后置/不变条件。这也是为什么类型如此有效的原因。随着类型变得更强大(最终使用从属类型来考虑实际值),它们自身就成为了建设性的正确性证明。使不一致的程序无法编译。
请记住,这不仅是愚蠢的错误。这也与保护代码库免受聪明的渗透者有关。在某些情况下,您不得不拒绝提交而没有令人信服的机器生成的重要属性证明,例如“遵循正式指定的协议”。
诸如Coverity之类的静态分析工具确实可以找到HeartBleed和类似的缺陷。全面披露:我与他人共同创立了Coverity。
请参阅我的博客文章,了解我们对HeartBleed的调查以及如何增强分析以检测到它:
http://security.coverity.com/blog/2014/Apr/on-detecting-heartbleed-with-static-analysis.html
自动/正式软件验证很有用,在某些情况下可能会有所帮助,但正如其他人指出的那样,这不是万灵丹。一个人可能会指出,OpenSSL易受攻击,因为它是开源的,但尚未在发布之前进行商业和行业范围的广泛使用,并且没有经过同行的严格审查(一个人想知道该项目上是否有付费开发人员)。缺陷基本上是通过发布后的代码审查发现的,并且代码显然是在发布前进行审查的(请注意,尽管可能无法跟踪谁进行了内部代码审查)。尤其是在发布高度敏感的代码(可能会更好地进行跟踪)之前,理想的情况是,最好是通过代码复习(最好是在其他情况下)进行“令人心动的教学时机”(尤其是在其他方面)。也许OpenSSL现在将受到更多审查。
媒体详细介绍了其起源:
Heartbleed是一个意外:开发人员承认造成了编码错误,并承认其影响“明显很严重” “德国开发人员Robin Seggelmann博士承认他编写了代码,然后由其他成员进行了审核并添加到OpenSSL软件中”
Codenomicon如何发现现在困扰着Internet的令人难以置信的错误 “安全公司的负责人解释了如何发现该缺陷,风险的严重程度以及人们现在可以采取的措施。”
从Heartbleed中学习的3大经验教训 “毁灭性的OpenSSL漏洞证明了数据中心编排的重要性,运行旧版本的智慧以及回馈OpenSSL项目的需要”