
2026/11/13 3:22
22歳のPostgreSQLのバグがリモートコード実行に発展した
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
「管理された PostgreSQL インダストリにおけるシステム的リスク」シリーズの第 3 部において、著者は PostgreSQL コア内での重要な 22 年前のゼロデイ脆弱性を詳述する。この問題は、2004 年のミュータブルロウタイプに関するコミットから発生し、PostgreSQL が Datum 値を処理する方法に関する誤解に基づいていた。この欠陥は、攻撃者がデータ型を操作(例:UUID を bytea に変換)し、システムをクラッシュさせたり機密メモリを漏洩したりすることを可能にした。発見のきっかけは、2026 年 6 月から 7 月の間に
pg_clickhouse 拡張機能におけるメモリエラー問題を利用したものである。このシステム的リスクにより、52 つの新しい CVE を含んだパッチリリースが 2026 年 11 月 9 日に公開された(主要な修正は CVE-2026-6460 で、その他 5 つ)。その結果、Databricks、Google Cloud(AlloyDB)、AWS Aurora/RDS、Microsoft Azure を含む管理された PostgreSQL プロバイダーに対して、サービス向けの緊急の修正調整が強制された。
キーポイント
- 著者は、PostgreSQL コア内でコンパウンドタイプにおけるタイプ混同脆弱性と関連する 22 年前のゼロデイを発見した。
- このブログ投稿は、「管理された PostgreSQL インダストリにおけるシステム的リスク」シリーズの第 3 部である。
- 調査は、拡張機能
のメモリエラー脆弱性を 2026 年 6 月から 7 月にかけて利用した後、開始された。pg_clickhouse - 研究の中で、PostgreSQL が Datum 値(8 バイトのジェネリックキャリア)を処理する方法に関する重要な誤解が特定された。
- プリミティブは、クエリの準備から実行の間でコンパウンドタイプの属性のタイプを変更することを可能にする。
- UUID を bytea に変更することで、著者は
およびrecord_out()
を介してセグメンテーション・フォールトを引き起こした。byteaout() - この攻撃は、varlena ヘッダー内の外部展開されたオブジェクト表現(具体的には 01 マーカー)を利用して制御フローを再指向させた。
- メモリアドレスは、配列要素のタイプを混乱させること(text から bigint)によって漏洩し、ポインタ値が整数として解釈されるようにした。
- 報告されたケースは計 21 件で、主要な修正である CVE-2026-6460 および他の 5 つ(CVE-2026-120142、CVE-2026-120148、CVE-2026-116329、CVE-2026-116306、CVE-2026-120139)を含む。
- 脆弱性は、2026 年 11 月 9 日に 52 つの新しい CVE を含むリリースで修正された。
- 複数の管理された PostgreSQL プロバイダーが影響を受け、修正を調整しており、Databricks、Google Cloud(AlloyDB)、Supabase、Ubicloud、AWS Aurora/RDS、Microsoft Azure が含まれる。
- ミュータブルロウタイプのプリミティブは、2004 年 5 月 5 日に導入されたコミット(コミット 077db40fa1f3)に遡る。
本文
PostgreSQL タイプ混同系ゼロデイ脆弱性の発見と修正:2026 年夏物語
序論:「ウサギの穴」への誘い
2026 年 4 月、「マネージド PostgreSQL を使用できるか?」という単純な質問が、私を最も深い「ウサギの穴」へと引きずり込みました。数ヶ月後、その底には PostgreSQL コアに存在する 22 年間の空白期間を抱えたゼロデイ脆弱性が発見されたのです。
本記事では、このタイプ混同(Type-Confusion)系脆弱性を発見した経緯を共有します。これは「マネージド PostgreSQL 業界におけるシステムリスク」と題するシリーズの第 3 弾です。初回と第 2 回の読覧をお勧めします。
【要約】
- 発見のきっかけ: PostgreSQL のフィールドデータ型を変更して読み込み直すことで、最も再現性の高いメモリ破損エクスプロイトが生まれました(当初は別の拡張機能
から調査を開始)。pg_clickhouse - 研究プロセス: AI エージェント(「pg_expert」)の指導を受けながら、PostgreSQL の内部機構を解明。一見無関係な要素からすべてを繋ぎ合わせたアイデアで、初期機能(primitives)に直面しました。
- 本記事の内容: 部品たちがどう組み合わさったかという完全版の実話です。
2026 年 8 月の始まり:Datums の謎と pg_clickhouse
の教訓
pg_clickhouse2026 年 6〜7 月、私は PostgreSQL 拡張機能におけるメモリ破損脆弱性を調査し、コード実行を達成しました。特に
で成功したケーススタディは重要な示唆を与えてくれました。pg_clickhouse
pg_clickhouse
エクスプロイトの教訓
pg_clickhouse- 問題の本質:
は ClickHouse の値を PostgreSQL の形式へ変換します。ClickHouse 配列と PostgreSQL 配列は構造が異なるため、拡張機能は要素を PostgreSQL が期待する形式のpg_clickhouse
値として再構築する必要があります。Datum - 脆弱性の原因: この変換プロセスにおけるミスが最終的にコード実行につながりました。
- Datums と OID の理解: これにより初めて「PostgreSQL が自身のデータをどのように理解し、表現しているか」という根本的な疑問を抱き、調査を PostgreSQL コアへと広げました。
免責事項: 上記
の OOB(Out-of-Bounds)書き込み+読み取り連鎖はベンダーへ報告され、6 月 23 日の修正済みです。pg_clickhouse
PostgreSQL タイプ入門:黒魔法の解明
「単純なクエリ
SELECT 1337」を分析し、Postgres の内部機構におけるデータ型の扱いを理解しました。
クエリの構文解析と Datums
SET debug_print_parse = on; SELECT 1337;
- パースツリーには
(int4) と"consttype": 23
が含まれます。"constvalue": [ 57 5 0 0 ... ] - 重要点:
は int32 ではなく、汎用的なキャリア(運搬媒体)である 8 バイトのconstvalue
で宣言されています。Datum
はその下の 4 バイトに格納されています。1337::int4- テキストやカスタム型も同じ 8 バイトの
キャリア内で表現されます(ポインタ値またはインライン値)。Datum
Datum
の本質:それは意味を「理解」していない
Datum- Datums は盲目的:
を運ぶ 8 バイト自体には「これは整数です」というラベルがありません。1337 - 型判別の仕組み: PostgreSQL が「これは int4 である」と判断するのは、
システムカタログに登録された OID (23) とその行内の定義によるものです。pg_type
SELECT oid, typname, typlen, typbyval FROM pg_type WHERE oid = 23; -- 結果: oid=23, typname=int4, typlen=4, typbyval=true
未定義の型と動的変換
タイプ: 引用符付きリテラル(例:unknown
)は、最終的な型が決まる前の一時的な'1337'
(OID 705) タイプを持っています。unknown- 機能の仕組み: PostgreSQL は意図的に未指定のリテラルに対して一時的な型を割り当て、後でクエリ文脈や関数呼び出し(
/typinput
)によって動的に変換します。typoutput
で Datum へ変換し、int4in()
でテキストへ戻す。int4out()
批判的考察: PostgreSQL は、値を構築した時と利用する時に間に 型を変更できないかという突破口を見つけました。これにより、意図しない型の出力関数(例:
textout)を Datum に適用させることが可能になり、タイプ混同(Type-Confusion) が生じます。
実証:意図的なタイプ混同
-- int4 に対して textout 関数を内部言語で定義 CREATE FUNCTION pg_temp.confused_textout(integer) RETURNS cstring LANGUAGE internal STRICT AS 'textout'; -- クラッシュ発生 SELECT pg_temp.confused_textout(1337); -- 結果: server closed the connection unexpectedly
- 成果: スーパーユーザーによる「LANGUAGE internal」機能を使って、ネイティブ関数に対するタイプ混同を実証しました。
寄り道:トマトと SQL インジェクションの教訓
準備済みステートメント(Prepared Statements)の分離原則における弱点について考察しました。
- 原則: 「知識はトマトが果物であること」「知恵はそれをフルーツサラダに使わないこと」。データベースはまずクエリ構造(レシピ)を理解し、その後データ(材料)を受け付けます。
- 発見: クエリの実行前に型を変更できるため、準備済みステートメント内の値を再解釈する余地があります。
22 年前のプリミティブの発掘
- アイデア: 準備したクエリにおいて、コンポジット型の属性データ型を変更し、実行時に混同を引き起こす。
-- 1. コンポジット型作成 (uuid) CREATE TYPE pg_temp.poc AS (key uuid); -- 2. クエリの準備 (uuid を文字列として) PREPARE pc_crash AS SELECT '(UUID_STRING)'::pg_temp.poc; -- 3. 実行前の型変更 (bytea に変更) ALTER TYPE pg_temp.poc ALTER ATTRIBUTE key TYPE bytea CASCADE; -- 4. クエリの実行 → セグメンテーションフォールト EXECUTE pc_crash;
根本原因分析:クラッシュのメカニズム
- 状況: PostgreSQL は属性を
と認識し、bytea
を呼び出します。byteaout() - 混同点: しかし、内部データは UUID バイト(
)のままです。01 02... - 崩壊プロセス:
が属性を見てrecord_out()
を選択。byteaout()
は外部拡張表現 (byteaout()
) とみなし、メモリアドレスを参照(ポインタ)と解釈。01 02- アドレスが NULL (
) であるため、00000000
で NULL ポインタへのアクセスが発生。DatumGetEOHP
内でポインタデコード失敗 → SIGSEGV。EOH_get_flat_size(NULL)
制御フローの乗っ取り:Fake Expanded Objects
- 目標: PostgreSQL が「拡張オブジェクト」を処理するコールバックインターフェース(ネイティブ関数アドレス)を利用し、制御フローを変更する。
- 構造:
typedef struct ExpandedObjectMethods { EOM_get_flat_size_method get_flat_size; EOM_flatten_into_method flatten_into; } ExpandedObjectMethods; // コールパス: H (Header) -> M (Methods) -> system() return eohptr->eoh_methods->get_flat_size(eohptr); - 戦略:
のヘッダー内のオフセット +0x08 に、任意のネイティブ関数(例:ExpandedObjectHeader
)のアドレスを配置。PostgreSQL がそのアドレスを呼び出すように誘導する。system()
メモリアドレス漏洩と ASLR 回避
- 課題: ハルデニングのため
を直接読み込めない。/proc/self/mem - 解決策: アリーデータ構造を利用したアドレス露出。
- PostgreSQL アリー(配列)は内部に要素型を格納し、
で抽出時にその型に従います。unnest() - 手法: 内部的には「テキストポインタ」を保持しつつ、外部から
と宣言させる。結果として、bigint[]
はメモリアドレスそのもの(バイナリー・バリュー)を SQL に返す。unnest()
- PostgreSQL アリー(配列)は内部に要素型を格納し、
POC: アドレス漏洩と制御
3 つの UUID を結合して 48 バイトのアリーキャリアを作成し、UUID の一部をポインタ値に変換する。
CREATE TYPE pg_temp.address_carrier AS (a uuid, b uuid, c uuid); CREATE FUNCTION pg_temp.leak_address(r pg_temp.address_carrier) RETURNS bigint LANGUAGE plpgsql AS $$ DECLARE pointer bigint; BEGIN -- 内部的には UUID のポインタを返す(文字列解釈) SELECT x FROM unnest(r.a) AS item(x) LIMIT 1 INTO pointer; -- 周囲の文脈で整数として処理するためオフセット調整 RETURN pointer + 4; END $$; -- 型変更による混同実行 ALTER TYPE pg_temp.address_carrier ALTER ATTRIBUTE a TYPE bigint[] CASCADE; -- アドレス取得 EXECUTE address_leak;
この手法により、メモリアドレスを漏洩させ、ASLR を回避して任意のネイティブ関数を呼び出すプリミティブが完成しました。
修正と対応:CVE-2026-6460 の発表
9 November 2026: PostgreSQL セキュリティコミットにより、52 件の新 CVE を含む大きなリリースが適用されました。
- 主な修正 (CVE-2026-6460): タイプ混同の主要なアンブレラ修正。コンポジット型における属性バージョン管理を強化し、データとスキーマの不整合によるメモリ破損を防ぎました。
- 関連 CVE: 匿名 RECORD デスクリプタ問題、統計インポート時の型不整合、整数オーバーフローなど、合計 21 件のケースが修正されました。
マネージド PostgreSQL プロバイダーとの調整
- 戦略: リスクの外部化は不可能なため、マネージド DB業界全体へ脆弱性の報告と POC を共有し、インフラの外部化を含めリスクを共有・管理する仕組みを確認しました。
- 対応状況: 全ての Postgres 互換オファリングが影響を受けており、セキュリティチームへの迅速な報告が行われました。
- Databricks, Google (AlloyDB), Supabase, Ubicloud, AWS Aurora/RDS, Microsoft Azure: すべて POC を受け取り、優先的に修正を行いました。
- 緩和措置: 一部のベンダーは、アップストリームリリースを待つことなく、独自のパッチを顧客向けに即時提供しました。
おわりに:コミュニティへの感謝と今後の展望
20 年間のセキュリティ活動の中で最も印象的だったのは、PGCONF.eu で受けた暖かい歓迎でした。オープンソースへの貢献は私にとって意义重大であり、このエコシステムの成長を心から応援しています。
- 講演情報: 「Nobody Gets to Be Superuser (Until I Broke In)」の録画版は公開されませんでしたが、90 分バージョンの発表があります。
- 会場: ハラダ PostgreSQL ミーティング
- 日程: 2026 年 11 月 30 日
- 詳細: https://cloomba.com/e/prague-postgresql-meetup-8-2026-11-30
この脆弱性は、単なるバグ修正を超え、セキュリティ研究コミュニティ全体の力と信頼を体現しました。ご感想をお聞かせください。