Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

C/C++/ObjC メモリ破壊系バグのつぶし方 その1

メモリ破壊系バグとは

メモリ破壊系バグとは、プログラマーが想定して割り当てたメモリ領域のサイズを超えた部分にデータを書き込んでしまい、
プログラムが意図通り動作しなくなるバグのことです。

このバグは以下の特徴を持っています。

  • 再現性が100%ではない場合が多い。
  • バグの原因箇所の特定が難しい。

破壊するメモリの領域としては、スタック領域、ヒープ領域、その他があります。
また、プログラマが明示的に確保した領域またはライブラリが確保した領域に分けられます。

スタック領域の例としては、関数内で以下のように宣言した a[32] の32バイトなどです。

void hello(void){
    char a[32];
    memset(a, 0, 33); // 1バイト分オーバー!
}

このようにスタック領域を破壊した場合は、hello()関数の戻り先アドレスを上書きしてしまい、この関数以降は命令自体
まともに実行されないため、どこかでクラッシュしたり、全く関係のないところに行ったりして、動作が不定になります。
いわゆるC言語の恐ろしい仕様「動作未定義」というやつです。

ヒープ領域は例えば以下のようにC++でのnewやC言語でのmallocで割り当てたbの 32バイトが該当します。

void hi(void){
    char* b = new char [32];
    memset(b, 0, 33); // 1バイト分オーバー!
}

この場合、ヒープ領域の先はおそらくヒープ領域なので、別のメモリデータが破壊されることになります。

バグの発生箇所を特定する方法

まず、バッファオーバーランを引き起こしやすい関数や処理に着目します。

  • strcpy
  • memcpy
  • memset
  • sprintf
  • vDSP系関数
  • for文でメモリ書きこみしている処理
    など

怪しそうな箇所の直前に、以下のようなassert文を追加します。

assert(その箇所で実際にアクセスしているメモリサイズ<=有効なメモリサイズ);

例えば、以下のような感じです。

#include <assert.h>

void hi(void){
    const int bufsize = 32;
    const int copysize = 33;
    char* b = new char [bufsize];
    assert(copysize <= bufsize); // メモリ破壊する前にassertで検知!
    memset(b, 0, copysize); // 1バイト分オーバー!
}

この例は分かりやすいですが、バグっている場合はサイズ指定がかなり複雑な変数になっている場合が多く、
正しくassert文を入れるのにも慣れが必要です。 これを怪しそうな箇所全てに入れてからデバッグ実行すれば、
バッファオーバーランしている箇所が炙り出せるというわけです。

続きはこちら
C/C++/ObjC メモリ破壊系バグのつぶし方 その2 - Pebble Coding

C言語入門 (ASCII SOFTWARE SCIENCE Language)

C言語入門 (ASCII SOFTWARE SCIENCE Language)

  • 作者: レスハンコック,サバゼミール,モーリスクリーガー,Les Hancock,Saba Zamir,Morris Krieger,倉骨彰,三浦明美
  • 出版社/メーカー: アスキー
  • 発売日: 1992/09
  • メディア: 単行本
  • 購入: 3人 クリック: 34回
  • この商品を含むブログ (14件) を見る

プログラミング言語C 第2版 ANSI規格準拠

プログラミング言語C 第2版 ANSI規格準拠