您的位置:首页 > 其它

整数相加溢出判断问题

2015-07-24 16:00 323 查看
查阅参考这篇文章进行理解:http://phrack.org/issues/60/10.html

整数相加溢出后一般不会报错。含符号整数,如果溢出则将符号位置位,两个正数相加的结果可能最后成了负数,这在有些场景中会引发一系列的bug。无符号整数相加,溢出的时候会利用最大数加1作为模值(UINT_MAX+ 1)取模,同样会引起一系列的bug。比如下面无符号数溢出的情况:

#include<stdio.h>
#include<stdlib.h>
int main()
{

unsigned int var_a,var_b;
unsigned int result;

var_a=0xffffffff;
var_b=0x3;

result=var_a+var_b;

printf("result:%#x\n",result);

}
运算的结果:



在32位系统中,其运算的过程如下,保证输出的数字取的是低32位数:

result = (0xffffffff + 0x3) % 0x100000000;

result = (0x100000002) % 0x100000000=0x2;

符号整数之间的相加减也是同样的操作,比如800-700.按照一般处理,我们看到的就是800-700=100.但是计算机中负数以补码的形式存在,如果将这两个数转换成无符号数,则可表达成下面的形式:

0xfffffd44 + 0x320 = 0x100000064。出现了溢出按照我们上面的约定取低32位为有效位,0xfffffd44 + 0x320 = (0x100000064)% 0x100000000=0x64=100.

还有几个算术运算溢出的例子,其处理过程和上面提到的过程类似:

#include<stdio.h>
#include<stdlib.h>
int main()
{
int l, x;

l = 0x40000000;
printf("l              =  %d %#x\n", l, l);
x = l + 0xc0000000;
printf("l + 0xc0000000 =  %d %#x\n", x, x);
x = l * 0x4;
printf("l * 0x4        =  %d %#x\n", x, x);
x = l - 0xffffffff;
printf("l - 0xffffffff =  %d %#x\n", x, x);

}
其运算的结果:



为了说明溢出取的是数的位数上的数,下面这段代码是一个测试:

#include<stdio.h>
#include<stdlib.h>
int main()
{
int var_int;
short var_short;
char var_char;

var_int = 0xdeadbeef;
var_short = var_int;
var_char = var_int;

printf("var_int  = %#x;%d bits\n", var_int, sizeof(var_int) * 8);
printf("var_short= %#x;%d bits\n", var_short, sizeof(var_short) * 8);
printf("var_char = %#x;%d bits\n", var_char, sizeof(var_char) * 8);
}
运算的结果:



从上面的结果可以看出,short类型与char类型的变量均存在溢出,溢出的部分全部赋值为1。则short类型前16位全部为1,char类型前24位全部为1.也就是大的数字被存储到小的数字中的时候将会被截断。

还有一种情况是无符号的数字被当成有符号的数使用,有时候也会引起越界溢出。有人在FreeBSD、OpenBSD的内核源代码均找到了这样的bug。下面代码表示出了这种bug:

int copy_something(char *buf, int len){
char kbuf[800];

if(len > sizeof(kbuf)){
return -1;
}
return memcpy(kbuf, buf, len);
}
这里执行memcpy的时候,len为符号整数,而memcpy实际需要的参数为无符号整数。如果传值为负数,则导致len的值会很大,导致访问程序中未定义的部分被重写,导致无法预料的错误,如果传递的值为-1,无符号表达-1的十六进制为0xFFFFFFFF,这个表示的整数为4294967295.如果把这个值传递给memcpy,则将试图将4GB的数据拷贝到目标buffer,这将会引起段错误,如果没有则会使很多的栈和堆数据成为垃圾。

还有下面这个程序:

int table[800];
int insert_in_table(int val, int pos){
if(pos > sizeof(table) / sizeof(int)){
return -1;
}
table[pos] = val;
return 0;
}
pos为有符号数,传递负数同样会引起程序无法预料的错误。

上面是传值引起的bug,也有运算中引起bug的情况,下面一段代码显示了这种情况:

int get_two_vars(int sock, char *out, int len){
char buf1[512], buf2[512];
unsigned int size1, size2;
int size;
if(recv(sock, buf1, sizeof(buf1), 0) < 0){
return -1;
}
if(recv(sock, buf2, sizeof(buf2), 0) < 0){
return -1;
}
memcpy(&size1, buf1, sizeof(int));
memcpy(&size2, buf2, sizeof(int));
size = size1 + size2;

if(size > len){
return -1;
}
memcpy(out, buf1, size1);
memcpy(out + size1,buf2,size2);
return size;
}
上面代码中,显示了一些网络进程中发生的情况,特别是当长度信息是包的一部分。代码中利用memcpy将buf1中一个int大小的数据拷贝到size1,再将buf2中一个int大小的数据拷贝到size2。如果值是如下的情况:

size1=0x7fffffff;

size2=0x7fffffff;

然后size=size1+size2=0x7fffffff+0x7fffffff=0xfffffffe(-2)。然后out缓存区中写入的数据要比预期的多。负数值传入改变为无符号数的时候将是很大的正数,这将引起段错误。

下面看下实际应用中的bug,下面是getpeername中的一个bug:

static int  getpeername1(p, uap, compat)
struct proc *p;
register struct getpeername_args /* {
int	fdes;
caddr_t asa;
int	*alen;
} */ *uap;
int compat;
{
struct file *fp;
register struct socket *so;
struct sockaddr *sa;
int len, error;
...
error = copyin((caddr_t)uap->alen, (caddr_t)&len, sizeof (len));
if (error) {
fdrop(fp, p);
return (error);
}
...
len = MIN(len, sa->sa_len);    /* [1] */
error = copyout(sa, (caddr_t)uap->asa, (u_int)len);
if (error)
goto bad;
gotnothing:
error = copyout((caddr_t)&len, (caddr_t)uap->alen, sizeof (len));
bad:
if (sa)
FREE(sa, M_SONAME);
fdrop(fp, p);
return (error);
}
这段代码中[1]处存在一个bug,当len为负数的时候,len最后的取值也是负数。当这个值被传到copyint函数中时,将会被转换为一个很大的无符号整数,会引起段错误。

从上面这些例子可以看出来,整数溢出错误是相当危险的,但是溢出发生后很不容易检测出来,溢出发生,有时候程序无法得知其计算是否正确,将会继续执行下面的语句,这将引起程序无法预料的行为,这在一个安全运行的系统是极其不好的。

了解了上面的这些,那么怎么判断整数溢出呢?

判断有符号数是否溢出,只需要将其转换为无符号数然后再与0x7fffffff相比较,其实这个值在limit.h也有定义为INT_MAX,为有符号数字的最大值。我们有下面的函数判断:

int signed_num_overflow(unsigned int var_a,unsigned int var_b)
{
unsigned int result;
result=var_a+var_b;
if(result > INT_MAX)
return 1;
else
return 0;
}


那么无符号的数怎么判断溢出呢?在32位系统中,最大的数是0xffffffff。这个数字在limit.h中的定义为UINT_MAX。那么var_a<UINT_MAX,并且var_b<UINT_MAX,检测输入的两个数是否溢出,如果两个数相加溢出则有UINT_MAX-var_a<var_b。代码如下:

int unsigned_num_overflow(unsigned int var_a,unsigned int var_b)
{
if(UINT_MAX-var_a < var_b)
return 1;
else
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: