考虑以下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>
and read返回-1
,即运行时错误。
我不明白的是:我构建最低(?)符号int32。然后这个带符号的i32被传递给read(..., ..., size_t buflen)
,其中size_t
是一个无符号整数。因此,阅读应该"解释"。buflen
作为零填充size_t
即0x00_00_00_00_80_00_00_00
,这正是当我手动将buflen
转换为size_t
时发生的情况。
为什么它会超出缓冲区大小,那么18446744071562067968(接近size_t
的最大值)是从哪里来的?
一点上下文:
- 是的,我很清楚这会溢出缓冲区。
- 这应该是可能被利用的坏代码。
我尝试了buflen
的几个值,有时行为不一致。我猜这是什么…我希望read将传递的参数解释为0x80000000
编辑:buflen
扩展到0xFFFFFFFF80000000
。但是为什么呢?
ssize_t read(int fd, void *buf, size_t count);
你的size_t
是64位长。(int32_t)0x80000000 == -2147483648
。当您将此负值转换为64位版本时,它将获得带符号扩展。-2147483648
的64位版本是0xffffffff80000000
.
它显示了使用正确的类型是多么重要。使用正确的size_t
类型代替int32_t
。
不要使用内部__intxx_t
类型。使用标准(在stdint.h
中定义)intxx_t
类型
指出:
char *buf[100]
定义了一个包含100个指向char
的指针的数组。我不认为这正是你想要的。- 您传递的缓冲区比最大读取大小小得多。它调用未定义行为
read
函数期望最后一个参数的size_t
类型(即您的buflen
)。但是,给它一个类型为int32_t
的对象。所以会有从int32_t
到size_t
的转换。
对于这种转换,C标准规定:
当将整型值转换为非_Bool类型的整型值时,if该值可以用新的类型表示,它是不变的.
所以问题是:你的buflen
的值可以在类型为size_t
的对象中表示吗?
你赋值的值是0x80000000
,那么让我们检查一下它是什么:
int32_t buflen = 0x80000000;
printf("%" PRId32 "n", buflen);
输出:
-2147483648
哦…一个负值…由于size_t
不能表示负值,因此上述规则不适用于您的情况。我们需要标准中的另一条规则。是:
如果新类型是unsigned,则,则通过重复添加或来转换值在新类型中可以表示的最大值之外减去一个直到值在新类型的范围内。
并注明:
规则描述对数学值的运算,而不是给定表达式类型的值
由于size_t
是无符号的,因此此规则适用。我们来求size_t
printf("%zun", SIZE_MAX);
输出:
18446744073709551615
所以根据标准我们需要做:
-2147483648
+ 18446744073709551615
+ 1
--------------------
18446744071562067968
====================
值18446744071562067968可以在size_t
对象中表示,因此将作为传递给read
的值
顺便说一句:这里
printf("%d", len);
使用%d
打印size_t
对象。这是错误的(未定义的行为)。用%zu
代替size_t
说……read
的返回值不是size_t
,而是ssize_t
,所以类型从一开始就是错误的。