11

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

4

4 回答 4

10

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

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

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

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

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

于 2012-08-31T07:09:34.583 回答
8

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

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


第一种选择是为memcpy. 这是我在我的职权范围内对代码的要求,并且我会定期对其进行审核。我还要求验证所有参数,并断言所有参数。

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

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_sand sprintf_s。它们提供一致的行为(例如始终确保NULL终止字符串)和一致的返回值(例如成功时为 0 或errno_t)。

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

不幸的是,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(). 如果编译器无法推断目标缓冲区大小,则不使用“更安全”的变体。

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

于 2014-09-12T16:12:32.440 回答
5

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

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

n永远不应大于srcdest大小。

于 2012-08-31T07:08:43.030 回答
1

例如,如果您有:

目标 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)));

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

于 2012-08-31T07:27:52.827 回答