ImageMagickの脆弱性:その2
同じくImageMagicの別の脆弱性について考えてみましょう。以下のコードは、ImageMagick バージョン 6.3.4、coders/dib.cからの抜粋です。解説の便宜上、余分なコードは省略しています。
static Image *ReadDIBImage(const ImageInfo *image_info,ExceptionInfo *exception)
{
...
/*
Microsoft Windows 3.X DIB image file.
*/
dib_info.width=(short) ReadBlobLSBLong(image);
...
image->columns=(unsigned long) dib_info.width;
...
/*
Read image data.
*/
...
bytes_per_line=4*((image->columns*dib_info.bits_per_pixel+31)/32);
length=bytes_per_line*image->rows;
pixels=(unsigned char *) AcquireMagickMemory((size_t) MagickMax(
bytes_per_line,image->columns+256)*image->rows*sizeof(*pixels));
if (pixels == (unsigned char *) NULL)
ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
if ((dib_info.compression == BI_RGB) ||
(dib_info.compression == BI_BITFIELDS))
{
count=ReadBlob(image,length,pixels);
...
ReadDIBImage()はマイクロソフトウィンドウズのビットマップ画像を読み込み、それを返す関数です。また、ReadBlobLSBLong()はunsigned long型の値を返します。どのような問題があるか分かりましたか?
脆弱性の解説:符号拡張の問題
LP64データモデルを前提に解説します。ReadBlobSBLong()関数の返り値は、short型に明示的にキャストされたのち、long型のdib_info.widthに代入されます。この代入式を変数の型名だけで示すと
long = (short) unsigned long
のようになります。問題はshort型にキャストされた値がlong型変数に代入されるときに発生します。例えば、
long = (short) 0x0...00008000
のような代入が行われると、longの値は0xf...fff8000という負の値になってしまいます。なぜこのようなことが起こるのかというと、longの幅にshortの値を入れる際、足りない上位ビットは「符号拡張」によって充足されるからです。符号拡張とは「符号付の数値を表現するビット列が格納領域のビット幅より短い場合に、隙間を適切に埋めることによって数値としての同一性を維持する手法」です(wikipedia)。つまり、上位ビットを符号ビットの'1'を使って埋めるため、dib_info.widthの値が0xf...ffff8000になってしまうのです。
その結果、非常に大きな値がimage->columnsに代入され、AcquireMagickMemory()の引数の式、
MagickMax(bytes_per_line,image->columns+256)*image->rows*sizeof(*pixels)
はsize_tで表現しきれない大きな値になることから、ラップアラウンドが発生してしまう可能性があるのです。符号付きの型と符号無しの型との間の型変換は、ここで問題になったような符号エラーにつながることが多く、注意が必要です。
この問題の修正は次のように行われました。
- image->columns=(unsigned long) dib_info.width; + image->columns=(unsigned long) MagickAbsoluteValue(dib_info.width);
MagickAbsoluteValue(a)は、aの値が0より小さい場合に‐aを返し、それ以外の場合は、aを返す関数です。この関数を途中に挟むことにより、符号拡張の問題の発生を防ぐように修正されています。
CERT C セキュアコーディングスタンダード
今回紹介した脆弱性を作り込まないコーディングについては、以下のルールに詳しく解説しています。こちらも併せて参照ください。
- STR31-C. 文字データとNULL終端文字を格納するために十分な領域を確保する
- INT02-C. 整数変換のルールを理解する
- INT31-C. 整数変換によってデータの消失や解釈間違いが発生しないことを保証する
