细微之处与安全性

摘要:该文章由比特币核心( Bitcoin Core)维护者和 BitMEX 客座作家 Michael Ford 撰写。 Michael HDR Global Trading Limited 比特币开发者资助计划的第一位受资助者。这接续了 Michael 为我们准备的第一篇文章:构建系统以及安全性 – 比特币正在改善。在此新文章中,Michael 解释了他一直致力于的针对比特币核心的四项安全性改善:

  1. 修复了导致无法进行安全检查的隐藏错误,
  2. 修复 Windows 上的安全缺陷,
  3. 解决由于无法在 MacOS 中检测功能而导致随机数生成器变弱的问题,以及
  4. 添加了在比较文档时针对 macOS 连接器行为差异的测试。

这项工作说明了在多个平台上进行测试的重要性。

最近,我花了一些时间来改善比特币核心的安全性。不仅是通过寻找和修复比特币核心本身中与安全相关的问题,同时通过完善构建系统并扩展我们的安全检查和向后兼容性(backward compatibility)脚本来确保不会发生回归。这篇文章记录了其中的一些改良,以及我还在进行中的一些工作。

对于像比特币核心这样的项目,一个运行良好的构建系统至关重要。它控制着我们可以使用的 C ++ 的版本和功能,相依性以及它们的配置方式,以及应用于二进制文件的强化和安全性功能。其同时可以确保跨平台以及向后兼容性,最终让我们对 bitcoind 的构建方式能够进行精细化控制。

security-check.py & symbol-check.py 跳过 bitcoind

`check-symbols` 和 `check-security` 规则被添加到了我们的 Makefile #7424 中。这些规则是将可执行文档传到一系列安全性和向后兼容性检查中的便捷方法,它们被用作发行过程的一部分。

但是,自从添加它们以来,它们受到了一个细微错误(subtle bug)的影响,该错误无提示地阻止了 `bitcoind` 被传送到任何一个脚本中。该问题可以用下列几行 Python 演示:

```python
# touch a, touch b, touch c
# python3 args.py < a b c

import sys
if __name__ == '__main__':
print(sys.argv)
# ['args.py', 'b', 'c']

# if you add some lines to "a",
# you'll see them here.
for line in sys.stdin:
print(line)
```

Bash 中使用 `<` 字符是一种输入重定向;在我们的例子中,导致传送给两个 bitcoind 脚本的第一个参数以标准输入被打开读取。这意味着其对于从 sys.argv 读取参数的两个脚本而言,基本上是“unseen”。

该问题已在#17857 中修复,并反向移植到一些分支机构。请知悉此问题的潜在影响实际上是很小的,因为 bitcoind 本质上是 bitcoin-qt 的子集 (从代码和依赖关系上看),其仍会被两个脚本检查。但是,它不能作为一个细微问题可能会带来更严重的后果的好例子,该问题在代码库中存在了将近 4 年的时间,而从未被注意。

地址空间布局随机化( ASLR )不适用于 Windows 中的 bitcoin-cli

地址空间布局随机化(以下称 ASLR)是一种安全技术,旨在通过在进程地址空间内随机放置数据,使攻击者更难以重用进程使用的数据作为攻击的一部分。我们在 Windows 版本(binutils-mingw-w64-x86-64)中使用的连接器(linker)中的一个错误,再加上我们其中一个可执行文档`bitcoin-cli` 没有导出任何 symbol 的事实,意味着直到最近,cli binary 文件在运行时并未获得 ASLR 的益处。

binutils(GNU ld)连接器在生成 Windows 二进制文件时曾经遇到一些问题。其中一个问题是,它会将二进制文件中的 .reloc 部分删除,即使这部分对于Windows 中的地址空间布局随机化功能(以及其他 header bits 等)是必要的,除非二进制文件刚好导出了某些 symbol 。

除了 bitcoin-cli 外,目前我们所有的 Windows 二进制文件都导出 secp256k1 symbol,因此不受此问题的影响。我们通过导出单个 symbol (`main()`))暂时解决了 bitcoin-cli #18702 的问题。最终,此问题将得到解决,并且当我们可以使用最新版本的 binutils 来构建我们的发行版时,此一权宜之计 (workaround) 将被移除。

