OpenSSH 3.3 xmalloc溢出疑问:不同架构下问题根源何在?
以下代码片段来自OpenSSH 3.3,展示了经典的整数溢出案例:
(不良代码)
示例语言:C
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的值为1073741824,且sizeof(char*)的典型值为4,那么
nresp*sizeof(char*)的运算结果会溢出,传入xmalloc()的参数将为0。大多数malloc()实现会欣然分配0字节缓冲区,导致后续循环迭代溢出堆缓冲区response。
问题拆解
这个经典溢出案例本来就是基于IA32(32位x86)架构分析的,这是理解整个问题的关键:
32位环境下的溢出逻辑
在IA32里,int和size_t都是32位宽度——int是带符号的,最大能到231-1;`size_t`是无符号的。当`nresp`是1073741824(也就是230),乘以char*的大小4,结果是2^32,这已经超出了32位带符号整数的范围,触发带符号整数溢出。虽然C标准里这是未定义行为,但绝大多数32位编译器都会直接截断高位,结果就变成了0。
别纠结sizeof返回size_t的问题——在32位环境下int和size_t宽度一样,nresp作为带符号数参与运算时,乘积超过带符号上限就会溢出回绕成0,再传给xmalloc,自然就分配了0字节的缓冲区,后面的循环直接越界写内存。x86_64架构的差异
到了64位环境,int还是32位带符号,但size_t变成了64位无符号。这时候nresp会被自动提升为64位无符号数,乘以8(x86_64下char*是8字节)得到2^33,远没到64位无符号数的上限,自然不会溢出。但这只是架构不同带来的结果,不是原案例的问题本身。问题的真正根源
原代码的致命问题是完全没做乘法溢出检查。不管是32位还是64位环境,只要nresp的取值让乘积超出了分配内存时可用的地址空间范围,就会出问题。32位下这个特定值刚好触发溢出导致分配0字节;64位下虽然这个值没事,但如果nresp取更大的32位带符号数(比如2^31-1),或者以后代码移植回32位环境,风险立刻就回来了。
内容的提问来源于stack exchange,提问作者HangingParens

