libdxfrwのDWGパース時の整数アンダーフロー修正

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.cppdwgreader21.cppの両方に適用しました。

修正後の計算結果:

  • maxClassNum = 236: endDataPos = 0(カスタムクラスなし)
  • maxClassNum = 499: endDataPos = 0(カスタムクラスなし)
  • maxClassNum = 500: endDataPos = 1(カスタムクラス1つ)
  • maxClassNum = 1000: endDataPos = 501(カスタムクラス501個)

テスト

今回の修正に合わせて、ユニットテストとインテグレーションテストも追加しました。境界値のテストや、メモリ安全性の確認を行っています。

符号なし整数のアンダーフローは、C/C++ではよくあるバグですが、今回のようにメモリ消費が爆発するケースは影響が大きいので、早めに対処できてよかったです。

関連リンク