2019年1月

修改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的方式来制作一个检测器进行一番检测。