ABV.GENERAL

缓冲区溢出 — 数组索引超出边界

缓冲区溢出或溢出是一种异常现象,其中将数据写入缓冲区的程序会溢出缓冲区的边界并覆盖相邻的内存。通常,当一个程序将字符串复制到缓冲区时,便会发生这种问题。

C 和 C++ 不提供任何内置保护功能来阻止访问或覆盖内存任何部分中的数据,也不自动检查写入某一数组(此语言的内置缓冲区类型)的数据是否在该数组的边界内。

ABV.GENERAL 检查器是一种通用检查器,用于查找数组边界违规情况,即对超出该数组边界的数组元素的任何访问。

漏洞与风险

旨在执行代码或改变程序操作方式的输入可触发缓冲区溢出。这可能导致程序行为不稳定,包括内存访问错误、错误的结果、故障或系统安全漏洞。

缓冲区溢出的后果包括有效数据被覆盖以及执行任意和潜在恶意代码。例如,缓冲区溢出可通过以下三种方式操纵程序:

  • 覆盖内存中缓冲区附近的局部变量,以更改程序的行为使之有利于攻击者
  • 覆盖堆栈帧中的返回地址,以便在攻击者指定的返回地址(通常为用户输入填充的缓冲区)处恢复执行
  • 覆盖随后将执行的函数指针或异常处理程序

漏洞代码示例 1

复制
  int main()
  {
      char fixed_buf[10];
      sprintf(fixed_buf,"Very long format string\n"); // Line 4.ABR
      return 0;
  }

Klocwork 为第 4 行生成了一个缓冲区溢出报告,指出 fixed_buf 的数组索引可能超出边界:大小为 10 的数组 fixed_buf 可以使用索引值 0 至 24。ABR 检查器可查找数组边界违规情况,在本例中,其将查找对超出该数组边界的数组元素 fixed_buf 的访问。

修正代码示例 1

复制
  int main()
  {
      char fixed_buf[10];
      snprintf(fixed_buf, sizeof(fixed_buf), "Very long format string\n"); 
      return 0;
  }

在该修正代码示例中,sprintf 函数(该函数假设输出缓冲区的大小足以容纳产生的字符串)已被 snprintf 函数替代(该函数会将最大字节数写入缓冲区)。请注意,这只是本示例代码中阻止缓冲区溢出的其中一种方法,此修正代码将导致字符串截断,这可能需要加以说明,具体取决于应用程序。

漏洞代码示例 2

复制
  void foo()
  {
      char a[8]; // holds two 4-byte ints
      for (int i = 0; i < sizeof(a); i++)
      {
          ((int*)a)[i] = i;
      }
  }

有时,数组或指向已分配缓冲区的指针会在访问其元素前转换为不同类型。在本示例中,第 4 行循环的上边界视为数组 a 的大小(以字符表示),但是数组 a 在第 6 行作为一个整数数组进行访问。Klocwork 为上文的代码段生成了一个缓冲区溢出报告,指出 a 的数组索引可能由于第 6 行 1 个字节的字符与 4 个字节的整数之间的差异而超出边界:大小为 2 的数组 a 可以使用索引值 2 至 7。该问题的回溯所包含的信息指出,声明为 char[8] 的数组 a 被视作大小为 2 的数组。

修正代码示例 2

复制
  void foo()
  {
      char a[8]; // holds two 4-byte ints
      for (int i = 0; i < sizeof(a) / sizeof(int); i++)
      {
          ((int*)a)[i] = i;
      }
  }

在该修正代码示例中,第 4 行循环的上边界已更改为查看被视作整数数组的数组 a 中的元素数量。

相关检查器

扩展

此检查器可通过 Klocwork 知识库进行扩展。有关详情,请参阅调整 C/C++ 分析。