Clang 5.0 和 UBsan 的指针加法和整数溢出?
Pointer addition and integer overflow with Clang 5.0 and UBsan?
我试图理解我们最近在使用 Clang 5.0 和未定义行为消毒器 (UBsan) 时解决的问题。我们有在向前或向后方向处理缓冲区的代码。简化后的大小写为 similar to the code shown below.
0-len
可能看起来有点不寻常,但它是早期 Microsoft .Net 编译器所需要的。 Clang 5.0 和 UBsan produced integer overflow findings:
adv-simd.h:1138:26: runtime error: addition of unsigned offset to 0x000003f78cf0 overflowed to 0x000003f78ce0
adv-simd.h:1140:26: runtime error: addition of unsigned offset to 0x000003f78ce0 overflowed to 0x000003f78cd0
adv-simd.h:1142:26: runtime error: addition of unsigned offset to 0x000003f78cd0 overflowed to 0x000003f78cc0
...
第1138、1140、1142行(和朋友)是增量,可能
由于 0-len
.
向后退步
ptr += inc;
根据 Pointer comparisons in C. Are they signed or unsigned?(也讨论了 C++),指针既没有符号也没有符号。我们的偏移量是无符号的,我们依靠无符号整数换行来实现反向步幅。
代码在 GCC UBsan 和 Clang 4 以及更早的 UBsan 下运行良好。我们最终用 help with the LLVM devs 为 Clang 5.0 清除了它。我们需要使用 ptrdiff_t
.
而不是 size_t
我的问题是,构造中的整数 overflow/undefined 行为在哪里? ptr + <unsigned>
是如何导致有符号整数溢出并导致未定义行为的?
这是一个镜像真实代码的 MSVC。
#include <cstddef>
#include <cstdint>
using namespace std;
uint8_t buffer[64];
int main(int argc, char* argv[])
{
uint8_t * ptr = buffer;
size_t len = sizeof(buffer);
size_t inc = 16;
// This sets up processing the buffer in reverse.
// A flag controls it in the real code.
if (argc%2 == 1)
{
ptr += len - inc;
inc = 0-inc;
}
while (len > 16)
{
// process blocks
ptr += inc;
len -= 16;
}
return 0;
}
指针加整数的定义是(N4659 expr.add/4):
我在这里使用了一张图片以保留格式(这将在下面讨论)。
请注意,这是一个新的措辞,它取代了以前标准中不太明确的描述。
在您的代码中(当 argc
为奇数时)我们最终得到的代码等同于:
uint8_t buffer[64];
uint8_t *ptr = buffer + 48;
ptr = ptr + (SIZE_MAX - 15);
对于应用于您的代码的标准引用中的变量,i
是 48
,j
是 (SIZE_MAX - 15)
,n
是 64
.
现在的问题是0≤i+j≤n是否成立。如果我们将 "i + j" 解释为 表达式 i + j
的结果,则等于 32
小于 n
。但是如果是指数学结果那么它比n
.
大很多
标准在这里使用数学方程的字体,不使用源代码的字体。 ≤
也不是有效的运算符。所以我认为他们打算用这个等式来描述数学值,即这是未定义的行为。
C 标准将类型 ptrdiff_t
定义为指针差分运算符产生的类型。一个系统可能有一个 32 位 size_t
和一个 64 位 ptrdiff_t
;这样的定义自然适合使用 64 位线性或准线性指针但确实要求每个对象小于 4GiB 的系统。
如果已知每个对象小于 2GiB,存储类型 ptrdiff_t
而不是 size_t
的值可能会使程序不必要地低效。然而,在这种情况下,代码不应使用 size_t
来保存可能为负的指针差异,而应使用 int32_t
[如果每个对象小于 2GiB,这将足够大]。即使 ptrdiff_t
是 64 位,类型 int32_t
的值在从任何指针中添加或减去之前也会被正确地符号扩展。
我试图理解我们最近在使用 Clang 5.0 和未定义行为消毒器 (UBsan) 时解决的问题。我们有在向前或向后方向处理缓冲区的代码。简化后的大小写为 similar to the code shown below.
0-len
可能看起来有点不寻常,但它是早期 Microsoft .Net 编译器所需要的。 Clang 5.0 和 UBsan produced integer overflow findings:
adv-simd.h:1138:26: runtime error: addition of unsigned offset to 0x000003f78cf0 overflowed to 0x000003f78ce0
adv-simd.h:1140:26: runtime error: addition of unsigned offset to 0x000003f78ce0 overflowed to 0x000003f78cd0
adv-simd.h:1142:26: runtime error: addition of unsigned offset to 0x000003f78cd0 overflowed to 0x000003f78cc0
...
第1138、1140、1142行(和朋友)是增量,可能
由于 0-len
.
ptr += inc;
根据 Pointer comparisons in C. Are they signed or unsigned?(也讨论了 C++),指针既没有符号也没有符号。我们的偏移量是无符号的,我们依靠无符号整数换行来实现反向步幅。
代码在 GCC UBsan 和 Clang 4 以及更早的 UBsan 下运行良好。我们最终用 help with the LLVM devs 为 Clang 5.0 清除了它。我们需要使用 ptrdiff_t
.
size_t
我的问题是,构造中的整数 overflow/undefined 行为在哪里? ptr + <unsigned>
是如何导致有符号整数溢出并导致未定义行为的?
这是一个镜像真实代码的 MSVC。
#include <cstddef>
#include <cstdint>
using namespace std;
uint8_t buffer[64];
int main(int argc, char* argv[])
{
uint8_t * ptr = buffer;
size_t len = sizeof(buffer);
size_t inc = 16;
// This sets up processing the buffer in reverse.
// A flag controls it in the real code.
if (argc%2 == 1)
{
ptr += len - inc;
inc = 0-inc;
}
while (len > 16)
{
// process blocks
ptr += inc;
len -= 16;
}
return 0;
}
指针加整数的定义是(N4659 expr.add/4):
我在这里使用了一张图片以保留格式(这将在下面讨论)。
请注意,这是一个新的措辞,它取代了以前标准中不太明确的描述。
在您的代码中(当 argc
为奇数时)我们最终得到的代码等同于:
uint8_t buffer[64];
uint8_t *ptr = buffer + 48;
ptr = ptr + (SIZE_MAX - 15);
对于应用于您的代码的标准引用中的变量,i
是 48
,j
是 (SIZE_MAX - 15)
,n
是 64
.
现在的问题是0≤i+j≤n是否成立。如果我们将 "i + j" 解释为 表达式 i + j
的结果,则等于 32
小于 n
。但是如果是指数学结果那么它比n
.
标准在这里使用数学方程的字体,不使用源代码的字体。 ≤
也不是有效的运算符。所以我认为他们打算用这个等式来描述数学值,即这是未定义的行为。
C 标准将类型 ptrdiff_t
定义为指针差分运算符产生的类型。一个系统可能有一个 32 位 size_t
和一个 64 位 ptrdiff_t
;这样的定义自然适合使用 64 位线性或准线性指针但确实要求每个对象小于 4GiB 的系统。
如果已知每个对象小于 2GiB,存储类型 ptrdiff_t
而不是 size_t
的值可能会使程序不必要地低效。然而,在这种情况下,代码不应使用 size_t
来保存可能为负的指针差异,而应使用 int32_t
[如果每个对象小于 2GiB,这将足够大]。即使 ptrdiff_t
是 64 位,类型 int32_t
的值在从任何指针中添加或减去之前也会被正确地符号扩展。