【缺陷周话】第59期:重复加锁
字数 1128 2025-08-18 11:39:08

重复加锁缺陷分析与防范指南

1. 重复加锁概念

重复加锁是指对已经加锁的资源进行再次加锁的操作。在多线程编程中,所有针对互斥量的加锁解锁操作都必须:

  • 针对同一模块
  • 在同一抽象层面进行

违反这些原则可能导致某些加锁/解锁操作不会依照多线程设计而被执行。

2. 重复加锁的危害

重复加锁可能导致以下严重后果:

  1. 死锁:第二次加锁操作等待前一次加锁的解锁操作,而由于加锁重复,被等待的行为永远无法达到
  2. 程序拒绝服务:由于死锁导致程序无法继续执行

实际案例:CVE-2019-14763 - Linux kernel 4.16.4之前版本的drivers/usb/dwc3/gadget.c中存在重复加锁错误,导致死锁问题。

3. 示例分析

3.1 缺陷代码示例

// 缺陷代码示例
pthread_mutex_lock(&double_lock_001_glb_mutex);  // 第一次加锁
// ... 一些操作 ...
pthread_mutex_lock(&double_lock_001_glb_mutex);  // 重复加锁,没有先解锁
// ... 其他代码 ...

问题分析

  • 第40行:首次对互斥量double_lock_001_glb_mutex加锁
  • 第42行:在未解锁的情况下再次对同一互斥量加锁
  • 导致重复加锁问题,可能引发死锁

3.2 修复后的代码

// 修复后的代码
pthread_mutex_lock(&double_lock_001_glb_mutex);  // 第一次加锁
// ... 一些操作 ...
pthread_mutex_unlock(&double_lock_001_glb_mutex); // 先解锁
pthread_mutex_lock(&double_lock_001_glb_mutex);  // 再次加锁
// ... 其他代码 ...

修复要点

  • 在两次加锁操作之间添加了解锁操作
  • 确保每次加锁前互斥量处于未锁定状态
  • 避免了重复加锁问题

4. 检测方法

使用静态代码分析工具(如奇安信代码卫士)可以检测出重复加锁缺陷:

  1. 工具会标记出对同一互斥量的连续加锁操作
  2. 检测结果显示为中等风险等级
  3. 修复后检测工具将不再报告该问题

5. 防范措施

5.1 编码实践

  1. 代码审查:在进行加锁操作时,仔细检查代码逻辑,避免对已锁定的互斥量重复加锁
  2. 锁的封装:将锁操作封装为函数或类方法,减少直接操作锁的机会
  3. 注释说明:对锁的使用范围和作用进行详细注释

5.2 使用错误检查互斥量

将互斥量类型设置为PTHREAD_MUTEX_ERRORCHECK

  • 当线程尝试重新锁定已由该线程锁定的互斥锁时,将返回错误
  • 可以及早发现重复加锁问题
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);

5.3 设计原则

  1. 锁的粒度:合理设计锁的粒度,避免过大或过小
  2. 锁的持有时间:尽量减少锁的持有时间
  3. 锁的顺序:在多锁情况下,保持一致的加锁顺序
  4. 锁的文档:对锁的使用规则进行文档化

6. 最佳实践建议

  1. 避免嵌套锁:尽量避免在已持有锁的情况下再次获取锁
  2. 使用RAII模式:在C++中,使用资源获取即初始化模式管理锁
  3. 单元测试:编写多线程单元测试,特别测试边界条件
  4. 静态分析:在开发流程中集成静态分析工具
  5. 动态检测:使用工具如Valgrind的Helgrind检测锁问题

7. 总结

重复加锁是多线程编程中常见但危险的问题,可能导致死锁和程序挂起。通过:

  1. 谨慎的编码实践
  2. 使用错误检查互斥量
  3. 采用良好的设计原则
  4. 利用工具进行检测

可以有效预防和发现重复加锁问题,提高多线程程序的稳定性和可靠性。

重复加锁缺陷分析与防范指南 1. 重复加锁概念 重复加锁 是指对已经加锁的资源进行再次加锁的操作。在多线程编程中,所有针对互斥量的加锁解锁操作都必须: 针对同一模块 在同一抽象层面进行 违反这些原则可能导致某些加锁/解锁操作不会依照多线程设计而被执行。 2. 重复加锁的危害 重复加锁可能导致以下严重后果: 死锁 :第二次加锁操作等待前一次加锁的解锁操作,而由于加锁重复,被等待的行为永远无法达到 程序拒绝服务 :由于死锁导致程序无法继续执行 实际案例 :CVE-2019-14763 - Linux kernel 4.16.4之前版本的drivers/usb/dwc3/gadget.c中存在重复加锁错误,导致死锁问题。 3. 示例分析 3.1 缺陷代码示例 问题分析 : 第40行:首次对互斥量 double_lock_001_glb_mutex 加锁 第42行:在未解锁的情况下再次对同一互斥量加锁 导致重复加锁问题,可能引发死锁 3.2 修复后的代码 修复要点 : 在两次加锁操作之间添加了解锁操作 确保每次加锁前互斥量处于未锁定状态 避免了重复加锁问题 4. 检测方法 使用静态代码分析工具(如奇安信代码卫士)可以检测出重复加锁缺陷: 工具会标记出对同一互斥量的连续加锁操作 检测结果显示为中等风险等级 修复后检测工具将不再报告该问题 5. 防范措施 5.1 编码实践 代码审查 :在进行加锁操作时,仔细检查代码逻辑,避免对已锁定的互斥量重复加锁 锁的封装 :将锁操作封装为函数或类方法,减少直接操作锁的机会 注释说明 :对锁的使用范围和作用进行详细注释 5.2 使用错误检查互斥量 将互斥量类型设置为 PTHREAD_MUTEX_ERRORCHECK : 当线程尝试重新锁定已由该线程锁定的互斥锁时,将返回错误 可以及早发现重复加锁问题 5.3 设计原则 锁的粒度 :合理设计锁的粒度,避免过大或过小 锁的持有时间 :尽量减少锁的持有时间 锁的顺序 :在多锁情况下,保持一致的加锁顺序 锁的文档 :对锁的使用规则进行文档化 6. 最佳实践建议 避免嵌套锁 :尽量避免在已持有锁的情况下再次获取锁 使用RAII模式 :在C++中,使用资源获取即初始化模式管理锁 单元测试 :编写多线程单元测试,特别测试边界条件 静态分析 :在开发流程中集成静态分析工具 动态检测 :使用工具如Valgrind的Helgrind检测锁问题 7. 总结 重复加锁是多线程编程中常见但危险的问题,可能导致死锁和程序挂起。通过: 谨慎的编码实践 使用错误检查互斥量 采用良好的设计原则 利用工具进行检测 可以有效预防和发现重复加锁问题,提高多线程程序的稳定性和可靠性。