CONC.DL

死锁

多线程程序中的死锁指的是两个或多个线程相互阻止,各自等待对方完成的情况。这通常由无序的锁争用引起,其中,每个线程均保留有另一个线程所需的资源,因此只有在获得资源后,两者才能解除阻止。

通常,死锁由无序保留的循环锁链引起。例如,线程 1 所执行代码的锁保留模式要求锁 A 先于锁 B 保留,同时,线程 2 所执行代码(可能相同)的锁保留模式要求锁 B 先于锁 A 保留。如果这两个线程在执行过程中发生冲突,线程 1 将可能在线程 2 已保留锁 B 后请求该锁。线程 2 可能请求锁 A,但会受到阻止,因为线程 1 已经保留该锁。

CONC.DL 检查器可查找死锁实例。

漏洞与风险

此类型的循环锁引用可能导致程序死机、UI 无效、设备无响应以及类似问题。现场调试这些问题很可能花费开发人员数周时间,同时也会让客户沮丧。

缓解与预防

为了帮助避免锁争用:

  • 请尝试让代码的锁定部分尽可能小和简单以便于理解。
  • 请勿锁定可能导致数据竞争等并发问题的代码部分。
  • 务必避免循环等待的状况。
  • 如果使用多个锁(通常在升级保护模式下),务必确保在每种情况下已执行完全相同的升级操作。

漏洞代码示例 1

复制
  #include <stdio.h>
  #include <pthread.h>
  static pthread_mutex_t A, B;

  void *printmsg1(void *msg) {
      pthread_mutex_lock(&A);  // Execution step 1
      pthread_mutex_lock(&B);  // Execution step 3
      printf("printmsg1\n");
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }

  void *printmsg2(void *msg) {
      pthread_mutex_lock(&B);  // Execution step 2
      pthread_mutex_lock(&A);
      printf("printmsg2\n");
      pthread_mutex_unlock(&A);
      pthread_mutex_unlock(&B);
      return 0;
  }

  int main(int argc, char**argv) {
      pthread_t pt1, pt2;
      pthread_mutex_init(&A, NULL);
      pthread_mutex_init(&B, NULL);
      pthread_create(&pt1,0, printmsg1, NULL);
      pthread_create(&pt2,0, printmsg2, NULL);
      pthread_join(pt1,0);
      pthread_join(pt2,0);
      pthread_mutex_destroy(&A);
      pthread_mutex_destroy(&B);
      return 0;
  }

Klocwork 报告了第 6 行中使用全局互斥 A 和 B 的两个线程之间可能发生死锁。主例程创建并启动在函数 printmsg1 和 printmsg2 中定义的两个线程。第一个线程在第 7 行等待获取互斥 B 上的锁,同时保留互斥 A 上的锁。第二个线程在第 16 行等待获取互斥 A 上的锁,同时保留互斥 B 上的锁。两个线程之间的这一循环锁链便可导致死锁。

修正代码示例 1

复制
  #include <stdio.h>
  #include <pthread.h>
  static pthread_mutex_t A, B;

  void *printmsg1(void *msg) {
      pthread_mutex_lock(&A);
      pthread_mutex_lock(&B);
      printf("printmsg1\n");
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }

  void *printmsg2(void *msg) {
      pthread_mutex_lock(&A);
      pthread_mutex_lock(&B);
      printf("printmsg2\n");
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }

  int main(int argc, char**argv) {
      pthread_t pt1, pt2;
      pthread_mutex_init(&A, NULL);
      pthread_mutex_init(&B, NULL);
      pthread_create(&pt1,0, printmsg1, NULL);
      pthread_create(&pt2,0, printmsg2, NULL);
      pthread_join(pt1,0);
      pthread_join(pt2,0);
      pthread_mutex_destroy(&A);
      pthread_mutex_destroy(&B);
      return 0;
  }

如果每个进程中均使用多个互斥进行同步,这些互斥应以相同顺序锁定。在该修正代码示例中,已更改第 15、16、18 和 19 行的锁和解锁顺序,使得它们与 printmsg2 和 printmsg1 中相同,以确保不会发生循环链。

漏洞代码示例 2

复制
  #include <stdio.h>
  #include <pthread.h>
  static pthread_mutex_t A, B;
   static pthread_cond_t cond;
  static int count;
  
  void *f1(void *msg) {
      pthread_mutex_lock(&A);  
      pthread_mutex_lock(&B);
      while (count) {
          pthread_cond_wait(&cond, &B);
      }  
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }   
  void *f2(void *msg) {
      pthread_mutex_lock(&A);
      pthread_mutex_lock(&B);
      count--;
      pthread_cond_broadcast(&cond);
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }   
       
  int main(int argc, char**argv) {
      pthread_t pt1, pt2;
      pthread_mutex_init(&A, NULL);
      pthread_mutex_init(&B, NULL);
      pthread_cond_init(&cond, NULL);
      count = 1;
      pthread_create(&pt1,0, f1, NULL);
      pthread_create(&pt2,0, f2, NULL);
  
      pthread_join(pt1, NULL);
      pthread_join(pt2, NULL);
      pthread_mutex_destroy(&A);
      pthread_mutex_destroy(&B);
      pthread_cond_destroy(&cond);
      return 0;
  }

Klocwork 报告了第 11 行中使用全局互斥 A 和条件变量 cond 的两个线程之间可能发生死锁。第一个线程可能在第 11 行无限期地等待条件变量 cond,同时保留互斥 A 上的锁。第二个线程在第 18 行等待获取互斥 A 上的锁,以更改全局变量 count 并向条件变量 cond 发送一个信号。

修正代码示例 2

复制
  #include <stdio.h>
  #include <pthread.h>
  static pthread_mutex_t A, B;
  static pthread_cond_t cond;
  static int count;
  
  void *f1(void *msg) {
      pthread_mutex_lock(&B);
      while (count) {
          pthread_cond_wait(&cond, &B);
      }  
      pthread_mutex_unlock(&B);
      return 0;
  }
  void *f2(void *msg) {
      pthread_mutex_lock(&A);
      pthread_mutex_lock(&B);
      count--;
      pthread_cond_broadcast(&cond);
      pthread_mutex_unlock(&B);
      pthread_mutex_unlock(&A);
      return 0;
  }
  
  int main(int argc, char**argv) {
      pthread_t pt1, pt2;
      pthread_mutex_init(&A, NULL);
      pthread_mutex_init(&B, NULL);
      pthread_cond_init(&cond, NULL);
      count = 1;
      pthread_create(&pt1,0, f1, NULL);
      pthread_create(&pt2,0, f2, NULL);
  
      pthread_join(pt1, NULL);
      pthread_join(pt2, NULL);
      pthread_mutex_destroy(&A);
      pthread_mutex_destroy(&B);
      pthread_cond_destroy(&cond);
      return 0;
  }

在条件等待函数之前,所设置的锁应仅包含在相同函数中需要并解锁的互斥。在该修正代码示例中,等待函数 pthread_cond_wait 之前的互斥 A 锁已删除,因为不需要。移除互斥 A 锁可消除第 10 行报告死锁的可能性。

相关检查器

安全培训

应用程序安全培训材料由 Secure Code Warrior 提供。

扩展

此检查器可进行扩展。相关的知识库记录类型包括:

  • CONC.LOCK
  • CONC.LOCK.TRY
  • CONC.UNLOCK
  • CONC.CONDSIGNAL
  • CONC.CONDWAIT

有关详情,请参阅调整 C/C++ 分析。