如何防止memcpy缓冲区溢出?

Mic*_*l D 7 c buffer-overflow memcpy fortify-source

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

Jon*_*ler 8

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

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

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

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

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


jww*_*jww 6

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

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


第一选择是提供"安全"功能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;
}
Run Code Online (Sandbox Code Playgroud)

如果您safe_memcpy返回非0,则会出现错误,例如错误的参数或潜在的缓冲区溢出.


第二种选择是使用C标准提供的"更安全"功能.C通过ISO/IEC TR 24731-1,边界检查接口具有"更安全"的功能.在符合标准的平台上,您可以简单地调用gets_ssprintf_s.它们提供一致的行为(如始终确保字符串NULL终止)和一致的返回值(如成功时的0或a errno_t).

errno_t  err = memcpy_s(dest, dsize, src, cnt);
...
Run Code Online (Sandbox Code Playgroud)

不幸的是,gcc和glibc不符合C标准.Ulrich Drepper(其中一个glibc维护者)称边界检查接口"非常低效的BSD垃圾",并且它们从未添加过.


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

在Apple和BSD上,你没有"更安全"的功能memcpy.但是,你有更安全的字符串函数一样strlcpy,strlcat和朋友.


在Linux上,您的第四个选择是使用FORTIFY_SOURCE.FORTIFY_SOURCE使用的类似高风险的功能"更安全"的变体memcpy,strcpygets.当编译器可以推导出目标缓冲区大小时,编译器会使用更安全的变体.如果副本超过目标缓冲区大小,则程序调用abort().如果编译器无法推断出目标缓冲区大小,则不使用"更安全"的变体.

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


phs*_*sym 5

您应该始终知道并检查 src 和 dest 缓冲区大小!

void *memcpy(void *dest, const void *src, size_t n);
Run Code Online (Sandbox Code Playgroud)

n永远不应该大于srcdest大小。