c - 如何防止 memcpy 缓冲区溢出?
我认为你有三四个选择(给予或接受)。
第一种选择是为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例如strcpy和gets。当编译器可以推断目标缓冲区大小时,它会使用更安全的变体。如果副本超出目标缓冲区大小,则程序调用abort(). 如果编译器无法推断目标缓冲区大小,则不使用“更安全”的变体。
-U_FORTIFY_SOURCE要禁用 FORTIFY_SOURCE 进行测试,您应该使用或编译程序-D_FORTIFY_SOURCE=0。
您应该始终了解并检查 src 和 dest 缓冲区大小!
void *memcpy(void *dest, const void *src, size_t n);
n永远不应大于src或dest大小。
例如,如果您有:
目标 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)));
根据您的应用程序,您还可以确保稍后将复制剩余的数据,或者如果可以忽略某些数据,则可以跳过它。

浙公网安备 33010602011771号