从一开始就不支持怪异的体系结构

2021-03-01 02:13:06

这篇文章包含我自己的观点,而不是我的雇主或我所属或贡献的任何开源组织的观点。

它也被重写了2½次,而且(我认为)在某些地方读起来令人困惑。但是我向自己保证,我会把它拿出门,而不是继续坐在上面,所以我们开始吧。

开源社区中最近有大量关于支持的戏剧性辩论,这主要源于pyca / cryptography决定对某些ASN.1解析例程使用Rust的决定。

总结一下情况:从头开始构建最新的pyca / cryptography版本现在需要Rust工具链。当前仅有的2个Rust工具链是基于LLVM构建的,它支持(相对)有限的一组体系结构。 Rust进一步将其分为支持层,一些目标没有接受自动测试(第2层)或官方构建(第3层)。

相比之下,上游3 GCC支持更大的体系结构集。但是C 4本身就是癌症,它在没有GCC(或LLVM)帮助的情况下进入了每种架构,从而引导了其他一切。

程序打包者和分发者(通常与项目维护者本身是分开的)非常习惯C的普遍存在。他们非常习惯,他们建立了通用机制,只需一个假设即可使用整个C编译器,即可将整个发行版放到新的体系结构中。

这是冲突的核心:Rust(以及许多其他现代,安全的语言)出于相对简单的目的而使用LLVM 5,但是LLVM不支持本机或交叉编译到无数流行(小众)架构。包装经理越来越发现他们最古老的假设之一很容易被违反,他们对此并不满意。

但这就是问题:这是一个错误的假设。它是默认值,代表着无可否认的安全性,可靠性和可复制性灾难。

一切都适合您:您拥有满意的用户,活跃的开发基础,甚至是公司赞助商。您还拥有一个CI / CD管道,可以在经过测试的体系结构上生成您的项目的规范版本;您将这些发行版的使用中的任何问题都视为项目本身的错误,因为您已经负责将其打包。

因为您的项目很受欢迎,所以其他人也会分发它:Linux发行版,第三方程序包管理器和寻求部署自己的受控内部版本的公司。这些其他的需求和设置略有不同,并且在不同程度上将:

禁用重要的安全功能,因为其生态系统的其他部分尚未赶上

修补项目或其构建,使其具有全新的依赖关系,编译器,工具链,体系结构和环境约束,使其“正常工作”(阅读:编译且不会立即崩溃)

在错误报告开始出现之前,您对上述任何一项都不了解:用户将报告已修复的错误,您明确记录的由不受支持的配置引起的错误,毫无意义的错误。

您无法调试用户的报表,因为您无权访问他们正在运行的小众硬件,环境或公司系统。您慢慢地烧掉了无尽的已经修复的bug,这些bug似乎永远不会出现在您的用户身上。您的用户群很不满意,您开始怀疑为什么要一开始就将所有这些精力投入到项目维护中。开源应该很有趣!

这个尖刻有什么意义? pyca / cryptography正是这样:没有人问他们尝试在HPPA上运行他们的代码是否是个好主意,更不用说System / 390 6了;一些包装商只是继续进行下去,并且对它不再起作用感到沮丧。人们只是以为itwould,因为仍然存在一切都来自C的规范,并且具有中途功能的C编译器的任何主机都应拥有整个开源生态系统。

程序的安全性取决于其自身的设计和测试,以及其底层平台的设计,测试和基本正确性:从用户空间,内核到编译器本身的一切。在最好的情况下,后者是一个未解决的问题:即使在最成熟的编译器(Clang,GCC)及其最成熟的后端(x86,ARM)中,也经常发现错误。对构建系统的微小更改或差异可能会对二进制级别产生深远的影响,例如意外删除安全缓解措施。看似无害的补丁程序可以使在其他漏洞的情况下仍然可以利用安全代码。

