Clang 5.0和UBsan的指针加法和整数溢出?

时间:2017-12-18 00:00:58

标签: c++ pointers clang undefined-behavior ubsan

我在尝试使用Clang 5.0和Undefined Behavior Sanitizer(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清除了它。我们需要使用size_t而不是ptrdiff_t

我的问题是,构造中的整数溢出/未定义行为在哪里? 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;
}

2 个答案:

答案 0 :(得分:2)

向指针添加整数的定义是(N4659 expr.add / 4):

expr.add/4

我在这里使用了一个图像以保留格式(这将在下面讨论)。

请注意,这是一种新的措辞,它取代了之前标准中不太清晰的描述。

在您的代码中(当argc为奇数时),我们最终得到的代码相当于:

uint8_t buffer[64];
uint8_t *ptr = buffer + 48;
ptr = ptr + (SIZE_MAX - 15);

对于应用于代码的标准报价中的变量,i48j(SIZE_MAX - 15)n64

现在的问题是0≤i+j≤n是否属实。如果我们解释&#34; i + j&#34;表示表达式i + j 的结果,那么等于32小于n。但如果它意味着数学结果,则它远大于n

标准在此处使用数学方程的字体,并且不使用字体作为源代码。 也不是有效的运算符。所以我认为他们打算用这个等式描述数学值,即这是未定义的行为。

答案 1 :(得分:1)

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的值也会在从任何指针添加或减去之前正确地进行符号扩展。