检测传递wchar_t*到BSTR的静态代码分析

13
由于 BSTR 只是 wchar_t* 的一个 typedef,我们的代码库中有很多地方将字符串字面值传递给期望 BSTR 的方法,这可能会影响到编组器或任何试图使用任何 BSTR 特定方法(如 SysStringLen)的人。

有没有办法静态检测此类误用?

我尝试使用 VC10 /Wall 编译并使用静态代码分析 Microsoft All Rules,但以下有问题的代码段都没有被标记。

void foo(BSTR str)  
{
    std::cout << SysStringLen(str) << std::endl; 
}

int _tmain()
{
    foo(L"Don't do that");
}

更新:尝试篡改wtypes.h以检测这些违规行为后,我放弃了。
我尝试了两种方法,两种方法在我的示例程序中都能够工作,但一旦我尝试一个真实项目,它们就失败了。
1.创建一个名为BSTR的类,但由于VARIANT具有BSTR作为联合成员,所以新类不能有任何构造函数或赋值运算符,这破坏了每个将NULL视为BSTR的地方。我试图用具有转换运算符的类型替换NULL,但是在添加了几十个新运算符(比较、转换等)之后,我开始遇到模棱两可的调用,然后放弃了。
2.然后我尝试了@CashCow和@Hans建议的方法(将BSTR作为指针的另一种类型的typedef)。那也不起作用,在将toBSTRfromBSTR方法添加到comutil.h_bstr_t)和其他地方,并将转换散布其中之后,编译器最终卡在从IDL产生的头文件处(默认值被转换为文字宽字符串)。
简而言之,我放弃了自己尝试实现这一点,如果有人知道可以帮助的代码分析工具,我会非常高兴听到它。

2
BSTR可能是这样的typedef,但这并不意味着它是一个以null结尾的字符串。它的字符串表示实际上从第三个wchar_t字符开始,前两个字符是长度前缀(因此允许长度达到0xFFFF)。它们可以包含嵌入的null。我认为它们通常是以null结尾的。 - CashCow
3
@CashCow,不完全正确,长度是放在实际数据之前的(这是SysStringLen要寻找的内容),这就是为什么将wchar_t *视为BSTR是不正确的原因。 - Motti
是的,长度在数据之前。当您调用SysAllocString时,它会返回第5个字节。当您传递BSTR时,您传递的是第5个分配字节的地址。 - CashCow
@CashCow 我理解了这一切,你的观点是什么? - Motti
1
很遗憾,typedef enum class BSTRchar : wchar_t {} *BSTR; 也不起作用... 带作用域的枚举没有隐式向上转换。 - Ben Voigt
显示剩余6条评论
4个回答

4

我相信Coverity声称可以检测这些类型的漏洞。我记得他们在向我工作的公司演示时特别提到了COM相关内容。

他们的数据表似乎暗示着他们检查不当使用BSTR的类别。他们有一个演示期,你可以尝试一下,看看它是否会标记你的样本输入。


在检查后,我发现COM.BSTR.CONV检测到了这个模式,谢谢! - Motti

1

你能否改变你的方法,使用 _bstr_t 或者 CComBSTR 代替?

如果不能的话,由于字面量实际上是一个 const wchar_t *,如果有编译器设置不允许字面量转换为非 const 指针,你可以这样做。

如果以上方法都行不通,有可能修改 BSTR 的定义为 unsigned short *。然后,如果你构建所有的源代码,你将会在传递字面量时得到编译器错误,并且你可以修复所有这些代码。然后我建议将其改回原来的定义...


选项2(防止转换为const wchar_t *)不够,因为并非所有实例都是字面字符串。选项3(typedef unsigned short* BSTR)无法使用,因为我们有时会将 BSTR 传递给 wcscmp(等等),而这些函数不接受unsigned short *类型. - Motti

1
你可以尝试使用Clang编译,它的静态/动态分析可能会找到你要找的东西。

Clang是否支持编译微软特定的C++代码? - Motti
相当确定它现在已经做到了,并且不断地改进。 - ticktock

0

使用BSTR重载所有函数,并使用适当的转换进行转发。

void foo( BSTR str )
{
    std::cout << SysStringLen(str) << std::endl; 
}

void foo( const WCHAR *str )
{
    foo( SysAllocString( str ));
}

int _tmain()
{
    foo( L"don't do this" );
    return 0;
}

或者,为了生成编译器错误,将所有参数类型从BSTR更改为其他类型,并查找错误:

typedef UINT bar;

void foo( bar _str )
{
    // make the compiler happy below
    BSTR str = (BSTR)_str;
    std::cout << SysStringLen(str) << std::endl;
}

int _tmain()
{
    foo( L"don't do this" );
    foo( (bar)42 );
    return 0;
}

错误 C2664: 'foo' : 无法将参数 1 从 'const wchar_t [14]' 转换为 'bar'

我猜测编译器标识的C2664错误和 'const wchar_t[]' 类型是您希望编译器针对每个使用BSTR调用函数的内部调用找到的内容?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接