こんにちは、情熱開発部プログラム課の青柳です。
今回はVisualStudio2019 16.9にて正式に利用できるようになったAddressSanitizerのに関してご紹介します。
間違いがありましたら優しく教えてください。
AddressSanitizerについて
AddressSanitizerはもともとGoogleの方がまずClang用にと実装した各種メモリバグ検出器の事です。これを用いる事により、配列外アクセス、開放済み領域へのアクセス、使用中領域への書き込み等が実行時に検出できるようになります。またソースの静的解析も行えます。
動作に関して
AddressSanitizerは、まずはじめに配置されるプログラムのMemoryLayoutにてHighとLowの2つに分け、それぞれShadowMemoryと名付けた領域にマッピングします。
メモリのアクセスの際にShadowの領域を調べに行ってShadowにきちんとした値が入っているか確認します。Shadowの領域はBadメモリへマッピングしBadメモリはPageProtectionをかけた領域とするそうです。(※どうやってマッピングするかは初めのUrlの資料をご覧ください。)
またMalloc、Freeの乗っ取りも行っておりMallocの際に確保メモリの左側にRedZoneと名付けたMetaデータ(ThreadIDなど)格納領域を追加します。レッドゾーンはアクセス不能、またはPoisonedとされShadowにマッピングされ検出がされます。
これと似たようなことはStackのオブジェクトやGlobalなオブジェクトにも行われます。
実際にVisualStudioで使ってみる
始めに書いたように VisualStudio2019 16.9にて正式にAddressSanitizerが利用できるように なりました。雰囲気だけの理解ですがAddressSanitizerがどんなものか分かりましたので実際に使ってみます。
設定
コンパイルオプションでAddressSanitizerを有効にし、デバッグ情報の形式をZi、インクリメンタルリンクをNOにします。
これでAddressSanitizerが有効になりました
静的解析
AddressSanitizerは静的解析機能を提供しています。こちらはIDEに組み込まれて自動で警告を出してくれるようです。画像の警告は古いバージョンでは出ていなかったと思います。
実行時解析
MSVCのAddressSanitizerの概要はこちらにまとまっています。
検出できるエラーを抜き出してみますと
・Alloc(new)/Dealloc(delete)の不一致
(配列に普通のdeleteを行ってしまう、継承元のデストラクタをVirtualにしていない)
・Alloc時のサイズが大きすぎる場合
・free済み領域をfreeしてしまう、free後の領域にアクセスしてしまう
・グローバル変数のバッファオーバーフロー
・ヒープのバッファオーバーフロー
・_aligned_mallocなどアライメントされたMallocの引数の違反
・memcpy、strncatの境界チェック
・ローカル変数のオーバーフロー、アンダーフロー
・関数内のローカル変数のアドレスの使用
・無効になったローカル変数のアドレスの使用
・__asan_poison_memory_regionを用いたPoisonAdressの追加
網羅されてるんじゃないかと思うくらい調べてくれますね。
今回は簡単にローカル変数の範囲外アクセスをチェックしてもらおうと思います。ただ配置Newを使う場合を考えてみます、配置Newに関しては弊社ブログをご覧ください。 __asan_poison_memory_region を使用します。
#include <new>
extern "C" void __asan_poison_memory_region(void*, size_t);
int main()
{
char* arrayV = new char[100];
char* arrayU = new(arrayV) char[50];
char* bound = arrayU + 50;
__asan_poison_memory_region(bound, 32);//PoisonedMemoryの追加、2番目はPoisonedな領域のサイズ
arrayU[50] = 1;//Error!
//arrayV[100] = 1;//Error!
return 0;
}
こちらを実行すると以下のようなエラーが出ます。
始めにエラーが書いてあり
==10600==ERROR: AddressSanitizer: use-after-poison on address 0x12df23380072 at pc 0x7ff7393210f4 bp 0x00f4e9affbb0 sp 0x00f4e9affbb8
WRITE of size 1 at 0x12df23380072
とあります、poisonなアドレスで1Byteの書き込みがあったようです。次に見るのはRedzoneなどが表示されている最後のShadowByteログです
Shadow bytes around the buggy address:
0x0525079effb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0525079effc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0525079effd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0525079effe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0525079efff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0525079f0000: fa fa fa fa fa fa fa fa 00 00 00 00 00 00[02]f7
0x0525079f0010: f7 f7 00 00 04 fa fa fa fa fa fa fa fa fa 00 00
0x0525079f0020: 00 00 00 00 00 00 00 00 00 00 00 03 fa fa fa fa
0x0525079f0030: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
0x0525079f0040: fd fa fa fa fa fa fa fa fa fa fd fd fd fd fd fd
0x0525079f0050: fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
この[02]の部分が今回エラーになった箇所です。他のfaやf7などが何かというと、ShadowByteの下に凡例が描かれていますfaはHeapのRedZone、fdはユーザーが指定したPosionedな領域のようですね。
実行時負荷
負荷に関してなのですが、適当な大きさのプロジェクトが無く。Unityのサンプルで吐き出したSlnでも大丈夫かなと思ったのですが、実行できませんでした。Googleの方曰く2倍から3倍の負荷増で収まっているという事です。
終わりに
Clangに追加されてから結構たちますが、MSVCでもAddressSanitizerが使えるようになりました。嬉しい!
という事で配置Newを使う場合はひと手間かかってしまうようなのですが、便利な道具を使ってなるべく安全にC++を使っていきたいですね。
参考にさせていただいたサイト様
以下のサイト様を参考にさせていただきました、ありがとうございます。
AddressSanitizer: A Fast Address Sanity Checker | USENIX
MSVCでのAddress Sanitizerの使い方を紹介【Visual Studio・C++】 | 充実ライフを目指すブログ (meltybk.net)
AddressSanitizer | Microsoft Docs
【免責事項】
本サイトでの情報を利用することによる損害等に対し、
株式会社ロジカルビートは一切の責任を負いません。