C语言的数据类型整型溢出
什么是整型溢出
C语言的整型问题相信大家并不陌生了。对于整型溢出,分为无符号整型溢出和有符号整型溢出。
对于unsigned整型溢出,C的规范是有定义的——“溢出后的数会以2^(8*sizeof(type))作模运算”,也就是说,如果一个unsigned char(1字符,8bits)溢出了,会把溢出的值与256求模。例如:
unsigned char x = 0xff;
printf("%dn", x);
上面的代码会输出:0 (因为0xff 1是256,与2^8求模后就是0)
对于signed整型的溢出,C的规范定义是“undefined behavior”,也就是说,编译器爱怎么实现就怎么实现。对于大多数编译器来说,算得啥就是啥。比如:
signed char x =0x7f; //注:0xff就是-1了,因为最高位是1也就是负数了
printf("%dn", x);
上面的代码会输出:-128,因为0x7f 0×01得到0×80,也就是二进制的1000 0000,符号位为1,负数,后面为全0,就是负的最小数,即-128。
另外,千万别以为signed整型溢出就是负数,这个是不定的。比如:
signed char x = 0x7f;
signed char y = 0x05;
signed char r = x * y;
printf("%dn", r);
上面的代码会输出:123
相信对于这些大家不会陌生了。
整型溢出的危害
下面说一下,整型溢出的危害。
示例一:整形溢出导致死循环
... ...
... ...
short len = 0;
... ...
while(len< MAX_LEN) {
len = readFromInput(fd, buf);
buf = len;
}
上面这段代码可能是很多程序员都喜欢写的代码(我在很多代码里看到过多次),其中的MAX_LEN 可能会是个比较大的整型,比如32767,我们知道short是16bits,取值范围是-32768 到 32767 之间。但是,上面的while循环代码有可能会造成整型溢出,而len又是个有符号的整型,所以可能会成负数,导致不断地死循环。
示例二:整形转型时的溢出
int copy_something(char *buf, int len)
{
#define MAX_LEN 256
char mybuf[MAX_LEN];</pre>
<pre> ... ...
... ...
if(len > MAX_LEN){ // <---- [1]
return -1;
}
return memcpy(mybuf, buf, len);
}
上面这个例子中,还是[1]处的if语句,看上去没有会问题,但是len是个signed int,而memcpy则需一个size_t的len,也就是一个unsigned 类型。于是,len会被提升为unsigned,此时,如果我们给len传一个负数,会通过了if的检查,但在memcpy里会被提升为一个正数,于是我们的mybuf就是overflow了。这个会导致mybuf缓冲区后面的数据被重写。
示例三:分配内存
关于整数溢出导致堆溢出的很典型的例子是,OpenSSH Challenge-Response SKEY/BSD_AUTH 远程缓冲区溢出漏洞。下面这段有问题的代码摘自OpenSSH的代码中的auth2-chall.c中的input_userauth_info_response() 函数:
nresp = packet_get_int();
if (nresp > 0) {
response = xmalloc(nresp*sizeof(char*));
for (i = 0; i < nresp; i )
response[i] = packet_get_string(NULL);
}
上面这个代码中,nresp是size_t类型(size_t一般就是unsigned int/long int),这个示例是一个解数据包的示例,一般来说,数据包中都会有一个len,然后后面是data。如果我们精心准备一个len,比如:1073741825(在32位系统上,指针占4个字节,unsigned int的最大值是0xffffffff,我们只要提供0xffffffff/4 的值——0×40000000,这里我们设置了0×4000000 1), nresp就会读到这个值,然后nresp*sizeof(char*)就成了 1073741825 * 4,于是溢出,结果成为了 0×100000004,然后求模,得到4。于是,malloc(4),于是后面的for循环1073741825 次,就可以干环事了(经过0×40000001的循环,用户的数据早已覆盖了xmalloc原先分配的4字节的空间以及后面的数据,包括程序代码,函数指针,于是就可以改写程序逻辑。关于更多的东西,你可以看一下这篇文章《Survey of Protections from Buffer-Overflow Attacks》)。
示例四:缓冲区溢出导致安全问题
int func(char *buf1, unsigned int len1,
char *buf2, unsigned int len2 )
{
char mybuf[256];
if((len1 len2) > 256){ //<--- [1]
return -1;
}
memcpy(mybuf, buf1, len1);
memcpy(mybuf len1, buf2, len2);
do_some_stuff(mybuf);
return 0;
}
上面这个例子本来是想把buf1和buf2的内容copy到mybuf里,其中怕len1 len2超过256 还做了判断,但是,如果len1 len2溢出了,根据unsigned的特性,其会与2^32求模,所以,基本上来说,上面代码中的[1]处有可能为假的。(注:通常来说,在这种情况下,如果你开启-O代码优化选项,那个if语句块就全部被和谐掉了——被编译器给删除了)比如,你可以测试一下 len1=0×104, len2 = 0xfffffffc 的情况。
这样的例子有很多很多,这些整型溢出的问题如果在关键的地方,尤其是在搭配有用户输入的地方,如果被黑客利用了,就会导致很严重的安全问题。
关于编译器的行为
在谈一下如何正确的检查整型溢出之前,我们还要来学习一下编译器的一些东西。请别怪我罗嗦。
编译器优化
如何检查整型溢出或是整型变量是否合法有时候是一件很麻烦的事情,就像上面的第四个例子一样,编译的优化参数-O/-O2/-O3基本上会假设你的程序不会有整形溢出。会把你的代码中检查溢出的代码给优化掉。
关于编译器的优化,在这里再举个例子,假设我们有下面的代码(又是一个相当相当常见的代码):
int len;
char* data;
if (data len < data){
printf("invalid lenn");
exit(-1);
}
上面这段代码中,len 和 data 配套使用,我们害怕len的值是非法的,或是len溢出了,于是我们写下了if语句来检查。这段代码在-O的参数下正常。但是在-O2的编译选项下,整个if语句块被优化掉了。
你可以写个小程序,在gcc下编译(我的版本是4.4.7,记得加上-O2和-g参数),然后用gdb调试时,用disass /m命信输出汇编,你会看到下面的结果(你可以看到整个if语句块没有任何的汇编代码——直接被编译器和谐掉了):
7 int len = 10;
8 char* data = (char *)malloc(len);
0x00000000004004d4 < 4>: mov $0xa,
您可能感兴趣的文章:
C语言的数据类型整型溢出
python和c语言的区别是什么
php是哪种类型语言?
Python是一门怎样的编程语言
c 跟 python的区别有哪些
Go:06---语言数据类型整体介绍、数据类型检查(reflect包)、数据类型转换
asp clng与cint的区别及防溢出函数(自定义)
c语言学习的简要笔记
C语言 的 整型数据和浮点型数据
python有short类型吗