随着我们朝着主要由小型业余爱好者社区使用的好的架构和目标迈进,问题变得更加严重。考虑m68k(受pyca / cryptography迁移到Rust的其他架构之一):甚至GCC也在考虑由于缺乏维护而取消支持,直到业余爱好者这并不是说任何特定的利基目标都充满了错误10;只是说,一般而言,利基定位的可能性更大。没有人会定期测试大量用户空间代码,这些代码与这些平台上的任意程序隐式地形成了运营合同。

项目维护人员不想追逐他们最初不想支持的ISA或系统上的编译器错误,也不会收到任何积极的支持反馈。他们尤其不希望由于与项目相关的漏洞在进行安全改进时,有问题的工具链或工具惯性。

仅凭功能和/或单元测试就可以确保在怪异的体系结构或平台上实现基线正确性,但要走很长的路要走,但是测试C并使程序运行这些测试的认知开销确保了经过良好测试的C程序构建将继续是例外,而不是规则。

Make很好,但不是标准的。使用hodgepodgeof Make,从自动工具自动生成的规则以及维护人员的精品Shell脚本,可以编译大量的关键开源基础结构。这样做的一个后果是,C编译倾向于对故障灵活:预期的打包程序可以注入各种行为修改标志,而这些标志可能无法在编译的二进制文件或其他编译产品中直接得到证明。结果:几乎不可能证明不同机器上的两个独立版本是相同的,这意味着维护人员会更加痛苦。

是的,我知道程序包管理器存在。是的,我知道如何静态链接。是的,我知道如何供应商库和分发独立的程序“捆绑包”。这些都不是完整的标准,也不构成完整的标准,每一个都会带来其他的后勤或安全问题。

尽管看起来很像PDP-11,C抽象机还是泄漏了目标架构的底层内存和排序语义。结果是,即使经验丰富的C程序员在表面上跨平台的代码编写时也经常依赖于体系结构特定的假设:有关读写原子性,操作顺序,自修改代码中的一致性和可见性,未对齐访问的安全性和性能的假设,等等。除了可能导致不安全之外,所有这些方法都无法在一般情况下进行静态检测:毕竟,它们在程序员的主机体系结构上是完全正确的(并且经常是故意的!)。

按照当代的编程语言标准,这些功能之间的差距非常明显:我们早已学会将测试,构建,分发和声音抽象机器语义纳入语言(以及语言设计本身)的标准工具中。但是他们的缺席带来了双重危害:他们确保C仍然是永久不安全的开发生态系统,并且是引导新平台时有吸引力的目标。

上面提到的一切都会导致低价的软件包维护者11的工作繁琐。他们(可能)也是免费的开源爱好者,并且他们的工作受到上游人员不太可能立即了解的约束:

需要链接到已打包(可能已打补丁)的依赖版本的链接

ABI和ISA子集的限制,是由于需要分发二进制文件,而这些二进制文件必须具有相对较旧版本的glibc或x86-64 CPU,而没有现代扩展

对每个项目的测试套件及其运行方式的可见性有限,而在失败时该怎么办

他们还必须与那些对这些报告表示同情的用户打交道,并且他们:

很少将报告提交给打包程序(他们直接将项目打扰了!),或者不跟踪报告

要求软件包中具有根本冲突的属性:上游的最新功能和最大功能,以及打包程序永远不要中断其部署(因此,源源不断的未经测试的非正式补丁)

所有这些都会导致程序包维护人员精疲力尽,并导致项目及其下游分销商之间的竞争关系(越来越多)。这些都不适合项目,关键包装生态系统的健康或用户本身(最重要的是)。

我只是勉强自满,以为我的潜在解决方案值得向全世界传播。他们来了。

构建系统是一团糟。我已经谈到了它们在专业环境中的复杂性。

解决项目作者最初未考虑的平台支持问题的长期解决方案将有两个方面:

构建必须是可观察和可审查的:项目维护者应该能够获取进行构建所用的确切调用和依赖关系,并对构建信息进行自动分类。这将需要在环境和整个生态系统范围内进行更改:对象和包装格式将需要更新;需要设计用于元数据和从任意分发者到项目的共享信息的标准。有关信息范围及其可用性的合理隐私问题将需要解决。

