强制类型转换之数值溢出


前言

     C语言允许在各种不同的数据类型之间做强制类型转换。但部分数据类型之间的隐式转换会导致错误或者漏洞,尤其是有符号数到无符号数的隐式类型转换。避免这类错误的一种方法是绝不使用无符号数。实际上,除了C语言之外很少有语言支持无符号整数。

强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。
——《深入理解计算机系统》

对于大多数C语言的实现,处理同样字长的有符号数和无符号数之间的相互转换的一般规则是:数值可能会变,但是位模式不变。
——《深入理解计算机系统》

有关计算机内原码、反码、补码表示,参见 计算机原码、反码、补码、机器数、真值解析


案例一、有符号整型转无符号整型(初级)

考虑如下代码:

short int v = -12345;
unsigned short uv = (unsigned short) v;
printf("v = %d, uv = %u\n", v, uv);

在一台采用补码的机器上,会产生如下输出:

v = -12345, uv = 53191

计算机采用补码形式

  • 有符号短整型数 v 在计算机中存储的二进制表示为 [1100 1111 1100 0111](补码)
  • 无符号短整型数 uv 在计算机中存储的二进制表示为 [1100 1111 1100 0111](补码)

结论

在计算机内部,同一串二进制序列,用不同方式解读时,得到的结果也不同。同样的一个序列 [1100 1111 1100 0111],当做有符号短整型解读时,为 -12345;当做无符号短整型数解释时,为 53191。强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。

案例二、无符号整型转有符号整型(初级)

考虑如下代码:

unsigned u = 4294967295u;  /* 无符号整型最大值 */
int tu = (int) u;
printf("u = %u, tu = %d\n", u, tu);

在一台采用补码的机器上,会产生如下输出:

u = 4294967295, uv = -1

结论

在计算机内部,同一串二进制序列,用不同方式解读时,得到的结果也不同。同样的一个序列 [1111 1111 1111 1111 1111 1111 1111 1111],当做无符号整型解读时,为 4294967295;当做有符号整型数解释时,为 -1。强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。

案例三、FreeBSD 开源项目漏洞(进阶)

2002年,从事FreeBSD开源项目的程序员意识到,他们对 getpeername 函数的实现存在安全漏洞,代码简化版本如下:

/*
 * Illustration of code vulnerablility similar to that found in 
 * FreeBSD's implementation of getpeername()
 */
 
 /* Decleartion of libaray function memcpy */
 void *memcpy(void *dest, void *src, size_t n);

 /* Kernel memory region holding user-assessible data */
 #define KSIZE 1024
 char kbuf[KSIZE];
 
 /* Copy at most maxlen bytes form kernel region to user buffer */
 int copy_from_kernel(void *user_dest, int maxlen) {
 	/* Byte count len is minimun of buffer size and maxlen */
 	int len = KSIZE < maxlen ? KSIZE : maxlen;
 	memcpy(user_dest, kbuf, len);
 	retrun len;
 }

解析

从第14行开始的函数 copy_from_kernel 试图将一些操作系统内核维护的数据复制到制定的用户可以访问的内存区域。对用户来说,大多数内核维护的数据结构应该是不可读的,因为这些数据结构可能包含其他用户和系统运行的其他作业的铭感信息,但是显示为 kbuf 的区域是用户可读的。参数 maxlen 给出的是分配给用户的缓冲区长度,这个缓冲区是用参数 user_dest 指定的。然后,第16行的计算确保复制的字节数据不会超过源或者目标缓冲可用的范围。

假设怀有恶意的程序员在调用 copy_form_kernel 的代码中对 maxlen 参数使用了负值,那么第16行的最小值运算将把这个负值赋给 len, 然后 len 会被作为参数传给 memcpy。不过,memcpy的参数 n 被声明为 size_t,这是一个无符号类型,既然参数是无符号类型,那么将传递的负值将被 memcpy 解释成一个超大的无符号正整数,并试图将这样多字节的数据从内存区域复制到用户的缓冲区。虽然这个复制操作实际上不会完成,因为程序会遇到进程中非法地址的错误,但是程序还是能读到它没有被授权访问的内核内存区域。

问题的根源在于数据类型不匹配。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值