C/C++ 代码检测工具
在 上文 中,笔者简单的介绍了 C/C++ 检测与调试常用的工具,在本文中,笔者将测试
- clang-tidy
- cppcheck
- AddressSanitizer
- valgrind memcheck
这 4 种工具,在笔者故意编造的简单的 C 语言代码中的常见错误中的表现情况.
笔者进行测试的环境信息为:
Arch Linux 5.13.6-arch1-1
1 |
clang --version |
clang version 13.0.0 (/startdir/llvm-project 5cd63e9ec2a385de2682949c0bbe928afaf35c91)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
1 |
clang-tidy --version |
LLVM (http://llvm.org/):
LLVM version 13.0.0
Optimized build.
Default target: x86_64-pc-linux-gnu
Host CPU: znver1Cppcheck 2.5
flawfinder 2.0.17
valgrind-3.17.0
测试环境所使用的命令如下:
1 |
clang-tidy "--checks=*" $filename >> /tmp/analyze |
error
ERROR
本文中代码片段均不该作为学习编程语言或代码风格的参考资料.本文代码片段意在构造常见的编程错误,并尝试使用工具对其分析.相关代码片段的修改和纠错请查阅编程语言相关学习资料.
数组越界
简单的数组越界
1 |
int main() { |
多么常见的编程错误,下面是各种工具给出的分析结果:
clang-tidy
1 |
arr01.c:3:5: warning: array index 10 is past the end of the array (which contains 10 elements) [clang-diagnostic-array-bounds] |
cppcheck
1 |
arr01.c:3:6: error: Array 'a[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds] |
AddressSanitizer
1 |
stack-buffer-overflow on address 0x7fffd170488a at pc 0x000000500c07 bp 0x7fffd1704850 sp 0x7fffd1704848 |
valgrind memcheck 未给出有价值的信息.
使用指针的数组越界
1 |
int main() { |
clang-tidy 未能给出与数组越界相关的错误信息.
cppcheck 未给出错误信息.
静态分析工具没有给出任何有价值的信息.
AddressSanitizer
1 |
stack-buffer-overflow on address 0x7fffcf8cde6a at pc 0x000000500c1f bp 0x7fffcf8cde30 sp 0x7fffcf8cde28 |
valgrind memcheck 也未能给出任何有价值的信息.
在这次实验中,只是使用指针代替数组完成操作便规避了绝大多数的分析工具.
二维数组的数组越界
1 |
int main() { |
clang-tidy
1 |
arr17.c:3:5: warning: array index 6 is past the end of the array (which contains 5 elements) [clang-diagnostic-array-bounds] |
cppcheck
1 |
arr17.c:3:8: error: Array 'arr[5][5]' accessed at index arr[4][6], which is out of bounds. [arrayIndexOutOfBounds] |
AddressSanitizer
1 |
AddressSanitizer: stack-buffer-overflow on address 0x7ffef126b89a at pc 0x000000500c20 bp 0x7ffef126b850 sp 0x7ffef126b848 |
valgrind memcheck 未输出有价值的信息.
使用字符串的数组越界
1 |
|
clang-tidy
1 |
arr04.c:7:5: warning: Call to function 'sprintf' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'sprintf_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling] |
clang-tidy 只发现了第二处错误,未能找到第一处错误.
cppcheck
1 |
arr04.c:10:12: error: Buffer is accessed out of bounds: buf [bufferAccessOutOfBounds] |
cppcheck 只发现了第二处错误,未能找到第一处错误.
AddressSanitizer
1 |
stack-buffer-overflow on address 0x7ffc1fa69e48 at pc 0x000000498768 bp 0x7ffc1fa69d20 sp 0x7ffc1fa694d0 |
AddressSanitizer 正确的定位到了第一次发生错误的位置:
1 |
#2 0x500bf1 in main arr04.c:7:5 |
valgrind memcheck 未能给出有价值的信息.
在上面的 3 次测试中,能看到 cppcheck、clang-tidy 都不能做到检测出所有的数组越界问题,AddressSanitizer 则都定位到了程序首次发生数组越界的位置.
内存管理
内存管理是 C/C++ 中的难点,让我们来看看与内存管理相关的错误是否能通过工具进行高效的定位.
多次 free 与释放后使用
1 |
#include <stdlib.h> |
clang-tidy
1 |
mem02.c:5:5: warning: Call to function 'strcpy' is insecure as it does not provide bounding of the memory buffer. Replace unbounded copy functions with analogous functions that support length arguments such as 'strlcpy'. CWE-119 [clang-analyzer-security.insecureAPI.strcpy] |
cppcheck
1 |
mem02.c:5:12: error: Buffer is accessed out of bounds: buf [bufferAccessOutOfBounds] |
AddressSanitizer
1 |
AddressSanitizer: heap-buffer-overflow on address 0x60200000001a at pc 0x000000489aef bp 0x7ffef9e30a40 sp 0x7ffef9e301f0 |
这次基础的测试中,clang-tidy、cppcheck 均能检测出内存的二次释放,也给出了有关不安全的 api
strcpy
的警告,但未能对使用释放后的内存给出提示.
valgrind memcheck
1 |
Invalid write of size 1 |
复杂的多次释放
1 |
#include <stdlib.h> |
clang-tidy
1 |
mem07.c:7:9: warning: Call to function 'strncpy' is insecure as it does not provide security checks introduced in the C11 standard. Replace with analogous functions that support length arguments or provides boundary checks such as 'strncpy_s' in case of C11 [clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling] |
很遗憾,clang-tidy 只是告诉开发者更换更安全的 api 来防止溢出,对本程序中的多次释放内存的问题视而不见.
cppcheck 也未能给出有价值的信息.
AddressSanitizer
1 |
attempting double-free on 0x602000000070 in thread T0: |
valgrind memcheck
1 |
Invalid free() / delete / delete[] / realloc() |
AddressSanitizer 和 valgrind memcheck 都给出了明确的多次释放的提示和错误位置.
总结
很多自动化的工具能够帮助开发者发现代码中的 bugs,但不可否认的是这些工具都还有很多不足.静态分析工具容易别较为复杂的代码绕过,而动态分析工具又容易因测试不能覆盖所有分支而被绕过.
这些工具能帮助开发者进行更加高效的开发与调试,但若仅仅依赖工具的自动检测则可能众多隐蔽的 bugs 深植于代码之中.
作为开发者,应该当增强自己寻找 bugs 的能力.bugs 是常见的,找到的 bugs 的能力是珍贵的.
参考资料
1. google/sanitizers:AddressSanitizer, ThreadSanitizer, MemorySanitizer [G/OL]. https://github.com/google/sanitizers. ↩
2. Clang 13 documentation [G/OL]. https://clang.llvm.org/docs/. ↩
C/C++ 代码检测工具