此修复程序 (fix) 也被反向移植为 0.20.0 发行版的一部分。在#18629 中,我们的安全性检查脚本中添加了一项附加测试,以确保所有 Windows 发行版二进位文件均包含 .reloc 部分。再加上现有的安全检查,应该能确保所有二进制文件都将获得 ASLR 的益处。

sysctl()API 在 macOS 和 * BSD 平台之间有所不同

`sysctl()` 系统调用可用于检索和设置某些系统信息。这些信息包括内核启动时间(kernel boot time),中央处理器(cpu) 类型,随机存取记忆体 (RAM) 数量等。根据您是在 macOS 中调用 `sysctl` 或是 BSD 变体(FreeBSD,NetBSD等)的不同,您需要使用略有不同的功能参数。在 macOS 中,sysctl()的第一个参数是 `int * name`,而在 * BSD 中,第一个参数是 `const int * name`。

这种微小的差异意味着,在我们用来侦测 sysctl()可用性的构建系统中的调用有时会在不应该调用时失败。我们最初使用 `const int * name` 参数来调用,这意味着在 macOS 上编译时,侦测将无提示失败(除非您在 config.log 中查找)。

您可能想知道这很有趣的原因,而修正该问题的 PR 描述中并未提及该原因,#18359原始更改未涉及 random.cpp。

移除 OpenSSL 之前,一个新的 randomenv 模块被添加到我们的 RNG 中,该模块每分钟被使用一次,以收集环境中的其他熵(entropy)。 sysctl()是用于收集熵(entropy)的方法之一。这意味着,如果构建系统在编译时未能侦测到 sysctl()的可用性,我们将不会以二进位中使用的那些熵收集调用。

在 macOS 系统中,这导致了与 sysctl()调用按预期运行时相比,向 RNG 传递的熵减少了大约 22 bytes。

这不是一个重大的问题,也没有损害新的 RNG 模块(将随 0.20.0 一起发行),因为在某些系统中 sysctl()呼叫是 “if-supported” 的附加功能。但是,此问题是细微构建时间故障的另一个很好的示例,该故障可能在二进制文件的其他地方产生不容易发现的影响。

延迟绑定(Lazy binding),ld64 和 macOS 中的加载器(loader)

回到#17686,我为 MacOS 构建添加了 -bind_at_load 作为 “hardened” 标志之一。此标志指示ld64 在程序 header 中设置一个 bit,该 bit 向动态加载程序(dyld)指示要在程序启动时解析所有symbol,而不是延迟解析(首次使用时)。这类似于通过将 -z,now 标志传递给 GNU ld 可以实现的行为。

但是,很明显,与文档相反,ld64 实际上没有在二进位 header 中设置 “ MH_BINDATLOAD” bit。这就显示了两个问题:该功能是否按预期正常工作?如果确实如此,如果没有 header bit 可以查找,我们该如何在安全检查中对其进行测试?

进一步的测试显示了当使用 `-bind_at_load` 构建时,symbol 在启动时被绑定,并且测试的关键可能在 MACHO header 不同部分的 lazy_bind_ * 结构中。

在迟来的 #18295 跟进中 ,以及花了一些时间查看某些 Apple 工具的来源之后,我们最初打算修补用于构建发行版的 ld64 来插入缺失的 bit。但是,在处理 Apple 的连接器和加载器的 Nick Kledzik 提供了一些进一步的见解之后,我们决定不需要进行任何修补,我们只会对#17686 中提到的 header 结构添加安全检查,而基本上忽略 MH_BINDATLOAD bit,类似于 dyld 当前的行为。

从 Apple 命令行工具的最新版本(ld64-556.6)开始,ld64 的联机帮助页 (manpage) 仍指出其在使用 bind_at_load 标志时将设置 header bit 。

结论

尽管这只是我一直在进行的近期更改的概述,但希望它们可以作为下列几点的一些示例:

  • 跨平台测试始终很重要。
  • 您不能总是信任上游工具做正确的事。
  • 即使他们做正确的事,您也不能总是信任文档。
  • 无论上述情况如何,您仍然需要您自己的(有效的)制衡方法。

Michael Ford, 比特币核心开发人员

CC BY-SA 4.0