libdxfrwは、DXF/DWGファイルの読み書きを行うC++ライブラリです。FessでDWGファイルのテキスト抽出に利用しているのですが、特定のDWGファイルをパースする際に、メモリを80GB以上消費して実質的に無限ループに陥るという問題が発生しました。
原因
問題の原因は、DWGファイルのクラス情報をパースする処理における符号なし整数のアンダーフローでした。
dwgreader18.cppにある元のコードは以下のようになっていました。
duint32 endDataPos = maxClassNum - 499;
DWGフォーマットでは、クラス番号0〜499は予約済みの組み込みクラスで、カスタムクラスは500番から始まります。そのため、maxClassNumが499以下の場合、カスタムクラスは存在しません。
しかし、duint32(符号なし32ビット整数)でmaxClassNum - 499を計算すると、maxClassNumが499以下の場合にアンダーフローが発生します。例えば、DWG TrueViewで作成されたAC1032形式のファイルではmaxClassNum = 236となることがあり、この場合:
- 元のコード:
236 - 499 = 4294967033(符号なし整数のラップアラウンド)
この巨大な値がループ回数として使われるため、メモリの大量消費と実質的な無限ループが発生していました。
修正内容
修正はシンプルで、減算前に境界チェックを追加しました。
// Classes 0-499 are built-in; custom classes start at 500.
// If maxClassNum <= 499, there are no custom classes to parse.
duint32 endDataPos = (maxClassNum > 499) ? (maxClassNum - 499) : 0;
maxClassNumが499以下の場合はendDataPos = 0となり、カスタムクラスのパースをスキップします。この修正はdwgreader18.cppとdwgreader21.cppの両方に適用しました。
修正後の計算結果:
maxClassNum = 236:endDataPos = 0(カスタムクラスなし)maxClassNum = 499:endDataPos = 0(カスタムクラスなし)maxClassNum = 500:endDataPos = 1(カスタムクラス1つ)maxClassNum = 1000:endDataPos = 501(カスタムクラス501個)
テスト
今回の修正に合わせて、ユニットテストとインテグレーションテストも追加しました。境界値のテストや、メモリ安全性の確認を行っています。
符号なし整数のアンダーフローは、C/C++ではよくあるバグですが、今回のようにメモリ消費が爆発するケースは影響が大きいので、早めに対処できてよかったです。