【缺陷周话】第59期:重复加锁
字数 1128 2025-08-18 11:39:08
重复加锁缺陷分析与防范指南
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. 检测方法
使用静态代码分析工具(如奇安信代码卫士)可以检测出重复加锁缺陷:
- 工具会标记出对同一互斥量的连续加锁操作
- 检测结果显示为中等风险等级
- 修复后检测工具将不再报告该问题
5. 防范措施
5.1 编码实践
- 代码审查:在进行加锁操作时,仔细检查代码逻辑,避免对已锁定的互斥量重复加锁
- 锁的封装:将锁操作封装为函数或类方法,减少直接操作锁的机会
- 注释说明:对锁的使用范围和作用进行详细注释
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 设计原则
- 锁的粒度:合理设计锁的粒度,避免过大或过小
- 锁的持有时间:尽量减少锁的持有时间
- 锁的顺序:在多锁情况下,保持一致的加锁顺序
- 锁的文档:对锁的使用规则进行文档化
6. 最佳实践建议
- 避免嵌套锁:尽量避免在已持有锁的情况下再次获取锁
- 使用RAII模式:在C++中,使用资源获取即初始化模式管理锁
- 单元测试:编写多线程单元测试,特别测试边界条件
- 静态分析:在开发流程中集成静态分析工具
- 动态检测:使用工具如Valgrind的Helgrind检测锁问题
7. 总结
重复加锁是多线程编程中常见但危险的问题,可能导致死锁和程序挂起。通过:
- 谨慎的编码实践
- 使用错误检查互斥量
- 采用良好的设计原则
- 利用工具进行检测
可以有效预防和发现重复加锁问题,提高多线程程序的稳定性和可靠性。