SQLインジェクションに関する脆弱性の修正などを行ったPostgreSQL 8.1.4,8.0.8,7.4.13,7.3.15の各バージョンが,5月23日一斉にリリースされた(関連記事)。いずれも同じメジャーバージョン系列であれば,dump/restoreによるデータ移行なしでアップグレードできる(ただし,8.1,8.1.1から8.1.4への移行については注意が必要。詳細は付属のリリースノートを参照されたい)。

修正が提供されないPostgreSQL 7.2以前のバージョン

 今回対策された脆弱性はPostgreSQL 7.2以前にも存在するが,開発者のポリシーにより,7.2以前はサポートの対象になっていない。いまだに7.2 以前のバージョンを使っているユーザーは,7.3以降にアップグレードするか,脆弱性が発生しないような使い方を考慮するしかない。

今までにない大規模な「マイナー」バージョンアップ

 バージョン番号の下2けた目が変わるマイナーバージョンアップでは,通常バグ修正など,最小限の修正が行われるためコードの修正量はわずかである。しかし今回の修正量は今までにない量であり(diff -cで約2万ステップ),しかも新しいC言語インターフェイス用の関数が追加されている。通常マイナーバージョンアップではこれは極めて異例のことである。

 そもそも今回の脆弱性に関する問題提起は,日本PostgreSQLユーザー会北海道支部長の石田氏と四国支部長の大垣氏の両氏から行われた。筆者がそれをコアメンバーに連絡したのが2月頃のこと。両氏と筆者がコアメンバーと協議する形で修正方針を議論してきた。脆弱性の修正はできる限り完璧に行う必要があるが,一方でできるだけ既存のアプリケーションの修正は少ない方がよい。このあたりのさじ加減をめぐって長い議論が行われ,結果的として膨大な量のパッチとなったが,当初の目的はほぼ達成できたと考えている。

日本語のユーザーに大きな影響

 今回対策された脆弱性は,SJISやEUC-JP,UTF-8などのマルチバイト文字エンコーディングに関連しており,日本語を使用するシステムでは非常に影響が大きい。そこで本稿では,今回対策された脆弱性の詳細な内容とそれに対する修正内容を詳細に解説する。

文字エンコーディングについて

 今回の修正内容を説明する前に,文字エンコーディング(以下「エンコーディング」)に関する知識を整理しておこう。ここが分かっていないと,今回の脆弱性,そしてそれに対する対策を理解するのが難しいからである。

 以下,文字エンコーディングについて詳しい読者は読み飛ばしていただきたい。

 コンピュータに文字を記録するには何らかの符号化が必要である。例えばASCIIコードではアルファベット小文字の「a」は97(16進数で0x61)に置き換えられて処理される。ASCIIでは英語と数字を符号化することしか考えていないので,1文字につき1バイトの記憶領域があればコンピュータに格納できる。そのため,長い間コンピュータは1文字=1バイトで処理を行ってきた。

 しかし日本語には漢字などの多数の文字が含まれているため,1文字を1バイトでは表現できない。そこで日本語を符号化する2つの方法が考えられた。一つは1文字を1バイトではなく,2バイト整数や4バイト整数で表現する方法である。これは「ワイド文字」と呼ばれることがある。一方,1文字を可変長の複数のバイトで表現することも考えられる。この方法によって符号化したものを「マルチバイト文字」と呼ぶ。前述のように,コンピュータは長い間文字列処理と言えばバイト単位で処理してきたため,ワイド文字を使うためにはプログラムの修正が必要になる。マルチバイト方式ならば修正量が少なくなる可能性がある。PostgreSQLもマルチバイト文字方式を採用している(一部ワイド文字も併用)。

符号化文字集合

 日本語を符号化する場合の「日本語」とは具体的にどのような文字を指しているのだろうか。平仮名や漢字は明らかに日本語だが,数字や記号やギリシャ文字はどうなるのだろう。これらは日本語ではないが,日常に必要な文字だ(ギリシャ文字は日常では使わないかもしれないが,これがないと教科書が書けない)。そこでJIS(日本工業規格)で符号化対象の文字種が決められている。

規格名含まれる主な文字種
JIS X 0201英数字およびカタカナ(いわゆる1バイトカナ)
JIS X 0208平仮名,カタカナ(いわゆる全角カタカナ),第1,第2水準漢字,記号,ギリシャ文字など
JIS X 0212補助漢字
JIS X 0213第3,第4水準漢字,記号
JIS X 0221ISO/IEC 10646 (UNICODE)

 これらは「符号化文字集合」ないし単に「文字集合」と呼ばれる。このように,日本語用の文字集合と言っても複数あり,しかも実際には複数の文字集合を組み合わせて使うことが多い。

 例えばJIS X 0201+JIS X 0208+JIS X 0212というのが多いパターンである。JIS X 0221は例外で,これ一つで単独で使われる。

PostgreSQLがサポートする日本語のエンコーディング方式

 PostgreSQLがサポートする日本語のエンコーディング方式は以下のようになる(すべてマルチバイト方式)。

EUC_JP

 文字集合のうち,ASCII,JIS X 0201,JIS X 0208,JIS X 0212を扱う。内部表現は以下のようになる。

文字集合内部表現
ASCII1バイトで表現。値の範囲は0x7fまで
JIS X 02010x8e+1バイトの2バイトで表現。値の範囲は0x8ea1から0x8edf
JIS X 02082バイトで表現。値の範囲は0xa1a0から0xa1fe
JIS X 02120x8f+2バイトで表現。値の範囲は0x8fa1a0から0x8fa1fe

 EUC_JPでは,1バイト目を見ると文字集合とバイト数が分かる。また,ASCII以外はすべてのバイトが0x80以上の値になっている。これはとても大切なことで,ASCIIには「\」(バックスラッシュ)や「'」(シングルクォート)などの特別な役割をする文字が含まれており,EUC_JPの文字種を構成するバイトはこれらの特殊文字と値がバッティングしないことが保証されているのである。したがって,ASCIIしか扱えないプログラムでも,0x80以上を通すようにすれば,最低限日本語が使えるプログラムにすることができるというわけだ。

 なおEUC_JPは通常「EUC-JP」と表記するが,PostgreSQLでは「-」は「"」で囲わないと使用できないため,あえてEUC_JPとしている。以下,UTF-8などについても同様である。

UTF_8

文字集合内部表現
ASCII1バイトで表現。値の範囲は0x7fまで
JIS X 02212から4バイトで表現。各バイトの値は0x80以上

 UTF_8では,ASCII以外の場合には各バイトが0x80以上になっているので,EUC_JPと同様,ASCIIの特殊文字とバッティングせず,プログラムで扱いやすいという特徴を持っている。またEUC_JPと同様,先頭バイトを見れば1文字を構成するバイト長が分かるようになっている。

SJIS

文字集合内部表現
ASCII1バイトで表現。値の範囲は0x7fまで
JIS X 02011バイトで表現。値の範囲は0xa1から0xdf
JIS X 02082バイトで表現。値の範囲は1バイト目が0x81から0x9fおよび0xe0から0xef。2バイト目が0x40から0x7eおよび0x8cから0xfc

 SJISでは,EUC_JPやUTF_8とは違って,JIS X 0208を表現する2バイト目にASCIIと重複する値が含まれている。このためプログラムでの扱いはやっかいであり,PostgreSQLではデータベース用のエンコーディングとしては使えない。フロントエンドでSJISを使いたいときは,データベース用のエンコーディングをEUC_JPかUTF_8にしておき,PostgreSQLが自動変換を行う。

 実は今回対策した脆弱性の半分はSJISにまつわるものである。