警报:对ResultRelInfo的更改是PostgreSQL 17.1的一次险些失误
作者:克雷格·克斯汀斯(Craig Kerstiens)
原文链接:
https://www.crunchydata.com/blog/a-change-to-relresultinfo-a-near-miss-with-postgres-17-1
自成立以来,Crunchy Data一直在社区在软件包发布当天发布 Postgres 的新版本和软件包。昨天的小版本发布是我们第一次决定暂停发布。为什么我们没有立即发布它?似乎存在破坏现有安装的真正风险。让我们回顾一下 Postgres 发布日的一次险些失误。
昨天(11月14日),Postgres 17.1 发布时,应用程序构建接口 (ABI) 似乎发生了重大变化。ABI 是 PostgreSQL 与其扩展之间存在的契约。初步报告显示,许多扩展可能会受到影响,从而引发社区警报。换句话说,如果您要从 17.0 升级到 17.1 并使用这些扩展,则可能会出现无法运行的 Postgres 数据库。进一步调查显示,TimescaleDB和Apache AGE是受影响的主要扩展,如果您正在使用它们,则应暂时不要升级到最新的次要版本,或者确保在升级过程中根据最新的 PostgreSQL 版本重建扩展。
对于那些好奇的人来说,初始扩展列表是:
首先,简单介绍一下 Postgres 的版本。Postgres 每年发布一次主要版本,大约每三个月发布一次次要版本。主要版本预计是向前兼容的,但会引入导致目录更改的较大更改。主要版本升级应谨慎对待。相比之下,次要版本发布仅与安全性和错误修复有关。它们旨在能够插入并继续在相同的现有主要版本系列中工作。
关于 Postgres ABI 和 Postgres 扩展
Postgres ABI(应用程序二进制接口)是指 Postgres 与编译的扩展、模块或与其交互的客户端之间的二进制级接口。ABI 包括各种结构,这些结构定义了系统内部工作的关键组件。这些结构表示 PostgreSQL 如何管理和操作数据、查询执行和内存。它们通常包括以下内容:
l 系统目录
l 函数签名
l 数据结构布局
为什么 ABI 很重要?
扩展的开发人员确保其扩展与 Postgres ABI 兼容。主要版本之间对 ABI 的更改需要重新编译任何扩展以防止出现运行时问题。
通常,主要版本之间不会保持 ABI 兼容性。例如,针对 PostgreSQL 14 编译的扩展可能需要针对 PostgreSQL 15 重新编译,因为可能会发生 ABI 更改。
PostgreSQL 通常致力于保持扩展在各个次要版本之间的兼容性。这意味着,如果您为 PostgreSQL 15.1 构建扩展,它应该适用于 15.2。然而,情况并非总是如此。PostgreSQL ABI 保证的细微差别一直是一个热门话题,他们在 7 月份就该主题制作了新的文档。
昨天,17.1 版本发生了重大结构变化。
到目前为止,我们了解了多少?让我们深入了解一下
在 PostgreSQL 扩展中,有包含 PostgreSQL 本身的头文件的 C 代码。在编译扩展时,来自这些头文件的函数以二进制形式表示为抽象符号。在根据函数名称加载扩展时,符号会链接到函数的实际实现。这样,针对 PostgreSQL 17.0 编译的扩展通常仍可加载到 PostgreSQL 17.1 中,只要来自头文件的函数名称和签名不变(即应用程序二进制接口或“ABI”稳定)。
头文件还声明了传递给函数的结构(作为指针)。严格来说,结构定义也是 ABI 的一部分,但还有更多微妙之处。编译后,结构主要由其大小和字段偏移量定义,因此例如名称更改不会影响 ABI(但会影响 API)。大小更改确实会影响 ABI,但影响不大。
typedef struct ResultRelInfo{NodeTag type;/*... (130 other lines) ...*//* updates do LockTuple() before oldtup read; see README.tuplock */bool ri_needLockTagTuple;} ResultRelInfo;
大多数情况下,PostgreSQL 使用一个宏在堆上分配结构,该宏查看结构体的编译时大小(“makeNode”)并将字节初始化为 0。在 17.1 中出现的差异是向 ResultRelInfo 结构体添加了一个新的布尔值,这使其大小从 376 字节增加到了 384 字节。
接下来会发生什么取决于谁调用 makeNode。如果是 PostgreSQL 17.1 代码,则使用新大小。如果是针对 17.0 编译的扩展,则使用旧大小。当它使用指向使用旧大小分配的块的指针调用 PostgreSQL 函数时,PostgreSQL 函数仍会采用新大小,并且可能会写入超出分配的块的内容。
这通常很成问题。它可能导致字节被写入不相关的内存部分,或者程序崩溃。在运行测试时,PostgreSQL 有内部检查(断言)来检测这种情况并发出警告。
因此,一般来说,结构中的这一特定更改实际上不会影响分配大小。可能存在未初始化的字节,但通常通过调用 InitResultRelInfo 来解决。该问题主要导致在测试/启用断言的构建中出现警告,这些构建针对分配 ResultRelInfo 的扩展,但仅在使用新 PostgreSQL 版本和针对旧 PostgreSQL 版本编译的扩展二进制文件运行这些测试时才会出现。
如果这让你感到困惑?结果该如何?
不幸的是,故事还没有结束。严重依赖 ResultRelInfo 的扩展(如 TimescaleDB)可能会因大小变化而受到影响。例如,在 TimescaleDB 的某个代码路径中,它需要在数组中找到 ResultRelInfo 指针的索引,为此它会进行指针运算。该数组由 PostgreSQL 分配(384 字节),但 Timescale 二进制文件假设为 376 字节,结果是一个无意义的数字,然后导致断言失败或分段错误。
需要明确的是,这里的代码实际上并没有错。与 PostgreSQL 的契约只是没有完全按照设想的那样。对于 Postgres 扩展的开发人员来说,这对我们所有人来说都是一个有趣的教训。
其他扩展中很可能也存在类似的问题。TimescaleDB 非常流行,因此经过了更广泛的测试才发现这个问题。话虽如此,根据过去 24 小时的调查,到目前为止,针对此标头构建的大多数扩展似乎都是安全的。另一个高级扩展是 Citus,但从我们的调查来看,Citus 扩展似乎确实是安全的。
你应该做什么?
如果您是 Crunchy Data 客户,则无需担心。如果您在任何平台上使用 Crunchy Data Postgres、Crunchy Bridge、Crunchy Postgres for Kubernetes - 我们的构建、发布和认证程序均按预期进行,并且已对我们的任何软件版本应用了适当的缓解措施。我们很幸运拥有一支出色的构建和发布团队,他们大部分时间都在幕后工作,但可以确保处理此类问题。如果你是 PostgreSQL 社区的用户,或者你自己打包了自定义的扩展,那么建议你阅读 psql-hackers 邮件列表中的相关讨论,以了解哪些扩展可能受到影响,并了解受影响版本的潜在缓解措施。
17.0 -> 17.1
16.4 (及更早版本) -> 16.5
15.8 (及更早版本) -> 15.9
14.13 (及更早版本) -> 14.14
13.16 (及更早版本) -> 13.17
12.20 (及更早版本) -> 12.21
简而言之:
如果您正在使用 TimescaleDB 扩展,Timescale建议用户此时不要执行次要版本安装。
如果您正在使用 pgsql-hackers 邮件列表中指出可能受到影响的扩展,则在升级之前需要格外小心(尽管我们自己的Marco Slot已确认 Citus 不受影响)
如果你正在从源代码编译 Postgres 扩展,请确保你的扩展已使用最新版本 17.1 进行编译
如果您正在开发或安装自定义 Postgres 扩展,那么值得花时间了解这个特定问题的影响和 Postgres ABI“承诺”。
最终,执行 Postgres 次要版本升级的默认指导方针得以实施,并且此问题的影响并不像最初担心的那样广泛。Postgres 社区再次及时发布了次要版本,以解决一系列 CVE 和修复程序,并且社区迅速响应了潜在问题的报告。Postgres 提供商发布流程的生态系统按预期运行,似乎任何潜在影响都已基本避免。
话虽如此,软件很难,数据库尤其棘手。随着 Postgres 扩展越来越受欢迎,这些风险将继续出现,了解这些细节或在选择谁来支持您的数据库时确保他们了解这些问题是有帮助的。