整数溢出排错功能

如果发生意外的整数溢出,可能会导致内存损坏,或导致与内存访问或内存分配关联的变量中出现信息披露漏洞。为了解决这个问题,我们在 Android 7.0 中添加了 Clang 的 UndefinedBehaviorSanitizer (UBSan) 有符号和无符号整数溢出排错程序,以增强媒体框架。在 Android 9 中,我们将 UBSan 扩展为涵盖更多组件,并改进了对它的编译系统支持。

这旨在增加对算术运算/指令(可能溢出)的检查,以便在实际发生溢出时安全地终止进程。这些排错程序可以减少主要是由整数溢出导致的各种内存损坏和信息泄露漏洞,例如原始 Stagefright 漏洞。

示例和源代码

整数溢出排错功能 (IntSan) 由编译器提供,可在编译期间将 Instrumentation 添加到二进制文件中,以便检测算法溢出。在整个平台的各个组件(例如 /platform/external/libnl/Android.bp)中,该功能会默认处于启用状态。

实现

IntSan 使用了 UBSan 的有符号和无符号整数溢出排错程序。该缓解功能是在各个模块级别启用,有助于确保 Android 关键组件的安全性,因此不应停用。

强烈建议您为更多组件启用整数溢出排错功能。理想的候选组件是特权原生代码或可解析不可信用户输入的原生代码。系统会产生一小笔与排错程序关联的性能开销,具体取决于代码的使用情况和算术运算的普遍性。预计开销所占的百分比会较小,您可以测试性能是否存在问题。

在 Makefile 中支持 IntSan

要在 Makefile 中启用 IntSan,请添加以下代码:

LOCAL_SANITIZE := integer_overflow
# Optional features
LOCAL_SANITIZE_DIAG := integer_overflow
LOCAL_SANITIZE_BLACKLIST := modulename_blacklist.txt
  • LOCAL_SANITIZE 会使用一个以英文逗号分隔的排错程序列表,其中 integer_overflow 是一组预封装的选项,用于各个有符号和无符号整数溢出排错程序,且带有默认黑名单
  • LOCAL_SANITIZE_DIAG 用于为排错程序启用诊断模式。请仅在测试期间使用诊断模式,因为它不会在溢出发生时中止,而这会完全抹杀缓解功能的安全优势。如需更多详细信息,请参阅问题排查
  • LOCAL_SANITIZE_BLACKLIST 用于指定黑名单文件,以防止函数和源文件成为排错对象。如需更多详细信息,请参阅问题排查

如果您想要进行更精细的控制,请使用一个或两个标志逐个启用排错程序:

LOCAL_SANITIZE := signed-integer-overflow, unsigned-integer-overflow
LOCAL_SANITIZE_DIAG := signed-integer-overflow, unsigned-integer-overflow

在 Blueprint 文件中支持 IntSan

要在 Blueprint 文件(例如 /platform/external/libnl/Android.bp)中启用整数溢出排错功能,请添加以下代码:

   sanitize: {
      integer_overflow: true,
      diag: {
          integer_overflow: true,
      },
      blacklist: "modulename_blacklist.txt",
   },

与 Makefile 一样,integer_overflow 属性也是一组预封装的选项,用于各个有符号和无符号整数溢出排错程序,且带有默认黑名单

diag 属性集用于为排错程序启用诊断模式。请仅在测试期间使用诊断模式。诊断模式不会在溢出发生时中止,而这会完全抹杀用户 Build 中该缓解功能的安全优势。如需更多详细信息,请参阅问题排查

blacklist 属性用于指定黑名单文件,以便开发者防止函数和源文件成为排错对象。如需更多详细信息,请参阅问题排查

要逐个启用排错程序,请使用以下代码:

   sanitize: {
      misc_undefined: ["signed-integer-overflow", "unsigned-integer-overflow"],
      diag: {
          misc_undefined: ["signed-integer-overflow",
                           "unsigned-integer-overflow",],
      },
      blacklist: "modulename_blacklist.txt",
   },

问题排查

如果要在新组件中启用整数溢出排错功能,或者依赖于启用了整数溢出排错功能的平台库,则可能会遇到良性整数溢出导致进程中止的一些问题。您应测试启用了排错功能的组件,以确保可以发现良性溢出。

要查找由用户 Build 中的排错功能导致的进程中止,请搜索带 Abort 消息(指示 UBSan 捕获了溢出)的 SIGABRT 崩溃,例如:

pid: ###, tid: ###, name: Binder:###  >>> /system/bin/surfaceflinger <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'ubsan: sub-overflow'

堆栈轨迹应包括导致进程中止的函数,不过,内联函数中发生的溢出在堆栈轨迹中可能不明显。

为了更轻松地确定根本原因,请在触发中止的库中启用诊断功能,尝试重现错误。启用诊断功能后,进程将不会中止,而是会继续运行。进程不中止有助于最大限度增加特定执行路径中良性溢出的数量,而无需在更正每个错误后重新进行编译。诊断功能会生成一条错误消息,其中包含导致进程中止的行号和源文件信息:

frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp:2188:32: runtime error: unsigned integer overflow: 0 - 1 cannot be represented in type 'size_t' (aka 'unsigned long')

一旦找到存在问题的算术运算,请确保溢出是良性的且符合预期(例如没有安全隐患)。您可以通过以下方式解决排错程序中止问题:

  • 重构代码以避免溢出(示例
  • 通过 Clang 的 __builtin_*_overflow 函数实现显式溢出(示例
  • 通过指定 no_sanitize 属性停用函数中的排错功能(示例
  • 通过黑名单文件来禁止对函数或源文件进行排错(示例

您应尽可能使用最精细的解决方案。例如,如果某大型函数具有多个算术运算和单个溢出运算,则应对单个溢出运算进行重构,而不是将整个函数列入黑名单。

可能导致良性溢出的常见模式包括:

  • 在转换为有符号类型之前发生无符号溢出的隐式类型转换示例
  • 删除关联的列表,且删除时循环索引会递减(示例
  • 将无符号类型指定为 -1,而不是指定实际最大值(示例
  • 在条件中使无符号整数递减的循环(示例示例

在停用排错功能之前,开发者最好是确保排错程序检测到的溢出确实是良性的,并且没有意外的副作用或安全隐患。

停用 IntSan

您可以使用黑名单或函数属性来停用 IntSan。请仅在重构代码不合理或者存在有问题的性能开销时,谨慎停用 IntSan。

要详细了解如何使用函数属性黑名单文件格式设置停用 IntSan,请参阅上游 Clang 文档。应将黑名单的作用范围限定为特定的排错程序,方法是使用区块名称指定目标排错程序,以免影响其他排错程序。

验证

目前没有专门针对整数溢出排错功能的 CTS 测试。因此请确保 CTS 测试在启用或未启用 IntSan 的情况下均能通过,以证明它不会给设备带来影响。