如何防止memcpy缓冲区溢出?

时间:2012-08-31 07:06:44

标签: c memcpy buffer-overflow fortify-source

程序中有一些固定大小的二进制缓冲区用于存储数据。 memcpy用于将缓冲区从一个缓冲区复制到另一个缓冲区。由于源缓冲区可能大于目标缓冲区。如何检测是否存在缓冲区溢出?

4 个答案:

答案 0 :(得分:8)

您必须知道源缓冲区中有多少数据以及目标缓冲区中有多少可用空间。

如果目标缓冲区中没有足够的空间用于要从源缓冲区复制的所有数据,请不要调用memcpy()。 (如果源大于目标,则必须决定是否可以截断数据。)

如果您不知道,请重写代码,以便知道有多少空间;否则,这是不安全的。

请注意,如果源缓冲区和目标缓冲区有可能重叠,则应使用memmove()而不是memcpy()

在C ++中,首先要考虑使用memcpy();这是一种C风格的操作,而不是C ++。

答案 1 :(得分:5)

  

如何检测是否存在缓冲区溢出?

我认为你有三个或四个选择(给予或接受)。


第一种选择是为memcpy提供“安全”功能。这是我在我的职权范围内的代码中所要求的,我会定期对其进行审核。我还要求验证所有参数,并声明所有参数。

断言创建自调试代码。我希望开发人员编写代码;我不希望他们浪费时间调试。所以我要求他们编写调试自己的代码。 ASSERT还可以很好地记录事情,因此他们可以吝啬文档。在发布版本中,ASSERT由preorcessor宏删除。

errno_t safe_memcpy(void* dest, size_t dsize, void* src, size_t ssize, size_t cnt)
{
    ASSERT(dest != NULL);
    ASSERT(src != NULL);
    ASSERT(dsize != 0);
    ASSERT(ssize != 0);
    ASSERT(cnt != 0);

    // What was the point of this call?
    if(cnt == 0)
        retrn 0;

    if(dest == NULL || src == NULL)
        return EINVALID;

    if(dsize == 0 || ssize == 0)
        return EINVALID;

    ASSERT(dsize <= RSIZE_MAX);
    ASSERT(ssize <= RSIZE_MAX);
    ASSERT(cnt <= RSIZE_MAX);

    if(dsize > RSIZE_MAX || ssize > RSIZE_MAX || cnt > RSIZE_MAX)
        return EINVALID;

    size_t cc = min(min(dsize, ssize), cnt);
    memmove(dest, src, cc);

    if(cc != cnt)
        return ETRUNCATE;

    return 0;
}

如果safe_memcpy返回非0,则表示存在错误参数或潜在缓冲区溢出等错误。


第二种选择是使用C标准提供的“更安全”功能。 C通过ISO/IEC TR 24731-1, Bounds Checking Interfaces具有“更安全”的功能。在符合要求的平台上,您只需拨打gets_ssprintf_s即可。它们提供一致的行为(如始终确保字符串NULL已终止)和一致的返回值(如成功时为0或errno_t)。

errno_t  err = memcpy_s(dest, dsize, src, cnt);
...

不幸的是,gcc和glibc不符合C标准。 Ulrich Drepper(其中一个glibc维护者)称为边界检查接口 "horribly inefficient BSD crap" ,它们从未添加过。


第三种选择是使用平台的“更安全”接口(如果存在)。在Windows上,这恰好与ISO/IEC TR 24731-1, Bounds Checking Interfaces中的相同。您还有String Safe库。

在Apple和BSD上,memcpy没有“更安全”的功能。但是你确实有更安全的字符串函数,如strlcpystrlcat和朋友。


在Linux上,您的第四选择是使用FORTIFY_SOURCE。 FORTIFY_SOURCE使用高风险函数的“更安全”变体,如memcpystrcpygets。当编译器可以推导出目标缓冲区大小时,编译器会使用更安全的变体。如果副本超过目标缓冲区大小,则程序将调用abort()。如果编译器无法推断出目标缓冲区大小,则不使用“更安全”的变体。

要禁用FORTIFY_SOURCE进行测试,您应该使用-U_FORTIFY_SOURCE-D_FORTIFY_SOURCE=0编译该程序。

答案 2 :(得分:4)

你应该总是知道并检查src和dest缓冲区的大小!

void *memcpy(void *dest, const void *src, size_t n);

n不得大于srcdest大小。

答案 3 :(得分:0)

例如,如果你有:

目标4字节大小

源5字节大小

您可以确保最多将4个字节复制到目标缓冲区:

size_t getCopySize(size_t sourceSize, size_t destSize)
{
    return (destSize <= sourceSize ? destSize : sourceSize);
}
memcpy(destination, source, getCopySize(sizeof(source),sizeof(destination)));

根据您的应用程序,您还可以确保以后复制剩余数据,或者如果可以忽略某些数据,则可以跳过它。