C++数值运算与类型安全
在理想情况下,对类型的错误应用会导致一些错误,并让我们第一时间发觉;在最糟的情况下,其错误在很久之后才被发现,而且那时我们的系统已经遭受了足够多的攻击。
C/C++几个基本的类型规则:
类型转换规则:
系统将精度高低规定如箭头所示。 char,short -> int -> unsigned -> long ->double <- float A.在运算中的自动转换:
- 任何两个长度低于int类型的值运算时必然转换为int类型(包括==、>=等逻辑判断)
- 等级等于或高于int的同类型运算时,仍为原类型
- 两个不同类型数据参与运算时,两个数据都转换为其中较高级别的类型(两个值都长度低于int则遵从第一条)
- 对于表达式中的常数,默认为int类型,如果该值为正数且超过INT_MAX(在头文件 limits.h中),则默认为 unsigned int (源文件中一般不允许常数超过long,故不讨论更大的数值)。
- 如果一个值为64位,则另一个也会向上转换为64位,无符号的64位值是这些64位值的上限。
强制类型转换:
1.强制类型转换不对目标变量进行直接的转换,而是产生了一个中间量
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++语法是这样),最终条件判断通过,有兴趣的可以试一试。
运算
在数值运算中,主要考虑其二进制变化即可。
加减运算
N位无符号数从0-2^N-1变化,N位有符号数从 -2^(N-1) - 2^(N-1)-1,如果加减运算超过上限或下限,则可以将数值范围视为一个环来推算。
乘法运算
-
无符号数的乘法结果超过 UINT_MAX( limits.h )时,即使使用一个足够长的类型接收结果,结果都是错的 【在G++4.4.3中验证】 对于这种情况,一种稳妥的检测方法是检验 b>UINT_MAX/a,更有效的方法是将结果存放在一个更大的整数中,查看是否存在溢出 PS.《软件安全的24宗罪》P105中原文为 a*b>MAX_INT,不知道原作者的表达式是否跟MS编译器有关,或者另有所指。
-
两个无符号短整数相乘,根据之前所述的类型转换原则,两个数最终会得到一个整型数
-
有符号数的乘法应当做额外检查,以确保结果的符号没有变 【未能写出验证】
-
对于无符号32位或者64位整数与带负号的整数之间的运算,及短类型负数(如8位整数-1)与整数的运算,注意类型转换会导致出现意料之外的结果。 例如 -1/UINT_MAX =1; 在该运算中,-1被视为整型数,运算中提升为无符号整数,其值为0xffffffff,与UINT_MAX相等,故结果为1. 此外,取模运算中返回值的符号依赖于具体的实现方案,对于 -1%4,有的得到1,有的得到-1
-
对于比较运算,如果使用有符号数,首先确保待比较的数大于或等于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的数据,则可使其发生溢出。