您的位置:首页 > 理论基础

[深入理解计算机系统][2.2.5]有符号数和无符号数不匹配导致的安全漏洞

2013-12-24 19:58 513 查看
    C语言支持所有整型数据类型的有符号和无符号运算。尽管C语言标准没有指定有符号数要采用某种表示,但是几乎所有的机器都使用补码(Two's Complement)。

    C语言允许无符号数和有符号数之间的转换,转换的原则是底层的位表示保持不变(只是把二进制表示解释为另一种类型)。关于signed和unsigned转换的具体过程,可以参考《深入理解计算机系统》第二章相应章节。如果一个表达式中同时有singed和unsigned变量参与运算,C语言会隐式地将signed转换为unsigned。这时可能会导致很难察觉到bug,造成严重的安全漏洞。

    首先来看第一个例子。下面几个表达式

-1 < 0 ;   //return true
-1 < 0U;  //may return false

2147483647  > -2147483647 - 1; //return true
2147483647U > -2147483647 - 1; //may return false
    第二个表达式中右边为unsigned类型,因此C标准会先将左边-1隐式转换为unsigned类型,结果为无符号0xFFFFFFFF(如果用4byte表示),显然0xFFFFFFFF大于0U,因此返回false。第四个表达式同理。

    再来看第二个例子。

/* Warning: This is buggy code */
float sum_element(float a[], unsigned lenght)
{
int i;
float sum = 0;

for (i=0; i<=lenght-1; i++) {
sum += a[i];
}

return sum;
}
当参数length等于0时,运行这段代码应该返回0.0。但实际上,运行时会遇到一个存储器错误。原因是length声明为unsigned类型,当length=0是,计算0-1将是无符号运行,得到MAX_INT,此时 i<=length-1为真,代码将访问数组a的非法元素。

第三个例子是FreeBSD开源操作系统项目2002年发现的一个安全漏洞,简化版本如下:

/* Declaration of library function memcpy */
void *memcpy(void *dest, void *src, size_t n);

/* Kernel memory region holding user-accessible data */
#define KSIZE 1024
char kbuf[KSIZE];

/* Copy at most maxlen bytes from kernel region to user buffer */
int copy_from_kernel(void *user_dest, int maxlen)
{
int len = KSIZE < maxlen ? KSIZE : maxlen;
memcpy(user_dest, kbuf, len);

return len;
}
如果调用copy_from_kernel时maxlen使用了负值,这个负值会传递给memcpy函数,而memcpy函数的第三个参数为size_t(32位机器上典型的定义为unsigned int)类型,该负值会被隐式转换为一个很大的正整数,用户程序就会访问非法内存或未被授权的内核存储区域!要修正这个bug,只要将maxlen的类型声明为size_t,即与memcpy的参数类型保持一致。同时,本地变量len的类型和返回值类型也应该声明为size_t。
我们已经看到,由于unsigned运算的细微特性,尤其是signed到unsigned隐式转换,会导致严重的错误或漏洞。避免这一类错误的一种方法是绝不使用无符号数。实际上,除C以外,很少有语言支持无符号整数。很明显,这些语言的设计中认为它们带来的麻烦要远多于益处。当然,无符号数也并非全无用处,在一些特殊场合,例如在用字表示位的集合时,无符号数是非常方便和高效的。

总的来说,能用signed类型代替unsigned类型的地方,尽量用signed类型,可以减少大量难以察觉的代码bug和安全漏洞。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: