Not only is the Internet dead, it's starting to smell really bad.:2014年06月下旬

2014/06/26(Thu)

[Citrus][FreeBSD][Security] FreeBSD-SA-14:15およびCVE-2014-3951 について(その1)

@重要なお知らせ

えーとCitrus iconvについて FreeBSD-SA-14:15なるものが出ています、より一般的には CVE-2014-3951の番号が割り当てられるYoですが、こっちは未だ未公開(馬に乗馬)かな?

@影響範囲

最も最悪(馬に乗馬)のケースとしては「リモートからのサービス拒否」攻撃が成立します。 これがツンデレ妹からのサービス拒否であればMッ気ムンムンのやきうのお兄ちゃん彡(゚)(゚)絶頂なんですが、まぁさっさと対策してどうぞ。

にぃに…にぃに…んちゅ…んちゅ…

今日時点でCitrus iconvを導入しているOSは現状

  • NetBSD (=Citrus本家)
  • FreeBSD
  • DragonFlyBSD

の3つのはず *1ですが、問題が発生するのはFreeBSDとDragonFlyBSDだけで NetBSD (=Citrus本家)には影響はありません*2これ重要なのでふっといふっとい字にしておきました、詳しい話はまた後で後述、馬に乗馬。

@発生しうる条件

文字コード変換をiconv(3)ないしiconv(1)に投げてるアプリとかスクリプトは全て影響受けます、最近は多いよね。

UTF-8とUCS4の相互変換だけに限定利用してるとかなら今回の問題には影響ないのですが、攻撃者が以下のいずれかの文字コードを選択可能だとダメっす。

  • Big5-2003, Big5-E, Big5-Eten, Big5-IBM, Big5-Plus, CP950(台湾)
  • Big5-HKSCS(香港)
  • HZ, HZ8(中国)
  • VIQR(ベトナム)

ユーザ入力を制限してなければこれのうちどれか選ぶだけでアウトだし、絞ってたとしても台湾と香港のBig5系はデファクトスタンダードなのでまずダメでしょう。影響範囲大きいと思います。

お使いのFreeBSD/DragonFlyBSDに問題が有るか無いかの確認はiconv(1)で簡単に試験を試みられるれろなのでやってみませう。

$ echo "opie vuln vuln" | iconv -f CP950 -t UTF-8
Segmentation fault (core dumped)

$ echo "opie vuln vuln" | iconv -f HZ -t UTF-8
Segmentation fault (core dumped)

$ echo "opie vuln vuln" | iconv -f VIQR -t UTF-8
Bus error (core dumped)

ひえっ、ちなこの結果はFreeBSD 10-RELEASE。オフコースもちろんNetBSDでは問題ありません。

@バタフライ効果による熱い風評被害を受けるアプリケーション達

影響を受けそうなサービス系のアプリとしては、脆弱性の西の横綱、 BINDのidnkitがiconv(3)使って国際化ドメイン名変換やってるようね。 idnalias.confにBig5とかあるし影響不可避っぽいです。

東の横綱 Apache HTTP ServerAPR(Apache Portable Runtime)で自前の文字コード変換 *3を抱えてるので大丈夫なはず、native iconv使うオプションあったっけ?

それと東前頭の SambaもUSE_NATIVE_ICONVだとmount_cifsのオプションでcodepageにCP950を指定したりするとダメそう。

また DovecotCourie-IMAPみたいなMDAに対して問題の起きる文字コードなメール投げるだけで気軽にリモートDoSできる可能性があります。

あと libxmlもiconv依存だし GNOMEあたりのアプリも軒並みクラッシュさせられそうやね、よう知らんけど(大阪人)。

わあい(白目)。

それ以外にも、昨今はgettext(3)みたいなメッセージカタログ機能もiconv(3)を内部的に使っとります *4。なんでlibintlをリンクしていればLANG環境変数を汚染することで攻撃可能ですやね。 ウェプアプリケーションフレームワークなんかも p5-Locale-gettextPHP-gettextあたりでUIの国際化をしてた場合、Big5系を選ぶとmod_perlとかphp-apc/fpmあたり巻き込んで死にそう *5

やっぱりdjb先生みたいにlibcなんか信用できるかksgが正義なんですかね(ぉ

@ワイ、責任を取ってharakiriするべきか調査開始。

こんな影響範囲の広いCVE出すような脆弱性を出した場合「これは harakiriやろうなぁ」とCitrusの偉い人も仰せになられておりますので。

SA番号が14と15の二つが割り当てられてることからとおり、問題は2つありますので次回はそれぞれについて解説しなぜNetBSDでは発生しなかったのかを解説する予定 *6

*1:めんどいので確認してないけどMacOS X/Darwin、Minux3、QNXあたりも微レ存、でも俺は嫌な思いはしてないから。
*2:だけどspz氏がこのFreeBSDでの patchをあたるところだけ(rejは無視して) commitしてくれやがってるので、NのSecurity Officerが検証せずにNetBSD-SAを出す可能性もあるけどね、タノシミダナー(棒)。
*3:最近はそれを使ってiconvをエミュレートするAPR iconvというものも出してるし。
*4:本来のCSI思想なら無用なんですがUnicode Hardwire野郎共の時代だししゃーない
*5:まぁ今時この手のウェブアプリでUTF-8以外使ってるようなところはなさそうだけども。
*6:すでにtwitterではネタばらし済ですがまー誰もあんなチラシの裏以下の下水垂れ流し読んでないだろう。

2014/06/27(Fri)

[Citrus][FreeBSD][Security] FreeBSD-SA-14:15およびCVE-2014-3951 について (その2)

昨日の続きです。

@citrus_prop パーサにおける NULL pointer dereference 問題

FreeBSD-SA-14がこれに相当します、II. Problem Description における以下の部分です。

A NULL pointer dereference in the initialization code of the HZ module

この文章だと libHZ( citrus_hz.c)側の問題っぽいですが、実際にはこれ citrus_prop パーサ( citrus_prop.c)のバグですやね。 なのでこいつを使ってる libBIG5( citrus_big5.c) にも同様の脆弱性があります。

最初にこの文言と対策patchを見たときに、明示的に NULL を渡さない限りありえねーし必要なところには契約プログラミング *1で NULL はこないと 表明しとんのにと思いました。

以下、実際の「the initialization code of the HZ module」の コードです。

694 static int
695 _citrus_HZ_encoding_module_init(_HZEncodingInfo * __restrict ei,
696 	const void * __restrict var, size_t lenvar)
697 {
698 	int errnum;
699 
700 	_DIAGASSERT(ei != NULL);
...
705 	errnum = _citrus_prop_parse_variable(
706 	    root_hints, (void *)ei, var, lenvar);

700行目の_DIAGASSERTマクロで_HZEncodingInfo 型のポインタ ei が NULL で無いと表明しています。

ちなどーでもいいですが _HZEncodingInfo 型というのは↓こんなん。

121 typedef struct {
122 	escape_list e0, e1;
123 	graphic_t *ascii, *gb2312;
124 } _HZEncodingInfo;

前回 影響範囲で触れたように libHZ は HZ と HZ8 という2種類の符号化文字手法を扱うのですが、それぞれで振る舞いが変わる部分の情報については この型に閉包されます(要はクロージャっす)。

そんで iconv_open(3) や setlocale(3) が呼ばれると、このクロージャが先ほどの _citrus_HZ_encoding_module_init() 内で初期されます。 初期化に必要なパラメータは、引数である

  • var (バイト列)
  • lenvar (バイト列長)

がそれ。このパラメータって実体はただの文字列で LC_CTYPE database や iconv database の中で定義されてる VARIABLE ちゅーものです。

mklocale(1)のマニュアルに

     VARIABLE   This keyword must be followed by a single tab or space charac-
                ter, after which encoding specific data is placed.  Currently
                only the EUC encoding requires variable data.

と説明ありますな *2

この文字列なんですが、これまで各モジュールがてんでバラバラにパーサを実装していました。 ですがサポートする符号化文字集合が増えるにつれ、必要なパラメータも増えて手間がかかるようになったので、 ワイが libHZ を実装したときに citrus_prop という共通パーサを実装を追加したのですよな。 モジュール側で実装しなくても、hint と callback function だけマクロ使って定義しとけば良くなるので、ほんのちょっと開発が楽になるような気がします *3

こいつを利用してるのが libHZ とlibBIG5 の2つのモジュールなので、今回の NULL pointer dereference 問題はこの2つでのみ発生してる事と完全に一致。 作者であるワイにはすぐに citrus_prop パーサの不具合(あっ、察し)となるわけです。

ここまで整理すると

  • クロージャはわざわざ表明して NULL にならないことを保証している
  • パラメータも LC_CTYPE/iconv database にちゃんと定義されてるので NULL にならないことは確実
  • 問題が発生してるのは citrus_prop パーサ内部

うーんこの、どう考えても NULL pointer dereference が発生する要素が微粒子レベルでも存在しません、ワイ混乱。

@俺達はとんでもない思い違いをしていたようだ。

話は聞かせてもらった、地球は滅亡する(キバヤシAA略)

これ( iconv.patch)を見てみろ。

まず citrus_prop の関数及びコールバック関数のプロトタイプで引数の void ** を void * に変更している、これはノイズと考えられるので削除し、残りの差分を取り出す。

Index: libc/iconv/citrus_prop.c
===================================================================
--- libc/iconv/citrus_prop.c	(revision 267897)
+++ libc/iconv/citrus_prop.c	(working copy)
@@ -436,7 +436,7 @@
 			break;
 		_memstream_ungetc(&ms, ch);
 		errnum = _citrus_prop_parse_element(
-		    &ms, hints, (void ** __restrict)context);
+		    &ms, hints, (void ** __restrict)&context);
 		if (errnum != 0)
 			return (errnum);
 	}
Index: libiconv_modules/BIG5/citrus_big5.c
===================================================================
--- libiconv_modules/BIG5/citrus_big5.c	(revision 267897)
+++ libiconv_modules/BIG5/citrus_big5.c	(working copy)
@@ -179,7 +179,7 @@
 
 	if (start > 0xFF || end > 0xFF)
 		return (EINVAL);
-	ei = (_BIG5EncodingInfo *)ctx;
+	ei = (_BIG5EncodingInfo *)*ctx;
 	i = strcmp("row", s) ? 1 : 0;
 	i = 1 << i;
 	for (n = start; n <= end; ++n)
@@ -197,7 +197,7 @@
 
 	if (start > 0xFFFF || end > 0xFFFF)
 		return (EINVAL);
-	ei = (_BIG5EncodingInfo *)ctx;
+	ei = (_BIG5EncodingInfo *)*ctx;
 	exclude = TAILQ_LAST(&ei->excludes, _BIG5ExcludeList);
 	if (exclude != NULL && (wint_t)start <= exclude->end)
 		return (EINVAL);

するとできあがる差分……『ノ ス ト ラ ダ ム ス』。

つまりこのバグの本質は NULL pointer dereference なんかではなく

NetBSD から FreeBSD へ移植された時に作業者が & や * を誤って消してしまった後テストしてなくて発覚しなかった

だったんだよ!

