C++关于类型及运算的安全问题

某些C/C++书籍教会了我们几十年前流行的写法,浑然不知的我们理所当然的写出这些代码,然后把当年造就了无数漏洞的恶魔重新放入新的体系里。
在理想情况下,对类型的错误应用会导致一些错误,并让我们第一时间发觉;在最糟的情况下,其错误在很久之后才被发现,而且那时我们的系统已经遭受了足够多的攻击。
几个基本的类型规则:
1.类型转换规则:
系统将精度高低规定如箭头所示。
char,short -> int -> unsigned -> long ->double <- float
A.在运算中的自动转换:
1. 任何两个长度低于int类型的值运算时必然转换为int类型(包括==、>=等逻辑判断)
2. 等级等于或高于int的同类型运算时,仍为原类型
3. 两个不同类型数据参与运算时,两个数据都转换为其中较高级别的类型(两个值都长度低于int则遵从第一条)
4. 对于表达式中的常数,默认为int类型,如果该值为正数且超过INT_MAX(在头文件 limits.h中),则默认为 unsigned int (源文件中一般不允许常数超过long,故不讨论更大的数值)。
5. 如果一个值为64位,则另一个也会向上转换为64位,无符号的64位值是这些64位值的上限。

B.强制类型转换:
1.强制类型转换不对目标变量进行直接的转换,而是产生了一个中间量

C.Notice:
1.在类型转换中,如果将长类型转换为短类型,则将其截断。
2.对于短类型转换为长类型:
a.无符号类型转换为符号类型,中间不发生符号拓展(原本就没有符号)
b.符号类型向一个更长类型转换,如果符号类型符号位为1(对大部分系统而言为负数),则在长类型中多出的空位中补充1

示例(全部假定为32位机):
1.一个条件判断中的类型转换问题
int flagA =0x7f;
char flagB =0x80;
if( (char)(flagA ^ flagB) == 0xff)
printf("Worked!");

真实执行情况是:
根据A-3,flagB转换为int类型;
根据C-2-b,flagB发生符号拓展。

flagA =0x0000007f;
flagB =0xffffff80;
flagA ^ flagB =0xffffffff
强制类型转换之后,发生截断,于是左侧值此时为 0xff(char)
根据A-4,0xff默认视为int类型,因此0xff(char)需要转换为int类型才能进行 ==比较。
0xff(char)转换后,经过符号拓展,结果为 0xffffffff(int)
故示例表达式始终为假。

PS:本例出自《软件安全的24宗罪——编程缺陷与修复之道》(清华大学出版社,2010.6) P106,本例中略有改动。原例中条件表达式有误。译文为 if( (char) flags ^ LowByte == 0xff),这个表达式应该是漏掉了在 flags ^ LowByte外加一层括号,这种写法不会导致符号拓展(至少根据目前的C++语法是这样),最终条件判断通过,有兴趣的可以试一试。
运算
在数值运算中,主要考虑其二进制变化即可。
A.对于加减运算,需要记住的是:
N位无符号数从0-2^N-1变化,N位有符号数从 -2^(N-1) - 2^(N-1)-1,如果加减运算超过上限或下限,则可以将数值范围视为一个环来推算。
B.对于乘法运算:
1.无符号数的乘法结果超过 UINT_MAX( limits.h )时,即使使用一个足够长的类型接收结果,结果都是错的 【在G++4.4.3中验证】
对于这种情况,一种稳妥的检测方法是检验 b>UINT_MAX/a,更有效的方法是将结果存放在一个更大的整数中,查看是否存在溢出
PS.《软件安全的24宗罪》P105中原文为 a*b>MAX_INT,不知道原作者的表达式是否跟MS编译器有关,或者另有所指。
2.两个无符号短整数相乘,根据之前所述的类型转换原则,两个数最终会得到一个整型数
3.有符号数的乘法应当做额外检查,以确保结果的符号没有变 【未能写出验证】
C.对于无符号32位或者64位整数与带负号的整数之间的运算,及短类型负数(如8位整数-1)与整数的运算,注意类型转换会导致出现意料之外的结果。
例如 -1/UINT_MAX =1; 在该运算中,-1被视为整型数,运算中提升为无符号整数,其值为0xffffffff,与UINT_MAX相等,故结果为1.
此外,取模运算中返回值的符号依赖于具体的实现方案,对于 -1%4,有的得到1,有的得到-1
D.对于比较运算,如果使用有符号数,首先确保待比较的数大于或等于0,然后确定它比上限小(可借助无符号类型完成)。例如:
char* someCopy( char *str) {
char result[20];
int len = strlen(str);
if(len <20) {
...
}
...
}
注意strlen的返回类型为size_t,这通常是一个unsigned long类型,如果传入字符串长度超过了INT_MAX,如当长度为INT_MAX+1时,根据A中的规则可知,这时len会变成一个负数,从而使接下来的len<20始终为真,攻击者如果传入一个2G的数据,则可使其发生溢出。
posted @ 2012-04-12 10:00  Ethan.Tang  阅读(1555)  评论(0编辑  收藏  举报