fs0x7fの日記: ちょっと変わったデベハトップ問題とisupperの危険性 5
パスに含まれるマルチバイト文字の後続バイトをASCIIの1バイト文字として大文字小文字変換してしまうバグのことをデベハトップ問題って自分で勝手に呼んでるのだけど、コレに関連するちょっと風変わりな現象に遭遇してソレにまつわるちょっと面白い事に気が付いたのでメモしておく。
# ShiftJIS(CP932)の「デスクトップ」を小文字化破壊すると「デベハトップ」で、大文字化破壊すると「ェスクエィシ」になります。
Microsoftが提供しているMD5・sha1ハッシュ計算ツールで、ファイル チェックサム整合性検証 (FCIV) ユーティリティ(fciv.exe)というのがある。
UIは日本語化されていないのだが、中途半端に多言語対応しててエラーメッセージなどはOSの言語で出力されるとかいう微妙に気持ち悪い仕様になっている。
このユーティリティのパスの取り扱いには少々癖があり、相対パスは受け付けるがカレントパス指定(.\)は受け付けず、しかし一度駆け上がってカレントを指定したり、カレントパスの二重指定(.\.\)は受け付けるというかなり気持ち悪い仕様になっている。
んで、パラメータに与えた引数はデベハトップ問題を発症するのだが、対象がパラメータで渡した分だけなので中々表面化しないとかいう気持ち悪い仕様になっている(修正版のテストするまでパラメータのみって事に気が付かなかった…orz)。
# あくまでもパラメータの問題なので、-rオプションを使ってfciv自身が探した場合は問題ない
受け付けるパスの問題を回避した次に気がついたのは「版」の含まれるパスを与えると1バイト片仮名で「エナ」となって失敗するとかいうデベハトップ問題をもっと気持ち悪くしたような現象だった。
バイト列は[版:0x94 0xC5]から[エ:0xB4][ナ:0xC5]に変化しているので0x94+0x20=0xB4の大文字→小文字変換がかかっている感じだが、0x94でisupperが真になるとか聞いたことがないし、Googleで探しても特に0x94がダメ文字だなんて話も特に聞いたことがない。
Google先生に聞いても0x94に関連する先例が見つからなかったのでOllyDbgでひたすら追いかける事にした。
問題:
fciv.00404825から始まる関数を要約すると、引数で受けた値をEAXに入れて以下のコードを実行する。411130が示す配列は512バイト有り、そのうち後ろ256バイトは0である。
訂正:411130は514バイトの配列の3バイト目を指しており、その配列の後ろ256バイトは0である。
この関数が受け入れ可能な値の範囲を書け。また受け入れ可能な値の範囲を超えたときに何が起こるか予測せよ。
00406508 MOV ECX,DWORD PTR DS:[411130]
0040650E MOVZX EAX,BYTE PTR DS:[ECX+EAX*2]
00406512 AND EAX,1
00406515 RETN
Ans1.
0-255
訂正:-1~255
Ans2.
・無関係なメモリを参照して不定値を返す。
・セグメンテーション違反その他でクラッシュする。
問題:
以下のコードは、マルチバイト文字が渡される可能性のある、大文字を小文字に変換する為のコードである。
問題点を2点書け。
00404812 MOVSX EAX,BYTE PTR DS:[ESI]
00404815 PUSH EAX
00404816 CALL fciv.004064ED
0040481B ADD ESP,4
0040481E TEST EAX,EAX
00404820 JE SHORT fciv.00404825
00404822 ADD BYTE PTR DS:[ESI],20
00404825 INC ESI
00404826 CMP ESI,EBP
00404828 JB SHORT fciv.00404812
Ans1.
mbcs判定が無くバイト単位で判別しているので、マルチバイト文字の後続バイトを破壊してデベハトップ問題を起こす。
Ans2.
MOVSXなので負数を使って文字を判別する可能性がある。
先のfciv.00404825の内容からすれば、最悪、セグメンテーション違反を起こす。
問題:このコードを生成しうるC++のコードを書け。
Ans3. for(char *p=str;*p;p++)if(isupper(*p))p+='a'-'A';
気が付いたら、なんのヒネリも無いisupper()判定でも最悪の場合セグメンテーション違反が発生するという恐ろしい話になってしまった。
よっぽど変なコンパイル&メモリ配置にならなければ前後に他のライブラリ変数が配置されてクラッシュはしないけど、たったコレだけのコードでもアウト。
isupperの実装で範囲チェックを省いた処理系が悪い?
とりあえず手近にあったC処理系のライブラリを大ざっぱに読んだ感じ
× GNU C Library
× Mingw(TCC)
× Microsoft Visual C++ 10.0
○ FreeBSD
と、FreeBSD以外全滅なんですが。(バージョンは確認してないから不公平だったらごめんね!)
というわけで、シングルバイト文字列圏含めて、isXXXXには注意してね!
ロケールの設定はちゃんとやろうね!
あとマルチバイト処理もきっちりやってほしいな!
さもないとアクセス失敗どころかクラッシュしかねないよ!
# どうでもいいけど、BSDのctypeの実装でCurrentRuneLocaleとか内部名がRune何とかってなってるんだけど、これルーン文字のRuneだよね。
# 浪漫っつうか中二病って言うか・・・なんか愛着沸きそうになっちゃうじゃないか!