在整数溢出的情况下(unsigned int)*(int)的结果是什么? unsigned还是int?

时间:2009-04-06 15:01:19

标签: c++ c overflow buffer

如果整数溢出,(unsigned int) * (int)的结果是什么? unsignedint?数组索引运算符(operator[])对char*采用什么类型:intunsigned 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;
}

考虑wh是非常大的无符号整数。第9行的乘法有机会通过验证。

现在问题出在第17行。将int iunsigned int w相乘:如果结果为int,则产品可能为负数,导致访问的位置为在buf之前。如果结果为unsigned int,则产品将始终为正数,从而导致访问buf之后的位置。

编写代码来证明这一点很难:int太大了。有没有人有这方面的想法?

是否有任何文件指明产品的类型?我已经搜索过了,但到目前为止还没有找到任何东西。

我认为就漏洞而言,(unsigned int) * (int)生成unsigned intint无关紧要,因为在编译的目标文件中,它们只是字节。无论产品类型如何,以下代码均相同:

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

因此,乘法返回的类型无关紧要。消费者功能是否需要intunsigned

这里的问题是功能有多糟糕,或者如何改进功能以使其更好。该功能无疑具有漏洞。问题是关于函数的确切行为,基于标准中规定的行为。

13 个答案:

答案 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)根据xy的类型提供不同的结果,即使没有未定义的行为也是如此。

我会通过检查第9行的溢出来解决您的问题;因为4096是一个非常小的常数,4096 * 4096在大多数架构上都没有溢出(你需要检查),我会做

if (w>4096 || h>4096 || w*h > 4096)
     return (NULL);

如果wh为0,则会遗漏这种情况,如果需要,您可能需要检查它。

一般情况下,你可以检查这样的溢出:

if(w*h > 4096 || (w*h)/w!=h || (w*h)%w!=0)

答案 4 :(得分:2)

在C / C ++中,p[n]符号实际上是写*(p+n)的快捷方式,而这个指针算法会考虑符号。因此p[-1]有效并且引用*p之前的值。

所以这里的符号真的很重要,带有整数的算术运算符的结果遵循标准定义的一组规则,这称为整数提升。

查看此页面:INT02-C. Understand integer conversion rules

答案 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)

如果w和/或h足够大并且以下验证可以通过,那么w * h可能会溢出。

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)

要实际回答您的问题,而不指定您正在运行的硬件,您不知道,并且在可移植的代码中,您不应该依赖任何特定的行为。