请考虑以下C程序:
#include <stdio.h>
#include <unistd.h>
int main()
{
char *buf[100] = {0};
__int32_t buflen = 0x80000000;
size_t len = read(0, buf, buflen);
printf("%d", len);
}
当使用gcc 12.2.0编译时,我收到警告消息:
<snip>
foo.c:8:18: warning: ‘read’ specified size 18446744071562067968 exceeds maximum object size 9223372036854775807 [-Wstringop-overflow=]
8 | size_t len = read(0, buf, buflen);
<snip>
并且读取返回-1
,即运行时的错误。
我不明白的是:我构建了一个最小(?)符号的int32。然后这个有符号的i32被传递给read(..., ..., size_t buflen)
,其中size_t
是一个无符号整数。因此read应该将buflen
“解释”为一个补零的size_t
,即0x00_00_00_00_80_00_00_00
,这正是我手动将buflen
转换为size_t
时所发生的情况。
为什么它超过了缓冲区大小,18446744071562067968(接近size_t
)是从哪里来的?
一点背景:
- 是的,我很清楚这会使缓冲区溢出。
- 这应该是可能被利用的坏代码。
我尝试了buflen
的几个值,有时行为不一致。我假设这是一些u.b.。我期望read将传递的参数解释为0x80000000
编辑:buflen
被扩展到0xFFFFFFFF80000000
。但是为什么呢?
2条答案
按热度按时间qnyhuwrf1#
您的
size_t
是64位长。(int32_t)0x80000000 == -2147483648
。当您将此负值转换为64位版本时,它将得到带符号扩展。-2147483648
的64位版本是0xffffffff80000000
。它显示了使用正确的类型是多么的重要。相反,
int32_t
使用正确的size_t
类型。不使用内部
__intxx_t
类型。使用标准(在stdint.h
中定义)intxx_t
类型附注:
char *buf[100]
定义了一个指向char
的100个指针的数组。我不认为这正是您想要的。1.您传递的缓冲区比最大读取大小小得多。这会调用未定义的行为
r6vfmomb2#
read
函数的最后一个参数应该是size_t
类型(即buflen
),但是,您给予它的对象类型是int32_t
,因此,将有一个从int32_t
到size_t
的转换。对于此类转换,C标准规定:
当整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则它不会改变。
所以问题是:
buflen
的值是否可以在size_t
类型的对象中表示?您已指定值
0x80000000
,让我们检查一下它是什么:输出量:
哦...一个负值...因为
size_t
不能表示负值,所以上面的规则不适用于你的情况。我们需要标准中的另一个规则。它是:否则,如果新型别是不带负数号的,则会重复加上或减去新型别所能表示的最大值加一或减一,直到值在新型别的范围内为止,以转换值。
并注明:
这些规则描述的是数学值的算术运算,而不是给定类型表达式的值
因为
size_t
是无符号的,所以这个规则适用。输出量:
所以根据这个标准我们需要做到:
值18446744071562067968可以在
size_t
对象中表示,因此将是传递给read
的值顺便说一句:
这里
使用
%d
打印size_t
对象。这是错误的(未定义的行为)。请将%zu
用于size_t
也就是说...
read
的返回值不是size_t
而是ssize_t
,因此类型从一开始就错误。