memset 如何提供比 bzero 或 explicit_bzero 更高的安全性?

Posted

技术标签:

【中文标题】memset 如何提供比 bzero 或 explicit_bzero 更高的安全性?【英文标题】:How does memset provide higher security than bzero or explicit_bzero? 【发布时间】:2018-12-15 11:51:48 【问题描述】:

bzero手册页指出,出于各种安全原因,它已被弃用,而应使用memset。他们主要指的是bzero 的问题或explicit_bzero 无法找到给定数据的所有副本(尤其是小到足以放入寄存器的数据),并且可能无法按预期完全擦除或覆盖。但memset 只接受一个指针地址。 memset 应该如何找到所有副本以弥补这种缺乏安全性?

【问题讨论】:

请看Why use bzero over memset? @WeatherVane 我已经看到了这个问题,但答案并不涉及安全问题。手册页基本上指出由于缺乏安全性,它已被弃用。但我不明白memset 应该如何提供它^^' 大多数优化器将 memset 视为内在函数,在适用的情况下用一些快速 MOV 替换它。我怀疑出现安全问题是因为 bzero() 被用于许多遗留网络代码中。当编译器对内联代码变得聪明时被击败,因此需要explicit_bzero() 解决方法。 但是explicit_bzero() 仍然存在类似的安全问题。当 memset 不能解决这个问题并且 bzero() 仅仅因为它被用于遗留网络代码而被弃用时,他们现在使用什么来擦除网络代码中的数据? 【参考方案1】:

我认为您误读了手册页。假设您正在谈论 Linux 手册页,它声称(正确地)explicit_bzeromemset_explicitmemset_smemsetbzero 更安全(出于某些目的)。它没有声称memsetbzero 之间存在任何安全差异。不推荐使用bzero 的原因是它是memset 的简单包装器,并且所有¹ C 实现都有memset,因此程序员不妨使用memset

memset/bzeroexplicit_/_s 变体之间的区别在于禁止编译器优化显式变体。这使得显式变体适用于清理机密数据。例如,考虑以下程序 sn-p:

bzero(password, password_length);
free(password);

只有bzeromemset,许多现代编译器会看到“哦,你正在写入内存然后释放它。没有办法回读bzero 刚刚写的内容,所以调用bzero 相当于什么都不做。什么都不做比调用bzero 更快,因此我不会为bzero 调用生成任何代码。”

编译器推理的缺陷在于,将内存归零的原因不是程序的已定义行为,而是在后续未定义或未指定行为的情况下会发生什么。从 C 编译器的角度来看,未定义的行为意味着任何事情都可能发生。从安全工程师的角度来看,对于未定义行为(例如缓冲区溢出或 use-after-free)究竟会发生什么是非常重要的。同样,从未初始化的内存中读回的确切内容是未指定的,但对安全工程师来说很重要。安全工程师试图减少此类未定义或未指定行为的安全影响。

所以对于安全工程师来说,memset 的优化是不幸的。一位安全工程师想要保证当内存被释放时,its former contents will not leak out, even, say, due to a buffer overflow。因此explicit_bzero:编译器被指示在此函数返回时将目标内存的内容视为可观察的,因此不允许它们基于程序没有从它读取回优化调用。从语义上讲,explicit_bzero(buffer, length) 等价于

bzero(buffer, length);
for (size_t i = 0; i < length; i++) __observe__(buffer[i]);

其中__observe__ 无效,但仍取决于其参数的值。因此不允许编译器删除对bzero 的调用,因为这样__observe__ 就不会读回正确的值。

显式归零有局限性。手册页强调它不会清除寄存器中变量的副本,但这通常不是一个大问题,因为缓冲区溢出或从未初始化的内存读取最终导致寄存器值泄漏的情况很少见。实践中最大的限制是realloc。当您使用动态分配的内存时,realloc 可能会移动它,并且无法清除旧值。出于这个原因,如果缓冲区的内容是敏感的,则不能在其上使用realloc

显式归零的另一个限制是它仅适用于程序级别,而不适用于系统级别。数据的副本可能保留在缓存、交换等中。将程序内的内存归零的目的是为了防止程序内的安全漏洞。它不能防止更大的系统妥协。

请注意,编写自己的explicit_bzero 是impossible to do portably。您能做的最好的事情就是让它与有限编译器的有限版本集一起工作,但不能保证下一个版本不会有一个更高级的优化器可以看穿您的尝试。这就是为什么 C11 将其作为标准函数添加为 memset_s

¹ 差不多。技术上独立的实现不一定要有memset,但它是如此简单和有用的功能,大多数人都会这样做,而且它通常由编译器提供,因此即使在没有通常的 C 运行时构建时也可以使用。

【讨论】:

以上是关于memset 如何提供比 bzero 或 explicit_bzero 更高的安全性?的主要内容,如果未能解决你的问题,请参考以下文章

bzero, memset ,setmem 区别

bzero - 向字符串写入零

为啥在 memset 中使用 '\0' 而不是 0?

函数--c

为啥或为啥不在 C++ 中使用 memset? [关闭]

嵌入式面试题