修改CppCheck,做自己的扫描器 - 03
上一篇文章中我们已经分析了,CppCheck是一个以Token为驱动的静态扫描器。让我们以一个简单的例子来说。
while(*src++ = *dst++)
此类代码一向被认为不安全(strcpy()的实现),但是CppCheck在默认情况下并不能对它进行报警。如果我们想添加一条规则,该怎么做呢?
在想好方案之前,我们先完善需求,除却上面这个情况,其实还有如下几个变体。 即dst和src自增顺序的差别。不过不论如何,产生的结果都是一样的——代码是有安全风险的。
//while(*src++ = *dst++)
//while(*++src = *++dst)
//while(*src++ = *++dst)
//while(*++src = *dst++)
不过代码的原理基本相同,我们从第一个例子开始,把它拆分为多个顺序的token:
0 while
1 (
2 *
3 oprand
4 ++
5 =
6 *
7 oprand
8 ++
9 )
在token迭代中,tok始终代表当前token,那么假设当前token是while,我们只需要依次检查后面的token即可。现在,相信你也能很快地写出代码来了。
在这之前,我们先定义一个错误报告函数,保持和其他函数类似结构即可:
void CheckBufferOverrun::potentialOutOfBoundsError(const Token *tok, const std::string &what, const bool show_size_info, const MathLib::bigint &supplied_size, const MathLib::bigint &actual_size)
{
std::ostringstream oss;
oss << what << " might have an out-of-bounds action. Try to check the length before assign.";
if (show_size_info)
oss << ": Supplied size " << supplied_size << " is larger than actual size " << actual_size;
oss << '.';
reportError(tok, Severity::error, "outOfBounds", oss.str(), CWE788, false);
}
然后,编写代码,运行程序:
if (tok->str() == "while" &&
tok->tokAt(1)->str() == "(" &&
tok->tokAt(2)->str() == "*" &&
tok->tokAt(4)->str() == "++" &&
tok->tokAt(5)->str() == "=" &&
tok->tokAt(6)->str() == "*" &&
tok->tokAt(8)->str() == "++" &&
tok->tokAt(9)->str() == ")") {
potentialOutOfBoundsError(tok->tokAt(3), "Dangerous operation! Oprands in while() ", false, 0, 0);
}
对给定代码:
int main()
{
char a[2];
char *b = "abcdefg";
while(*a++ = *b++);
return 0;
}
得到以下结果:
Checking d:\test.c ...
[d:\test.c:5]: (error) Dangerous operation! Oprands in while() might have an out-of-bounds action. Try to check the length before assign..
那么,再让我们继续“完善”代码,因为这里实际上我们并没有判断自增的对象是什么,查看token的细节,我们可以发现它是有记录token类型的,而只需要调用getTokType()==Variable,valueType()==CHAR就可以进行简单判断:
- mValueType 0x03867260 {sign=UNKNOWN_SIGN (0) type=CHAR (7) bits=0 ...} ValueType *
sign UNKNOWN_SIGN (0) ValueType::Sign
type CHAR (7) ValueType::Type
bits 0 unsigned int
pointer 1 unsigned int
constness 0 unsigned int
修改完以后,就可以在保证结果正确的前提下,同时减少误报率了。 当然,*(int*)++ = *(int*)++
一样会导致安全问题,如果需要的话,也可以去除对ValueType::CHAR
的比较。
if (tok->str() == "while" &&
tok->tokAt(1)->str() == "(" &&
tok->tokAt(2)->str() == "*" &&
tok->tokAt(3)->tokType() == Token::eVariable &&
tok->tokAt(3)->valueType()->type == ValueType::CHAR &&
tok->tokAt(4)->str() == "++" &&
tok->tokAt(5)->str() == "=" &&
tok->tokAt(6)->str() == "*" &&
tok->tokAt(7)->tokType() == Token::eVariable &&
tok->tokAt(7)->valueType()->type == ValueType::CHAR &&
tok->tokAt(8)->str() == "++" &&
tok->tokAt(9)->str() == ")") {
potentialOutOfBoundsError(tok->tokAt(3), "Dangerous operation! Oprands in while() ", false, 0, 0);
}
// 文章来源nul.pw 作者leonwxqian
其余几个也大同小异,只需调整修改即可。
这便是使用token来进行安全检查的一种方法了,CppCheck还提供很多方式,条条大路通罗马,通过token来检查是其中一种最简单的方案了,如果你也有抽象好的安全规则,不妨通过token的方式来制作一个检测器进行一番检测。