UFM.FFM.MUST

已释放内存再次被释放

如果内存在释放后又被使用或引用,或对内存进行了两次释放,则可能导致无法预料的结果。内存引用问题可能导致使用无法预料的值,并因此导致程序崩溃或执行任意代码。

通常,如果存在错误条件或出现异常、争用条件未解决,或是程序不同部分之间的内存责任界定不清晰,就会出现在释放后使用或重复释放的内存错误。UFM 检查器会查找代码中各种内存释放后又被使用以及重复释放内存的实例。UFM.FFM.MUST 检查器标记出那些再次释放已被释放的内存的情况。

漏洞与风险

内存引用问题可能导致严重的问题。使用之前释放的内存可能导致损坏有效数据或执行任意代码,视具体情况而定。释放内存后,如果没有进行重新分配或回收,则其内容仍会保持完整且可以访问。被释放的位置的数据可能看来仍有效,但也可能发生了无法预料的变化,并导致意外的代码行为。

如果在释放后,内存分配给了其他指针,而原来的指针又得到再次使用,则原来的指针可能指向新分配中的位置。由于数据发生了变化,因此可能导致损坏有效使用的内存并导致未定义的操作。如果函数指针被指向有效代码的地址覆盖,则恶意用户将可以执行任意代码。

当程序重复释放相同的内存时,内存管理数据结构将被损坏,从而导致程序崩溃,或在之后的两个函数调用中返回相同的指针。在这种情况下,攻击者可以控制那些写入被重复分配的内存中的数据,因此很容易遭到缓冲区溢出攻击。

缓解与预防

要避免内存引用问题:

  • 在指针被释放后将其设为空值。
  • 确保全局变量仅被释放一次。
  • 在循环或条件语句中释放内存,以及使用重新分配函数时,尤其要多加小心。
  • 确保清理途径遵循内存分配的状态。

漏洞代码示例

复制
  #include <stdlib.h>
  
  typedef struct x {
      char * field;
  } tx;
  
  void release(tx * a){
      free(a->field);
      free(a);
 }
 
 int main() {
     tx *a = (tx *)malloc(sizeof(tx));
     if (a==NULL) return;
     a->field = (char *)malloc(10);
     release(a);
     free(a->field);
     free(a);
     return 0;
 }

Klocwork 生成报告,表明 a->field 所指向的内存已经在第 18 行被释放,而在此之前,它已经在第 16 行通过调用 release 函数被释放过。通常的情况与本示例并不相同,在代码中都是分配内存并在后面之后又被删除的地方释放它,这导致很难辨识出这种情况。在任何情况下,释放已被释放的内存都会导致无法预料的结果,并且结果通常都很严重。