Basic Integer Overflows
转载:xundi(xundi)
Volume 0x0b, Issue 0x3c, Phile #0x0a of 0x10
|=——————–=[ Basic Integer Overflows ]=———————-=|
|=———————————————————————–=|
|=——————-=[ by blexim <blexim@hush.com> ]=——————-=|
中文翻译整理: Sam@sst <Sam@vertarmy.org>
airsupply@sst <airsupply@vertarmy.org>
1: 目录
1.1 什么是整数?
1.2 什么是整数溢出?
1.3 为什么那是危险的?
2: 整数溢出
2.1 Widthness 溢出
2.1.1 Exploiting
2.2 运算(Arithmetic)溢出
2.2.1 Exploiting
3: 符号类型的问题
3.1 它们看起来像什么?
3.1.1 Exploiting
3.2 符号类型的问题导致的整数溢出
4: 真实的例子
4.1 整数溢出类
4.2 符号问题类
–[ 1.0 目录
在这篇文章中我将会讲述2种由于不安全编程引发的问题,导致一些恶意的用户修改受影响的进程
改变程序执行流程.这2种类型的问题都是由于某一程序变量包含一个不可预料的值,因此这种类型
的问题不同于那些程序内存被改写的问题,比如:缓冲区溢出,格式化溢出.所有文章中给出的程序例
子都是用C语言编写,所以需要读者熟悉C语言.一些整数在内存中存储方法的知识也是会有所帮助
的,但不是全部.
—-[ 1.1 什么是整数?
一个整数, 在计算范围内, 是一个变量可能是一个没有小数部分的实数的.在系统上被编译处理后,
整型和指针的尺寸一般是相同的(比如: 在32位的系统中,例如i386, 一个整数是32字节长,在64位的
系统中,例如SPARC,一个整数是64字节长).然而一些编译器不使整型和指针为同样尺寸 ,所以为了
通俗易懂,所有这里谈到的例子是在32位的系统环境和32位的整数,长度和指针.
整数,如同所有的变量只是内存的一个区域, 当我们谈及关于整数,我们通常用10进制来表示它们.
换句话说也就是人们经常使用的一种编码方式.计算机是基于数字的,不能直接处理10进制,所以在
计算机中整数是以2进制的方式存储的.2进制是另一种编码方式,它们只有2个数字,1和0,与之不同
的10进制是用10个数字来表示的.2进制和10进制,16进制是广泛的被使用的在电脑中能够很简单的
转换2进制和16进制.
因为存储负数通常是必要的,这样就需要一种机制仅仅用位来代表负数,这种方法已经完成了,通过
一个变量的最高为来决定正负.如果最高位置1,这个变量就被解释为负数; 如果置0,这个变量就解释
为整数.这会导致一些混淆,这可以说明一些符号类型问题的概念,因为不是所有的变量都是有符号
之分的,意思就是说并不是所有的类型都需要使用MSB来区分正负.这些变量被定义为无符号,它只能
被赋予正数值.如果变量可正可负,可以被称做是无正负的。
—-[ 1.2 什么是整数溢出?
既然一个整数是一个固定的长度 (在本篇文章中使用32位),它能存储的最大值是固定的,当
尝试去存储一个大于这个固定的最大值时,将会导致一个整数溢出.在ISO C99的标准中讲
到整数溢出将会导致"不能确定的行为",也就是说编译器遵从了这个的规则,那就是完全忽略
溢出而退出这个程序.很多编译器似乎忽略了这个溢出,结果是一个意想不到的错误值被存储.
—-[ 1.3 为什么那是危险的?
整数溢出是不能被立即察觉,因此没有办法去用一个应用程序来判断先前计算的结果是否实
际上也是正确的.如果是用来计算缓冲区的大小或者计算数组索引排列的距离,这会变的危险.
当然很多整数溢出并不是都是可利用的,因为并没有直接改写内存,但是有时,他们可导致其他
类型的bugs,缓冲区溢出等.而且,整数溢出很难被发现,因此,就算是审核过的代码也会产生意外。
–[ 2.0 整数溢出
所以当一个整数溢出已经发生时会发生什么呢? ISO C99 是这样说的:
"A computation involving unsigned operands can never overflow,
because a result that cannot be represented by the resulting unsigned
integer type is reduced modulo the number that is one greater than
the largest value that can be represented by the resulting type."
译者注:
大致的意思是:
涉及到无符号操作数计算的时候从不会溢出,因为结果不能被无符号类型表示的时候,
就会对比该类型能表示的最大值还大的数求余.这样就能用该结果来表示这种类型了.
NB:取模的运算方法是2个数相除取余数的值
例子:
10 modulo 5 = 0
11 modulo 5 = 1
所以在减轻体重法里面,一个大数被和(最大的int值 + 1)取模,在C语言中,取模操作的符号是%.
</NB>
这里有一个字节是多余的,可能是一个很好的象征性例子证明我们说的"导致不确定的行为".
我们有2个无符号的整数,a和b, 2个数都是32位字节长,我们赋值给a 一个32为整数的最大值,
b被赋值为1.然后我们让a和b相加然后存储结果到第3个无符号32位的整数r:
a = 0xffffffffff
b = 0x1
r = a + b
现在,当相加起来的结果不能用32位的的值来表示,结果,为了和ISO 标准一致,被和0x100000000
取模.
r = (0xffffffff + 0x1) % 0x100000000
r = (0x100000000) % 0x100000000 = 0
减轻体重法的取模算法只能计算低于32位的计算结果,所以整数溢出导致结果被截断到一个范围,
通常用一个变量来存储这个结果。这个经常被称作一个"环绕"(译者注:类似成语中"否极泰来"的
意思,在这篇文章中我们理解为一个正数大到了极点就会变成负数,负数小到了极点就会变成正数),
作为这里的结果,就出现了环绕到0.
—[ 2.1 Widthness 溢出
所以整数溢出是尝试存储一个大数到一个变量中,由于这个变量太小不足以存储该大数导致的结
果.用最简单的例子来说明这个问题,存储一个大变量到一个小变量中去:
/* ex1.c – loss of precision */
#include <stdio.h>
int main(void){
int l;
short s;
char c;
l = 0xdeadbeef;
s = l;
c = l;
printf("l = 0x%x (%d bits)\n", l, sizeof(l) * 8);
printf("s = 0x%x (%d bits)\n", s, sizeof(s) * 8);
printf("c = 0x%x (%d bits)\n", c, sizeof&169; * 8);
return 0;
} /* EOF */
让我们看看执行结果
nova:signed {48} ./ex1
l = 0xdeadbeef (32 bits)
s = 0xffffbeef (16 bits)
c = 0xffffffef (8 bits)
当我们把一个大的变量放入一个小变量的存储区域中,结果是只保留小变量能够存储的位,而其他的位
都被截短了.
有必要在这里提及整数进位.当一个计算包含大小不同的操作数时,通过计算较小的操作数会被进位到
较大的操作数.如果结果将被存储在一个较小的变量里,这个结果将会被重新减小,直到较小的操作数
可以容纳.
这个例子里:
int i;
short s;
s = i;
这里计算结果将被赋给一个不同尺寸的操作数,将发生的是变量s被提升为一个整型(32位),然后整数i的
内容被拷贝给新的提升后的s,接着,提升后的变量内容为了能存在s里面被降低回16位.如果超过了s能
存储的最大值降位将导致结果被截断..
——[ 2.1.1 Exploiting
整数溢出并不像普通的漏洞类型, 它们不允许直接的改写内存或者直接改变程序的控制流程.而是更加精巧.
程序的所有者面临的事实是没有办法在进程里面检查计算发生后的结果,所以有可能计算结果和正确
结果之间有一定的偏差.就因为这样,大多数的整数溢出不能被利用,即使这样,在一些情况下,我们还是有可能
强迫一个变量包含错误的值,从而在后面的代码中出现问题.
由于这些漏洞的精巧,导致有大量的地方能被利用,所以我就不尝试覆盖到所有能被利用的环境.相反
,我将提供一些能被利用的情况,希望读者能自己来探索.我将提供一些能被利用的情况的例子.
Example 1:
/* width1.c – exploiting a trivial widthness bug */
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]){
unsigned short s;
int i;
char buf[80];
if(argc < 3){
return -1;
}
i = atoi(argv[1]);
s = i;
if(s >= 80){ /* [w1] */
printf("Oh no you don’t!\n");
return -1;
}
printf("s = %d\n", s);
memcpy(buf, argv[2], i);
buf[i] = ‘\0’;
printf("%s\n", buf);
return 0;
}
然而像这种构造可能从来不会在真实的代码里面出现.这里只是一个简单的例子,让我们看看执行后
的输出:
nova:signed {100} ./width1 5 hello
s = 5
hello
nova:signed {101} ./width1 80 hello
Oh no you don’t!
nova:signed {102} ./width1 65536 hello
s = 0
Segmentation fault (core dumped)
程序从命令行参数中得到一个整数值存放在整形变量i当中,当这个值被赋予unsigned short类型
的整数s,如果这个值大于unsigned short类型所能够存储的将被截短.(比如 这个值大于65535,
unsigned short存储的范围是0 – 65535),因次,可能绕过代码中的[w1]部分的边界检测,导致缓冲
区溢出.只要使用一般的栈溢出技术就能够溢出利用这个程序.
—-[ 2.2 运算(Arithmetic) 溢出
在2.0章节中讲到,如果尝试存储一个大于整数变量最大值的整数到整数变量中,这个值将被截短.如
果存储值是一个运算操作,稍后使用这个结果的程序的任何一部分都将错误的运行,因为这个计算结
果是不正确的.这个可以完整的解释"环绕"的例子:
/* ex2.c – an integer overflow */
#include <stdio.h>
int main(void){
unsigned int num = 0xffffffff;
printf("num is %d bits long\n", sizeof(num) * 8);
printf("num = 0x%x\n", num);
printf("num + 1 = 0x%x\n", num + 1);
return 0;
}
/* EOF */
程序执行的结果:
nova:signed {4} ./ex2
num is 32 bits long
num = 0xffffffff
num + 1 = 0x0
Note:
聪明的读者可能注意到了0xffffffff 是10进制中的-1,所以看起来我们只是做了以下操作1 + (-1) = 0
同时,这是一种方法可以看看它正在做了什么,可能会导致一些混淆,因为在这里这个变量是无符号的,
因此所有基于它的计算都是无符号的.当它发生了,很多整数溢出都是依赖符号运算的,正如下面这个
例子(2个操作数都是32位的变量):
-700 + 800 = 100
0xfffffd44 + 0x320 = 0x100000064
因为加出来的结果超出了整数变量的范围,最小的32位就会被当作结果来使用。这些最小
的32位是0x64,相当于十进制的100。
</note>
如果一个整数缺省是有符号的,一个整数溢出能导致这个符号变化,这将对随后的代码发生有
趣的影响。正如下面这个例子:
/* ex3.c – change of signedness */
#include <stdio.h>
int main(void){
int l;
l = 0x7fffffff;
printf("l = %d (0x%x)\n", l, l);
printf("l + 1 = %d (0x%x)\n", l + 1 , l + 1);
return 0;
}
/* EOF */
程序执行结果:
nova:signed {38} ./ex3
l = 2147483647 (0x7fffffff)
l + 1 = -2147483648 (0x80000000)
在这里整数被初始化为相当于最高的有符号的长整形的值,当它的值加1时,很重要的一个字节
(标志符号位的)被重新设置,这个整数将被解释成一个负数.加法不仅仅是一个运算方法,它能导
致一个整数溢出,几乎任何的改变变量的值的操作都会引发整数溢出,正如下面的这个例子:
/* ex4.c – various arithmetic overflows */
#include <stdio.h>
int main(void){
int l, x;
l = 0x40000000;
printf("l = %d (0x%x)\n", l, l);
x = l + 0xc0000000;
printf("l + 0xc0000000 = %d (0x%x)\n", x, x);
x = l * 0x4;
printf("l * 0x4 = %d (0x%x)\n", x, x);
x = l – 0xffffffff;
printf("l – 0xffffffff = %d (0x%x)\n", x, x);
return 0;
}
/* EOF */
输出:
nova:signed {55} ./ex4
l = 1073741824 (0x40000000)
l + 0xc0000000 = 0 (0x0)
l * 0x4 = 0 (0x0)
l – 0xffffffff = 1073741825 (0x40000001)
相加导致一个整数溢出,这也恰恰和第一个例子相同,接下来是一个乘法操作,虽然看起来似乎不一样.
在这2个例子中,运算结果太大以至无法保存成一个整数,所以它被缩短了,就如前面表述的一样.减法稍许
有些不同,因为它引起的是下溢,而不是溢出.如果尝试去储存一个比整数可容纳的最小值更小的值,将会
引起一个"环绕".这样,我们可以强迫更改一个加法变成减法,一个乘法变成除法,或者一个减法变成加法.
——[ 2.2.1 Exploiting
数值溢出能被利用的最通常的方法是当计算结果给用来分配多大的缓冲区.通常一个程序必需分配空间
给一个数组对象,所以用malloc(3)或calloc(3)函数来保留空间并且通过用元素的数量乘以对象的尺寸来
计算需要多大的空间.前面已经提到,如果我们能控制这些操作数中的一个(元素的数量或对象的尺寸),
我们或许能够导致错误分配缓冲区,接下来的代码片段体现了这点:
int myfunction(int *array, int len){
int *myarray, i;
myarray = malloc(len * sizeof(int)); /* [1] */
if(myarray == NULL){
return -1;
}
for(i = 0; i < len; i++){ /* [2] */
myarray[i] = array[i];
}
return myarray;
}
这看起来无害的函数能带来系统的崩溃就因为没有检查len参数.在(1)处通过提供一个足够大的值
给len,乘法操作后能够被溢出,这样我们随意控制缓冲区的大小.通过选择一个合适的值给len,我们
能使得(2)处的循环写缓冲区的后面,这导致了一个heap溢出.通过改写malloc的控制结构能够在正
常的运行里面插入可执行的任意代码,但是这超出了本文的讨论范围.
另一个例子:
int catvars(char *buf1, char *buf2, unsigned int len1,
unsigned int len2){
char mybuf[256];
if((len1 + len2) > 256){ /* [3] */
return -1;
}
memcpy(mybuf, buf1, len1); /* [4] */
memcpy(mybuf + len1, buf2, len2);
do_some_stuff(mybuf);
return 0;
}
在这个例子中,通过给合适的值给len1和len2能够欺骗过(3)处的检查,那样将导致加法的溢出并且
包含了一个低值.举个例子,看下面的数值:
len1 = 0x104
len2 = 0xfffffffc
当加在一起时将导致一个包含了0x100的结果(整数 256),这能通过(3)处的检查,然后在(4)处的
memcpy(3)将拷贝数据到缓冲区的后面.
-[ 3 符号问题
当一个无符号的变量被看成有符号时,或者当一个有符号的变量被看成无符号时,符号问
题漏洞就发生了.这种类型的反应发生是因为在电脑内部有符号的变量和无符号的变量存储
时是没有区别的.最近,一些符号漏洞在FreeBSD和OpenBSD的内核中发生,因此我们可以很
容易的例举许多例子.
—-[ 3.1 它们看起来像什么?
符号问题可以是多种多样的,但是应该留意一下以下的几点:
out for are:
有符号整数的比较
有符号整数的运算操作
无符号整数和有符号整数的对比.
这里有一个关于符号问题漏洞的典型例子:
int copy_something(char *buf, int len){
char kbuf[800];
if(len > sizeof(kbuf)){ /* [1] */
return -1;
}
return memcpy(kbuf, buf, len); /* [2] */
}
这里的问题在于memcpy 使用无符号整数作为len参数,但是在之前的数据越界检测使用了有符号
整数,我们可以提供一个负数的len,可以绕过[1]的检测,但是这个值同样被使用在[2]的memcpy函
数的参数里面,len可能被转换成一个非常大的正整数,导致kbuf缓冲区后面的数据被重写.
当进行运算操作时,有符号和无符号的混淆可能产生另一个问题,参考下面的这个例子:
int table[800];
int insert_in_table(int val, int pos){
if(pos > sizeof(table) / sizeof(int)){
return -1;
}
table[pos] = val;
return 0;
}
因为这行
table[pos] = val;
等于
*(table + (pos * sizeof(int))) = val;
我们可以看出,这里的问题是代码没有预计到在加法里会有一个负的操作数,它预计(table +
pos)比table大,因此提供一个负的pos值将引起程序的运行状况在预料之外,也就不能正
常运行下去了.
——[ 3.1.1 Exploiting
这种类型的漏洞想要被利用还是有问题的,是因为当把有符号的整数转换成无符号的整数,这
个值将会变的非常大,比如: -1 用16进制表示为 0xffffffff.当它被转换成无符号时,它的值
用整数来表示可能是(4,294,967,295),如果把这个值传递给类似memcpy的函数(memcpy (char*
dest, char *src, int len),memcpy这类的函数只会单一的执行从 src中拷贝len大小的数据
到dest中,而不会考虑dest是否能容纳,或者src是否能取出这么多数据).memcpy 函数将会尝试
拷贝近4GB的数据到目的缓冲区.很明显的,这将会导致段错误,就算不会,也只会使堆栈中充满垃
圾数据.有时,通过传递一个很小的值到源地址有可能解决这个问题,但是这不是总是可行的.
—-[ 3.2 符号问题导致的缓冲区溢出
有时,可能溢出一个整数而导致环绕成一个负数.既然应用程序没有预料会得到这样一个值,于是
就可能引发一个如前所述的符号漏洞.
一个这种类型漏洞的例子:
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;
}
/* packet begins with length information */
memcpy(&size1, buf1, sizeof(int));
memcpy(&size2, buf2, sizeof(int));
size = size1 + size2; /* [1] */
if(size > len){ /* [2] */
return -1;
}
memcpy(out, buf1, size1);
memcpy(out + size1, buf2, size2);
return size;
}
这个例子展示给我们在网络后台程序中有时会发生的事情,尤其,当length信息是从网络数据包的一部分传递而来
的(换句话说:可能是一个未知用户提交的数据),[1]中的加法,用于检查数据没有超出了out缓冲区的范围,在设置size1
和size2相加时可能会产生一个很大的值,这个值会引起size变量环绕成一个负数.例题值可能是:
size1 = 0x7fffffff
size2 = 0x7fffffff
(0x7fffffff + 0x7fffffff = 0xfffffffe (-2)).
当上面操作执行时,绕过了[2]的越界检测,而且导致很多out缓冲区的数据被故意改写(事实上:是任意的内存被改写,
在(out + size1)的目标参数中,是我们可以控制的,允许我们指向任意的内存区域).
这个漏洞只要精确的构造数据是可以被利用的,和符号类型问题的漏洞一样,同样也有困扰的问题存在 – 比如产生的负数值将会
解释成一个非常大的正整数,那么在执行memcpy的时候就会很容易的导致段错误,但是这类问题可以利用bsd memcpy反向拷贝的
原理来利用.
–[ 4 真实的例子
这里有很多真实的应用程序包含整数溢出和符号问题,包括网络后台程序,操作系统内核的
的例子.
—-[ 4.1 整数溢出
这里有一个例子来自linux的内核的安全模块里(不可利用的),这个代码在内核中运行:
int rsbac_acl_sys_group(enum rsbac_acl_group_syscall_type_t call,
union rsbac_acl_group_syscall_arg_t arg)
{
…
switch(call)
{
case ACLGS_get_group_members:
if( (arg.get_group_members.maxnum <= 0) /* [A] */
|| !arg.get_group_members.group
)
{
…
rsbac_uid_t * user_array;
rsbac_time_t * ttl_array;
user_array = vmalloc(sizeof(*user_array) *
arg.get_group_members.maxnum); /* [B] */
if(!user_array)
return -RSBAC_ENOMEM;
ttl_array = vmalloc(sizeof(*ttl_array) *
arg.get_group_members.maxnum); /* [C] */
if(!ttl_array)
{
vfree(user_array);
return -RSBAC_ENOMEM;
}
err =
rsbac_acl_get_group_members(arg.get_group_members.group,
user_array,
ttl_array,
arg.get_group_members.max
num);
…
}
在这个例子中,[A]的数据越界检查部分不能足够的防止[B][C]能造成的整数溢出,绕过这个检查只需
提供一个足够高的(比如:高于 0xffffffff / 4 )的值给 arg.get_group_members.maxnum,我们能引
起[B]和[C]处和这个值相乘的整数溢出,并且强制ttl_array 和user_array分配的缓冲区要小于程序
中预期的大小.因此rsbac_acl_get_group_members函数拷贝我们能控制的数据到这个缓冲区里,可能
改写tty_array和user_array指向的缓冲区后面的一些数据.在这里,程序中使用了vmalloc 分配缓冲
区,所以尝试改写tty_array和user_array指向的缓冲区后面的一些数据仅仅只是造成一个错误,所以
并不能被利用,尽管如此,它提供了一个例子以提醒我们在现实生活中编写代码要注意这些问题.
另外一个例子是最近SUN rpc XDR库中所带的xdr_array()函数中存在一个整数溢出漏洞的问题(见 I
SS X-Force的安全公告).在这个例子里面,用户提交的数据用来计算动态分配缓冲区的大小,该缓冲区
用来存储用户提交的数据,看如下有问题的代码:
bool_t
xdr_array (xdrs, addrp, sizep, maxsize, elsize, elproc)
XDR *xdrs;
caddr_t *addrp; /* array pointer */
u_int *sizep; /* number of elements */
u_int maxsize; /* max numberof elements */
u_int elsize; /* size in bytes of each element */
xdrproc_t elproc; /* xdr routine to handle each element */
{
u_int i;
caddr_t target = *addrp;
u_int c; /* the actual element count */
bool_t stat = TRUE;
u_int nodesize;
…
c = *sizep;
if ((c > maxsize) && (xdrs->x_op != XDR_FREE))
{
return FALSE;
}
nodesize = c * elsize; /* [1] */
…
*addrp = target = mem_alloc (nodesize); /* [2] */
…
for (i = 0; (i < c) && stat; i++)
{
stat = (*elproc) (xdrs, target, LASTUNSIGNED); /* [3] */
target += elsize;
}
正如你看到的,当提供一个很大的值给 elsize 和 c (sizep),可能引发[1]乘法操作导致整数溢出,并且
使nodesize的值小于应用程序中所预期的值,既然nodesize被用于[2]的缓冲区分配操作,该缓冲区将会被
错误的定位大小,导致[3]的堆溢出.更多关于这个漏洞的信息,见参考资料中CERT安全咨询公告.
—add by Sam@sst
还有一个关于整数溢出导致堆溢出的很典型的例子,OpenSSH Challenge-Response SKEY/BSD_AUTH 远程缓冲区溢出漏洞.
下面这段有问题的代码摘自OpenSSH的代码中的auth2-chall.c中的input_userauth_info_response() 函数:
unsigned int nresp,
nresp = packet_get_int();
if (nresp > 0) {
response = xmalloc(nresp * sizeof(char*)); [1]
for (i = 0; i < nresp; i++)
response[i] = packet_get_string(NULL);
}
packet_get_int是从用户提交的数据中取得一个int值,保存在整数变量nresp中,由于没有考虑到[1]处的乘法会导致程序的异常
(在32位的系统环境中,unsigend int的最大值是0xffffffff,我们只要提供0xffffffff/4 的值,比如0x40000000).程序执行如下
操作.比如:
0x40000000 + 1 (nresp的值)
= 0x40000001 (满足 nresp > 0的要求)
0x40000001 * 4 (分配 nresp * 4 的缓冲区空间)
= 0x100000004 (计算结果已经超出了32位的值)
由于以上的值已经超出了32位的值,将被截断成0x00000004.也就是说其实 xmalloc 只在堆上分配了4个字节的缓冲区空间.接下来
的for 循环操作:
for (i = 0; i < 0x40000001; i++ )
response[i] = packet_get_string(NULL);
packet_get_string函数从用户数据中读取4个字节的内容写入 response + i的地址.经过0x40000001的循环,用户的数据早已覆盖
了xmalloc原先分配的4字节的空间以及后面的数据(如同样分配在heap区的函数指针等等来获得程序流程的控制权).
—-[ 4.2 符号问题的漏洞
最近,几个freebsd 内核符号问题的漏洞给我们带来了一些光亮.这些漏洞存在于很多个系统调用里面,
当提供一个负数的length参数给这些系统调用,可以绕过读取大块内核内存的限制. getpeername (2)
函数就有这个问题,如下:
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)的检测没有把len可能是负数的事实考虑进去,在这个例子里,
MIN宏将会一直返回len.当这个负数的参数传递给copyout函数,它会被解释成一个巨大的正整数导致copyout
函数拷贝近4GB的内核内存到用户空间.
–[ 结论
整数溢出是非常危险的,部分原因是因为它在发生后不可能被发现.如果一个整数溢出发生了,应用程序并不知道它的
计算是错误的.因此应用程序在在假定它是正确的情况下,会继续运行下去.即便如此,整数溢出还是很难被利用的.通
常是几乎不可能被利用.它们能引起意想不到的结果,在安全的系统中,这种结果绝对不是一件好事.
–[ 参考资料
CERT advisory on the XDR bug:
http://www.cert.org/advisories/CA-2002-25.html
FreeBSD advisory:
http://online.securityfocus.com/advisories/4407
Phrack60 另外一篇关于整数溢出的文章:
http://www.phrack.org/phrack/60/p60-0x09.txt