(; ・`д・´) ナ、ナンダッテー !! (`・д´・ (`・д´・ ;)

そりゃーNetBSDで問題が発生するわけありませんわ、FreeBSD-SA-14についてワイは完全無罪の勝訴判決ガッツポーズ淫夢くん。

@次回予告

残りの FreeBSD-SA-15 も調査します、みとけよ~みとけよ~。

*1:NetBSD では 契約プログラミングが推奨されとりまして、関数の引数についての事前条件はすべて_DIAGASSERTマクロで違反が無いかをチェックしています
*2:the typical NetBSD style、内容が古いのでlibEUCでしか使ってないような事書いてありますが、今は使ってないのはlibUTF8くらいじゃなかろうか。
*3:今考えると hint とか定義すんのクッソめんどいし実装ウンコやね。前からもっとマシなものに書き直したかったんだけど後方互換の壁があってだな。 ちな正月くらいに新しいプロパティ機構実装してテストケース書くのめんどくさくて放置中、そのうち bitbacket の Portable Citrus iconv に入ります。 こっちは後方互換とか全部捨てていくスタイルなので。

2014/06/29(Sun)

[Citrus][FreeBSD][Security] FreeBSD-SA-14:15およびCVE-2014-3951 について (その3)

前回の続きです。

一転攻勢一点訂正、FreeBSD-SA-14:15 って14と15連番の Security Advisary の意味じゃなくて、2014年の15番目の意味なのね… まぁ俺西暦2114年まで生きてないから困らんしどーでもいいけど。

@citrus_viqr モジュールにおける Out Of Array Index Bounds 問題

これは II. Problem Description における以下の部分です。

an out of bounds array access in the initialization code of the VIQR module

libVIQR( citrus_viqr.c)の問題で、配列にその長を超えた添字でアクセス発生しとるちゅー指摘です。

最初にこの文言と対策 patch を見たとき(おっ off-by-oneか?)とおもいまんた。 どれだけ気をつけて実装してもチョロリとはみ出すのは、皆さん日常生活でも鼻毛、ズボンからシャツの裾、チャックから陰茎とか良くあることです(ボロリ)。

ところが実際に FreeBSD をクラッシュさせてみると Bus Error 起こしてるのでちょっと普通じゃないのですな

$ echo "Any *.exe that crash is a VC, Anyone that stands still is a well disciplined VC6." | iconv -f VIQR -t UTF-8
Bus error (core dumped)

普通 off-by-one 程度ならクラッシュする原因は Stack Smashing Protection によるバッファオーバーラン絶対殺すマンか Segmention Fault 程度の致命傷で済むわけですが。

あとは 新井が悪いアライメント問題?そんなの気にするコード書いた覚えないしのう、サクラメント中島ならスターの球団でショート守ってくれてええんやで?年俸次第だけど。

考えても理由わからんので、今回の Security Advisary によるコードの修正箇所を当バグにおいてのみ抜き出してみましょう。

Index: lib/libiconv_modules/VIQR/citrus_viqr.c
===================================================================
--- lib/libiconv_modules/VIQR/citrus_viqr.c	(revision 267591)
+++ lib/libiconv_modules/VIQR/citrus_viqr.c	(working copy)
@@ -431,7 +431,6 @@ static int
 _citrus_VIQR_encoding_module_init(_VIQREncodingInfo * __restrict ei,
     const void * __restrict var __unused, size_t lenvar __unused)
 {
-	const mnemonic_def_t *p;
 	const char *s;
 	size_t i, n;
 	int errnum;
@@ -455,7 +454,10 @@ _citrus_VIQR_encoding_module_init(_VIQREncodingInf
 			return (errnum);
 		}
 	}
-	for (i = 0;; ++i) {
+	/* a + 1 < b + 1 here to silence gcc warning about unsigned < 0. */
+	for (i = 0; i + 1 < mnemonic_ext_size + 1; ++i) {
+		const mnemonic_def_t *p;
+
 		p = &mnemonic_ext[i];
 		n = strlen(p->name);
 		if (ei->mb_cur_max < n)

変更前は、for(i = 0;; ++i) で無限ループだった部分を、for (i = 0; i + 1 < mnemonic_ext_size + 1; ++i) に変更してます。

ファッ!? 俺は無限ループするコードなんぞ書いた覚えないってば、どういうこと!?(驚愕)

@そもそも VIRQ って何?

まずワイの書いたコードを理解するため寄り道、VIRQ(=VIetnamese Quoted Readable)ってのはかつてベトナムで利用されていた文字コードです、 RFC1456になってます。

ヴィカーと発音するけど、モリッシーの巻き舌とはたぶん無関係、 デュルルヴィカーインチュッチュー

実際には文字コードちゅうか、7bitなUS-ASCIIの文字のみを使ってラテン文字の ダイアクリティカルマークをニーモニックで表現する 翻字ですやね *1。古のUsenet時代の非8bit clean環境における遺物(日本におけるISO-2022-JP的存在)ですが、今でも情報交換用としてではなく TelexVNIと共にベトナム語入力メソッド用として生き残ってます。

かつてベトナム語の記述には漢字と チュノムという文字が使われていましたが、フランスによる植民地支配の結果 クオック・グーと呼ばれる方法でラテン文字でベトナム語を記述するようになった結果ですな。 *2

日本でも某巨大匿名掲示板や某動画サイトで、某やきう選手の名前がアルファベットに翻字される例がみられますが、たった一度の過ちを深く追求してはいけない(戒め)。

ちょww角川書店がKADOKAWAに!?ww

@VIQRのニーモニックには多くの変種があり、実用上 RFC1456 では十分でない

これは RFC1456 が VISCII との変換のみ定義してることによる問題。 ワイが libVIRQ の実装した頃にはまだインターネット上で VIQR の変換サービスを提供してるサイトがあったんですが、どこも RFC1456 以外にもニーモニック定義してるようです。 ですがドキュメント化されてないので、ワイ仕様が無いから実装できません状態に。

なもんで現地ベトナムの人が必要であれば、後から拡張ニーモニックを追加できるよう、 拡張性を持たせたコードにしたわけです。

 94 typedef struct {
 95 	const char *name;
 96 	wchar_t value;
 97 } mnemonic_def_t;
 98 
 99 static const mnemonic_def_t mnemonic_ext[] = {
100 /* add extra mnemonic here (should be sorted by wchar_t order). */
101 };
102 static const size_t mnemonic_ext_size =
103 	sizeof(mnemonic_ext) / sizeof(mnemonic_def_t);
...

524 	for (i = 0; i < mnemonic_ext_size; ++i) {
525 		p = &mnemonic_ext[i];
526 		_DIAGASSERT(p != NULL && p->name != NULL);
527 		n = strlen(p->name);
528 		_DIAGASSERT(n <= MB_LEN_MAX);
529 		if (ei->mb_cur_max < n)
530 			ei->mb_cur_max = n;
531 		errnum = mnemonic_append_child(ei->mroot,
532 		    p->name, p->value, ei->invalid);
533 		if (errnum != 0) {
534 			_citrus_VIQR_encoding_module_uninit(ei);
535 			return errnum;
536 		}
537 	}
538 

RFC1456 で足りないニーモニックが後に明文化されれば、mnemonic_ext配列に随時追加していけばいいという親切設計。

親切といってもl10nの原則「余計な親切心を持ち込まない」の精神で、どのニーモニックを追加するかは現地ベトナムの人に丸投げしてます。 罷り間違っても アイルランドのアレのような自分勝手は NG ちゅうことで。

@話を戻して、問題のコードに注目

んでんで 524行目に注目、ちゃんとワイの書いたコードは無限ループではなく i < mnemonic_ext_size という条件になってます。

つまり今回の Out Of Array Index Bounds を引き起こした無限ループは、FreeBSDへのCitrus iconv移植の際に元のコードの意図を無視して勝手に削られたちゅうことです。

ば~~~っかじゃねえの!?(AA略)

@なぜ FreeBSD は愚かにもループ終了条件を削ってしまったのか?

すでに Citrus iconv のツリーへの インポート時点でこの脆弱性は 存在するので、誰のやらかしかは判りません。

ですがこの脆弱性報告者による 最初の修正、そしてその後の 一連のcommit logをみると

102 static const size_t mnemonic_ext_size =
103 	sizeof(mnemonic_ext) / sizeof(mnemonic_def_t);

が、mnemonic_extにニーモニックが1つも定義されてない現状だと、mnemonic_ext_sizeはコンパイル時にゼロになるから

524 	for (i = 0; i < mnemonic_ext_size; ++i) {

は gcc のバージョン問題なのか最適化オプションなのかバグなのか知らんけど

for (i = 0; i < 0; ++i)

と解釈されてしまった結果

-Wtype-limits
	Warn if a comparison is always true or always false due to the limited range of the data type,
	but do not warn for constant expressions. For example, warn if an unsigned variable is compared against zero with ‘<’ or ‘>=’.
	This warning is also enabled by -Wextra. 

の警告に引っかかったちゅー感じ?ちな手元のgcc 4.5.3と4.8.3では問題なさそうなんだけど。

よく考えたらFはもうclangだっけ、そっちだと-Wtype-limitsではなくて-Wtautological-compareだけども、これだって定数マクロじゃないんだし警告出るのがおかしい。 というか手元でいくら試しても再現しないんだが、これamd64以外の環境でのclangバグじゃない?(適当)

まぁ細けぇことはいいんだよ、要するにFreeBSDでこの作業やってた連中は

  • 警告が出たコードのビルドを通すため
  • 中身も読まずに終了条件を消すという愚かな行為をやらかして
  • 無限ループによる Out Of Array Index Bounds で脆弱性を引き起こした
  • その上ろくすっぽ原因を検証してないので、修正patchで「i + 1 < mnemonic_ext_size + 1」みたいないずれoff-by-oneやらかしそうなコードを平気で出してくる

つまりは極めつけのアホということです。もしかして サルによるコーディングかな?

@次回予告

めんどくさいから放置するかもしれないけど、前回今回みたいな馬鹿げたバグを出さないためにlibc開発者はどうすべきかを書く予定。

*1:同様の翻字の例としては、チベット文字をローマ字転写する ワイリー法とかあります。
*2:国際政治( 落合信彦感)による文字強制としては、モンゴル人民共和国がソ連による支援で独立した後にモンゴル文字を捨ててラテン文字→後にキリル文字での記法に移行してますな。