报告需要更好地指导:单个(至少是技术性的!)最终用户应该能够弄清楚到底是什么故障以及故障发生时应该打电话给谁。这意味着要严格跟踪分发者所应用的补丁程序(请参阅上文的构建可观察性),并创建将信息传递给需要它的人员的机制。这些相同的机制也需要某种交互机制:没有比上下文13泛滥的自动化错误报告泛滥的地方了。

Rust当然不是第一个提供不同支持层的生态系统,但是它们做得很好:

明确列出了层并记录了层。如果您在特定级别的存储桶中,那么您将确切地知道所获得的东西,可以得到的保证以及您自己需要做的事情。

官方构建提供了传递保证:它们可以将修补程序携带到编译器和其他组件,而无需修补整个系统。携带补丁仍然不是很好,但是目前无法避免。

层级已嵌入工具本身:您不能在DEC ALPHA上使用rustup,并且(错误地)期望提取成熟的,经过测试的工具链。您不能,因为那是骗人的。这与C范例相反,在C范例中,未经测试的编译器将很高兴被一大批autotoolsshell进行欠检查,从而产生了不确定的正确性。

期望得到管理。这一点实际上只是前三个方面的高潮:有了明确的层级,再也没有隐含的保证,即最低功能的构建工具链需要功能齐全且受支持的软件。可以将用户指向一个页面,告诉他们他们正在做某人没有尝试过(或当前想得到)支持的事情,并向他们提供选择:帮助,资助该项目,na劳他们的雇主和&c。

我把它放在最后是因为它很浮躁,但它可能是最重要的:除了业余爱好者在玩怪异的架构以娱乐之外(并接受绝大多数项目无法立即为他们服务的压倒性可能性),开源组织不应无条件地提供支持。大型公司的硬件和/或平台的生态系统。

公司应该直接为此付出代价:如果pyca /密码术实际上在HPPA或IA-64上崩溃了,那么HP或Intel或任何人都应该花钱修理它,或使用自己的成群的工程师自己修理。对于仅公司使用14的平台,没有免费的工作。关于OSS的任何事情都没有说明您必须向后弯腰以支持您最初并不关心的公司平台。

对于不熟悉的人:ASN.1是一种大而凌乱的IDL和序列化格式,从历史上一直是加密软件中容易利用的bug的主要来源。加密协议定期解析不受信任的ASN.1;用安全语言(例如Rust)重写任何数量的ASN.1处理都可带来显着的安全益处。 ↩

Rust有一个进行中的GCC前端,但尚不能编译有意义的程序(撰写本文时)。还有起重机,这可能会打破Rust在未来对LLVM的依赖,但目前尚不支持那么多目标。 ↩

野蛮的GCC特定于供应商和平台的版本太多了。 ↩

在这篇文章的各个部分中,用“ C”代替“ C和/或C ++”。 ↩

LLVM在最近十年左右的语言研究和开发中具有多么重要,以及使它工作起来多么容易,这一点不能高估。但这是一个完全独立的帖子的主题。 ↩

请注意,这就是原始的S / 390,而不是64位的“ s390x”(也称为z / Architecture)。想一想您自己的C项目:您是否敢打赌它们可以在甚至Linux都不再支持的31位架构上正常运行? ↩

我还将安全性和可靠性混为一谈,这可能会引起争议。 也许是将来的帖子。 ↩ 尽管m68k似乎确实有其应有的份额。 具有讽刺意味的是:由于Debian可能已修补了编译器,因此GCC维护人员无法复制某些报告! ↩ 查看供您选择的经理使用的维护人员周转率和/或未维护的包裹比率。 两项统计可能都高于您的预期。 ↩ 而且,没有办法提前猜测合适的上下文量。 一个核心转储并不总是能够削减它,而且对我们成像整个用户机器而言,对我们来说可能不是很酷。 ↩ Reddit讨论