WRITING SECURE CODE 第2版 のサンプルコードにおける整数オーバーフロー
WRITING SECURE CODE 第2版の第11章「標準表記の問題」という章には、ファイル名を正規化する GetCanonicalFileName という関数の実装が例として掲載されている(P.414)。
以下、そのコードの一部分。szFilename と szDir はファイル名とディレクトリ名を示す外部から受け取った文字列であり、汚染されている可能性がある。
size_t cFilename = lstrlen(szFilename); size_t cDir = lstrlen(szDir); // 一時的な新しいバッファいのサイズ。追加された'\'を許容する size_t cNewFilename = cFilename + cDir + 1; // 手順3 // ファイル名の長さが十分に小さいことを確認する if (cNewFilename > MAX_PATH ) throw ERR_CANON_TOO_BIG; // 新しいファイル名用のメモリを割り当てる // 先頭の\\?\と末尾の'\0'を考慮する LPCTSTR szPrefix = _T("\\\\?\\"); size_t cchPrefix = lstrlen(szPrefix); size_t cchTempFullDir=cNewFilename+1+cchPrefix; szTempFullDir = new TCHAR[cchTempFullDir];
手順3で、ディレクトリ名とファイル名を足した長さが MAX_PATH より小さいかを検証している。ここで szFilename および szDir に非常に長い文字列を与えると、cNewFilename つまり lstrlen( szFilename ) + lstrlen( szDir ) + 1 を 0 とすることが可能になる*1。cNewFilename == 0 の場合には、手順3 における MAX_PATH との比較を突破することができ、結果、szTempFullDir という変数は "\\?\" と終端のヌル文字の5文字分しか確保されないことになる。整数オーバーフローの具体的なサンプルともなっている?
もう少し読み進める。5文字分の領域として確保された szTempFullDir を実際に使用している箇所は以下のようになっている。
// 手順4 // ディレクトリとファイル名を結合する // 先頭に\\?\を付加してOSに文字をそのまま処理させる // これで余計な解釈/標準化のステップが実行されなくなる if (StringCchPrintf(szTempFullDir, cchTempFullDir, _T("%s%s\\%s"), szPrefix, szDir, szFilename) != S_OK) throw ERR_CANON_INVALID_FILENAME;
単純な sprintf や strcat ではなく、StringCchPrintf が使用されている。StringCchPrintf では、書き込み先のバッファサイズの指定が必須である。また、バッファ領域が足りない場合には STRSAFE_E_INSUFFICIENT_BUFFER というエラーが返り、バッファサイズが足りないからといってコピー後の文字列が勝手に切り詰められることはない。そのため、単純に szDir や szFilename の書き込みに失敗するだけであり、GetCanonicalFileName の呼び出し側には正しくエラーが返される。
ということで、整数オーバーフローによる exploit は不能ということである。
*1:実際のサンプルコードではこの前に szDir の長さをチェックしているので、細工ができるのは szFilename のみである。szFilename の長さも事前にチェックしておけばよりよいサンプルコードになるだろうに…。