如果整数溢出,(unsigned int) * (int)
的结果是什么? unsigned
或int
?数组索引运算符(operator[]
)对char*
采用什么类型:int
,unsigned int
或其他什么类型?
我正在审核以下功能,突然出现了这个问题。该功能在第17行有一个漏洞。
// Create a character array and initialize it with init[]
// repeatedly. The size of this character array is specified by
// w*h.
char *function4(unsigned int w, unsigned int h, char *init)
{
char *buf;
int i;
if (w*h > 4096)
return (NULL);
buf = (char *)malloc(4096+1);
if (!buf)
return (NULL);
for (i=0; i<h; i++)
memcpy(&buf[i*w], init, w); // line 17
buf[4096] = '\0';
return buf;
}
考虑w
和h
是非常大的无符号整数。第9行的乘法有机会通过验证。
现在问题出在第17行。将int i
与unsigned int w
相乘:如果结果为int
,则产品可能为负数,导致访问的位置为在buf
之前。如果结果为unsigned int
,则产品将始终为正数,从而导致访问buf
之后的位置。
编写代码来证明这一点很难:int
太大了。有没有人有这方面的想法?
是否有任何文件指明产品的类型?我已经搜索过了,但到目前为止还没有找到任何东西。
我认为就漏洞而言,(unsigned int) * (int)
生成unsigned int
或int
无关紧要,因为在编译的目标文件中,它们只是字节。无论产品类型如何,以下代码均相同:
unsigned int x = 10;
int y = -10;
printf("%d\n", x * y); // print x * y in signed integer
printf("%u\n", x * y); // print x * y in unsigned integer
因此,乘法返回的类型无关紧要。消费者功能是否需要int
或unsigned
。
这里的问题是不功能有多糟糕,或者如何改进功能以使其更好。该功能无疑具有漏洞。问题是关于函数的确切行为,基于标准中规定的行为。
答案 0 :(得分:4)
长时间进行w * h计算,检查是否大于MAX_UINT
编辑:替代:如果溢出(w * h)/ h!= w(总是这样吗?!应该是,对吗?)
答案 1 :(得分:2)
通过限制w和h确保w * h不会溢出。
答案 2 :(得分:2)
回答你的问题:表达式的类型乘以int和unsigned int将是C / C ++中的unsigned int。
要回答您的隐含问题,处理整数运算中可能出现溢出的一种不错的方法是使用Microsoft的“IntSafe
”例程:
http://blogs.msdn.com/michael_howard/archive/2006/02/02/523392.aspx
它在SDK中可用并包含内联实现,因此如果您在另一个平台上,您可以研究他们正在做什么。
答案 3 :(得分:2)
w*i
的类型在您的案例中未签名。如果我正确读取的标准,规则是,操作数被转换为更大的类型(与它的符号类型),或对应于该符号的类型无符号的类型(它是unsigned int
在你的情况)。
然而,即使是无符号的,它不会阻止绕进(前写入内存buf
),因为它可能是这种情况(在i386平台,它是),即p[-1]
与p[-1u]
相同。无论如何,在您的情况下,buf[-1]
和buf[big unsigned number]
都是未定义的行为,因此签名/未签名的问题并不重要。
请注意在其他情况下签名/未签名的事项 - 例如。 (int)(x*y/2)
根据x
和y
的类型提供不同的结果,即使没有未定义的行为也是如此。
我会通过检查第9行的溢出来解决您的问题;因为4096是一个非常小的常数,4096 * 4096在大多数架构上都没有溢出(你需要检查),我会做
if (w>4096 || h>4096 || w*h > 4096)
return (NULL);
如果w
或h
为0,则会遗漏这种情况,如果需要,您可能需要检查它。
一般情况下,你可以检查这样的溢出:
if(w*h > 4096 || (w*h)/w!=h || (w*h)%w!=0)
答案 4 :(得分:2)
在C / C ++中,p[n]
符号实际上是写*(p+n)
的快捷方式,而这个指针算法会考虑符号。因此p[-1]
有效并且引用*p
之前的值。
所以这里的符号真的很重要,带有整数的算术运算符的结果遵循标准定义的一组规则,这称为整数提升。
答案 5 :(得分:1)
2次更改使其更安全:
if (w >= 4096 || h >= 4096 || w*h > 4096) return NULL;
...
unsigned i;
另请注意,写入或读取缓冲区末尾并不是一个坏主意。所以问题不在于我 w是否会变为负数,而是0 <= i h + w <= 4096是否成立。
所以这不是重要的类型,而是h * i的结果。 例如,无论是(无符号)0x80000000还是(int)0x80000000都没有区别,程序无论如何都会出现段错误。
答案 6 :(得分:1)
对于C,请参阅“常用算术转换”(C99:第6.3.1.8节,ANSI C K&amp; R A6.5),了解有关如何处理数学运算符的操作数的详细信息。
在您的示例中,以下规则适用:
C99:
否则,如果是操作数的类型 带符号整数类型可以表示 所有类型的值 具有无符号整数类型的操作数, 然后是带无符号整数的操作数 type被转换为的类型 带有符号整数类型的操作数。
否则,两个操作数都被转换 到无符号整数类型 对应的类型 带有符号整数类型的操作数。
ANSI C:
否则,如果任一操作数是unsigned int,则另一个操作数转换为unsigned int。
答案 7 :(得分:0)
为什么不将i声明为unsigned int?然后问题就消失了。
在任何情况下,i * w保证为&lt; = 4096,因为代码会对此进行测试,所以它永远不会溢出。
答案 8 :(得分:0)
memcpy(&amp; buf [i w&gt; -1?i w&lt; 4097?i w:0:0],init,w); 我不认为i w的三重计算确实会降低性能)
答案 9 :(得分:0)
9. if (w*h > 4096)
10. return (NULL);
在int,unsigned int mixed操作中,int被提升为unsigned int,在这种情况下,负值'i'将成为一个大的正值。在那种情况下
&buf[i*w]
将访问一个超出界限的值。
答案 10 :(得分:0)
无符号算术以模块化(或环绕)方式完成,因此两个大的无符号整数的乘积很容易小于4096. int和unsigned int的乘法将导致unsigned int(参见第4.5节) C ++标准)。
因此,如果给出大w和合适的h值,你确实会遇到麻烦。
确保整数运算不会溢出很困难。一种简单的方法是转换为浮点并进行浮点乘法,并查看结果是否合理。正如qwerty所建议的那样,如果你的实现可用,那么很长时间都可用。 (这是C90和C ++中的常见扩展,确实存在于C99中,并且将在C ++ 0x中。)
答案 11 :(得分:0)
当前C1X草案中有3段关于计算(UNSIGNED TYPE1)X(签名类型2)在6.3.1.8通常算术覆盖中,N1494,
WG 14: C - Project status and milestones
否则,如果具有无符号整数类型的操作数的等级大于或等于 等于另一个操作数的类型的等级,然后是操作数 有符号整数类型转换为带有unsigned的操作数的类型 整数类型。
否则,如果带有符号整数类型的操作数的类型可以表示 那么,带有无符号整数类型的操作数类型的所有值 具有无符号整数类型的操作数将转换为该类型 带有符号整数类型的操作数。
否则,两个操作数都将转换为无符号整数类型 对应于带有符号整数类型的操作数的类型。
因此,如果a是unsigned int且b是int,则解析(a * b)应该生成代码(a *(unsigned int)b)。如果b&lt;将溢出0或a * b> UINT_MAX。
如果a是无符号int且b长度更大,则(a * b)应生成((long)a *(long)b)。如果a * b>将溢出LONG_MAX或a * b&lt; LONG_MIN。
如果a是unsigned int且b长度相同,则(a * b)应生成((unsigned long)a *(unsigned long)b)。如果b&lt;将溢出0或a * b> ULONG_MAX。
关于“索引器”所期望的类型的第二个问题,答案显示为“整数类型”,它允许任何(带符号)整数索引。
6.5.2.1数组下标
约束
1其中一个表达式的类型''指向完整的对象类型'',另一个 expression应具有整数类型,结果类型为''type''。
语义
2后缀表达式后跟方括号[]中的表达式是下标 指定数组对象的元素。下标运算符[]的定义 是E1 [E2]与(*((E1)+(E2)))相同。由于转换规则 应用于二进制+运算符,如果E1是一个数组对象(等效地,指向 数组对象的初始元素),E2是整数,E1 [E2]表示E2 E1的元素(从零开始计数)。
当指针表达式是数组变量并且索引可能是负数时,由编译器执行静态分析并警告开发人员缓冲区溢出的可能性。即使索引为正数或无符号,也可能会警告可能的数组大小溢出。
答案 12 :(得分:-1)
要实际回答您的问题,而不指定您正在运行的硬件,您不知道,并且在可移植的代码中,您不应该依赖任何特定的行为。