8

如果整数溢出,结果是(unsigned int) * (int)什么?unsigned还是int?数组索引运算符 ( 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 行的乘法有机会通过验证。

int i现在问题出现在第 17 行。乘以unsigned int w:如果结果是int,则乘积可能是负数,导致访问的位置是 before 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

因此,乘法返回什么类型并不重要。消费者功能是否采用int或很重要unsigned

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

4

13 回答 13

4

在 long long 中进行 w*h 计算,检查是否大于 MAX_UINT

编辑:替代:如果溢出 (w*h)/h != w (总是这样吗?!应该是,对吧?)

于 2009-04-06T15:13:05.537 回答
2

通过限制 w 和 h 确保 w * h 不会溢出。

于 2009-04-06T15:08:59.867 回答
2

要回答您的问题:将 int 和 unsigned int 相乘的表达式的类型将是 C/C++ 中的 unsigned int。

要回答您隐含的问题,处理整数算术中可能的溢出的一种体面方法是使用IntSafeMicrosoft 的 " " 例程集:

http://blogs.msdn.com/michael_howard/archive/2006/02/02/523392.aspx

它在 SDK 中可用并包含内联实现,因此如果您在另一个平台上,您可以研究它们在做什么。

于 2009-04-06T15:31:56.393 回答
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);

这忽略了worh为 0 的情况,如果需要,您可能需要检查它。

一般来说,您可以像这样检查溢出:

if(w*h > 4096 || (w*h)/w!=h || (w*h)%w!=0)
于 2009-04-06T15:38:13.227 回答
2

在 C/C++ 中,p[n]符号实际上是编写的快捷方式*(p+n),并且此指针算术考虑了符号。Sop[-1]是有效的,并且指的是紧接在 之前的值*p

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

查看此页面:INT02-C。了解整数转换规则

于 2009-04-06T15:40:06.517 回答
1

2 项更改使其更安全:

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

...

unsigned i;

另请注意,在缓冲区结束后写入或读取并不是一个坏主意。所以问题不是 i w 是否会变成负数,而是 0 <= i h +w <= 4096 是否成立。

所以重要的不是类型,而是 h*i 的结果。例如,它是 (unsigned)0x80000000 还是 (int)0x80000000 都没有区别,程序无论如何都会出现 seg-fault。

于 2009-04-06T15:30:26.007 回答
1

对于 C,请参阅“通常的算术转换”(C99:第 6.3.1.8 节,ANSI C K&R A6.5)了解如何处理数学运算符的操作数的详细信息。

在您的示例中,适用以下规则:

C99:

否则,如果有符号整数类型的操作数的类型可以表示无符号整数类型的操作数类型的所有值,则将无符号整数类型的操作数转换为有符号整数类型的操作数的类型。

否则,两个操作数都转换为与带符号整数类型的操作数类型对应的无符号整数类型。

ANSI C:

否则,如果任一操作数为 unsigned int,则将另一个操作数转换为 unsigned int。

于 2009-04-06T15:58:58.310 回答
0

为什么不直接将 i 声明为 unsigned int?然后问题就消失了。

在任何情况下,i*w 都保证为 <= 4096,因为代码对此进行了测试,因此它永远不会溢出。

于 2009-04-06T15:07:22.990 回答
0

memcpy(&buf[i w > -1 ? i w < 4097? i w : 0 : 0], init, w); 我不认为 i w 的三重计算会降低性能)

于 2009-04-06T15:11:18.347 回答
0

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

9.      if (w*h > 4096)
10.         return (NULL);

在 int 、 unsigned int 混合操作中,int 提升为 unsigned int,在这种情况下,“i”的负值将变为较大的正值。在这种情况下

&buf[i*w]

将访问超出范围的值。

于 2009-04-06T15:20:45.547 回答
0

无符号算术是作为模块化(或环绕)完成的,因此两个大型无符号整数的乘积很容易小于 4096。 int 和 unsigned int 的乘法将导致 unsigned int (参见 C++ 标准的第 4.5 节) .

因此,给定较大的 w 和合适的 h 值,您确实会遇到麻烦。

确保整数算术不会溢出是困难的。一种简单的方法是转换为浮点并进行浮点乘法,然后查看结果是否合理。正如 qwerty 建议的那样,如果在您的实现中可用,那么 long long 将是可用的。(它是 C90 和 C++ 中的常见扩展,确实存在于 C99 中,并将在 C++0x 中。)

于 2009-04-06T15:21:41.553 回答
0

当前的 C1X 草案中有 3 段关于计算 (UNSIGNED TYPE1) X (SIGNED TYPE2) 在 6.3.1.8 通常的算术覆盖,N1494,

WG 14:C - 项目状态和里程碑

否则,如果无符号整数类型的操作数的等级大于或等于另一个操作数类型的等级,则将有符号整数类型的操作数转换为无符号整数类型的操作数的类型。

否则,如果有符号整数类型的操作数的类型可以表示无符号整数类型的操作数类型的所有值,则将无符号整数类型的操作数转换为有符号整数类型的操作数的类型。

否则,两个操作数都转换为与带符号整数类型的操作数类型对应的无符号整数类型。

因此,如果 a 是 unsigned int 且 b 是 int,则 (a * b) 的解析应生成代码 (a * (unsigned int)b)。如果 b < 0 或 a * b > UINT_MAX 将溢出。

如果 a 是 unsigned int 并且 b 是更大的 long,则 (a * b) 应该生成 ((long)a * (long)b)。如果 a * b > LONG_MAX 或 a * b < LONG_MIN 将溢出。

如果 a 是 unsigned int 且 b 是大小相同的 long,则 (a * b) 应该生成 ((unsigned long)a * (unsigned long)b)。如果 b < 0 或 a * b > ULONG_MAX 将溢出。

关于“索引器”预期类型的​​第二个问题,答案显示为“整数类型”,它允许任何(有符号)整数索引。

6.5.2.1 数组下标

约束

1 一个表达式的类型应为“指向完整对象类型的指针”,另一个表达式应为整数类型,结果的类型为“类型”。

语义

2 后缀表达式后跟方括号 [] 中的表达式是数组对象元素的下标名称。下标运算符[]的定义是E1[E2]等同于(*((E1)+(E2)))。由于适用于二元 + 运算符的转换规则,如果 E1 是数组对象(等效地,指向数组对象的初始元素的指针)并且 E2 是整数,则 E1[E2] 指定第 E2 个元素E1(从零开始计数)。

当指针表达式是数组变量并且索引可能为负时,由编译器执行静态分析并警告开发人员缓冲区溢出的可能性。即使索引为正数或无符号数,也会警告可能的数组大小溢出。

于 2010-09-18T01:11:07.347 回答
-1

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

于 2009-04-06T15:19:26.177 回答