在Rust中重写Firefox组件的后果

在本系列的前几篇文章中,我们讨论了Rust中的内存 安全性线程安全性 在上一篇文章中,我们将研究使用Quantum CSS项目进行的真正Rust应用程序的含义。

CSS引擎将CSS规则应用于页面。 这是一个下降过程,使DOM树下降,在计算父CSS之后,可以独立计算子样式:非常适合并行计算。 到2017年,Mozilla进行了两次尝试,使使用C ++的样式系统并行化。 都失败了。

量子CSS的开发已经开始提高生产率。 改善安全性只是一个好的副作用。


内存保护和信息安全错误之间存在一定的联系。 因此,我们期望使用Rust可以减少Firefox中的攻击面。 本文将研究自2002年Firefox首次发布以来CSS引擎中已发现的潜在漏洞。 然后看看使用Rust可以预防和预防的事情。

一直以来,Firefox的CSS组件中一直检测到69个安全错误。 如果我们有一台时间机器,并且从一开始就可以将其编写为Rust,那么将不可能出现51(73.9%)个错误。 尽管Rust使编写好的代码更加容易,但它也没有提供绝对的保护。

铁锈


Rust是一种现代的系统编程语言,对于类型和内存都是安全的。 作为这些安全保证的副作用,Rust程序在编译时也是线程安全的。 因此,Rust特别适合:

  • 安全处理不可靠的传入数据;
  • 并发以提高性能;
  • 将各个组件集成到现有代码库中。

但是,Rust并未明确修复某些错误类别,尤其是正确性错误。 实际上,当我们的工程师重写Quantum CSS时,他们意外地重复了一个以前由C ++代码修复的关键安全性错误,他们不小心删除了错误修复641731 ,该错误修复允许通过SVG泄漏全局历史记录。 该错误已重新注册为bug 1420001 。 历史漏洞被评为严重安全漏洞。 最初的修复是额外的检查,以查看SVG文档是否为图像。 不幸的是,重写代码时错过了该检查。

尽管自动化测试应该发现违反规则:visited像这样:visited ,但实际上他们没有发现此错误。 为了加快自动测试的速度,我们暂时停用了测试该功能的机制-如果不执行测试,则没有特别的用处。 良好的测试覆盖范围可以降低重新实现逻辑错误的风险。 但是仍然存在新的逻辑错误的危险。

随着开发人员对Rust的熟悉,他的代码变得更加安全。 尽管Rust并不能阻止所有可能的漏洞,但它修复了一整类最严重的错误。

Quantum CSS安全错误


通常,默认情况下,Rust防止与内存,边界,空/未初始化变量和整数溢出有关的错误。 上面提到的非标准错误仍然可能:由于内存分配失败而导致崩溃。

按类别的安全错误


  • 记忆体:32
  • 边界:12
  • 实施:12
  • 空:7
  • 堆栈溢出:3
  • 整数溢出:2
  • 其他:1
在我们的分析中,所有错误均与安全性相关,但只有43个获得了官方评级(由Mozilla的安全工程师根据有关“可利用性”的合格假设进行分配)。 普通错误可能表示功能缺失或某种故障,这不一定会导致数据泄漏或行为更改。 官方安全错误的范围从低级(如果对攻击面有严格的限制)到严重漏洞(可能使攻击者可以在用户平台上运行任意代码)。

内存漏洞通常被归类为严重的安全问题。 在34个严重/严重问题中,有32个与内存有关。

安全漏洞的严重性分布


  • 合计:70
  • 安全错误:43
  • 严重/严重:34
  • 固定锈迹:32

比较Rust和C ++


错误955913 - GetCustomPropertyNameAt函数中的堆缓冲区溢出。 该代码为索引使用了错误的变量,从而导致在数组结束后对内存进行解释。 当访问错误的指针或将内存复制到传递给另一个组件的字符串时,这可能会导致崩溃。

所有CSS属性 (包括自定义,即自定义)的顺序都存储在mOrder数组中。 每个元素都由CSS属性值表示,或者在自定义属性的情况下,以eCSSProperty_COUNT (非自定义CSS属性的总数)开头的值表示。 要获取自定义属性的名称,您首先需要从mOrder获取值,然后在mVariableOrder数组的相应索引中访问该名称,该数组按顺序存储自定义属性的名称。

脆弱的C ++代码:


  void GetCustomPropertyNameAt(uint32_t aIndex, nsAString& aResult) const { MOZ_ASSERT(mOrder[aIndex] >= eCSSProperty_COUNT); aResult.Truncate(); aResult.AppendLiteral("var-"); aResult.Append(mVariableOrder[aIndex]); 

使用aIndex访问mVariableOrder数组的元素时,在第6行出现问题。 事实是aIndex应该与mOrder数组一起使用,而不是mVariableOrder 。 在mOrderaIndex表示的自定义属性的对应元素实际上是mOrder[aIndex] - eCSSProperty_COUNT

已更正的C ++代码:


  void Get CustomPropertyNameAt(uint32_t aIndex, nsAString& aResult) const { MOZ_ASSERT(mOrder[aIndex] >= eCSSProperty_COUNT); uint32_t variableIndex = mOrder[aIndex] - eCSSProperty_COUNT; aResult.Truncate(); aResult.AppendLiteral("var-"); aResult.Append(mVariableOrder[variableIndex]); } 

对应的Rust代码


尽管Rust在某种程度上类似于C ++,但它使用其他抽象和数据结构。 Rust代码与C ++会有很大不同(有关更多详细信息,请参见下文)。 首先,让我们看一下如果将漏洞代码尽可能地按字面意义翻译会发生什么情况:

  fn GetCustomPropertyNameAt(&self, aIndex: usize) -> String { assert!(self.mOrder[aIndex] >= self.eCSSProperty_COUNT); let mut result = "var-".to_string(); result += &self.mVariableOrder[aIndex]; result } 

Rust编译器将接受此代码,因为无法在执行之前确定向量的长度。 与应该知道其长度的数组不同,Rust中的Vec类型具有动态大小。 但是,边界检查内置于标准库向量的实现中。 如果出现无效索引,程序将立即以受控方式终止,以防止任何未经授权的访问。

Quantum CSS 实际代码使用非常不同的数据结构,因此没有完全等效的数据结构。 例如,我们使用Rust强大的内置数据结构来统一排列和属性名称。 这样就无需维护两个独立的阵列。 Rust数据结构还可以改善数据封装并减少此类逻辑错误的可能性。 由于代码必须与浏览器其他部分中的C ++代码交互,因此新的GetCustomPropertyNameAt函数看起来不像Rust惯用代码。 但是,它仍然提供所有安全性保证,同时提供了对基础数据的更易于理解的抽象。

tl;博士


由于漏洞通常与内存安全漏洞相关联,因此Rust代码应大大减少关键CVE的数量。 但是,即使Rust也不是完美的。 开发人员仍然需要跟踪正确性错误和数据泄漏攻击。 对安全库的支持仍然需要代码审查,测试和模糊测试。

编译器无法捕获所有程序员错误。 尽管如此,Rust减轻了我们的内存安全性负担,使我们能够专注于代码的逻辑正确性。

Source: https://habr.com/ru/post/zh-CN445670/


All Articles