缺少 memset_s (C11) 的重要原因

LuC*_*LuC 7 c c11

我确信编码中存在很多缓冲区溢出风险,其中许多风险是通过标准库的“_s”安全函数解决的。尽管如此,我发现自己有时对其中一些感到困惑。

假设我有一些这样的片段

uint8_t a[5];
...
size_t  z = 6;
...
memset(a, 0, z);  // Overflow!
Run Code Online (Sandbox Code Playgroud)

一些编译器(C11)可能建议更好地使用memset_s; 因为我是一个糟糕的程序员,所以我刚刚将我的代码更新为这个全新的东西,我的方式:

uint8_t  a[5];
...
rsize_t  max_array = 56;  // Slipped finger, head in the clouds, etc.
rsize_t  z = 6;
...
memset_s(a, max_array, 0, z);  // So what?
Run Code Online (Sandbox Code Playgroud)

如果我只是将错误添加到另一个参数,那会memset_s更好吗?memset

增加的安全性在哪里,如果添加一个新参数只是添加一个可能会出错的新参数。我本可以在第一个修订版中更正我的代码,并且仍然可以合法地调用缓冲区上定义良好的操作。

抛开带有未经检查的零指针的情况,怎么会memset糟糕呢memset_s

[编辑]

经过一番努力,我还发现了导致我的设置中出现警告的设置。这可能对某人有帮助。

该警告来自 Clang-tidy,在 Visual Studio 扩展“Clang power tools”中调用。在其默认设置中,它启用了 Clang-tidy检查器security.insecureAPI.DeprecatedOrUnsafeBufferHandling ”,它模仿默认的 MSVC 警告。请注意,这不能从命令行选项中获得(或者至少没有在其中列出),而是从 .clang-tidy 文件中读取。
在第一次运行中,我什至没有 .clang-tidy 文件,尽管这是默认强制执行的。

就我而言,该消息出现在 Clang-tidy 中而不是MSVC 中的原因很简单。
我在我的代码中定义了

#define  _CRT_SECURE_NO_WARNINGS
Run Code Online (Sandbox Code Playgroud)

为了不被 MSVC 警告感染,但这种方法对 Clang-tidy 无效,这一直困扰着我(由于Clang 电动工具默认值)

chq*_*lie 7

我不提倡使用任何有争议的安全措施功能:Microsoft 平台上的原始实现不符合标准规范,并且许多其他平台故意选择根本不实现它们。

以下是memset_s它所memset缺乏的一些功能:

  • 对目标数组进行空指针检查。
  • 对块大小进行部分健全性检查:检测大于的大小,RSIZE_MAX处理从产生负值的数据计算出的错误大小的情况。仅此一项不应要求将类型从 更改为size_trsize_t但确实会捕获一些编程错误,尽管仅在运行时发生。
  • memset_s()不能被省略:K.3.7.4.1: 与 不同memset,对函数的任何调用memset_s都应严格根据(5.1.2.3)中描述的抽象机规则进行评估。也就是说,对该函数的任何调用都应假定和memset_s指示的内存将来可以访问,因此必须包含 和 指示的值。snc

我同意你的观点,前两个功能提供的保护有限,因为这两种情况都会在受保护的内存架构上产生分段错误,并且在这种情况下错误处理方案也很难比退出程序更好。

第三个有助于确保在函数返回之前删除本地数组中包含的敏感信息,从而防止编译器优化清除数组的代码。由于memset_s支持是可选的,因此memset_explicit在 C23 中添加了一个新函数,其原型与 相同memset。类似的函数在某些目标中以不同的名称提供:explicit_memset(Oracle),或用于清除数组的安全替代方案:explicit_bzero(BSD)、memzero_explicit(Linux) 和SecureZeroMemory(Windows)。

像您展示的那样的尺寸错误将不会被发现,并导致难以发现错误。它们通常在目标数组未在本地定义并且其大小与指针分开传递时发生,否则sizeof array无论如何都是更安全的选择。

即使是最精明的程序员也会遇到的一个典型错误是:

void handle_array(some_type *dest, size_t size) {
    /* clear the array */
    memset(dest, 0, size);  // BUG: using number of elements instead of byte size
    for (size_t i = 0; i < size; i++) {
        /* do other stuff... */
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器无法检测到此编程错误,并且使用memset_s也无法捕获它。

另一个经典的编程错误是交换0和 大小参数。

我个人使用宏来避免这些陷阱:

#define array_clear(a, n)  memset(a, 0, sizeof(*(a)) * (n))
Run Code Online (Sandbox Code Playgroud)

向语言添加标记指针,将组合对象的类型、地址和长度,并从数组参数或赋值自动构造,将有助于避免许多此类问题。这种与当前 C 语言定义的背离在 C++ 中也不存在,可惜不太可能出现在下一版本的 C 标准中。