该手册页的bzero状态,对于各种安全原因,已被废弃,memset应改为使用。
他们主要提到的问题是,bzero或者explicit_bzero无法找到给定数据的所有副本(尤其是小到足以放入寄存器的数据),并且可能没有按预期完全擦除或覆盖。
但memset只接受一个指针地址。应该如何memset才能找到所有副本来关闭这种缺乏安全性?
我认为您误读了手册页。假设你在谈论Linux手册页,它声称(正确地)explicit_bzero和memset_explicit和memset_s比更安全(为了某些目的)memset和bzero。它没有声明memset和之间的任何安全差异bzero。bzero不推荐使用的原因是它是一个微不足道的包装器,memset并且所有¹ C 实现都有memset,因此程序员不妨使用memset.
memset/bzero和explicit_/_s变体之间的区别在于禁止编译器优化显式变体。这使得显式变体适用于清理机密数据。例如,考虑以下程序片段:
bzero(password, password_length);
free(password);
Run Code Online (Sandbox Code Playgroud)
仅使用bzeroor memset,许多现代编译器会看到“哦,您正在写入内存然后释放它。没有办法读回bzero刚刚写的内容,所以调用bzero相当于什么都不做。什么都不做比调用 更快bzero,因此我不会为bzero调用生成任何代码。”
编译器推理中的缺陷是将内存归零的原因不是程序的定义行为,而是在后续未定义或未指定行为的情况下会发生什么。从 C 编译器的角度来看,未定义的行为意味着任何事情都可能发生。从安全工程师的角度来看,在未定义的行为(例如缓冲区溢出或释放后使用)上究竟发生了什么非常重要。同样,从未初始化的内存中读回的内容是不确定的,但对安全工程师来说很重要。安全工程师试图减少此类未定义或未指定行为的安全影响。
所以对于安全工程师来说,优化memset是不幸的。安全工程师希望保证当内存被释放时,其先前的内容不会泄漏,甚至不会因为缓冲区溢出而泄漏。因此explicit_bzero:当此函数返回时,编译器被指示将目标内存的内容视为可观察的,因此不允许他们基于程序没有从它回读而优化调用。在语义上,explicit_bzero(buffer, length)相当于
bzero(buffer, length);
for (size_t i = 0; i < length; i++) __observe__(buffer[i]);
Run Code Online (Sandbox Code Playgroud)
where__observe__没有效果,但仍然取决于其参数的值。因此,不允许编译器删除对 的调用bzero,因为这样__observe__将不会读回正确的值。
显式归零有局限性。手册页强调它不会清理寄存器中变量的副本,但这通常不是一个大问题,因为缓冲区溢出或从未初始化的内存读取最终泄漏寄存器值的情况很少见。实践中最大的限制是realloc. 当您使用动态分配的内存时,realloc可能会移动它,并且无法清除旧值。因此,如果缓冲区的内容是敏感的,则不得realloc在其上使用。
显式归零的另一个限制是它仅适用于程序级别,而不适用于系统级别。数据的副本可能保留在缓存、交换等中。将程序内的内存清零的目的是防止程序内的安全漏洞。它不能防止更大的系统受到损害。
请注意,编写自己的代码explicit_bzero是不可能移植的。你能做的最好的事情就是让它在有限的编译器集的有限版本集上工作,但不能保证下一个版本不会有更好的优化器来看穿你的尝试。这就是为什么 C11 将它添加为带有memset_s.
¹差不多。从技术上讲,独立实现不是必须的,memset但它是一个简单而有用的函数,大多数人都这样做,并且它通常由编译器提供,因此即使在没有通常的 C 运行时构建时也可用。
| 归档时间: |
|
| 查看次数: |
846 次 |
| 最近记录: |